TensorflowとKerasでよく使うもので、かつ最初よくわからなかったものを調べてコメントアウトと補足でまとめてみました。
今後コードを書いていく上で、毎回毎回同じものを
「ここなんだろう?」→調べる
という作業をするのは時間がもったいないので、基本的なことはこのページさえ見れば大体わかるっていうものを書いてみました。
詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~
Tensorflow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
import numpy as np import tensorflow as tf from sklearn.utils import shuffle # 乱数シード np.random.seed(0) tf.set_random_seed(0) M = 2 # 入力データの次元 K = 3 # クラス数<em>,出力の次元</em> n = 100 # クラスごとのデータ数 N = n * K # 全データ数 ''' データの生成 ''' # 訓練データ【補足1】 X1 = np.random.randn(n, M) + np.array([0, 10]) X2 = np.random.randn(n, M) + np.array([5, 5]) X3 = np.random.randn(n, M) + np.array([10, 0]) # 教師データ【補足2】 Y1 = np.array([[1, 0, 0] for i in range(n)]) Y2 = np.array([[0, 1, 0] for i in range(n)]) Y3 = np.array([[0, 0, 1] for i in range(n)]) # concatenateで行列を連結させる。axis=0のときは行方向に連結する。【補足3】 X = np.concatenate((X1, X2, X3), axis=0) Y = np.concatenate((Y1, Y2, Y3), axis=0) ''' モデル設定 ''' # W,bをtensorflowの独自の型を持つ変数に変換する W = tf.Variable(tf.zeros([M, K])) b = tf.Variable(tf.zeros([K])) # placeholderを使うと、モデルの定義のときにはデータの次元だけを決め、モデルの学習など実際にデータが必要になったタイミングで値を入れて実装の式を評価することを可能にする。 # 第1引数はテンソル内の要素の型を表す。float32は浮動小数点数32ビット。第2引数のshapeはテンソルの形状。 x = tf.placeholder(tf.float32, shape=[None, M]) t = tf.placeholder(tf.float32, shape=[None, K]) # matmul()は行列の積を計算する。 y = tf.nn.softmax(tf.matmul(x, W) + b) # reduce_meanはミニバッチごとの平均値を返す。 # reduce_sumは全ての要素で和をとり0階のテンソルとしてスカラー値を返却する。 # reduction_indicesは引数に一つの値を渡すと「[その引数+1]次元目」を足し合わせた結果を返す。【補足4】 cross_entropy = tf.reduce_mean(-tf.reduce_sum(t * tf.log(y),reduction_indices=[1])) # GradientDescentOptimizer()は「SGD(lr)」に相当。 minimizeで交差エントロピー誤差を最小化する。 train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy) # correct_predictionで学習後の結果が正しいかを確認する。 # argmax(a,n)はn+1次元目についての最大値の添字を返す。つまりn=1のときは列成分についての最大値を持つ要素の添字を返す。【補足5】 # equal(a,b)は a==bのときTrue, a!=bのときFalseを返す。 correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(t, 1)) ''' モデル学習 ''' # 初期化。以下3行でモデルの定義で宣言した変数と式の初期化が行われる。 init = tf.global_variables_initializer() sess = tf.Session() sess.run(init) batch_size = 50 # ミニバッチサイズ n_batches = N // batch_size # '//'は整数の割り算 # ミニバッチ学習 # 各エポックごとにデータをシャッフルする。 for epoch in range(20): # epoch=20 X_, Y_ = shuffle(X, Y) # データをランダムにシャッフルする。【補足6】 for i in range(n_batches): start = i * batch_size end = start + batch_size # train_stepは勾配降下法による学習に相当する。 # feed_dictによってplaceholderである変数に実際の値を代入する。 sess.run(train_step, feed_dict={ x: X_[start:end], t: Y_[start:end] }) ''' 学習結果の確認 ''' # 300個全部の結果を見てもいいが流石に多すぎるのでそのうちの10個をランダムに選ぶ。 X_, Y_ = shuffle(X, Y) # まずシャッフルする。 # 次に0番目から10番目までをスライスする。 # eval()で、ニューロンが発火する・しないを適切に分類できるようになっているか確認できる。 classified = correct_prediction.eval(session=sess, feed_dict={ x: X_[0:10], t: Y_[0:10] }) # probは各入力に対する出力確率。 prob = y.eval(session=sess, feed_dict={ x: X_[0:10] }) print('classified:\n{0}\n'.format(classified)) print('output probability:\n{0}\n'.format(prob)) |
結果
以下補足。「○行目」は上のコードの行数を指します。
pythonインタプリタで
1 2 |
>>> import numpy as np >>> import tensorflow as tf |
を事前に実行しているものとします。
補足1 random.randnと18行目でしたいこと
np.random.randn(10,2)は標準正規分布に従う乱数で10*2行列を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> np.random.seed(0) >>> X1 = np.random.randn(10,2) >>> X1 array([[ 1.76405235, 0.40015721], [ 0.97873798, 2.2408932 ], [ 1.86755799, -0.97727788], [ 0.95008842, -0.15135721], [-0.10321885, 0.4105985 ], [ 0.14404357, 1.45427351], [ 0.76103773, 0.12167502], [ 0.44386323, 0.33367433], [ 1.49407907, -0.20515826], [ 0.3130677 , -0.85409574]]) |
このX1の要素に[0,10]を足してあげることで、X1[n][2]が(標準正規分布+10)になり、乱数の値の幅をコントロールしています。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> X = X1 + np.array([0,10]) >>> X array([[ 1.76405235, 10.40015721], [ 0.97873798, 12.2408932 ], [ 1.86755799, 9.02272212], [ 0.95008842, 9.84864279], [ -0.10321885, 10.4105985 ], [ 0.14404357, 11.45427351], [ 0.76103773, 10.12167502], [ 0.44386323, 10.33367433], [ 1.49407907, 9.79484174], [ 0.3130677 , 9.14590426]]) |
【参考】Numpyによる乱数生成まとめ – Qiita
【参考】正規分布とは何なのか?その基本的な性質と理解するコツ | アタリマエ!
補足2 行列をforで作成
これも打ってみると自明ですが、forループを回すことで1行で10*3行列ができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> Y = np.array([[1,0,0] for i in range(10)]) >>> Y array([[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]]) |
補足3 concatenate
適当に2*3行列の配列X作ります。
1 2 3 4 5 |
>>> X = np.arange(6) >>> X = X.reshape(2,3) >>> X array([[0, 1, 2], [3, 4, 5]]) |
axis=0のとき、行方向に連結します。
1 2 3 4 5 6 |
>>> X0 = np.concatenate((X,X), axis=0) >>> X0 array([[0, 1, 2], [3, 4, 5], [0, 1, 2], [3, 4, 5]]) |
axis=1のとき、列方向に連結します。
1 2 3 4 |
>>> X1 = np.concatenate((X,X), axis=1) >>> X1 array([[0, 1, 2, 0, 1, 2], [3, 4, 5, 3, 4, 5]]) |
補足4 reduce_sumのreduction_indices
これかなりわかりにくかったんですが、reduction_indicesに何も指定しないと、全要素の和が0階のテンソル(スカラー値,1*1行列の形)で返されます。
1 2 3 4 |
>>> x = tf.constant([[1,2,3],[4,5,6],[7,8,9]]) >>> sess = tf.Session() >>> sess.run(tf.reduce_sum(x)) 45 |
reduction_indicesに0を入れると、1次元目を足し合わせた和が返されます。
例えば、x[n][1]を見てみると答えの0次元目は
x[1][0] + x[2][0] + x[3][0]
となっています。
1 2 |
>>> sess.run(tf.reduce_sum(x,reduction_indices=[0])) array([12, 15, 18], dtype=int32) |
reduction_indicesに1を入れると、2次元目を足し合わせた和が返されます。
例えば、x[0][n]を見てみると答えの0次元目は
x[0][1] + x[0][2] + x[0][3]
となっています。
1 2 |
>>> sess.run(tf.reduce_sum(x,reduction_indices=[1])) array([ 6, 15, 24], dtype=int32) |
【参考】
[TensorFlow] APIドキュメントを眺める -Math編- | Developers.IO
算術記号とTensorFlow関数の対応 | SaintSouth.NET
補足5 argmax
1次元配列だとわかりやすいです。
以下の例ではx1[1]が9なので最も大きいですね。
なので「1」と返ってきます。
1 2 3 4 |
>>> sess = tf.Session() >>> x1 = tf.constant([1,9,6,5,2,4]) >>> sess.run(tf.argmax(x1,0)) 1 |
順番を変えてみました。
x1[5]が最も大きいので「5」と返ってきます。
1 2 3 |
>>> x2 = tf.constant([6,5,4,2,1,9]) >>> sess.run(tf.argmax(x2,0)) 5 |
2次元配列を考えてみます。
argmaxの第2引数を0にすると、1次元目(行成分)についての最大値をそれぞれ返します。
つまりy[0][n]とy[1][n]を比べて前者が大きければ「0」、後者が大きければ「1」を返しています。
同様に、argmaxの第2引数を1にすると、2次元目(列成分)についての最大値をそれぞれ返します。
y[n][1]とy[n][2]とy[n][3]とy[n][4]を比較しています。
1 2 3 4 5 |
>>> y = tf.constant([[1,4,5,7],[6,3,2,8]]) >>> sess.run(tf.argmax(y,0)) array([1, 0, 0, 1]) >>> sess.run(tf.argmax(y,1)) array([3, 3]) |
補足6 shuffle
sckit-learnのshuffleを使っています。
1 |
>>> from sklearn.utils import shuffle |
結果を見たらわかると思いますが、シャッフルを行うたびに要素がシャッフルされます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
>>> X array([[1, 2], [3, 4], [5, 6]]) >>> X_ = shuffle(X) >>> X_ array([[1, 2], [3, 4], [5, 6]]) >>> X_ = shuffle(X) >>> X_ array([[3, 4], [1, 2], [5, 6]]) >>> X_ = shuffle(X) >>> X_ array([[3, 4], [1, 2], [5, 6]]) |
Keras
上のコードと全く同じ挙動を示すkeras版のコードです。
tensorflown方で書いたコメントと同じものは端折っているところもあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import numpy as np from keras.models import Sequential from keras.layers import Dense, Activation from keras.optimizers import SGD from sklearn.utils import shuffle import matplotlib.pyplot as plt np.random.seed(0) # 乱数シード M = 2 # 入力データの次元 K = 3 # クラス数,出力の次元 n = 100 # クラスごとのデータ数 N = n * K # 全データ数 ''' データの生成 ''' X1 = np.random.randn(n, M) + np.array([0, 10]) X2 = np.random.randn(n, M) + np.array([5, 5]) X3 = np.random.randn(n, M) + np.array([10, 0]) Y1 = np.array([[1, 0, 0] for i in range(n)]) Y2 = np.array([[0, 1, 0] for i in range(n)]) Y3 = np.array([[0, 0, 1] for i in range(n)]) X = np.concatenate((X1, X2, X3), axis=0) Y = np.concatenate((Y1, Y2, Y3), axis=0) ''' モデル設定 ''' model = Sequential() # Sequential()で層構造のモデルを定義する。 model.add(Dense(input_dim=M, units=K)) # Denseは全結合層,input_dimは入力の次元,unitsはクラス数。 model.add(Activation('softmax')) # Activation層でsoftmax関数を使う。 # lossは損失関数,optimizerは重みの更新方法,lrは学習率。 model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.1), metrics=['accuracy']) ''' モデル学習 ''' minibatch_size = 50 # 固定のエポック数でモデルを学習する model.fit(X, Y, epochs=20, batch_size=minibatch_size) # バッチごとにある入力データにおける損失値を計算する loss_and_metrics = model.evaluate(X, Y) ''' 学習結果の確認 ''' X_, Y_ = shuffle(X, Y) # predict_classesはバッチごとに入力サンプルに対するクラスの予測をnumpy配列で返す classes = model.predict_classes(X_[0:10], batch_size=minibatch_size) # predict_probaはバッチごとに入力サンプルに対する各々のクラスに所属する確率の予測値をnumpy配列で返す prob = model.predict_proba(X_[0:10], batch_size=minibatch_size) print('classified:\n{0}\n'.format(np.argmax(model.predict(X_[0:10]), axis=1) == classes)) print('output probability:\n{0}\n'.format(prob)) print(loss_and_metrics) # 本と同じようにプロットしてみる。 for i in range(n): plt.plot(X1[i][0], X1[i][1], 'ro') plt.plot(X2[i][0], X2[i][1], 'b^') plt.plot(X3[i][0], X3[i][1], 'g+') plt.show() |
結果
最初のxのデータの分布図。
つまり今回学習させたかったことは
赤○のところ付近にある数字の場合は[1,0,0]
青△のところ付近にある数字の場合は[0,1,0]
緑+付近にある数字の場合は[0,0,1]と返す。
という感じのモデルです。
おまけ
最初の変数宣言の部分の「入力データ」や「クラス数」などの意味が文字から連想しにくいので簡単に図を書いてみました。M,K,nがどこを指しているのかひと目でわかると思います。