単純パーセプトロンによる論理回路(2)

単純パーセプトロンで、ANDゲート・NANDゲート・ORゲートを作ることができる。また、これらを組み合わせることで、XORゲートも作ることができる。

このことについては、

を参照。

実は、NANDゲートだけで、他の全ての論理回路を実現できる。論理回路どころか、あらゆる回路を実現できるが、今回は論理回路に話を限定する。

まずはNOTゲートだ。

NANDゲートはすでに作成しているものとする。NANDゲートの特性から、

[0, 0] -> 1
[1, 1] -> 0

のように入力を変換する。

そこで、同じ値をNANDゲート2つの入力に通すような回路を作れば、NOTゲートになる。

次にANDゲートを作る。

ANDゲートは、NANDゲートの出力を反転させたものだ。出力を反転させるにはNOTゲートを通せばいい。

さらに、ORゲートを作る。

ORゲートは、[0, 0]のみ0に変換して、その他は1に変換する。一方、NANDゲートは、[1, 1]のみ0に変換して、その他は1に変換する。このことから、NANDゲートの入力を反転してやれば、ORゲートになることが分かる。

最後に、XORゲートだ。少し複雑なので、先に完成した配線を見よう。

NANDゲートは、入力のどちらか一方でも0であれば、必ず1を出力することに注目する。

入力が[0, 0]であれば、真ん中の2つのNANDゲートには共に0の入力がある。0の入力があるから、共に1を出力する。その2値を入力とする右端のNANDゲートは0を出力する。

入力が[1, 1]であれば、左端のNANDゲートは0を出力する。この場合も、真ん中のNANDゲートには共に0の入力がある。あとは入力が[0, 0]の場合と同じで、最後に0を出力する。

入力が[0, 1]であれば、左端のNANDゲートの出力は1になる。真ん中の下側のNANDゲートの入力は[1, 1]だから、その出力は0になる。すると、右端のNANDゲートには0の入力があるから、1を出力する。

対称性から、入力が[1, 0]の場合も同様に1を出力する。

これで、XORゲートも実現していることが分かった。

論理回路には、他にもNORゲート・XNORゲートがあるが、それぞれORゲート・XORゲートの出力を反転させればいい。つまり、最後にNOTゲートをつなげば実現する。

単純パーセプトロンによる論理回路(1)

単純パーセプトロンで、ANDゲート・NANDゲート・ORゲート・XORゲートを実装する。ただし、XORゲートは単層では実装できない。非線形領域は単純パーセプトロンで分離できないからだ。そこで、他の3種を組み合わせることで、XORゲートも実装する。

入力([x1, x2])は、

import numpy as np

X = np.array([
    [0, 0],
    [1, 0],
    [0, 1],
    [1, 1]
])

の4通りで、1が真(信号を流す)、0が偽(信号を流さない)に対応している。

出力(y)は、ANDゲートであれば、[x1, x2] = [1, 1]の場合のみ1で、その他は0になる。つまり、

Y = np.array([
    0,
    0,
    0,
    1
])

となる。

NANDゲートであれば、[1, 1]の場合のみ0で、その他は1になる。

Y = np.array([
    1,
    1,
    1,
    0
])

ORゲートであれば、[0, 0]の場合のみ0で、その他は1になる。

Y = np.array([
    0,
    1,
    1,
    1
])

XORゲートであれば、[1, 0], [0, 1]の場合に1で、その他は0になる。

Y = np.array([
    0,
    1,
    1,
    0
])

さて、単純パーセプトロンは、入力の加重総和にヘヴィサイドステップ関数を適用して出力する。

一般的には、出力層以外の層にバイアスニューロンをつけ加える。バイアスニューロンは、常に1を出力する特別なタイプのニューロンだ。これと次の層の接続部に与えられる重みのことをバイアスという。バイアス(b)は、ニューロンの発火のしやすさをコントロールし、その他の重み(w1, w2)は、各信号の重要性をコントロールする。

パラメータ(バイアスと重み)を適切に設定することで、AND・NAND・ORは実現できる。適切なパラメータの組み合わせは無限にある。

そして、XORゲートは、以下のような配線で実現できる。

Logic Gates by Perceptron

In [1]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
def heaviside_step(z):
    return np.where(z < 0, 0, 1)
In [3]:
x = np.arange(-5.0, 5.0, 0.1)
y = heaviside_step(x)
In [4]:
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
In [5]:
class Perceptron:
    def  __init__(self, weights):
        self.weights = weights
        
    def output(self, input):
        return self.__activate(np.dot(input, self.weights[1:]) + self.weights[0])
    
    def __activate(self, weighted_sum):
        return self.__heaviside_step(weighted_sum)
    
    def __heaviside_step(self, z):
        return np.where(z < 0, 0, 1)
