人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

Machine Learning with Scikit Learn (Part I)

今年の7月に開催されたSciPy2015の講演動画がEnthoughtのチャンネルで公開されている。今年も面白い講演が多いのでいろいろチェックしている。

今年の目標(2015/1/11)にPythonの機械学習ライブラリであるscikit-learnを使いこなすというのが入っているので、まずはscikit-learnのチュートリアルを一通り見ることにした。

Part IとPart IIを合わせると6時間以上あり非常に充実している。IPython Notebook形式の資料やデータは下記のGitHubアカウントで提供されている。ノートブックをダウンロードし、実際に手を動かしながらチュートリアルを進めると理解がより進むかもしれない。

あとで振り返りやすいように内容を簡単にまとめておきたい。

1.1 Introduction to Machine Learning

機械学習システムの流れ。教師あり学習と教師なし学習の違い。訓練データとテストデータは分けましょうという一般的なお話。

1.2 IPython Numpy and Matplotlib Refresher

scikit-learnのベースとなるNumPy、SciPy、Matplotlibのおさらい。言語処理では疎行列表現(Sparse Matrix)が効率的なのでscipy.sparseを使える。NumPy、SciPy、Matplotlibは非常に巨大なライブラリなので別途習得する必要がある。NumPyやMatplotlibについては以下の講演が過去にあった。これらもあとでチェックしておこう。

np.newaxisを使った列ベクトルへの変換法は覚えておくと便利。1次元配列を行がサンプル、列が特徴量のscikit-learnのデータ形式に変換するときなどに使える。

# make into a column vector
print y[:, np.newaxis]

1.3 Data Representation for Machine Learning

scikit-learnのデータ表現は、行がサンプル、列が特徴量の2次元配列で表す。有名なデータセットはsklearn.datasetsモジュールでロードまたは生成できる。iris、digits、S-curve、olivetti_facesが例として取り上げられている。顔画像データなんてのも提供されているのか。あとで使ってみたいな。

f:id:aidiary:20150808112642p:plain

1.4 Training and Testing Data

訓練データとテストデータの作り方。データはシャッフルされていないこともあるので必ずシャッフルする。データのシャッフルは、permutationをインデックスに指定すると簡単にできる。

import numpy as np
rng = np.random.RandomState(0)
permutation = rng.permutation(len(X))
X, y = X[permutation], y[permutation]

訓練データとテストデータの分割は、sklearn.cross_validation.train_test_split()を使うと簡単にできる。random_stateに乱数の種を指定すれば自分でシャッフルしなくてもOK。

from sklearn.cross_validation import train_test_split
train_X, test_X, train_y, test_y = train_test_split(X, y, train_size=0.5, random_state=1999)

2.1 Supervised Learning - Classification

教師あり学習の一種である分類について。sklearn.datasets.make_blobs()で人工的に生成した二次元データを使用。scikit-learnでは、どの機械学習アルゴリズムも

  1. 分類器のオブジェクトを生成
  2. fit()に訓練データを与えて分類器のパラメータを学習
  3. predict()にテストデータを与えて予測
  4. score()で分類器の性能を評価

というAPIで統一されているので覚えやすい。例としてロジスティック回帰K-近傍法の例が紹介されている。

from sklearn.linear_model import LogisticRegression
classifier = LogisticRegression()
classifier.fit(X_train, y_train)
prediction = classifier.predict(X_test)
classifier.score(X_test, y_test)

f:id:aidiary:20150808112655p:plain

学習したパラメータは、分類器オブジェクトのプロパティから取得できる。

print(classifier.coef_)
print(classifier.intercept_)

K-近傍法の例。ロジスティック回帰とメソッド名が同じなので覚えやすい。分類器を作成するときにアルゴリズムによって独自のパラメータを指定できる。

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train, y_train)
knn.score(X_test, y_test)

f:id:aidiary:20150808112942p:plain

2.2 Supervised Learning - Regression

教師あり学習の一種である回帰について。回帰は教師信号が連続値である点が分類と異なる点。例として線形回帰K-近傍法が取り上げられている。K-近傍法は分類だけでなく、回帰にも使えるのか。

from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(X_train, y_train)

f:id:aidiary:20150808125332p:plain

from sklearn.neighbors import KNeighborsRegressor
kneighbor_regression = KNeighborsRegressor(n_neighbors=1)
kneighbor_regression.fit(X_train, y_train)

f:id:aidiary:20150808125335p:plain

2.3 Unsupervised Learning - Transformations and Dimensionality Reduction

教師なし学習のデータの標準化と次元削減について。

データの標準化を学習とは言わないような気がするけど、scikit-learnでは他の学習器と同じインタフェースで実装されている。たとえば、平均を0、分散を1にする正規化は、StandardScalarを使って以下のように書ける。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)

