第13回及び第14回では、Logistic Regression(ロジスティクス回帰)による2項分類器(Binary Classifier)を利用してTensorFlowによる機械学習の流れをそれぞれご紹介しました。今回は、多項分類器(Multinomial Classifier)利用したTensorFlowによる機械学習の流れをご紹介します。多項分類器とは、分類すべきラベルが3つ以上ある場合の機械学習アルゴリズムのモデルを言います。

 

使用するデータは、第6回及び第7回と同様に、オープンソースであるUCI機械学習リポジトリから、「ワインデータ」を使用します。178のデータからなり、変数は、'Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium', 'Total phenols', 'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline'の化学特性による計13種類の変量からなります。

 

https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data

 

各サンプルは、3つの異なるクラス(1または 2またh 3)に属します。それぞれ、イタリアの異なる地域で栽培された異なるぶどうのワインの種類を表しています。

 

13の変量がありますが、2次元の図にプロットする都合上、主成分分析により、2つの主成分を抽出し、TensorFlowとPythonを使用してワインの種類を予測する機械学習モデルを作成します。主成分分析とTensorFlowおよびPythonを使用した機械学習の流れは、概ね以下になります。なお、Pythonのバージョンは3.5以上を想定しています。

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

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

 

①データを入力する。

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

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

 

今回は、冒頭でご紹介した「ワインデータ」を、オープンソースとして提供しているサイトのURLから、pandasライブラリーを使用して以下のようにデータを抽出します。変量Xの配列(178 x 13)に、ラベル(ワインの種類)を y(178x 1)という配列に178サンプル分のデータを格納します。

 

import pandas as pd
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
            

 

入力データを、トレーニングデータとテストデータに分ける。

scikit-learning.model_selectionライブラリーのtrain_test_split関数を使用して、

変量配列Xとラベル配列yについて、トレーニングデータとテストデータに分けます。変量配列Xを、それぞれ、X_train配列, X_test配列に分割し、ラベル配列yは、y_tarin配列, y_test配列へそれぞれ分割します。test_sizeのパラメータにより、テストデータの割合を指定できます。ここでは、0.3を指定することで、テストデータの割合を全体の30%と指定しています。全178サンプルの30%(= 53サンプル)がテストデータで、残りの125サンプルがトレーニングデータとなります。random_state=0を指定することにより、ランダムにトレーニングデータとテストデータを分割することができます。

import numpy as np
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.3, 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の変数の数は13ずつありますが、主成分分析により2つの主成分の変数を抽出し、transformメソッドを用いて、それぞれX_train_pcaとX_test_pcaに格納します。これにより、X_train_pca は125x2の配列に、X_test_pcaは53x2の配列になります。

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の実際の記述になります。はじめに、softmaxを使用した多項分類器の仕組みについて少し説明しておきたいと思います。入力データの各変量 x1, x2, ... ,xmについて、各係数w1, w2, ... ,wmを掛け合わせた和を「Net Input 関数」と言います。 f = w0 + x1*w1 + x2*w3, ... ,xm*wm のように表せます。 このNet Input関数の結果を入力にしてラベルを分類する関数を「活性化関数」と呼びます。多項分類器の活性化関数の1つにsoftmax関数があります。softmax関数は、分類すべきラベルがk個ある場合、k個の要素のうち、n( n < k)番目の要素だけが最も大きくなるように値を返します。例えば、分類クラスが3つで、1番目のクラスを返す場合は[1,0,0]、2番目のクラスを返す場合は[0,1,0]、3番目のクラスを返す場合は[0,0,1]のように値を返します。活性化関数による計算結果と元の分類データとを比較して、誤差(Error)を計算し、誤差が最小になるように、各係数wの値を再調整するセッションを繰り返します。

 

図1. 出典:Python Machine Learning: ISBN 978-1-78355-513-0

 

 

 

import tensorflow as tf
x = tf.placeholder(tf.float32, [None, 2])
w = tf.Variable(tf.zeros([2, 3]))
w0 = tf.Variable(tf.zeros([3]))
f = tf.matmul(x, w) + w0
p = tf.nn.softmax(f)
            

