前回(第13回)では、Logistic Regression(ロジスティクス回帰)による2項分類器(Binary Classifier)を利用してTensorFlowによる機械学習の流れをご紹介しました。しかしながら、入力データを全てトレーニングデータとして使用していました。今回は、入力データをトレーニングデータとテストデータに分割する手法とTensorFlowにおいてテストデータの精度を評価する方法をご紹介したいと思います。また、主成分分析を使用して、入力データの変量の次元の削減を行います。

 

今回、使用するデータは、オープンソースである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つの主成分を抽出し、TensorFlowとPythonを使用して肺がんが悪性か良性かを予測する機械学習モデルを作成します。主成分分析とTensorFlowを使用した機械学習の流れは、概ね以下になります。なお、Pythonのバージョンは3.5以上を想定しています。

  1. データを入力する。
  2. 入力データを、トレーニングデータとテストデータに分ける。
  3. トレーニングデータを使用してデータの標準偏差と平均値を求める。
  4. 標準偏差と平均値を使用して、トレーニングデータとテストデータを、それぞれ標準化する。
  5. 主成分分析により、トレーニングデータから上位2つの主成分を抽出し、主成分分析の結果をトレーニングデータとテストデータに反映させる。
  6. TensorFlowにより最適化すべきVariableとNet Input関数および活性化関数を定義する。
  7. TensorFlowにより誤差関数とその最適化法を定義する。
  8. TensorFlowにより精度を定義する。
  9. TensorFlowによりセッションを定義する。
  10. TensorFlowによりセッションを実行する。
  11. 分類結果を表示するためのプロット関数を定義する。
  12. 分類結果を図に表示する。

では、各ステップを詳しく見ていきましょう。

 

①データを入力する。

まず、対話形式ではなく、Pythonのスクリプトをファイルで用意します。Pythonスクリプトの先頭に以下の2行を添付しておくことをお勧めします。

#!/usr/bin/env python
# -*- coding:utf-8 -*-            
            

 

今回は、冒頭でご紹介した「ウィスコンシン肺がんデータ」を、オープンソースとして提供しているサイトのURLから、pandasライブラリーを使用して以下のようにデータを抽出します。変量Xの配列(569 x 30)に、ラベル(悪性か良性か)を y(569x 1)という配列に569サンプル分のデータを格納します。sklearn.preprocessingライブラリーのLabelEncoder関数を使用して、ラベルの'M'(悪性)を数字の'1'に、'B'(良性)を数字の'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により最適化すべき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)のみを指定し、サンプルの数(ここではトレーニングデータの455)は指定しなくて良いです(ここでは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を用いて定義します。サンプル数(ここではトレーニングデータの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と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には、データ数(ここではトレーニングデータの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.Session()
sess.run(tf.initialize_all_variables())
            

⑩TensorFlowによりセッションを実行する。

誤差関数が最小になるよう、Variableの最適化を900回繰り返します。ここでは、90回繰り返すごとに、その時点での誤差関数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(900):
    i += 1
    sess.run(train_step, feed_dict={x:X_train_pca, t:y_matrix_train})
    if i % 90 == 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: 90, Loss: 256.507294, Accuracy(train): 0.918681, Accuracy(test): 0.912281

Step: 180, Loss: 216.117584, Accuracy(train): 0.920879, Accuracy(test): 0.912281

Step: 270, Loss: 187.103714, Accuracy(train): 0.916484, Accuracy(test): 0.921053

Step: 360, Loss: 165.529190, Accuracy(train): 0.918681, Accuracy(test): 0.929825

Step: 450, Loss: 149.080322, Accuracy(train): 0.925275, Accuracy(test): 0.929825

Step: 540, Loss: 136.255508, Accuracy(train): 0.927473, Accuracy(test): 0.938596

Step: 630, Loss: 126.047150, Accuracy(train): 0.931868, Accuracy(test): 0.938596

Step: 720, Loss: 117.766479, Accuracy(train): 0.931868, Accuracy(test): 0.947368

Step: 810, Loss: 110.933914, Accuracy(train): 0.929670, Accuracy(test): 0.956140

Step: 900, Loss: 105.209320, Accuracy(train): 0.934066, Accuracy(test): 0.956140

 

 

誤差関数(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.519047, w1 = 0.593413, w2 = -0.292790

 

(11)分類結果を表示するためのプロット関数を定義する。

 

⑩の結果より最終的に得られたw0_val、w_valの値を使用したnet_input関数を定義すると共に、活性化関数predictを定義し、分類結果を表示するためのplot_decision_regions関数を以下のように定義します。        

import numpy as np
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)            
            

(12)分類結果を図に表示する。

(11)で定義した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()
        

赤の四角が「良性のがん」で、青のX印が「悪性のがん」を表しています。いくつかのの例外はありますが、テストデータについて、概ね直線で2つのクラスが分類されていることがわかります。

 

なお、今回の内容は、以下の書籍の2章を参考に執筆いたしました。ご興味のある方は、ご参考いただければと思います。

 

「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
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())

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(900):
    i += 1
    sess.run(train_step, feed_dict={x:X_train_pca, t:y_matrix_train})
    if i % 90 == 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, 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))

import numpy as np
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_test_pca, y_test)
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()