昨今、機械学習の分野において、ディープラーニングが脚光を浴びています。そのアルゴリズムは人工ニューラルネットワークとも呼ばれています。ディープラーニングと人工ニューラルネットワークは、学術研究のみならず、Google, Microsoft, Facebookなどの大手IT企業が大きな投資をして、ホットな話題となっています。ディープラーニングによるニューラルネットワークは、画像認識や音声認識などの複雑な問題において、最高水準技術であると考えられています。生活に身近な例では、Google画像検索やGoogle翻訳、画像からテキストを自動的に認識してリアルタイムで20ヵ国の言語に翻訳するスマートフォンのアプリなどがあります(出典:Python Machine Learning: ISBN 978-1-78355-513-0)。TensorFlowは、米Google社がオープンソフトウェアとして公開している機械学習ライブラリーで、特にディープラーニングや画像認識への適用を意識した仕組みを持っています。今回は、TensorFlowを使用した最も簡単なディープラーニングの仕組みについてご紹介いたします。
第14回で、Logistic Regression(ロジスティック回帰)による2項分類器(Binary Classifier)を使用したTensorFlowによる機械学習の流れをご紹介しました。2項分類器は、本来、ディープラーニングを適用する必要がなく、シンプルでかつ線形な機械学習アルゴリズムですが、ディープラーニングの仕組みを理解する上で、わかりやすいため、ここでは、2項分類器を使用して人工ニューラルネットワークをご紹介いたします。
最もシンプルな人工ニューラルネットワークは、以下の図のように、「入力層」、1つの「隠れ層」、「出力層」の3つの層からなるニューラルネットワークです。より複雑なディープラーニング(多段ニューラルネットワーク)では、この隠れ層の数が増えていくとお考えください。今回は隠れ層が1層のみを扱います。この隠れ層において、入力データを一次関数(Net Input関数)に代入し更に活性化関数で変換した値が次の出力層へ入力されます。更に、最後の出力層では、これらの値を一次関数(Net Input関数)に代入したものを活性化関数の1つであるsigmoid関数で0〜1の確率の値に変換します。
図1. (出典:Python Machine Learning: ISBN 978-1-78355-513-0)
隠れ層で使用される活性化関数には、以下の3種類があります。ニューラルネットワークの研究の歴史において、当初は、ニューロンの動作に対応するという理由でsigmoid関数が用いられていましたが、その後、活性化関数は原点を通る方が計算効率がよくなるとの理由から、双曲線関数が利用されるようになりました。更に、最近では、デープラーニングで用いられる多段ニューラルネットワークでは、ReLUの方が係数の最適化がより高速に行われることがわかってきました。今回は、隠れ層が1つしかないシンプルな構造であるため、隠れ層の活性化関数に双曲線関数を使用することにします(出典:「TensorFlowで学ぶディープラーニング入門」ISBN978-8399-6088-9)。
図2. sigmoid関数 : φ(f) = 1/( 1+ exp(-f) )
図3. 双極線関数 : φ(f) = tanh(f)
図4. ReLU関数 : φ(f) = max(0, f)
今回、入力データとして使用するデータは、第8回〜第12回および第14回と同様に、オープンソースであるUCI機械学習リポジトリから、「ウィスコンシン肺がんデータ」を使用します。569個のデータからなり、第1列目はサンプルのID番号、2列目に’M’(悪性)または'B'(良性)かの分類クラスが、3列目から32列目までが変量になります。
https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data
30の変量がありますが、2次元の図にプロットする都合上、主成分分析により、2つの主成分を抽出することにより変量を2に削減し、TensorFlowによる人工ニューラルネットワークを使用して肺がんが悪性か良性かを予測する機械学習モデルを作成します。TensorFlowによる人工ニューラルネットワークを使用した機械学習の流れは、概ね以下になります。なお、Pythonのバージョンは3.5以上を想定しています。まだ、Pythonや関連ライブラリーをまだ導入されていない方は、第2回および第13回を参考に導入してください。
- データを入力する。
- 入力データを、トレーニングデータとテストデータに分ける。
- トレーニングデータを使用してデータの標準偏差と平均値を求める。
- 標準偏差と平均値を使用して、トレーニングデータとテストデータを、それぞれ標準化する。
- 主成分分析により、トレーニングデータから上位2つの主成分を抽出し、主成分分析の結果をトレーニングデータとテストデータに反映させる。※これにより、トレーニングデータもテストデータも2次元の変量になります。
- TensorFlowにより、隠れ層(hidden layer)の最適化すべき係数(Variable)と活性化関数を定義する。
- TensorFlowにより、出力層(output layer)の最適化すべき係数(Variable)と活性化関数を定義する。
- TensorFlowにより誤差関数とその最適化法を定義する。
- TensorFlowにより精度を定義する。
- TensorFlowによりセッションを定義する。
- TensorFlowによりセッションを実行する。
- 学習結果に基づき、新データを予測分類するpredict関数を定義する。
- 分類結果および予測値を表示するためのプロット関数を定義する。
- 分類結果および予測値を図に表示する。
では、各ステップを詳しく見ていきましょう。
①データを入力する。
まず、対話形式ではなく、Pythonのスクリプトをファイルで用意します。Pythonスクリプトの先頭に以下の2行を添付しておくことをお勧めします。
#!/usr/bin/env python # -*- coding:utf-8 -*- |
今回は、冒頭でご紹介した「ウィスコンシン肺がんデータ」を、オープンソースとして提供しているサイトのURLから、pandasライブラリーを使用して以下のようにデータを抽出します。変量Xの配列(569 x 30)に、ラベル(悪性か良性か)を y(569x 1)という配列に569サンプル分のデータを格納します。sklearn.preprocessingライブラリーのLabelEncoder関数を使用して、ラベルの'M'( Malignant 悪性)を数字の'1'に、'B'( Benign 良性)を数字の'0'に、それぞれ変換しています。
import pandas as pd df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header=None) from sklearn.preprocessing import LabelEncoder X = df.loc[:, 2:].values y = df.loc[:, 1].values le = LabelEncoder() y = le.fit_transform(y) |
②入力データを、トレーニングデータとテストデータに分ける。
scikit-learning.model_selectionライブラリーのtrain_test_split関数を使用して、
変量配列Xとラベル配列yについて、トレーニングデータとテストデータに分けます。変量配列Xを、それぞれ、X_train配列, X_test配列に分割し、ラベル配列yは、y_tarin配列, y_test配列へそれぞれ分割します。test_sizeのパラメータにより、テストデータの割合を指定できます。ここでは、0.2を指定することで、テストデータの割合を全体の20%と指定しています。全569サンプルの20%(= 114サンプル)がテストデータで、残りの455サンプルがトレーニングデータとなります。random_state=0を指定することにより、ランダムにトレーニングデータとテストデータを分割することができます。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=0)
|
③トレーニングデータを使用してデータの標準偏差と平均値を求める。
sklearn.preprocessingライブラリーのStandardScaler関数を用いて、変量配列X_trainとX_testを標準化します。まず、標準化のための標準偏差と平均値は、トレーニングデータのみを使用して計算しなければなりません。fitメソッドを使用して以下のように行います。
from sklearn.preprocessing import StandardScaler sc = StandardScaler() sc.fit(X_train) |
④標準偏差と平均値を使用して、トレーニングデータとテストデータを、それぞれ標準化する。
次に、変量配列のトレーニングデータとテストデータを、transformメソッドを用いて、それぞれ標準化します。標準化した変量配列をそれぞれ、X_train_std, X_test_stdに格納します。
X_train_std = sc.transform(X_train) X_test_std = sc.transform(X_test) |
⑤主成分分析により、トレーニングデータから上位2つの主成分を抽出し、主成分分析の結果をトレーニングデータとテストデータに反映させる。
変量配列X_train_std, X_test_stdの変数の数は30ずつありましたが、主成分分析により2つの主成分の変数を抽出し、transformメソッドを用いて、それぞれX_train_pcaとX_test_pcaに格納します。これにより、X_train_pcaは455x2の配列に、X_test_pcaは114x2の配列になります。
from sklearn.decomposition import PCA pca = PCA(n_components=2) X_train_pca = pca.fit_transform(X_train_std) X_test_pca = pca.transform(X_test_std) |
⑥TensorFlowにより、隠れ層(hidden layer)の最適化すべき係数(Variable)と活性化関数を定義する。
ここからが、TensorFlowの実際の記述になります。隠れ層について以下のように記述します。
import tensorflow as tf num_units = 2 x = tf.placeholder(tf.float32, [None, 2]) w1 = tf.Variable(tf.truncated_normal([2, num_units])) b1 = tf.Variable(tf.zeros([num_units])) hidden1 = tf.nn.tanh(tf.matmul(x, w1) + b1) |
num_unitsは隠れ層におけるノードの数です。ここでは2を指定していますが、2以上の自然数を指定することにより、ノードの数を増やすことができます。ノードの数を増やすことにより、より複雑なモデルに対応することができますが、それに伴いより多くのコンピュータ資源が必要となります。この例では、最もシンプルなモデルであるため、最小の2を指定します。
TensorFlowでは、入力データを「placeholder」に格納し、機械学習により最適化すべき隠れ層の係数b1およびw1を「Variable」に格納します。ここでは、placeholderのデータの型と変量の数(ここでは2)のみを指定し、サンプルの数(ここではトレーニングデータの455)は指定しなくても構いません(ここではNoneを指定)。実際のデータ内容はセッション実行時に設定するため、ここでは指定しません。最適化すべき係数のb1の値は、0(ゼロ)に初期化しますが、w1は乱数を用いて初期値を決定しています。Net Input関数と活性化関数による隠れ層の出力としてhidden1を定義します。ここで注意すべきことは、Net Input関数も活性化関数も、必ずtensorflowライブラリーのものを使用しなければならないということです。例えば、Pythonプログラミングとしては、tf.matmul(x,w1)もnp.dot(x,w1)も全く同じ機能で計算結果も同じですが、np.dot(x,w1)を使用するとTensorFlowのセッション実行時にエラーとなるので、注意が必要です。tf.tanh関数に関しても同様で、np.tanhと指定するとセッション実行時にエラーになります。
⑦TensorFlowにより、出力層(output layer)の最適化すべき係数(Variable)と活性化関数を定義する。
最適化すべき係数 w0 と b0を0(ゼロ)に初期化します。隠れ層からの出力であるhidden1の値を入力にNet Input関数を計算し、sigmoid関数で0〜1の確率に変換します。
w0 = tf.Variable(tf.zeros([num_units, 1])) b0 = tf.Variable(tf.zeros([1])) p = tf.nn.sigmoid(tf.matmul(hidden1, w0) + b0) |
ここでも注意すべきことは、Net Input関数も活性化関数も、必ずtensorflowライブラリーのものを使用しなければならないということです。例えば、Pythonプログラミングとしては、tf.matmul(hiddne1,w0)もnp.dot(hidden1,w0)も全く同じ機能で計算結果も同じですが、np.dot(hidden1,w0)を使用するとTensorFlowのセッション実行時にエラーとなるので、注意が必要です。tf.sigmoid関数に関しても同様です。
⑧TensorFlowにより誤差関数とその最適化法を定義する。
入力データ(肺がんが悪性か良性かの分類クラス)tのデータの型と大きさ(ここでは1)をplaceholderを用いて定義します。サンプル数(ここではトレーニングデータの455)については指定は不要です(ここでは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、b0、w1、b1の値を最適化すれば良いことになります。これを最尤推定法(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.GradientDescentOptimizer(0.005).minimize(loss) |
⑨TensorFlowにより精度を定義する。
1行目は、(p-0.5)と(t -0.5)の符号を比較することで、計算値pが元データtと一致しているかどうかを判定しています。signは符号を取り出す関数で、equalは、2つの符号が等しいかどうかを判定して、Bool値を返す関数です。correct_predictionには、データ数(ここではトレーニングデータの455個またはテストデータの114個)分の要素の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.InteractiveSession() sess.run(tf.initialize_all_variables()) |
(11)TensorFlowによりセッションを実行する。
誤差関数が最小になるよう、Variableの最適化を12回繰り返します。ここでは、1回繰り返すごとに、その時点での誤差関数lossとトレーニングデータの精度Accuracy(train)とテストデータの精度Accuracy(test)の値をそれぞれ計算し、表示しています。sess.runの中で、feed_dictにより、placeholderのx と t にそれぞれ、実際のデータ(変量と分類ラベル)を割り当てています。
y_matrix_train = y_train.reshape(len(y_train),1) y_matrix_test = y_test.reshape(len(y_test),1) i = 0 for _ in range(12): i += 1 sess.run(train_step, feed_dict={x:X_train_pca, t:y_matrix_train}) if i % 1 == 0: loss_val, acc_val = sess.run( [loss, accuracy], feed_dict={x:X_train_pca, t:y_matrix_train}) acc_test_val =sess.run(accuracy, feed_dict={x:X_test_pca, t:y_matrix_test}) print ('Step: %d, Loss: %f, Accuracy(train): %f, Accuracy(test): %f' % (i, loss_val, acc_val, acc_test_val)) |
(実行結果)
Step: 1, Loss: 245.416458, Accuracy(train): 0.795604, Accuracy(test): 0.780702
Step: 2, Loss: 165.641541, Accuracy(train): 0.934066, Accuracy(test): 0.929825
Step: 3, Loss: 119.389214, Accuracy(train): 0.951648, Accuracy(test): 0.912281
Step: 4, Loss: 97.195045, Accuracy(train): 0.960440, Accuracy(test): 0.912281
Step: 5, Loss: 85.122742, Accuracy(train): 0.958242, Accuracy(test): 0.912281
Step: 6, Loss: 78.022354, Accuracy(train): 0.958242, Accuracy(test): 0.912281
Step: 7, Loss: 73.830986, Accuracy(train): 0.956044, Accuracy(test): 0.903509
Step: 8, Loss: 72.203262, Accuracy(train): 0.953846, Accuracy(test): 0.929825
Step: 9, Loss: 71.888344, Accuracy(train): 0.953846, Accuracy(test): 0.894737
Step: 10, Loss: 67.579643, Accuracy(train): 0.949451, Accuracy(test): 0.929825
Step: 11, Loss: 68.194397, Accuracy(train): 0.953846, Accuracy(test): 0.903509
Step: 12, Loss: 65.711449, Accuracy(train): 0.951648, Accuracy(test): 0.947368
実行Stepが進むにつれ、誤差関数(Loss)の値は減少していますが、トレーニングデータとテストデータの精度(Accuracy)の値は、それ程、増加していないことわかります。特にテストデータの結果として、第14回のディープラーニングを使用しない機械学習アルゴリズムの精度の方がディープラーニングを使用したアルゴリズムの精度より良いことがわかります。これは、ディープラーニングが必ずしも、いつも最善のアルゴリズムとは限らないことを意味します。シンプルなモデルにはシンプルなアルゴリズムを適用すべきで、何でもかんでもディープラーニングに頼るのは最適ではないことがわかります。
次に、最終時点での係数(w0, w1, b0, b1)の値を取得します。
w0_val, w1_val,b0_val,b1_val = sess.run([w0, w1,b0,b1]) |
(12)学習結果に基づき、新データを予測分類するpredict関数を定義する。
学習結果に基づき得られた係数(w0, w1, b0, b1)の値を用いて、予測モデル predict関数を以下のように定義します。
import numpy as np def net_input(X): return np.dot(X,w0_val)+b0_val def predict(X): hidden_1 = np.tanh(np.dot(X,w1_val) + b1_val) return np.where(1/(1+np.exp(-net_input(hidden_1))) >=0.5, 1, 0) |
(13)分類結果および予測値を表示するためのプロット関数を定義する。
(12)で定義したpredict関数を用いて、分類結果および予測データを表示するためのplot_decision_regions関数を以下のように定義します。
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) |
(14)分類結果および予測値を図に表示する。
(13)で定義したplot_decision_regions関数を実行し、テストデータの分類結果を表示させます。
plot_decision_regions(X_test_pca, y_test) plt.xlabel('PC 1') plt.ylabel('PC 2') plt.legend(loc='lower left') plt.tight_layout() plt.show() |
図5.
赤の四角が「良性のがん」で、青のX印が「悪性のがん」を表しています。いくつかのの例外はありますが、テストデータについて、概ね曲線で2つのクラスが分類されていることがわかります。
なお、今回の内容は、以下の書籍の3章を参考に執筆いたしました。ご興味のある方は、ご参考いただければと思います。
「TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~」
中井 悦司 (著)
出版社: マイナビ出版
全体を通してのコードは以下のようになります。なお、本コードの稼働環境は、Python3.5以上を想定しています。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header=None)
from sklearn.preprocessing import LabelEncoder
X = df.loc[:, 2:].values
y = df.loc[:, 1].values
le = LabelEncoder()
y = le.fit_transform(y)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=0)
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)
import tensorflow as tf
num_units = 2
x = tf.placeholder(tf.float32, [None, 2])
w1 = tf.Variable(tf.truncated_normal([2, num_units]))
b1 = tf.Variable(tf.zeros([num_units]))
hidden1 = tf.nn.tanh(tf.matmul(x, w1) + b1)
w0 = tf.Variable(tf.zeros([num_units, 1]))
b0 = tf.Variable(tf.zeros([1]))
p = tf.nn.sigmoid(tf.matmul(hidden1, w0) + b0)
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.GradientDescentOptimizer(0.005).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.InteractiveSession()
sess.run(tf.initialize_all_variables())
y_matrix_train = y_train.reshape(len(y_train),1)
y_matrix_test = y_test.reshape(len(y_test),1)
i = 0
for _ in range(12):
i += 1
sess.run(train_step, feed_dict={x:X_train_pca, t:y_matrix_train})
if i % 1 == 0:
loss_val, acc_val = sess.run(
[loss, accuracy], feed_dict={x:X_train_pca, t:y_matrix_train})
acc_test_val =sess.run(accuracy, feed_dict={x:X_test_pca, t:y_matrix_test})
print ('Step: %d, Loss: %f, Accuracy(train): %f, Accuracy(test): %f'
% (i, loss_val, acc_val, acc_test_val))
w0_val, w1_val,b0_val,b1_val = sess.run([w0, w1,b0,b1])
import numpy as np
def net_input(X):
return np.dot(X,w0_val)+b0_val
def predict(X):
hidden_1 = np.tanh(np.dot(X,w1_val) + b1_val)
return np.where(1/(1+np.exp(-net_input(hidden_1))) >=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_test_pca, y_test)
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()
|