In [6]:
X = np.array([
    [0, 0],
    [1, 0],
    [0, 1],
    [1, 1]
])
In [7]:
and_gate = Perceptron(np.array([-0.7, 0.5, 0.5]))
out = and_gate.output(X)
out
Out[7]:
array([0, 0, 0, 1])
In [8]:
nand_gate = Perceptron(np.array([0.7, -0.5, -0.5]))
out = nand_gate.output(X)
out
Out[8]:
array([1, 1, 1, 0])
In [9]:
or_gate = Perceptron(np.array([-0.3, 0.5, 0.5]))
out = or_gate.output(X)
out
Out[9]:
array([0, 1, 1, 1])
In [10]:
class XOR:
    def __init__(self):
        self.and_gate = Perceptron(np.array([-0.7, 0.5, 0.5]))
        self.nand_gate = Perceptron(np.array([0.7, -0.5, -0.5]))
        self.or_gate = Perceptron(np.array([-0.3, 0.5, 0.5]))
        
    def output(self, input):
        nand_output = self.nand_gate.output(input)
        or_output = self.or_gate.output(input)
        and_input = np.vstack([nand_output, or_output]).T
        return self.and_gate.output(and_input)
In [11]:
xor_gate = XOR()
out = xor_gate.output(X)
out
Out[11]:
array([0, 1, 1, 0])

ソースコード

https://github.com/aknd/machine-learning-samples

2層フィードフォワードニューラルネットワークによる手書き数字認識(Kerasでの実装)

2層フィードフォワードニューラルネットワークで、MNISTの手書き数字を認識する。

MNISTについては、

を参照。

一般的に、入力層はニューラルネットワークの層に含めない。2層だと、中間層が1層と、出力層のみの構成になる。

また、フィードフォワード(順伝播)なので、データは一方向にのみ流れる。

入力は、28×28=784次元のベクトルで、出力は10次元のベクトルとなる。出力の各要素は、分類されるクラスに対応している。今回は0-9の整数である。

出力層ではソフトマックス関数を使う。すると、出力の各要素は0-1の小数値で、和は1になる。これを、入力データがそれぞれのクラスに分類される確率と解釈する。そして、確率が最大となるクラスに分類する。

答えであるラベルデータは、あらかじめ10次元ベクトルに変換しておく。該当クラスの要素だけ1で、他の要素は0にする。いわゆるone-hot表現である。

最適化アルゴリズムにはAdamを使う。Adamは、トレーニングデータの一部(ミニバッチ)を用いた学習(パラメータ更新)を繰り返す。今回、ミニバッチのサイズ(batch_size)を1,000とする。

トレーニングデータが60,000個だとすると、60,000 / 1,000 = 60回のパラメータ更新で、データを全て使うことになる。この1サイクルのことをエポックという。

エポック数は100とする。最後までいけば、60 × 100 = 6,000回のパラメータ更新が行われる。しかし、実際にはearly stoppingを使うので、より少ないエポック数のサイクルが終わったところで打ち切られる。後述するように、トレーニングデータも60,000個より少なくなる。

early stoppingをするためには、バリデーションデータが必要となる。Kerasでは、model.fitの引数validation_splitを渡せばいい。例えばvalidation_split=0.1とすると、自動的にトレーニングデータの10%をバリデーションデータとして使ってくれる。

バリデーションデータは、自分でトレーニングデータから一部をよけておいてもいい。この場合、model.fitの引数validation_dataに、そのデータを渡すことになる。

validation_dataにテストデータを渡しているソースコードもあるが、本当は良くない。テストの時に、実際よりも高い正答率が出てしまう。テストデータを使って最適なパラメータを見つけたのだから、当然そうなる。テストデータは、最後にパフォーマンスを見るためだけにとっておく。

さて、early stoppingをする場合、バリデーションデータのクロスエントロピー誤差が変化しなくなった時点でエポックを打ち切る。これによって、手動で最適なエポック数を調整する必要がなくなる。

また、学習すればするほど正答率が上がる訳でもない。トレーニングデータに適合し過ぎて、未知データに適合できなくなることがある。これは、オーバーフィッティング(過学習)と呼ばれる。early stoppingには、オーバーフィッティングを防ぐ役割もある。むしろ、こちらが重要である。

中間層の活性化関数には、ReLUを使う。Sigmoidの場合と比較すると、正答率が上昇することが確認できる。Sigmoidでも、中間層のユニット数を増やせば 正答率は上がるが、計算時間が多くかかってしまう。

