今回は、Logistic Regression(ロジスティクス回帰)による2項分類器(Binary Classifier)を利用してTensorFlowによる機械学習の流れをご紹介します。2項分類器とは、分類するラベルが、例えば、表か裏か、ウィルスに感染しているかいないか、と行ったように2通りのみに分類できる場合のシンプルな機械学習アルゴリズムを言います。また、TensorFlowは、米Google社がオープンソースソフトウェアとして公開している機械学習ライブラリーです。特にディープラーニングへの適用を意識した仕組みとなっていますが、ここでは、最も基礎的なTensorFlowの処理の流れを追って見たいと思います。
まずTensorFlowライブラリーを導入するにあたって、Python 3.6以上と以下のパッケージ・ライブラリーが事前に導入されていることをご確認ください。未導入の場合は、第2回を参考に導入していただければと思います。。 ※Python 2.7においても、Tensorflowは使用可能なのですが、機械学習のための機能が豊富に揃ったscikit-learnライブラリーは、Python3以上でないと使用できないため、私はPython 3以上をお勧めいたします。
- Numpy
- SciPy
- scikit-learn
- matplotlib
- pandas
次にTensorFlowのライブラリーを導入します。
[1] Windows
DOSコマンドプロンプトから以下のコマンドを実行します。
C:\> pip3 install --upgrade tensorflow |
[2] Mac OS X
ターミナルから以下のコマンドを実行します。
$ pip3 install --upgrade tensorflow |
[3] Ubuntu
Xterm から以下のコマンドを実行します。
$ sudo apt-get install python3-pip python3-dev $ pip3 install tensorflow |
なお、TensorFlowライブラリーが導入できるのは64bitのOSのみです。32bitのOSには導入できませんので、ご注意ください。その他、導入の詳細は、以下のサイトをご参照ください。
https://www.tensorflow.org/install/
正しく導入できたかの確認は、Pythonを対話モードで起動し、以下のコマンドで、エラーが表示されなければ成功です。
>>> import tensorflow as tf >>> |
今回は、第4回でご紹介した「アイリス花データ」を使用して、TensorFlowによる機械学習を実際に実行してみたいと思います。アイリス花データの場合、変量として、がく片(sepal)の長さと幅、花びら(petal)長さと幅をcmで計測した4つの変量があり、ラベルとしては、アイリスの花の種類(Iris setosa/Iris versicolor/Iris virginica)の3種類がありました(詳しくは第4回をご参照ください)。4つの変量がありますが、2次元の図にプロットする都合上、がく片の長さ[cm]と花びらの長さ[cm]の2つの変量から、TensorFlowを使用してアイリスの花の種類を予測する機械学習モデルを作成します。TensorFlowを使用した機械学習の流れは、概ね以下になります。なお、Pythonのバージョンは3.5以上を想定しています。
- データを入力する。
- 変量のデータを標準偏差と平均値を使用して標準化する。
- TensorFlowにより最適化すべきVariableとNet Input関数および活性化関数を定義する。
- TensorFlowにより誤差関数とその最適化法を定義する。
- TensorFlowにより精度を定義する。
- TensorFlowによりセッションを定義する。
- TensorFlowによりセッションを実行する。
- 分類結果を表示するためのプロット関数を定義する。
- 分類結果を図に表示する。
では、各ステップを詳しく見ていきましょう。
①データを入力する。
まず、対話形式ではなく、Pythonのスクリプトをファイルで用意します。Pythonスクリプトの先頭に以下の2行を添付しておくことをお勧めします。
#!/usr/bin/env python # -*- coding:utf-8 -*- |
第4回でご紹介した「アイリス花データ」を、オープンソースとして提供しているサイトのURLから、pandasライブラリーを使用して入力します。「アイリス花データ」では、3種類の花(Iris-setosa/Iris-versicolor/Iris-virginica)の150個のデータの内、最初の50行目までが「Iris-setosa」、51行目から100行目までが「Iris-versicolor」、101行目から150行目までが「Iris-virginica」の順で並んでいます。今回は、最初の100行目のみを抽出し、「Iris-setosa」か「Iris-versicolor」を判別する2項分類器を作成します。ラベル(花の種類)を、「Iris-setosa」の場合は"0"に,「Iris-versicolor」の場合は"1"に変換後、y_matrix(100 x 1)という配列に100サンプル分のデータを格納します。変量の内、がく片と花びらの長さだけを取り出すために、iris.dataの変量の内、0番目と2番目の要素だけを抽出してXの配列(100 x 2)に格納します。
import pandas as pd # extract iris.data in csv format df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None) import numpy as np # select setosa and versicolor y = df.iloc[0:100, 4].values y = np.where(y == 'Iris-setosa', 0, 1) y_matrix = y.reshape(len(y),1) # extract sepal length and petal length X = df.iloc[0:100, [0, 2]].values |
②変量のデータを標準偏差と平均値を使用して標準化する。
次に、変量配列のデータを、sklean.preprocessingライブラリーのStandardScaler関数とそのfit_transformメソッドを用いて、標準化します。標準化した変量配列をX_stdに格納します。
from sklearn.preprocessing import StandardScaler sc = StandardScaler() X_std = sc.fit_transform(X) |
③TensorFlowにより最適化すべきVariableとNet Input関数および活性化関数を定義する。
ここからが、TensorFlowの実際の記述になります。その前にLogistic Regression(ロジスティクス回帰)の仕組みについて少し説明しておきたいと思います。Logistic Regressionでは、入力データの各変量 x1, x2, ... ,xmについて、各係数w1, w2, ... ,wmを掛け合わせた和を「Net Input 関数」と言います。 f = w0 + x1*w1 + x2*w3, ... ,xm*wm のように表せます。 そして、事象がある一方の分類である確率をpとすると、このNet Input関数 fを用いて、p = 1/( 1 + exp(-f) ) と定義すると、図2のように滑らかな曲線で表すことができます。pは、0から1の間を変動します。2項分類器の場合は、p ≧ 0.5( f ≧ 0 )の場合はある一方の分類で、p < 0.5 ( f < 0 )の場合は他方の分類であることを示します。Net Input関数の結果を入力にしてラベルを分類する関数を「活性化関数」と呼び、特に図2の関数を「sigmoid関数」と呼びます。活性化関数による計算結果と元の分類データとを比較して、誤差(Error)を計算し、誤差が最小になるように、各係数wの値を再調整するセッションを繰り返します。
図1. 出典:Python Machine Learning: ISBN 978-1-78355-513-0
図2. sigmoid関数
import tensorflow as tf x = tf.placeholder(tf.float32, [None, 2]) w = tf.Variable(tf.zeros([2, 1])) w0 = tf.Variable(tf.zeros([1])) f = tf.matmul(x, w) + w0 p = tf.sigmoid(f) |
TensorFlowでは、入力データを「placeholder」に格納し、機械学習により最適化すべき係数w0およびwは「Variable」に格納します。ここでは、placeholderのデータの型と変量の数(ここでは2)のみを指定し、サンプルの数(ここでは100)は指定しなくて良いです(ここではNoneを指定)。実際のデータ内容はセッション実行時に設定するため、ここでは指定しません。w0とwの値は、それぞれ0(ゼロ)に初期化します。Net Input関数fと活性化関数による確率pを定義します。ここで注意すべきことは、Net Input関数も活性化関数も、必ずtensorflowライブラリーのものを使用しなければならないということです。例えば、Pythonプログラミングとしては、tf.matmul(x,w)もnp.dot(x,w)も全く同じ機能で結果も同じですが、np.dot(x,w)を使用するとTensorFlowのセッション実行時にエラーとなるので、注意が必要です。sigmoid関数に関しても同様で、1/( 1 + np.exp( -f) ) と指定するとセッション実行時にエラーになります。
④TensorFlowにより誤差関数とその最適化法を定義する。
入力データ(花の分類クラス)tのデータの型と大きさ(ここでは1)をplaceholderを用いて定義します。サンプル数(ここでは100)については指定は不要です(ここではNoneを指定)。また、実際のデータ内容はセッション実行時に指定するため、ここでは指定しません。
次に誤差関数を定義します。i番目のデータの分類がtである確率をpとすると、tでない確率は( 1- p )となります。したがって、i番目のデータを正しく予測する確率Piは、Pi = p^t x ( 1-p)^(1-t) のように表すことができます。N個のデータ全てを正しく予測する確率は、P = P1 x P2 x ... xPN = ΠPi = Π{ p^t x ( 1-p)^(1-t) }となり、この確率Pを最大にするようにVariable w0とwの値を最適化すれば良いことになります。これを最尤推定法(maximum likelihood method)と呼びます。したがって誤差関数としては、-Pを最小になるように定義すれば良いわけです。対数関数を使って、次のように記述できます。
loss = - log P = -log Π{ p^t x ( 1-p)^(1-t) } = -Σ{ t*log(p) + (1-t)*log(1-p) }
ここで、Σは、tesorflowライブラリーで、 reduce_sum関数が用意されています。
また、train.AdamOptimizer関数で、誤差関数 lossを最小化するよう設定しています。
t = tf.placeholder(tf.float32, [None, 1]) loss = -tf.reduce_sum(t*tf.log(p) + (1-t)*tf.log(1-p)) train_step = tf.train.AdamOptimizer().minimize(loss) |
⑤TensorFlowにより精度を定義する。
1行目は、(p-0.5)と(t -0.5)の符号を比較することで、計算値pが元データtと一致しているかどうかを判定しています。signは符号を取り出す関数で、equalは、2つの符号が等しいかどうかを判定して、Bool値を返す関数です。correct_predictionには、データ数(ここでは100個)分の要素のBool値が格納されます。2行目では、cast関数で、correct_predictionの各要素のBool値を1,0の値に変換します。reduce_mean関数は、1,0の値に変換されたcorrect_predictionの各要素の平均値を返します。これが結局、機械学習アルゴリズムの精度となります。
correct_prediction = tf.equal(tf.sign(p-0.5), tf.sign(t-0.5)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) |
⑥TensorFlowによりセッションを定義する。
Variable(w0とw)の値を最適化するための、セッションを定義して、Variableの値を初期化します。
sess = tf.Session() sess.run(tf.initialize_all_variables()) |
⑦TensorFlowによりセッションを実行する。
誤差関数が最小になるよう、Variableの最適化を6,000回繰り返します。ここでは、500回繰り返すごとに、その時点での誤差関数lossと精度accuracyの値を計算し、表示しています。sess.runの中で、feed_dictにより、placeholderのx と t にそれぞれ、実際のデータ(変量と分類ラベル)を割り当てています。
i = 0 for _ in range(6000): i += 1 sess.run(train_step, feed_dict={x:X_std, t:y_matrix}) if i % 500 == 0: loss_val, acc_val = sess.run( [loss, accuracy], feed_dict={x:X_std, t:y_matrix}) print ('Step: %d, Loss: %f, Accuracy: %f' % (i, loss_val, acc_val)) |
(実行結果)
Step: 500, Loss: 40.299004, Accuracy: 0.960000
Step: 1000, Loss: 27.179596, Accuracy: 0.960000
Step: 1500, Loss: 20.187582, Accuracy: 0.960000
Step: 2000, Loss: 15.867456, Accuracy: 0.960000
Step: 2500, Loss: 12.905346, Accuracy: 0.970000
Step: 3000, Loss: 10.718846, Accuracy: 0.970000
Step: 3500, Loss: 9.015998, Accuracy: 0.980000
Step: 4000, Loss: 7.637422, Accuracy: 0.980000
Step: 4500, Loss: 6.491001, Accuracy: 0.990000
Step: 5000, Loss: 5.521625, Accuracy: 1.000000
Step: 5500, Loss: 4.694992, Accuracy: 1.000000
Step: 6000, Loss: 3.988387, Accuracy: 1.000000
誤差関数(Loss)の値が減少すると共に、精度(Accuracy)の値が上昇していることがわかります。最終時点での係数(w0とw)の値を取得します。
w0_val, w_val = sess.run([w0, w]) w0_val, w1_val, w2_val = w0_val[0], w_val[0][0], w_val[1][0] print("w0 = %f, w1 = %f, w2 = %f" % ( w0_val, w1_val, w2_val)) |
(実行結果)
w0 = 0.757100, w1 = 1.325688, w2 = 3.527589
⑧分類結果を表示するためのプロット関数を定義する。
>⑦の結果より最終的に得られたw0_val、w_valの値を使用したnet_input関数を定義すると共に、活性化関数predictを定義し、分類結果を表示するためのplot_decision_regions関数を以下のように定義します。
def net_input(X): return np.dot(X,w_val)+w0_val def predict(X): return np.where(1/(1+np.exp(-net_input(X))) >=0.5, 1, 0) import matplotlib.pyplot as plt from matplotlib.colors import ListedColormap def plot_decision_regions(X, y, resolution=0.02): # setup marker generator and color map markers = ('s', 'x') colors = ('red', 'blue') cmap = ListedColormap(colors[:len(np.unique(y))]) # plot the decision surface x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution)) Z = predict(np.array([xx1.ravel(), xx2.ravel()]).T) Z = Z.reshape(xx1.shape) plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap) plt.xlim(xx1.min(), xx1.max()) plt.ylim(xx2.min(), xx2.max()) # plot class samples for idx, cl in enumerate(np.unique(y)): plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=cmap(idx), marker=markers[idx], label=cl) |
⑨分類結果を図に表示する。
⑧で定義したplot_decision_regions関数を実行し、分類結果を表示させます。
plot_decision_regions(X_std, y) plt.xlabel('sepal length [standard]') plt.ylabel('petal length [standard]') plt.legend(loc='upper left') plt.tight_layout() plt.show() |
赤の四角が「Iris-setosa」で、青のX印が「Iris-versicolor」の花を表しています。1つの例外もなく、直線で2つのクラスが分類されていることがわかります。
なお、今回の内容は、以下の書籍の2章を参考に執筆いたしました。ご興味のある方は、ご参考いただければと思います。
「TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~」
中井 悦司 (著)
出版社: マイナビ出版
全体を通してのコードは以下のようになります。なお、本コードの稼働環境は、Python3.5以上を想定しています。
#!/usr/bin/env python # -*- coding:utf-8 -*- import pandas as pd # extract iris.data in csv format df = pd.read_csv('https://archive.ics.uci.edu/ml/' 'machine-learning-databases/iris/iris.data', header=None) import numpy as np # select setosa and versicolor y = df.iloc[0:100, 4].values y = np.where(y == 'Iris-setosa', 0, 1) y_matrix = y.reshape(len(y),1) # extract sepal length and petal length X = df.iloc[0:100, [0, 2]].values from sklearn.preprocessing import StandardScaler sc = StandardScaler() X_std = sc.fit_transform(X) import tensorflow as tf x = tf.placeholder(tf.float32, [None, 2]) w = tf.Variable(tf.zeros([2, 1])) w0 = tf.Variable(tf.zeros([1])) f = tf.matmul(x, w) + w0 p = tf.sigmoid(f) t = tf.placeholder(tf.float32, [None, 1]) loss = -tf.reduce_sum(t*tf.log(p) + (1-t)*tf.log(1-p)) train_step = tf.train.AdamOptimizer().minimize(loss) correct_prediction = tf.equal(tf.sign(p-0.5), tf.sign(t-0.5)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) sess = tf.Session() sess.run(tf.initialize_all_variables()) i = 0 for _ in range(6000): i += 1 sess.run(train_step, feed_dict={x:X_std, t:y_matrix}) if i % 500 == 0: loss_val, acc_val = sess.run( [loss, accuracy], feed_dict={x:X_std, t:y_matrix}) print ('Step: %d, Loss: %f, Accuracy: %f' % (i, loss_val, acc_val)) w0_val, w_val = sess.run([w0, w]) w0_val, w1_val, w2_val = w0_val[0], w_val[0][0], w_val[1][0] print("w0 = %f, w1 = %f, w2 = %f" % ( w0_val, w1_val, w2_val)) def net_input(X): return np.dot(X,w_val)+w0_val def predict(X): return np.where(1/(1+np.exp(-net_input(X))) >=0.5, 1, 0) import matplotlib.pyplot as plt from matplotlib.colors import ListedColormap def plot_decision_regions(X, y, resolution=0.02): # setup marker generator and color map markers = ('s', 'x') colors = ('red', 'blue') cmap = ListedColormap(colors[:len(np.unique(y))]) # plot the decision surface x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution)) Z = predict(np.array([xx1.ravel(), xx2.ravel()]).T) Z = Z.reshape(xx1.shape) plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap) plt.xlim(xx1.min(), xx1.max()) plt.ylim(xx2.min(), xx2.max()) # plot class samples for idx, cl in enumerate(np.unique(y)): plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=cmap(idx), marker=markers[idx], label=cl) plot_decision_regions(X_std, y) plt.xlabel('sepal length [standard]') plt.ylabel('petal length [standard]') plt.legend(loc='upper left') plt.tight_layout() plt.show() |