TensorFlowでは、入力データを「placeholder」に格納し、機械学習により最適化すべき係数w0およびwは「Variable」に格納します。ここでは、placeholderのデータの型と変量の数(ここでは2)のみを指定し、サンプルの数(ここではトレーニングデータの125)は指定しなくて良いです(ここでは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のセッション実行時にエラーとなるので、注意が必要です。softmax関数に関しても同様で、以下のように指定してもPythonプログラミング上は同じなのですが、TensorFlowのセッション実行時にエラーになります。

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()
            

 

⑦TensorFlowにより誤差関数とその最適化法を定義する。

入力データ(3種類のワインは、それぞれ、[1,0,0]、[0,1,0]、[0,0,1]と表される)tのデータの型と大きさ(ここでは3)をplaceholderを用いて定義します。サンプル数(ここではトレーニングデータの125)については指定は不要です(ここではNoneを指定)。また、実際のデータ内容はセッション実行時に指定するため、ここでは指定しません。

次に誤差関数を定義します。i番目のデータの正解がkだった場合の確率をpとすると

正解を表すラベルは、t = [0,...0,1,0,...0) (k番目の要素のみ1)で与えられます。i番目のデータを正しく予測する確率Piは、Pi = Πp^t となります。

N個のデータ全てを正しく予測する確率は、P = P1 x P2 x ... xPN = ΠPi = ΠΠ p^t  となり、この確率Pを最大にするようにVariable w0とwの値を最適化すれば良いことになります。これを最尤推定法(maximum likelihood method)と呼びます。したがって誤差関数としては、-Pを最小になるように定義すれば良いわけです。対数関数を使って、次のように記述できます。

 

loss = - log P = -log ΠΠp^t  = -ΣΣt*log(p) 

 

ここで、ΣΣは、tesorflowライブラリーで、 reduce_sum関数が用意されています。また、train.AdamOptimizer関数で、誤差関数 lossを最小化するよう設定しています。

 

t = tf.placeholder(tf.float32, [None, 3])
loss = -tf.reduce_sum(t*tf.log(p))
train_step = tf.train.AdamOptimizer().minimize(loss)            
            

 

⑧TensorFlowにより精度を定義する。

1行目のargmax関数は、複数の要素が並んだリストから、最大値を持つ要素のインデックスを取り出す関数で、要素のインデックスを比較することで、計算値pが元データtと一致しているかどうかを判定しています。equalは、2つのインデックスが等しいかどうかを判定して、Bool値を返す関数です。correct_predictionには、データ数(ここではトレーニングデータの125個またはテストデータの53個)分の要素のBool値が格納されます。2行目では、cast関数で、correct_predictionの各要素のBool値を1,0の値に変換します。reduce_mean関数は、1,0の値に変換されたcorrect_predictionの各要素の平均値を返します。これが結局、機械学習アルゴリズムの精度となります。

 

correct_prediction = tf.equal(tf.argmax(p,1), tf.argmax(t,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))            
            

 

⑨TensorFlowによりセッションを定義する。

Variable(w0とw)の値を最適化するための、セッションを定義して、Variableの値を初期化します。

 

sess = tf.InteractiveSession()
sess.run(tf.initialize_all_variables())
            

⑩分類ラベルのデータをsoftmax用に変換する

sklearn.preprocessingライブラリーのOneHotEncoder関数を使用して、ワインの3種類のラベルを表す、1,2,3のデータをそれぞれ、

1 -> [1,0,0]

2 -> [0,1,0]

3 -> [0,0,1]

と変換して、トレーニングデータとテストデータ用にそれぞれ、y_ohe_train、y_ohe_testへ格納します。

from sklearn.preprocessing import OneHotEncoder
y_matrix_train = y_train.reshape(len(y_train),1)
ohe = OneHotEncoder(categorical_features=[0])
y_ohe_train = ohe.fit_transform(y_matrix_train).toarray()

y_matrix_test = y_test.reshape(len(y_test),1)
y_ohe_test =  ohe.transform(y_matrix_test).toarray()
            

 

(11)TensorFlowによりセッションを実行する。

誤差関数が最小になるよう、Variableの最適化を130回繰り返します。ここでは、10回繰り返すごとに、その時点での誤差関数lossとトレーニングデータの精度Accuracy(train)とテストデータのAccuracy(test)の値をそれぞれ計算し、表示しています。sess.runの中で、feed_dictにより、placeholderのx と t にそれぞれ、実際のデータ(変量と分類ラベル)を割り当てています。

 

 

i = 0
for _ in range(130):
    i += 1
    sess.run(train_step, feed_dict={x:X_train_pca, t:y_ohe_train})
    if i % 10 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x:X_train_pca, t:y_ohe_train})
        acc_test_val =sess.run(accuracy, feed_dict={x:X_test_pca, t:y_ohe_test})
        print ('Step: %d, Loss: %f, Accuracy(train): %f, Accuracy(test): %f'
               % (i, loss_val, acc_val, acc_test_val))            
            

 

