はじめに
本チュートリアルでは「テキスト分類問題その1」コンペティションを題材にして、Pythonとscikit-learnを利用した分類器の実装方法を解説します。まず第一部で、Bag-of-featuresデータの読み込み方法と初歩的な分類器の学習手順を紹介します。第二部では、発展的な話題に触れます。なお、本チュートリアルでは学習手法の詳細には立ち入りません。
掲載コードは、Python2.7.8、scikit-learn 0.15、NumPy 1.9.1、SciPy 0.14.0で動作確認を行っております。scikit-learnのインストール方法は、Installing scikit-learn — scikit-learn 0.15.2 documentationを参照してください。
第一部:初歩的な分類器の作成
訓練データの読み込み
まずは、正解ラベルが与えられている「訓練データ」を読み込みます。本コンペティションで用いられているsvmlightフォーマットのデータは、scikit-learnのload_svmlight_fileにより読み込むことができます。コンペティションページより予測用データをダウンロードし、以下のコードを./text-classification-001/上で実行すると、Xに特徴量、yに正解ラベルが格納されます。
In [1]: import os In [2]: from sklearn.datasets import load_svmlight_file In [3]: BOW_DIR = 'bag-of-words' In [4]: DATA_TRAIN_PATH = os.path.join(BOW_DIR, 'data-train.dat') In [5]: N_FEATURES = 37040 In [6]: X, y = load_svmlight_file(DATA_TRAIN_PATH, n_features=N_FEATURES, dtype=int, zero_based=True)
次に読み込んだデータを、分類器学習に用いるサンプル(X_train, y_train)と、分類器の評価に用いるサンプル(X_val, y_val)に分割します。分割には、train_test_splitが利用できます。
In [7]: from sklearn.cross_validation import train_test_split In [8]: TEST_SIZE = 0.2 In [9]: RANDOM_STATE = 0 In [10]: X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
ここでは、全体の20%を評価用としています。以下のコードにより、2,400件のサンプルが1,920件(学習用)と480件(評価用)に分割されていることが確認できます。
In [11]: X.shape, y.shape Out[11]: ((2400, 37040), (2400,)) In [12]: X_train.shape, y_train.shape Out[12]: ((1920, 37040), (1920,)) In [13]: X_val.shape, y_val.shape Out[13]: ((480, 37040), (480,))
ナイーブベイズ分類器の学習と評価
初歩的な分類器であるナイーブベイズ分類器の学習手順を紹介します。scikit-learnにはいくつかのナイーブベイズの実装がありますが、ここではMultinomialNBを用います。
以下のコードで、分類器の学習を実行できます。
In [14]: from sklearn.naive_bayes import MultinomialNB In [15]: clf = MultinomialNB(alpha=0.1) In [16]: clf.fit(X_train, y_train) Out[16]: MultinomialNB(alpha=0.1, class_prior=None, fit_prior=True)
ここでは、分類器のパラメータをalpha=0.1に設定しています。
次に、評価用データを用いて、分類器の性能を確認します。まず、評価用データに対する予測結果を出力します。
In [17]: y_pred = clf.predict_proba(X_val)[:, 1]
ここでは、コンペティションで指定されている通り、「ラベルが1である確率」を出力しています。
コンペティションで採用されている評価指標 (Area under the ROC curve, AUC)により、性能を評価します。roc_auc_scoreを用います。
In [18]: from sklearn.metrics import roc_auc_score In [19]: roc_auc_score(y_val, y_pred) Out[19]: 0.60255682394209131
評価用データに対するAUCスコアを確認できました。次に、alphaの値を変えて新しい分類器を学習してみましょう。
In [20]: clf_new = MultinomialNB(alpha=1.0) In [21]: clf_new.fit(X_train, y_train) Out[21]: MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True) In [22]: y_pred_new = clf_new.predict_proba(X_val)[:, 1] In [23]: roc_auc_score(y_val, y_pred_new) Out[23]: 0.65065187858634599
alpha=1.0にするとAUCスコアが向上することを確認できました。
予測結果の提出
alpha=1.0を採用し、予測結果を提出しましょう。これまでは評価用のデータは学習に用いませんでしたが、提出時には全てのデータ(X, y)を用いて分類器を再度学習します。
In [24]: clf_submit = MultinomialNB(alpha=1.0) In [25]: clf_submit.fit(X, y) Out[25]: MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)
次に、学習した分類器を用いてテストデータに対する予測結果を出力します。まずは、テストデータを読み込みます。
In [26]: DATA_TEST_PATH = os.path.join(BOW_DIR, 'data-test.dat') In [27]: X_test, y_test_dummy = load_svmlight_file(DATA_TEST_PATH, n_features=N_FEATURES, dtype=int, zero_based=True)
テストデータに対する予測を行い、結果を出力します。
In [28]: SUBMIT_PATH = 'sample-submission-basic.dat' In [29]: y_submit = clf_submit.predict_proba(X_test)[:, 1] In [30]: import numpy as np In [31]: np.savetxt(SUBMIT_PATH, y_submit, fmt='%.10f')
コンペティションページにて、sample-submission-basic.datを提出しましょう。以上で、分類器の学習と予測結果の提出が完了しました。
第二部:発展的な話題
予測性能向上のための発展的な話題に触れていきます。以下のコードにより、必要なモジュールと変数が設定されているものとします。
In [1]: import os In [2]: import numpy as np In [3]: from sklearn.datasets import load_svmlight_file In [4]: from sklearn.cross_validation import train_test_split In [5]: from sklearn.metrics import roc_auc_score In [6]: BOW_DIR = 'bag-of-words' In [7]: DATA_PATH = os.path.join(BOW_DIR, 'data-train.dat') In [8]: DATA_TEST_PATH = os.path.join(BOW_DIR, 'data-test.dat') In [9]: N_FEATURES = 37040 In [10]: TEST_SIZE = 0.2 In [11]: RANDOM_STATE = 0 In [12]: X, y = load_svmlight_file(DATA_PATH, n_features=N_FEATURES, dtype=int, zero_based=True) In [13]: X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
様々な分類手法
ナイーブベイズ以外にも、scikit-learnでは様々な分類手法が提供されています。代表的なものに、ロジスティック回帰やSVMがあります。
L1正則化ロジスティック回帰の例 (LogisticRegression)
In [14]: from sklearn.linear_model import LogisticRegression In [15]: clf_lr = LogisticRegression(penalty='l1') In [16]: clf_lr.fit(X_train, y_train) Out[16]: LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, penalty='l1', random_state=None, tol=0.0001) In [17]: y_pred_lr = clf_lr.predict_proba(X_val)[:, 1] In [18]: roc_auc_score(y_val, y_pred_lr) Out[18]: 0.64002717005616505
線形SVMの例 (SVC)
In [19]: from sklearn.svm import SVC In [20]: clf_svc = SVC(kernel = 'linear', probability = True) In [21]: clf_svc.fit(X_train, y_train) Out[21]: SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0, degree=3, gamma=0.0, kernel='linear', max_iter=-1, probability=True, random_state=None, shrinking=True, tol=0.001, verbose=False) In [22]: y_pred_svc = clf_svc.predict_proba(X_val)[:, 1] In [23]: roc_auc_score(y_val, y_pred_svc) Out[23]: 0.71369046412133252
色々な手法を試してみましょう。
特徴抽出
新しいBag of words表現の作成
あらかじめ提供されているbag-of-words/以外の特徴量を用いることで、予測性能が向上する可能性があります。text/内のデータを用いて新しい特徴量を作成してみましょう。まずは、text/内のデータを読み込みます。
In [24]: ROOT_DIR = 'text' In [25]: TEXT_DIR = os.path.join(ROOT_DIR, 'train') In [26]: TEXT_TEST_DIR = os.path.join(ROOT_DIR, 'test') In [27]: FILES_PATH = os.path.join(ROOT_DIR, 'text-files-train.dat') In [28]: FILES_TEST_PATH = os.path.join(ROOT_DIR, 'text-files-test.dat') In [29]: files = np.loadtxt(FILES_PATH, dtype=str) In [30]: texts = np.array([file(os.path.join(TEXT_DIR, f), 'r').read().strip() for f in files]) In [31]: files_test = np.loadtxt(FILES_TEST_PATH, dtype=str) In [32]: texts_test = np.array([file(os.path.join(TEXT_TEST_DIR, f), 'r').read().strip() for f in files_test])
第一部と同様に、訓練データを学習用(80%)と評価用(20%)に分割します。
In [33]: train, val, y_train, y_val = train_test_split(np.arange(texts.shape[0]), y, test_size=TEST_SIZE, random_state=RANDOM_STATE) In [34]: texts_train = texts[train] In [35]: texts_val = texts[val]
bag-of-words/内の特徴量は「5つ以上の文書で登場する単語のみ」を用いて作成されていました。ここでは、「2つ以上の文書で登場する単語」を用いた特徴量を作成してみましょう。CountVectorizerを用います。
In [36]: from sklearn.feature_extraction.text import CountVectorizer In [37]: MIN_DF = 2 In [38]: count_vect = CountVectorizer(lowercase=True, min_df=MIN_DF) In [38]: X_cnt_train = count_vect.fit_transform(texts_train) In [40]: X_cnt_val = count_vect.transform(texts_val) In [41]: X_cnt_test = count_vect.transform(texts_test)
CountVectorizerは単語の出現回数を特徴として抽出しますが、TF-IDF特徴として抽出するTfidfVectorizerも用意されています。
In [42]: from sklearn.feature_extraction.text import TfidfVectorizer In [43]: tfidf_vect = TfidfVectorizer(lowercase=True, min_df=MIN_DF) In [44]: X_tfidf_train = tfidf_vect.fit_transform(texts_train) In [45]: X_tfidf_val = tfidf_vect.transform(texts_val) In [46]: X_tfidf_test = tfidf_vect.transform(texts_test)
CountVectorizerとTfidfVectorizerには様々なオプションが用意されています。色々な特徴抽出方法を試してみましょう。
単語の符号化について
本コンペティション用のデータでは、単語はある規則にしたがって符号化されています。たとえば、以下のように符号化されています。
- Fly -> F86155
- Flying -> F86155b43
- flight -> f86155j152
ここから、以下のような対応関係が推測されます。
| F | ly | ing |
| F | 86155 | b43 |
末尾のb43には何か意味がありそうです。b43の出現回数を特徴として抽出してみましょう。CountVectorizerのtoken_patternパラメータでは、抽出パターンを正規表現で指定することができます。
In [47]: TOKEN_PATTERN_B43 = u'(?u)\\b\\w+b43\\b' In [48]: count_vect_b43 = CountVectorizer(lowercase=True, min_df=MIN_DF, token_pattern=TOKEN_PATTERN_B43) In [49]: X_cnt_b43_train = count_vect_b43.fit_transform(texts_train).sum(axis=1) In [50]: X_cnt_b43_val = count_vect_b43.transform(texts_val).sum(axis=1) In [51]: X_cnt_b43_test = count_vect_b43.transform(texts_test).sum(axis=1)
各単語の出現回数を用いた特徴(X_cnt_train, X_cnt_val, X_cnt_test)と新しく作成した特徴量(X_cnt_b43_train, X_cnt_b43_val, X_cnt_b43_test)を結合することもできます。
In [52]: import scipy In [53]: X_cnt_combine_b43_train = scipy.sparse.hstack([X_cnt_train, X_cnt_b43_train]) In [54]: X_cnt_combine_b43_val = scipy.sparse.hstack([X_cnt_val, X_cnt_b43_val]) In [55]: X_cnt_combine_b43_test = scipy.sparse.hstack([X_cnt_test, X_cnt_b43_test])
色々な特徴量を作ってみましょう。
クロスバリデーション
これまでは訓練データを学習用(80%)と評価用(20%)に分割して分類器の学習と性能確認を行いました。より精緻な性能確認手法に、クロスバリデーションがあります。クロスバリデーションでは、データをK分割し、「1つを評価用」「残りK-1個を学習用」に利用するプロセスをK回繰り返して、分類器の平均的な性能を確認します。scikit-learnではcross_validationが提供されています。ここでは、K=5の場合に、AUCの平均と標準偏差を出力するコードを紹介します。
In [56]: from sklearn.cross_validation import KFold In [57]: from sklearn.naive_bayes import MultinomialNB In [58]: kf = KFold(y.shape[0], n_folds=5, shuffle=False) In [59]: aucs = [] In [60]: for train, val in kf: X_train_cv, y_train_cv = X[train], y[train] X_val_cv, y_val_cv = X[val], y[val] clf_cv = MultinomialNB(alpha=1.0) clf_cv.fit(X_train_cv, y_train_cv) y_pred_cv = clf_cv.predict_proba(X_val_cv)[:, 1] auc = roc_auc_score(y_val_cv, y_pred_cv) aucs.append(auc) In [61]: np.mean(aucs), np.std(aucs) Out[61]: (0.67273428817310921, 0.030980080080578294)