学習後に、テストデータを使って結果を確認する。正答率を出すだけではなく、最初の50個の具体的な結果も出す。画像の上に予測を表示し、間違えている場合は括弧つきで正解も表示する。背景色を赤にすることで、間違いを見つけやすくしておく。

最後に、パラメータ(weights)を視覚化しておくのも面白い。白い部分に文字の一部があると、そのユニットは活性し、黒い部分だと抑制する。いくつかは、数字の"2"や"3"の形が浮かび上がっているように見える。それらの数字を認識するのに役立っているのかもしれない。

Handwritten Digit Recognition using Two-Layer Feedforward Neural Networks in TensorFlow with Keras

1. Importing Modules and Loading the MNIST Data

In [1]:
from keras.datasets import mnist
from keras.utils import np_utils
from keras.layers import Dense
from keras.models import Sequential
from keras.callbacks import EarlyStopping
import time
import math
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Using TensorFlow backend.
In [2]:
(x_train, y_train), (x_test, y_test) = mnist.load_data()

2. Reshaping and Normalization

In [3]:
img_rows, img_cols = 28, 28

in_size = img_rows * img_cols
out_size = 10
In [4]:
x_train = x_train.reshape(-1, in_size).astype('float32') / 255
x_test = x_test.reshape(-1, in_size).astype('float32') / 255
In [5]:
x_train.shape
Out[5]:
(60000, 784)
In [6]:
# x_train[0]
In [7]:
# one-hot encoding
y_train = np_utils.to_categorical(y_train.astype('int32'), out_size) 
y_test = np_utils.to_categorical(y_test.astype('int32'), out_size)
In [8]:
y_train[0]
Out[8]:
array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], dtype=float32)

3. Modeling

In [9]:
units = 16
# units = 512
# activation = 'sigmoid'
activation = 'relu'

optimizer = 'adam'
In [10]:
model = Sequential()
model.add(Dense(units, activation=activation, input_dim=in_size))
model.add(Dense(out_size, activation='softmax'))

model.compile(loss='categorical_crossentropy',
             optimizer=optimizer,
             metrics=['accuracy'])

4. Fitting

In [11]:
batch_size = 1000
epochs = 100

callbacks = [
    EarlyStopping(patience=0, verbose=1)
]
In [12]:
start_time = time.time()

history = model.fit(x_train,
                    y_train,
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=0,
                    validation_split=0.1,
                    callbacks=callbacks)

print('Executed in {0:.3f}s'.format(time.time() - start_time))
Epoch 00036: early stopping
Executed in 12.249s

5. Evaluation

In [13]:
plt.figure(1, figsize=(10, 4))
plt.subplots_adjust(wspace=0.5)

plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='training', color='black')
plt.plot(history.history['val_loss'], label='validation', color='dodgerblue')
plt.legend()
plt.ylim(0, 10)
plt.grid()
plt.title('Cross-Entropy Loss')
plt.xlabel('epoch')
plt.ylabel('loss')

plt.subplot(1, 2, 2)
plt.plot(history.history['acc'], label='training', color='black')
plt.plot(history.history['val_acc'], label='validation', color='dodgerblue')
plt.legend(loc='lower right')
plt.ylim(0, 1)
plt.grid()
plt.title('Recognition Accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')

plt.show()

score = model.evaluate(x_test, y_test, verbose=1)
print('Test loss: ', score[0])
print('Test accuracy: ', score[1])
10000/10000 [==============================] - 0s 16us/step
Test loss:  0.20039956135302783
Test accuracy:  0.9417

6. Test

In [14]:
test_count = 50
y_predict = model.predict(x_test)

plt.figure(1, figsize=(5, 14))
plt.subplots_adjust(hspace=0.4)

for i in range(test_count):
    plt.subplot(10, 5, i + 1)
    x = x_test[i, :].reshape(28, 28)
    y = y_predict[i, :]
    prediction = np.argmax(y)
    label = np.argmax(y_test[i, :])
    if prediction == label:
        plt.title(prediction, pad=10)
    else:
        plt.title('{}({})'.format(prediction, label), pad=10,
                  backgroundcolor='red')
    plt.axis('off')
    plt.imshow(x, cmap='gray') 

plt.show()

7. Visualizing Weights

In [15]:
weights = model.layers[0].get_weights()[0]

columns = 4
rows = math.ceil(units / columns)
plt.figure(1, figsize=(columns * 1.5, rows * 1.5))
plt.subplots_adjust(wspace=0.25, hspace=0.25)

for i in range(weights.shape[-1]):
    plt.subplot(rows, columns, i + 1)
    w = weights[:, i].reshape(img_rows, img_cols)
    plt.axis('off')
    plt.imshow(w, cmap='gray')
 
plt.show()

ソースコード

https://github.com/aknd/machine-learning-samples