(実行結果)

Step: 10, Loss: 132.589722, Accuracy(train): 0.895161, Accuracy(test): 0.944444
Step: 20, Loss: 129.043671, Accuracy(train): 0.903226, Accuracy(test): 0.944444
Step: 30, Loss: 125.598816, Accuracy(train): 0.895161, Accuracy(test): 0.962963
Step: 40, Loss: 122.260231, Accuracy(train): 0.903226, Accuracy(test): 0.962963
Step: 50, Loss: 119.029411, Accuracy(train): 0.911290, Accuracy(test): 0.962963
Step: 60, Loss: 115.905640, Accuracy(train): 0.911290, Accuracy(test): 0.962963
Step: 70, Loss: 112.887108, Accuracy(train): 0.911290, Accuracy(test): 0.962963
Step: 80, Loss: 109.971527, Accuracy(train): 0.911290, Accuracy(test): 0.962963
Step: 90, Loss: 107.156441, Accuracy(train): 0.919355, Accuracy(test): 0.962963
Step: 100, Loss: 104.439209, Accuracy(train): 0.927419, Accuracy(test): 0.981481
Step: 110, Loss: 101.817184, Accuracy(train): 0.935484, Accuracy(test): 0.981481
Step: 120, Loss: 99.287613, Accuracy(train): 0.943548, Accuracy(test): 0.981481
Step: 130, Loss: 96.847702, Accuracy(train): 0.943548, Accuracy(test): 0.981481

 

 

誤差関数(Loss)の値が減少すると共に、トレーニングデータとテストデータの精度(Accuracy)の値が共に上昇していることがわかります。最終時点での係数(w0とw)の値を取得します。

        
w0_val, w_val = sess.run([w0, w])         
         

 

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

 

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

def net_input(X):
    return np.dot(X,w_val)+w0_val

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

def predict(X):
    p = softmax(net_input(X))
    predicted = np.zeros(len(X), dtype=np.int)
    i = 0
    for i in range(len(X)):
        if np.argmax(p[i]) == 0:
            predicted[i] =1
        elif np.argmax(p[i]) == 1:
            predicted[i] = 2
        else:
            predicted[i] = 3
        i += 1
    return predicted

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', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    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)            
            

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

(12)で定義した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印および緑の丸が3種類のワインのラベルをそれぞれ表しています。1つの例外はありますが、テストデータについて、直線で3つのクラスがよく分類されていることがわかります。

 

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

 

「TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~」 

中井 悦司 (著)

出版社: マイナビ出版

 

全体を通してのコードは以下のようになります。なお、本コードの稼働環境は、Python3.5以上を想定しています。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pandas as pd
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values

import numpy as np
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.3, 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, 3]))
w0 = tf.Variable(tf.zeros([3]))
f = tf.matmul(x, w) + w0
p = tf.nn.softmax(f)

t = tf.placeholder(tf.float32, [None, 3])
loss = -tf.reduce_sum(t*tf.log(p))
train_step = tf.train.AdamOptimizer().minimize(loss)

correct_prediction = tf.equal(tf.argmax(p,1), tf.argmax(t,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

sess = tf.InteractiveSession()
sess.run(tf.initialize_all_variables())

from sklearn.preprocessing import OneHotEncoder
y_matrix_train = y_train.reshape(len(y_train),1)
ohe = OneHotEncoder(categorical_features=[0])
y_ohe_train = ohe.fit_transform(y_matrix_train).toarray()

y_matrix_test = y_test.reshape(len(y_test),1)
y_ohe_test =  ohe.transform(y_matrix_test).toarray()

i = 0
for _ in range(130):
    i += 1
    sess.run(train_step, feed_dict={x:X_train_pca, t:y_ohe_train})
    if i % 10 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x:X_train_pca, t:y_ohe_train})
        acc_test_val =sess.run(accuracy, feed_dict={x:X_test_pca, t:y_ohe_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])

def net_input(X):
    return np.dot(X,w_val)+w0_val

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

def predict(X):
    p = softmax(net_input(X))
    predicted = np.zeros(len(X), dtype=np.int)
    i = 0
    for i in range(len(X)):
        if np.argmax(p[i]) == 0:
            predicted[i] =1
        elif np.argmax(p[i]) == 1:
            predicted[i] = 2
        else:
            predicted[i] = 3
        i += 1
    return predicted

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', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    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()