本記事は「TensorFlow チュートリアル Part1:XORゲートの実装を体験 」の続きです。今回はより複雑なデータの分類に挑戦します。
本記事の構成は下記の通りです。
2クラスデータセットの生成とデータの確認
今回は、2クラスデータセットの分類に挑戦します。用いるデータセットはScikit-learn(sklearn)というpython用の機械学習フレームワークの機能を使って生成します。sklearnのdatasetsモジュールには多数のデータセットが存在しますが、独自にデータセットを作り出す機能もあります。今回はデータセットを作り出す機能の方を使用して、プロットすると円形になるようなデータセットを作成します。
では、円形データセットを生成してプロットしてみます。クラスはクラス0とクラス1の2つ存在するので色分けしてプロットします。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
# データセットを用意
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1, random_state = 0)
# 可視化のために2クラスを別の変数に分ける
X_red = X[Y == 0]
X_blue = X[Y == 1]
plt.scatter(X_red[:, 0], X_red[:, 1], color='red') #ラベルが0のデータ
plt.scatter(X_blue[:, 0], X_blue[:, 1], color='blue') #ラベルが1のデータ
上では、クラス0のデータは赤い点、クラス1の点は青い点で描画しています(以降本記事では、データセットの中のデータを「データ」、データセットの中で各データが属するクラスを「ラベル」と呼びますので注意してください)。今回はモデル自身に学習させることによってデータセットを上の図のように上手く分類させることが目標となります。
生成したデータセットについてより詳しく見てみましょう。データX、ラベルYの形状を出力してみます。
デフォルトのコード
import numpy as np
from sklearn import datasets
# データセットを用意
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1)
#データの形状
print(X.shape)
print(Y.shape)
よって、このデータセットは以下の図のような構造であることがわかります。
図1
ディープラーニング:モデルの学習による2クラス分類
データセットの構造がわかったところで、モデルを構築して学習させていこうと思います。一般的に、ディープラーニングの流れは以下のようになります。
訓練用データセット、試験用データセットの用意 モデルを用意 訓練用データセットでモデルに学習させる 試験用データセットで推論
訓練用データセット、試験用データセットの用意
最初の「訓練用データセット、試験用データセットの用意 」ですが、我々はすでにsklearnの機能で円形データセットを生成しました。しかし、訓練用データセットと試験用データセットの二つのデータセットを用意したわけではないので、生成した円形データセットを訓練用データセットと試験用データセットに分割することにします。訓練用データセット : 試験用データセット = 8 : 2 の割合で分割します。
ここで、データセットの分割方法には注意しましょう。Yを出力してみるとわかりますが、データセットはシャッフルされてない状態です。
デフォルトのコード
from sklearn import datasets
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1)
#Yの確認
print(Y)
よって、データセットをシャッフルしてから分割する必要があります。sklearnの train_test_splitメソッドは、データセットをシャッフルしながら分割してくれます。
デフォルトのコード
from sklearn import datasets
from sklearn.model_selection import train_test_split #train_test_splitをインポート
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1)
#データを8:2に分割
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2)
#分割後のデータサイズ確認
print(train_X.shape)
print(test_X.shape)
print(train_Y.shape)
print(test_Y.shape)
学習には訓練用データセットのみを用います。上のコードの出力結果より、分割後のデータセットの構造は以下のようになっています。
図2
分割された後のtrain_Yを出力してみても、ちゃんとシャッフルされています。
デフォルトのコード
from sklearn import datasets
from sklearn.model_selection import train_test_split
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1)
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2)
#分割後のtrain_Y確認
print(train_Y)
これで訓練用、試験用のデータセットを用意できました。
モデルを用意
続いて、モデルを用意します。今回のモデルはSequential APIで作成していきます。
デフォルトのコード
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
model = Sequential()#Sequentailオブジェクトを生成
model.add(Dense(input_dim = 2, units = 8, activation = 'relu'))#1層目、活性化関数はReLU関数
model.add(Dense(units = 1,activation = 'sigmoid'))#2層目(出力層)、活性化関数はsigmoid関数
#コンパイル
model.compile(optimizer = SGD(lr = 0.5), loss = 'binary_crossentropy')
#モデルのサマリ(全体像)を表示
model.summary()
このモデルは入力層のサイズが2、隠れ層のサイズが8、出力層のサイズが1のニューラルネットワークです。隠れ層の活性化関数はReLU関数、出力層はシグモイド関数を用います。なお、ここでコンパイルも行っています。出力層のサイズが1なので、損失関数はBCE損失(Binary Cross Entropy Loss)を用います。出力層のサイズが1で、活性関数がシグモイド関数ならば一般的にはBCE損失が用いられます。今回は最適化手法についてはそこまで気を付ける必要がないです。よく用いられるSGD(確率的勾配降下法)を用います。
モデルが定義できましたが、学習させていく前にミニバッチ学習 について説明します。
ニューラルネットワークに一度に全てのデータセットを入力して学習させることを「バッチ学習」と言います。バッチとは、全てのデータセットの塊のことです。データが少ないうちはこのバッチ学習でもよいのですが、データが多くなるとこの方法ではコンピューターに多大な負荷がかかります。そのため、一般的にはデータセットを小さなミニバッチに分けて、少しずつモデルに入力して学習させます。この方法を「ミニバッチ学習」と言います。今回はそれほどデータ数は多くないのですが、勉強のためにミニバッチ学習を試してみます。
Sequential APIではミニバッチ学習は容易に行えます。modelのfitメソッドの引数でミニバッチのサイズを指定するだけで、自動的にミニバッチ学習を行ってくれます。ここでは、ミニバッチのサイズは8に設定しました(ミニバッチのサイズは大きすぎなければ任意の値で大丈夫です)。
訓練用データセットでモデルに学習させる
では、モデルに学習させていきましょう。訓練ループを作成し、訓練用データで100エポック学習させます。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
# データセットを用意
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1, random_state = 0)
#データを8:2に分割
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2)
#モデル定義
model = Sequential() #Sequentailオブジェクトを生成
model.add(Dense(input_dim = 2, units = 8, activation = 'relu')) #1層目、活性化関数はReLU関数
model.add(Dense(units = 1,activation = 'sigmoid')) #2層目(出力層)、活性化関数はsigmoid関数
#コンパイル
model.compile(optimizer = SGD(lr = 0.5), loss = 'binary_crossentropy')
history = model.fit(
train_X, #訓練データ
train_Y, #ラベル
epochs = 100, #エポック数
batch_size = 8, #ミニバッチのサイズを指定
verbose = 0
)
#描画
plt.plot(history.history['loss']) #fit()メソッドの出力に保存されている損失を描画
plt.show()
損失が一気に減少していっているのがよくわかります。上手く学習できているようです。
試験用データセットで推論
最後に、試験用データで推論させてみましょう。
推論させる前に、このニューラルネットワークの出力層における活性化関数はシグモイド関数なので、出力値は少数です。しかし出力がtest_Yとどれほど一致しているかを確認したいので、0か1のどちらかに出力値の離散化を行います。ここでは0.5より大きければ1、0.5より小さければ0という様に離散化を行いました。
# 離散化を行う関数
def discretize(proba):
threshold = np.array([0.5]) # 0か1かを分ける閾値を0.5に設定
discretized = (proba >= threshold).astype(int) # 閾値未満で0、以上で1に変換
return discretized
試験用データをモデルに入力し、その出力を離散化します。さらにその結果をリストに格納します。
pred_labels = [] #結果格納用
pred = model.predict(test_X)#試験データを入力して出力を得る
for p in pred:
pred_label_ = discretize(p) #離散化する
pred_labels.append(pred_label_[0])
pred_labels = np.array(pred_labels) #numpy arrayに変換
では実際に推論させ、得られた推論値からtest_Xをプロットしてみます。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
# データセットを用意
samples = 500
X, Y = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1, random_state = 0)
#データを8:2に分割
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2)
#モデル定義
model = Sequential() #Sequentailオブジェクトを生成
model.add(Dense(input_dim = 2, units = 8, activation = 'relu')) #1層目、活性化関数はReLU関数
model.add(Dense(units = 1,activation = 'sigmoid')) #2層目(出力層)、活性化関数はsigmoid関数
#コンパイル
model.compile(optimizer = SGD(lr = 0.5), loss = 'binary_crossentropy')
history = model.fit(
train_X, #訓練データ
train_Y, #ラベル
epochs = 100, #エポック数
batch_size = 8, #ミニバッチのサイズを指定
verbose = 0
)
# 離散化を行う関数
def discretize(proba):
threshold = np.array([0.5]) # 0か1かを分ける閾値を0.5に設定
discretized = (proba >= threshold).astype(int) # 閾値未満で0、以上で1に変換
return discretized
pred_labels = [] #結果格納用
pred = model.predict(test_X)#試験データを入力して出力を得る
for p in pred:
pred_label_ = discretize(p) #離散化する
pred_labels.append(pred_label_[0])
pred_labels = np.array(pred_labels) #numpy arrayに変換
# 推論結果を描画
import numpy as np
import matplotlib.pyplot as plt
X_red = test_X[pred_labels == 0]
X_blue = test_X[pred_labels == 1]
plt.scatter(X_red[:, 0], X_red[:, 1], color='red') #ラベルが0のデータ
plt.scatter(X_blue[:, 0], X_blue[:, 1], color='blue') #ラベルが1のデータ
かなり上手く分類できていますね。
なお、この流れをSubclassing APIで実装するとどのようになるのかを下に示しました。興味がある人は参考にしてみてください。
import tensorflow as tf
from tensorflow.keras.losses import BinaryCrossentropy
from tensorflow.keras.optimizers import SGD
class Model(tf.keras.Model):
def __init__(self,input_dim, hidden_dim, output_dim):
super(Net,self).__init__()
self.f1 = tf.keras.layers.Dense(
units = hidden_dim,
input_dim = input_dim,
activation = 'relu'
)
self.f2 = tf.keras.layers.Dense(
units = output_dim,
activation = 'sigmoid'
)
def call(self, x, training = None):
x = self.f1(x)
y = self.f2(x)
return y
#モデルをNetクラスのインスタンスとして生成
model = Model(2, 8, 1)
#損失関数
loss_fn = BinaryCrossentropy()
#オプティマイザ
optimizer = SGD(learning_rate = 0.5)
#勾配計算、パラメータ更新を行う関数
def train(x,t):
with tf.GradientTape() as tape:
pred = model(x)
loss = loss_fn(t,pred)
grad = tape.gradient(
loss,
model.trainable_variables
)
optimizer.apply_gradients(zip(grad, model.trainable_variables))
return loss
#学習させる
for epoch in range(100):
epoch_loss = 0.
loss = train(train_X,train_Y)
epoch_loss += loss.numpy()
if epoch % 100 == 0:
print("epoch: %d loss: %f" % (epoch+1 ,float(loss)))
#推論
pred = model(test_X)
2クラス以上の分類のためのテクニック「ワンホットエンコーディング」の紹介
ここまでで2クラス分類は完了ですが、最後に少しだけ補足しておくことがあります。
今回のデータセットはsklearnのdatasets.make_circles関数を用い、ラベルはその機能で自動的に割り振られた0もしくは1という一つの数字でした。このように一つの数字でラベルを表すことをラベルエンコーディングなどと呼びます。例えば今回のように赤は0、青は1、といった具合です。
しかし他にもラベルの表し方があり、よく用いられるものにワンホットエンコーディング というものがあります。これは0と1の配列でラベルを表現するものです。この方法では各クラス毎に0と1の配列を作り、一か所だけ1にして他の値は0に設定し、その1の位置により区別を行います。言葉で説明されてもよくわからないかもしれませんが、下の図を見てください。三つのクラス、りんご、みかん、ももがあったとき、それぞれに番号を割り振るのがラベルエンコーディングです。それに対し、0と1のベクトルでラベルを表現するのがワンホットエンコーディングです。
図3
今回は2クラス分類するときのラベルはラベルエンコーディングで表しましたが、ワンホットエンコーディングでラベルを表現する方法でも実装してみようと思います。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
import tensorflow as tf
# データセットを用意
samples = 500
X, t = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1, random_state = 0)
#ワンホットエンコーディング(tensorflowの機能を用いる。引数に元のラベル、クラス数、データ型を指定)
Y = tf.one_hot(t, depth=2, dtype=tf.float32)
Y = np.array(Y)#ndarrayに直す
# 可視化のために2クラスを別の変数に分ける
X_red = X[t == 0]
X_blue = X[t == 1]
plt.scatter(X_red[:, 0], X_red[:, 1], color='red')
plt.scatter(X_blue[:, 0], X_blue[:, 1], color='blue')
#Yを出力してみる
print(Y)
上のコードのワンホットエンコーディングのところでは、ラベル0は[1,0]に変換し、ラベル1は[0,1]に変換しています。わざわざラベルを一つの数字から配列に置き換えるのは無駄に思えるかもしれませんが、よりクラス数が増えたときにはこのワンホットエンコーディングが便利です。また、tensorflowのone_hotメソッドの引数にもともとのラベル、クラス数を指定すると自動でラベルをワンホットエンコーディングしてくれます。Yを出力してみた結果、実際に0と1の二次元配列が出力されていることもわかります。
ラベルをワンホットエンコーディングすることができたので、次に学習に入ります。流れはワンホットエンコーディングしなかったときと同じです。まず、データセットを訓練用データセットと試験用データセットに分割します。
from sklearn.model_selection import train_test_split
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2) #データを分割
次に、モデルの構築です。注意すべきはモデルの出力層と損失関数です。ワンホットエンコーディングしたことにより、ラベルが要素数2の配列として表されているためそれに合わせて出力層のサイズを2にし、損失関数はクロスエントロピー誤差を使用する必要があります。クロスエントロピー誤差は多クラス分類を行うときに良く用いられる損失関数です。TensorFlowでは、多クラス分類におけるクロスエントロピー誤差としてcategorical_crossentropyが用意されています。また、多クラス分類における出力層の活性化関数としてはソフトマックス関数 という関数を用います。
デフォルトのコード
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
model = Sequential()#Sequentailオブジェクトを生成
model.add(Dense(input_dim = 2, units = 8, activation = 'relu'))#1層目、活性化関数はReLU関数
model.add(Dense(units = 2,activation = 'softmax'))#2層目(出力層)、活性化関数はsoftmax関数
#モデルのサマリ(全体像)を表示
model.summary()
#コンパイル(オプティマイザは変更なし)
model.compile(optimizer = SGD(lr = 0.5), loss = 'categorical_crossentropy')
では、学習させます。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from sklearn import datasets
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
# データセットを用意
samples = 500
X, t = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1, random_state = 0)
#ワンホットエンコーディング(tensorflowの機能を用いる。引数に元のラベル、クラス数、データ型を指定)
Y = tf.one_hot(t, depth=2, dtype=tf.float32)
Y = np.array(Y)#ndarrayに直す
#データを8:2に分割
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2)
#モデル定義
model = Sequential() #Sequentailオブジェクトを生成
model.add(Dense(input_dim = 2, units = 8, activation = 'relu')) #1層目、活性化関数はReLU関数
model.add(Dense(units = 2,activation = 'softmax')) #2層目(出力層)、活性化関数はsoftmax関数
#コンパイル
model.compile(optimizer = SGD(lr = 0.5), loss = 'categorical_crossentropy')
history = model.fit(
train_X, #訓練データ
train_Y, #ラベル
epochs = 100, #エポック数
batch_size = 8, #ミニバッチのサイズを指定
verbose = 0
)
#描画
plt.plot(history.history['loss']) #fit()メソッドの出力に保存されている損失を描画
plt.show()
しっかり損失が下がっています。推論を行わせてみましょう。ここでも、モデルの出力値が二つであるということに注意します。ソフトマックス関数の出力は0から1までの実数ですので、確率として解釈できます。よってあるデータを入力したときの二つの出力の内、値が大きい方のクラスにそのデータが分類される確率が高いということです。
図4
よって、以下では二つの出力値の内、値が大きい方のインデックスを取得してその値が0か1かでラベルを判断しています。
pred_labels_2 = [] #結果格納用
pred = model.predict(test_X)#試験データを入力して出力を得る
for p in pred:
if np.argmax(p) == 0 :
pred_labels_2.append([1.,0.])
else:
pred_labels_2.append([0.,1.])
pred_labels_2 = np.array(pred_labels_2) #numpy arrayに変換
では、test_Xを推論させてその結果を描画してみましょう。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from sklearn import datasets
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
# データセットを用意
samples = 500
X, t = datasets.make_circles(n_samples=samples, shuffle=False, factor=0.3, noise=0.1, random_state = 0)
#ワンホットエンコーディング(tensorflowの機能を用いる。引数に元のラベル、クラス数、データ型を指定)
Y = tf.one_hot(t, depth=2, dtype=tf.float32)
Y = np.array(Y)#ndarrayに直す
#データを8:2に分割
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2)
#モデル定義
model = Sequential() #Sequentailオブジェクトを生成
model.add(Dense(input_dim = 2, units = 8, activation = 'relu')) #1層目、活性化関数はReLU関数
model.add(Dense(units = 2,activation = 'softmax')) #2層目(出力層)、活性化関数はsoftmax関数
#コンパイル
model.compile(optimizer = SGD(lr = 0.5), loss = 'categorical_crossentropy')
history = model.fit(
train_X, #訓練データ
train_Y, #ラベル
epochs = 100, #エポック数
batch_size = 8, #ミニバッチのサイズを指定
verbose = 0
)
pred_labels_2 = [] #結果格納用
pred = model.predict(test_X)#試験データを入力して出力を得る
for p in pred:
if np.argmax(p) == 0 :
pred_labels_2.append([1.,0.])
else:
pred_labels_2.append([0.,1.])
pred_labels_2 = np.array(pred_labels_2) #numpy arrayに変換
pred_array = []
#ワンホットエンコーディングしたラベルを一つの数字に戻す
for i in pred_labels_2:
if i[0] == 1:
pred_array.append(0)
else:
pred_array.append(1)
pred_array = np.array(pred_array)
X_red = test_X[pred_array == 0] #ラベルにより色分け
X_blue = test_X[pred_array == 1]
#描画
plt.scatter(X_red[:, 0], X_red[:, 1], color='red') #ラベルが0のデータ
plt.scatter(X_blue[:, 0], X_blue[:, 1], color='blue') #ラベルが1のデータ
上手く分類することができています。このときもSubclassing APIで実装するとどのようになるのか考えてみるのもよいかもしれません。
まとめ
今回は、sklearnの機能を用いて生成した2クラスデータセットの分類に挑戦しました。本格的に深層学習らしいことをやれるようになってきたのではないかと思います。データセットの分割、データローダーの作成、ワンホットエンコーディングなど非常に重要な要素も盛りだくさんでした。忘れないようによく復習しておきましょう。
TensorFlow チュートリアル Part3 を公開!
本記事の続編を公開しました!!犬、猫、鳥といった3クラスの分類をPart3では行っています。より複雑になったデータを分類するために必要なテクニックを公開しています。是非見ていただければと思います。
参考文献