ホーム < ゲームつくろー! < IKD備忘録

VisualStudio + Pythonでディープラーニング
KerasでXOR問題を解いてみよう

(2019. 12. 18)


 前章で活性化関数に非線形関数を用いると非線形分離問題に対処できることを示してみました。ここでは実際にKerasで非線形問題の単純な例であるXOR問題が解けるか見てみましょう。



@ 薬の飲み合わせ問題を考える

 今回解きたい問題を整理します。入力値として薬Aと薬Bを服用していない/服用したという2値を与えます。この薬は個々に服用すると病状を改善して健康になれます。でも、両方を一度に服用すると副作用が起こり健康を害してしまいます。薬の服用と健康との関係を表にするとこうなります:

薬A 薬B 健康
服用していない(0) 服用していない(0) 悪い(0)
服用した(1) 服用していない(0) 良い(1)
服用していない(0) 服用した(1) 良い(1)
服用した(1) 服用した(1) 悪い(0)

この関係はXOR(排他的論理和)です。作るモデルは薬Aと薬Bの服用(1)/非服用(0)を入力すると健康の良し(1)悪し(0)を出力するモデルです。これを実現するには活性化関数にReLUを用いた2ニューロンを持つ中間層(バイアスあり)を持ったニューラルネットワークが必要です(なぜそれらが必要かは前章でガッツリ説明しています):

このニューラルネットワークモデルをKerasで再現してテストしてみましょう。



A Kerasで実験

 Keras側のコードで必要な情報をまとめます。まず上の連結はシーケンシャルモデルです。入力層は2つなのでinput_shapeは(2,)のタプル指定。第1層はunits=2で活性化関数(activation)はReLU("relu")。第2層は前の層の値を単純に足して排他的論理和な結果を1つ吐き出したいのでunits=1で活性化関数には線形("linear")を指定します。ここSoftmaxでも良いのですが、まだ「分類」についてちゃんと説明(=勉強)していないので、見知ったLinearを使います。単純に足して欲しいのでバイアスは使いません。

 学習データは薬AとBの服用具合を0と1のセットで表現するので2次元配列になります。答えは冒頭に挙げた表になるように計算します。これらを組み込んだコードがこちら:

import keras
import numpy

#モデル作成
model = keras.Sequential()

#全結合レイヤー
dense1 = keras.layers.Dense( units = 2, activation='relu', use_bias=True, input_shape=(2,) )
model.add( dense1 )
dense2 = keras.layers.Dense( units = 1, activation='linear', use_bias=False )
model.add( dense2 )

#確率的勾配降下法で検索、平均二乗誤差で評価
sgd = keras.optimizers.SGD( learning_rate=0.1 )
model.compile( optimizer=sgd, loss='mse' )

#訓練開始
#0 or 1のセットをdataNum個作る -> XOR
dataNum = 1000
train_x = numpy.random.randint(0, 2, size=(dataNum, 2) )
train_y = numpy.logical_xor( train_x[ 0:,0:1 ], train_x[ 0:,1:2 ] )
model.fit( train_x, train_y, epochs=100 )

#テスト実施
testDataNum = 10
test_x = numpy.random.randint( 0, 2, size=(testDataNum, 2) )
answer = numpy.logical_xor( test_x[ 0:,0:1 ], test_x[ 0:,1:2 ] )
estimate = model.predict( test_x )
print( "test: ", test_x )
print( "answer: ", answer )
print( "estimate: ", estimate )

#概要表示
print( model.summary() )
print( dense1.get_weights() )

シンプルで短く書けるのが素敵ですKeras (^-^)

 データの作り方の所でちょっとだけポイント。Numpyで一様整数乱数はnumpy.random.randint関数で作れます。randin(0,2)とすると0以上2未満の整数乱数となります。2未満なので0か1という事ですね。配列の要素ごとにXORを取るのはnumpy.logical_xor関数を使います。第1引数の配列と第2引数の配列全部でXORを取ってくれます。上の場合、第1要素と第2要素でXORを取りたかったのでスライス機能を使って要素ごとの配列に変換して関数に渡しています。

 で、このコードを実行すると次のような結果をえられました(出力テキストは整形しています):

test:[
[1 1]
[0 0]
[0 0]
[1 0]
[1 1]
[0 1]
[1 0]
[1 0]
[0 1]
[1 1]]
answer:[
[False]
[False]
[False]
[ True]
[False]
[ True]
[ True]
[ True]
[ True]
[False]]
estimate: [
[1.9769354e-07] -> 0
[3.8899511e-08] -> 0
[3.8899511e-08] -> 0
[1.0000000e+00] -> 1
[1.9769354e-07] -> 0
[9.9999982e-01] -> 1
[1.0000000e+00] -> 1
[1.0000000e+00] -> 1
[9.9999982e-01] -> 1
[1.9769354e-07]] -> 0

testがテストデータで第1要素と第2要素をXORするような出力を期待しています。答えであるanswerに対してモデルからの推定値estimateはというと…、浮動小数点演算なので正確に0と1が出るわけではないのですが、見事にXORな演算をしてくれています!ニューラルネットワークでXOR問題を解決できる事を確認できました。

 で、各ニューロンに入ってくる入力値にかかる重みwとバイアスは次のような数値になったようです:

Layer1 重みw バイアスb
-0.8486822
0.85000557
-4.659321e-03
0.9494388
-0.95242774
1.364049e-07
Layer2 1.1829472
1.0532534
0

第1層、2層共にx1とx2にかかる重みの絶対値がほぼ似たような値になっていて、符号は反転しています。ReLUなのでマイナス値はゼロになる事に注意すると、これが確かにXORな振る舞いをする事が分かります。実際にグラフにプロットするとこんなモデルになっているようです:

確かにXOR演算、できてますね(^-^)



A 局所解に落ちる事も

 実際に学習させてみると、うまく学習してくれる時とうまく行かない時がありました。評価関数が発散するのではなくて局所解に落ち込んでしまう事があるようです。これだけパラメータが少ないと局所解でのモデルは全然XORしてくれなかったのですが(^-^;、パラメータが多くなると局所解でもそれっぽい解答を出してくれるようになります。ニューロンが多くなると個々のニューロンの働きが均されるので出力結果への寄与度が相対的に下がるためです。とは言え最適解が推定される事が最良なのは間違いないので、何とかそこに近付くようにハイパーパラメータを工夫する努力はしましょうw