標準化はデータの平均、標準偏差というパラメータを推定するという見方もできるためfit()というメソッドでも特に違和感はない。学習したモデルを使ってデータを変換する(標準化する)ときはpredict()ではなく、transform()というメソッドが定義されている。

主成分分析もほとんど同じように書ける。

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_blob)
X_pca = pca.transform(X_blob)

n_componentsで固有値が大きい順にいくつの固有ベクトル(軸)を取るか指定している。この例では、n_components=2なのでデータを2次元平面に圧縮することを意味する。次元削減はtransform()で行える。X_blobが元のデータでX_pcaが低次元空間に写像したデータになる。8x8ピクセル=64次元の数字画像データを2次元に写像すると下の結果が得られる。

f:id:aidiary:20150808134347p:plain

高次元データを低次元のデータに縮約する手法をまとめて多様体学習(manifold learning)と呼ぶようだ。有名どころは主成分分析や多次元尺度構成法(MDS)だが、それ以外にも様々な手法が提案されており、一部はsklearn.manifoldに実装されている。たとえば、t-SNEを同じ数字画像データに適用すると

f:id:aidiary:20150808142506p:plain

となり2次元空間で元の数字をきれいに分類できるようになる。この分野は非常に興味があるので勉強してみたいところ。

2.4 Unsupervised Learning - Clustering

教師なし学習の代表であるクラスタリングについて。有名なK-meansが例として取り上げられている。

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3, random_state=42)
labels = kmeans.fit_predict(X)

K-meansはユーザがクラスタ数をあらかじめ指定する必要がある。この例ではクラスタ数を3としている。講演では、K-means以外の様々なクラスタリング手法も紹介されている。

f:id:aidiary:20150808142434p:plain

こんなにいろいろあったのか。K-meansとWard法くらいしか知らなかった。私の主観的なクラスタの判断だとDBSCANってのが一番近いかな?ここもしっかり勉強してみたいところ。個人的に教師あり学習よりも教師なし学習や表現学習の分野の方が興味ある。

2.5 Review of Scikit-learn API

scikit-learnで使われる fit(), predict(), transform() などのメソッド名についてのまとめ。教師あり学習、教師なし学習で提供されているメソッド名が異なる。さらにアルゴリズムによってもメソッドの意味が異なる場合がある。たとえば、score()は教師あり学習では分類精度、平均二乗誤差を返すが、一部の教師なし学習では尤度を返す。このノートブックにまとめられているので適宜参照したい。

3.1 Case Study - Supervised Classification of Handwritten Digits

ここからしばらく具体的なケーススタディが続く。最初は手書き数字認識

f:id:aidiary:20150808195811p:plain

  1. 手書き数字データはsklearn.datasets.load_digits()でロードできる。
  2. データ数が多いときはPCAより高速なsklearn.decomposition.RandomizedPCAを使ってデータの分布を可視化する。
  3. PCAは線形の次元圧縮なのでデータの面白い関係を見逃している可能性がある。そのような場合は、sklearn.manifold.Isomapのような非線形な多様体学習手法を使う。
  4. まずは簡単で高速な分類器(たとえば、sklearn.naive_bayes.GaussianNB)を使ってベースラインとする。
  5. テストデータを用いて定量的な評価をする。分類問題ではmodel.score()が使える。また、適合率・再現率・F1値をまとめて返すsklearn.metrics.classification_report()やConfusion Matrixを返すsklearn.metrics.confusion_matrix()も便利。
from sklearn import metrics
print metrics.classification_report(expected, predicted)

             precision    recall  f1-score   support

          0       1.00      1.00      1.00        37
          1       0.68      0.91      0.78        43
          2       1.00      0.39      0.56        44
          3       0.76      0.84      0.80        45
          4       0.93      0.71      0.81        38
          5       0.98      0.90      0.93        48
          6       0.95      1.00      0.97        52
          7       0.70      0.98      0.82        48
          8       0.55      0.77      0.64        48
          9       1.00      0.57      0.73        47

avg / total       0.85      0.81      0.80       450
print metrics.confusion_matrix(expected, predicted)

[[37  0  0  0  0  0  0  0  0  0]
 [ 0 39  0  0  0  0  1  0  3  0]
 [ 0  9 17  3  0  0  0  0 15  0]
 [ 0  0  0 38  0  0  0  2  5  0]
 [ 0  1  0  0 27  0  2  8  0  0]
 [ 0  1  0  1  0 43  0  3  0  0]
 [ 0  0  0  0  0  0 52  0  0  0]
 [ 0  0  0  0  1  0  0 47  0  0]
 [ 0  5  0  1  0  1  0  4 37  0]
 [ 0  2  0  7  1  0  0  3  7 27]]