Kerasに含まれるMNISTのデータ構造

機械学習の例として、手書き数字の判定をさせることがある。サンプルデータとしては、MNISTがよく使われる。Kerasのサンプルデータセットにも、MNISTが含まれている。

今回は、そのデータ構造を確認する。

まず、

from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

でMNISTのデータを読み込む。

すると、60,000個のトレーニング用データ(x_train, y_train)と、10,000個のテスト用データ(x_test, y_test)で構成されている。x_train, x_testの要素は、28×28の二次元配列である。これは、28×28ピクセルの画像と対応している。

つまり、二次元配列の要素は[0..255]の整数値で、グレイスケール画像の1画素の濃淡の階調を表現している。8ビットで表現しており、0が黒、255(=28-1)が白である。可視化してみると、黒背景に白文字で書かれていることが分かる(下記プログラム参照)。

試しに、x_trainの最初の要素を取り出す。視覚的に分かりやすいように、値が正であれば全て1に変換した上で、画素の並び順に合わせて数値を並べる。すると、なんとなく"5"であることが分かる(下記プログラム参照)。

一方、y_train, y_testには、画像に対応した[0..9]のラベルデータが格納されている。確認のため、x_trainの最初の15個を画像で表示し、対応するy_trainの値をタイトル(画像上)につけておく。

MNIST Dataset

In [1]:
from keras.datasets import mnist
import matplotlib.pyplot as plt
%matplotlib inline
import math
Using TensorFlow backend.
In [2]:
(x_train, y_train), (x_test, y_test) = mnist.load_data()
In [3]:
x_train.shape
Out[3]:
(60000, 28, 28)
In [4]:
some_digit = x_train[0]
some_digit.shape
Out[4]:
(28, 28)
In [5]:
digit_binary = [[math.ceil(eight_bit_int / 255) for eight_bit_int in row] for row in some_digit]

for row in digit_binary:
    print(row)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [6]:
y_train.shape
Out[6]:
(60000,)
In [7]:
some_digit_label = y_train[0]
some_digit_label
Out[7]:
5
In [8]:
for i in range(15):
    plt.subplot(3, 5, i + 1)
    plt.axis('off')
    plt.title(y_train[i])
    plt.imshow(x_train[i], cmap='gray')
    
plt.show()

ソースコード

https://github.com/aknd/machine-learning-samples

FX, EURUSD(ユーロドル), 2018/05/18

【メモ】

4時間足の下降トレンドが一旦終了して、大きく戻してからのもう一段の下げ途中だった。

4時間足の短期移動平均線(1時間足の中期移動平均線)への戻しからの戻り売り。

基本的に週持ち越しはしたくない(本日は金曜日)し、既にお酒を飲んでいたこともあり、節目到達で利益確定した。

節目を抜けかけていた(1時間足で見た感じは抜けてる)ので、この後ニューヨーク時間にかけて大きく下がるかもしれないが、利食い千人力ということで。

欲張らずにまた来週。

下手したら、節目を抜けると見せかけて反発し、ダブルボトムを作って上昇という可能性もあるし。

FX, GBPUSD(ポンドドル), 2018/05/15

【メモ】

4時間足の大きな下降トレンド終了からの横ばいが続いていた。

日足の短期移動平均線が追いついてきて、もう一段下げそうな局面になっていた。

1時間足でヘッドアンドショルダーズを形成したところでショート。

レートが大きく戻してきたので、建値決済で逃げてから再エントリーした。

まだ数日レンジが続く可能性もあるので、ポジション2分割で、片方はレンジの底で決済し、もう片方は様子を見て伸ばす予定だった。

しかし、急激な下降が生じたため、急反発で含み益がごっそり無くなるのを恐れて全決済した。

結果的に、レンジの底には到達した(これを書いている時点では、さらに下降するかは不明)。

ちょっと焦って早く決済し過ぎたかもしれない。

結果論かもしれないが、これだけ勢いがつけば一旦レンジの底を試すだろうと思い、落ち着いてもう少し待てば良かった。

なんにせよ、デイトレードとしてはまずまずの利益が出たので良しとしよう。

ユーロドルも売りたかったし、こちらの方がキレイに下げたが、既にポンドドルを全力で売っていたので売れなかった。

FX, EURUSD(ユーロドル), 2018/05/11

【メモ】

4時間足レベルの下降トレンド終了後の戻しを取りに行った。

ダブルボトムのネックラインでさらに小さくダブルボトムからのネックライン抜け。

金曜日の夜なので、早めの利益確定。

結果的にプラスだが、早めに利益確定することを考えると、損切り幅を大きく取りすぎた。これではリスクリワード比率が悪いので気をつける必要がある。