機械学習とは、その名の通り「機械」を「学習」させることで、あるデータに対して予測を行えるようにすることです。
学習の方法は大きく分けて2つあります。
scikit-learnは、Python製の機械学習ライブラリです。
この中には様々な「機械」が実装されており、その「学習」のための仕組みも備わっています。
以下では、このscikit-learnを利用しデータを準備するところから実際にモデルを構築し学習・評価を行うまでの手順を解説します。
環境のセットアップについては、以下にまとめてあるのでご参考ください。
In [1]:
# enable showing matplotlib image inline
%matplotlib inline
scikit-learnでは、よく例として利用されるデータセット(irisのデータや手書き文字のデータなど)を以下のように簡単に取得することができます。
In [2]:
from sklearn import datasets
iris = datasets.load_iris()
digits = datasets.load_digits()
dataset
は、以下の内容で構成されています。
data
: データ本体(常にサンプル×特徴量の二次元配列。画像などデータ自体が2次元で表示される場合は、images
からアクセスできる)target
: データから予測されるべき正解(教師データ)feature_names
: 特徴量項目の名前target_names
: 予測値項目の名前DESCR
: データの説明
In [3]:
print(iris.keys())
通常のデータ読み込みには、Pythonに標準で搭載されているcsv
などが使えます。
In [4]:
import csv
import numpy as np
encoding = "utf-8"
ratings = []
with open("./data/ratings.txt", encoding="utf-8") as f:
content = csv.reader(f, delimiter="\t")
lines = list(content)
ratings = np.array(lines)
print(ratings)
なお、このようなデータの読み込み、また読み込んだデータに対する操作をサポートするライブラリとしてpandasがあります。
データの中の各特徴量は、別々の平均・分散を持っています(例: 体重と身長では平均も分散も異なる)。
この状態だと学習が効率的に進まないため、各特徴量の平均を0・分散を1にそろえる正規化(Normalization)を行うことが一般的です。
(これに加え、特徴量間の相関を消す白色化(Whitening)まで行うこともあります)。
scikit-learnではpreprocessing
を使用することでこの作業をとても簡単に行うことができます。以下では、StandardScaler
を使って処理を行っています。
In [5]:
from sklearn import datasets
import numpy as np
from sklearn import preprocessing
iris_data = iris["data"]
scaler = preprocessing.StandardScaler().fit(iris_data)
describe = lambda t, x: (t + ":\n {0}").format({"mean": np.mean(x, axis=0), "std": np.std(x, axis=0)})
# before scaling
print(describe("Before scaling", iris_data))
# scaling
iris_data_scaled = scaler.transform(iris_data)
print(describe("After scaling (mean is almost 0, std = 1)", iris_data_scaled))
# inverse
iris_data_inv = scaler.inverse_transform(iris_data_scaled)
print(describe("Inverse the scaling", iris_data_inv))
※preprocessing
にはNormalization
というモジュールがありますが、これは一般的に言う正規化を行うためのものではないので注意してください。
また、データの中にはテキストである項目が含まれていることもあります。
In [6]:
print(ratings)
上記のgood
などのテキスト項目は、最終的には数値にしないとデータを学習させることができません。 これもpreprocessing
を利用することで簡単に数値へ変換することができます。
In [7]:
from sklearn import preprocessing
le = preprocessing.LabelEncoder()
le.fit(["bad", "nbad", "good", "vgood"])
encoded_rating = le.transform(ratings[:, 1])
print("{0} is encoded to {1}".format(ratings[:, 1], encoded_rating))
Feature Extractionでは、テキスト/画像についてより強力に特徴量の数値化(ベクトル化)を行う機能がサポートされています。以下では、cityというテキストの項目がDubai/London/San Fransiscoを表す0/1の特徴量へと変換されています。
In [8]:
from sklearn.feature_extraction import DictVectorizer
measurements = [
{"city": "Dubai", "temperature": 33.},
{"city": "London", "temperature": 12.},
{"city": "San Fransisco", "temperature": 18.},
{"city": "Dubai", "temperature": 32.},
]
vec = DictVectorizer()
vectorized = vec.fit_transform(measurements).toarray()
print(vectorized)
feature_names = vec.get_feature_names()
print(feature_names)
preprocessing
には、他にも欠損値の修正を行うImputer
などデータの整備に役立つモジュールが含まれています。
データを図示することは、この後のモデルの選択を行う時を含め、様々なシーンで非常に重要です。 しかし、単純に特徴量が4つになっただけでもデータを図示することができなくなってしまいますし(4次元の図になってしまうため)、場合によっては非常に多くなることもあります(テキスト解析など)。
そのため、データをなるべく少ない、必要最小限の特徴量で表現することが重要になります。これを行うのがDimensionality reduction(次元削除/次元圧縮)と呼ばれる手法です。
具体的には、データの中に身長と体重があった場合、これらは体が大きくなれば両方とも増える特徴量のため、データの特性を表す上ではどちらかひとつで十分です。このように片方が増えれば片方も増えるといった、互いに相関のある特徴量を消していけば必要最小限の特徴量でデータを表現することができる・・・というのが基本的な考え方です。
scikit-learnではdecomposition
を利用しこの処理を行うことができます。以下では、TruncatedSVDによって数字データの特徴量を、上記で述べたとおり互いに相関のない、2つの特徴量へと圧縮しています。
実際にデータを図示するには、scikit-learnではなくmatplotlibを利用します。 以下では、最初の2つの特徴量をピックアップし、irisのデータをプロットしています。
In [9]:
import matplotlib.pyplot as plt
features = iris.data[:, :2] # select first 2 feature
label = iris.target
plt.scatter(features[:, 0], features[:, 1], c=label, cmap=plt.cm.Paired)
for i in range(features.shape[1]):
f_data = features[:, i]
if i == 0:
plt.xlabel(iris.feature_names[i])
plt.xlim(f_data.min(), f_data.max())
else:
plt.ylabel(iris.feature_names[i])
plt.ylim(f_data.min(), f_data.max())
plt.title("iris data")
Out[9]:
In [10]:
from sklearn import decomposition
digits_data = digits["data"]
show_dimension = lambda dset: len(dset[0])
dimension = 2
digits_recuced = decomposition.TruncatedSVD(n_components=dimension).fit_transform(digits_data)
print("Dimension is reduced from {0} to {1}.".format(show_dimension(digits_data), show_dimension(digits_recuced)))
機械学習に使えるモデルには色々なものがあり、scikit-learnでも様々なモデルが使えるようになっています。
ただ、その分一体どれを選べば良いのかは非常に悩ましい問題です。
一つの基準として、以下のようなフローチャートがあります。これは、scikit-learnの中のアルゴリズムをどのような基準で選択したらよいのかを図示したものです。
scikit-learnにはNeural Networkがないため図中にもありませんが、基本的にはSVC/SVRの代替であり、データが多いほど精度が向上します。
ポイントとしては以下になります。
機械学習で正しい結果を出すにはデータの整備(図中ではJust looking、前章に当たる部分)が欠かせません。データ整備した上で単純なモデルで検証をしてみて、必要に応じ他のモデルを試していくというのが基本的な進め方になります。
特徴量が多い場合は、どの特徴量をモデルに使うのかも重要な問題です。scikit-learnには、どの特徴量が予測値に寄与しているか調べるための機能があります。 以下では、Feature selectionを利用し特徴量をもっとも有用な2つに絞っています(k=2)。
In [11]:
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
X, y = iris.data, iris.target
print(X.shape)
X_new = SelectKBest(chi2, k=2).fit_transform(X, y)
print(X_new.shape)
学習に当たっては、データを学習用(training set)と評価用(test set)に分けておきます。学習に使ったデータに対して予測がうまくできるのは当たり前なので、正確に精度を測定するため、評価用のデータは学習用とは別にしておきます。
単純に学習用と評価用に2分割するのでなく、データ全体を何個かに分割し、評価用として使うデータを切り替えていくという方法もあります。これにより、少ないデータでも効率的に学習を行うことができます。
K-FOLD CROSS-VALIDATION, WITH MATLAB CODE
これはCross Validationと呼ばれる手法ですが、scikit-learnでは単純な分割からこのCross Validationまで、cross-validation
で行えるようになっています。
In [14]:
from sklearn.model_selection import train_test_split
test_size = 0.3 # use 30% of data to test the model
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=test_size, random_state=0)
test_data_rate = X_test.shape[0] * 100 / (X_train.shape[0] + X_test.shape[0])
print("test data is {0}% of data".format(test_data_rate))
Cross Validationを利用する際は、データの分割と学習を合わせて行ってくれるcross_val_score
を利用するのが簡単ですが(後述します)、データの分割のみ行う場合はKFold
を利用します。
In [16]:
from sklearn.model_selection import KFold
kf = KFold(n_splits=3) # divide into 3 set
i = 0
for train_index, test_index in kf.split(iris.data):
x_train = iris.data[train_index]
y_train = iris.target[train_index]
x_test = iris.data[test_index]
y_test = iris.target[test_index]
print("{0}: training {1}, test {2}".format(i, len(y_train), len(y_test)))
i += 1
これでデータの準備は整ったので、いよいよ学習を行っていきます。
以下では、分類を行う際によく利用されるSupport Vector Machineをベースにその学習方法などを解説していきます。
In [17]:
from sklearn import svm
clf = svm.SVC(gamma=0.001, C=100.)
モデルの構築はたったこれだけでおしまいです。そして、学習もたった一行で済ませることができます(以下の例では、最後の1データ以外を学習データとして渡しています)。
In [18]:
clf.fit(digits.data[:-1], digits.target[:-1])
Out[18]:
そして、取っておいた最後の一つのデータについて、モデルを使って予測させてみます。
In [20]:
clf.predict([digits.data[-1]])
Out[20]:
実際判定した画像は以下です。「8」という予測はそこそこ的を得ているのではないかと思います。
In [21]:
import matplotlib.pyplot as plt
plt.figure(1, figsize=(3, 3))
plt.imshow(digits.images[-1], cmap=plt.cm.gray_r, interpolation='nearest')
plt.show()
今度はCross Validationを使ってみます。cv
ではデータの分割数(foldの数)を指定します。
In [22]:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf, digits.data, digits.target, cv=5)
print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
上記ではモデルのパラメータを固定で指定しましたが(gamma=0.001
など)、実際どんなパラメータを設定すべきかは非常に悩ましい問題です。
最適なパラメータを探すため、各パラメータが取りうる範囲を決め、その組み合わせを試していくという手法があります。これをGrid Searchと呼びますが、scikit-learnではこれを行うためのGrid Searchモジュールが提供されています。
In [27]:
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
candidates = [{'kernel': ['rbf'], 'gamma': [1e-3, 1e-4], 'C': [1, 10, 100]},
{'kernel': ['linear'], 'C': [1, 10, 100]}]
clf = GridSearchCV(SVC(C=1), candidates, cv=5)
clf.fit(digits.data, digits.target)
print(clf.best_estimator_)
for params, mean_, std_ in zip(clf.cv_results_["params"], clf.cv_results_["mean_test_score"], clf.cv_results_["std_test_score"]):
print("%0.3f (+/-%0.03f) for %r" % (mean_, std_ / 2, params))
学習の結果、優秀なモデルができることもあればそうでないこともあります。 特徴量が増える、つまり多次元になるほど「よさそうに見えるパラメーターの組み合わせ」は増えていくので、何度も学習させてみないとなかなか最適と思える結果にはたどり着けなくなります。
こうした問題に対応するため、複数のモデルを別々に学習させ、最終的にはそれらの組み合わせで決めることで精度を上げるという手法があります。
これがアンサンブル学習と呼ばれる手法です。「複数のモデル」は、同じモデルのこともあれば(Bagging)それぞれ異なるモデルを使うこともあります(Boosting)。
scikit-learnでは、ensembleによってこのアンサンブル学習を行うことができます。以下では、Baggingによって10個のモデルを並列で学習させ、その組み合わせで決めるモデルを作成しています。
In [28]:
from sklearn.ensemble import BaggingClassifier
from sklearn.svm import SVC
base_clf = svm.SVC()
bagging_clf = BaggingClassifier(base_estimator=clf, n_estimators=10, max_samples=0.9, max_features=2, n_jobs=4)
scores = cross_validation.cross_val_score(clf, iris.data, iris.target, cv=5)
print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
n_jobs
により並列で学習させる際のプロセス数を簡単に調整することができ、高速な学習が可能です
学習した結果、つまりモデルの精度はどのように測ればよいでしょうか。 単純には予測と実際の2つを比較することで精度を算出することができます。ただ、この手法だと例えば「90%がAで10%がB」というデータがあった場合、常にAという単純なモデルが生まれていたとしてもその精度は90%となってしまいます。
こうした問題を防ぐため、分類結果を以下のようにまとめ評価を行う混同行列(confusion matrix)があります。
ここでは、以下3点の観点が重要です。
先ほどの例だと、偽とは予測しないため再現率は常に1になりますが、その分予測したもののうち間違いだったものが増えるため、データが増えるにつれ適合率が悪化します。適合率を上げる場合は逆にほぼ間違いないもの以外を予測しなければ良いですが、その分本当は真だったものが増えるため再現率が下がることになります。
このように適合率と再現率はトレードオフの関係にあり、良いモデルとは基本的にはこの2つの値のバランスが取れているものになります。このバランスを評価する値としてF値があり、これらの値を図ることがモデルの精度を測る上で重要になります。
scikit-learnでは、metricsを利用することでこれらの値を簡単に参照することができます。
In [29]:
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
actual = [1, 0, 1, 1, 0, 0, 0, 1]
predict = [0, 0, 1, 1, 0, 1, 1, 1]
c_mx = confusion_matrix(actual, predict)
print(c_mx)
# calculate each score
print(precision_score(actual, predict))
print(recall_score(actual, predict))
print(accuracy_score(actual, predict))
print(f1_score(actual, predict))
classification_report
を使用することで、簡単に一覧表を取得できます(単純に値だけ取得したい場合はprecision_recall_fscore_support
)。
In [30]:
from sklearn.metrics import classification_report
actual = [0, 1, 2, 2, 2]
predict = [0, 0, 2, 2, 1]
target_names = ["class 0", "class 1", "class 2"]
print(classification_report(actual, predict, target_names=target_names))
学習したモデルはファイルとして出力し保存しておくことができます。
以下は、標準のpickle
を使う手法です。
In [31]:
from sklearn import svm
from sklearn import datasets
clf = svm.SVC()
iris = datasets.load_iris()
X, y = iris.data, iris.target
clf.fit(X, y)
import pickle
s = pickle.dumps(clf) #serialize model data
clf2 = pickle.loads(s) #load serialized model data
このほか、sklearn.externals
のjoblib
を利用しファイルに保管することもできます。大規模なモデルなどにはこちらの方がよいでしょう。
In [32]:
from sklearn.externals import joblib
joblib.dump(clf, "data/model.pkl")
clf = joblib.load("data/model.pkl")
以上で解説は終了です。scikit-learnは公式ドキュメントが充実しているので、そちらもご参照ください。