3.2 Methods - Unsupervised Preprocessing

Labeled Faces in the Wildの顔画像データに対して主成分分析を適用する。いわゆる固有顔(eigenface)の実験。このデータもメソッド一つでロードできる。

from sklearn import datasets
lfw_people = datasets.fetch_lfw_people(min_faces_per_person=70, resize=0.4, data_home='datasets')

これが訓練データの平均顔。著作権の問題からブッシュ元大統領の顔写真が多いんだってさ(笑)

f:id:aidiary:20150808202443p:plain

これが各固有ベクトルを画像化した固有顔。

f:id:aidiary:20150808202230p:plain

最初の数個の軸は画像の明るさの違いを表していることがわかる。これら固有顔の線形結合でいろいろな顔が表せる。

3.3 Case Study - Face Recognition with Eigenfaces

オリジナル画像の1850次元の顔画像データを150次元の固有顔空間に次元圧縮したデータを用いて顔認識の実験。分類にはサポートベクトルマシン(SVM)を使用。

from sklearn import svm
clf = svm.SVC(C=5., gamma=0.001)
clf.fit(X_train_pca, y_train)

メソッド名はsklearn.svm.SVCという名前なので注意。Support Vector Classificationの略。sklearn.svm.SVRという回帰版もある。こちらはSupport Vector Regressionの略。

f:id:aidiary:20150808203237p:plain

オリジナル画像データをそのまま使うと40%くらいの精度しか出ないが、固有顔に次元圧縮してから分類すると85%くらいの精度が出る。

PCAをしてからSVMのような流れ作業はPipelineの機能を使うと便利。

from sklearn.pipeline import Pipeline
from sklearn import metrics
clf = Pipeline([('pca', decomposition.RandomizedPCA(n_components=150, whiten=True, random_state=1999)),
                ('svm', svm.SVC(C=5., gamma=0.001))])
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print metrics.classification_report(y_pred, y_test)

3.4 Methods - Text Feature Extraction

ここから対象がテキストになる。英語のテキストを特徴量に変換するbag-of-wordsの作り方について。CountVectorizerを使うとテキスト集合を単語の出現頻度を用いたbag-of-words表現に簡単に変換できる。

from sklearn.feature_extraction.text import CountVectorizer
X = ["Some say the world will end in fire,",
     "Some say in ice."]
vectorizer = CountVectorizer()
vectorizer.fit(X)
X_bag_of_words = vectorizer.transform(X).toarray()

単語の頻度TFだけでなく、逆文書頻度IDFも考慮したTF-IDFTfidfVectorizerを使うと簡単に求められる。IDFを考慮すると多くの文書に出てくる単語が一般的な単語と判断されてその重要度が低くなる。

from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer()
tfidf_vectorizer.fit(X)
tfidf = tfidf_vectorizer.transform(X).to_array()

単語単位ではなく、N-gram単位でbag-of-wordsやTF-IDFを求めたい場合は、ngram_rangeオプションを指定すればOK。

# look at sequences of tokens of minimum length 2 and maximum length 2
bigram_vectorizer = CountVectorizer(ngram_range=(2, 2))
bigram_vectorizer.fit(X)

日本語では形態素解析が必要なのでこれらのメソッドはそのままでは使えなさそう。誰か日本語用の拡張も書いてたりするかな?

3.5 Case Study - SMS Spam Detection

テキストをbag-of-words表現に変換し、スパムのテキスト分類を行う例が紹介されている。訓練データの文章数は4180でボキャブラリ数は7464。ロジスティック回帰を使うと98%の分類精度が出ている。

3.6 Case Study - Titanic Survival

講演では時間が足りなくて取り上げられなかった(PartIIの最後に時間があまったため説明されていた)。カテゴリ特徴量を使うときは、各カテゴリに数値を割り当てるのではなく、いわゆる1-of-K表現にしましょうというお話。DictCategorizerを使うと簡単にできる。以下の例では、カテゴリ特徴量 city = {Dubai, London, San Francisco}と数値特徴量 temperature が混ざったデータをベクトル化している。

measurements = [
    {'city': 'Dubai', 'temperature': 33.},
    {'city': 'London', 'temperature': 12.},
    {'city': 'San Francisco', 'temperature': 18.},
]
from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer()
vec.fit_transform(measurements).toarray()

実行結果は、

array([[  1.,   0.,   0.,  33.],
       [  0.,   1.,   0.,  12.],
       [  0.,   0.,   1.,  18.]])

カテゴリ特徴量のみ1-of-K表現になって、数値特徴量はそのままになることがわかる。より複雑なデータセットとしてカテゴリ特徴量と数値特徴量が混ざったデータであるTitanic Dataを例に説明している。

ここで動画のPart Iは終わり。Part IIにつづきます。