東京にいるけどfukuokaexのYOSUKEです。
最近、エリクサーちゃんで学ぶ Elixirの動画を作成し始めてるので良かったらチャンネル登録お願いします。本格的に来週後半からの予定です。
ゼロから作る Deep Learningという本を見ながら Elixirの計算ライブラリ系を学ぶシリーズです。
正直このアウトプットは試行錯誤を書き連ねる予定なので、手っ取り早く知りたいって方は、学習し終えたらものからまとめ書くつもりなのでそれまでお待ちください。m(_ _)m
学習開始
この書籍を元にElixirで学びます。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
と言う事で、今回は 第2章のパーセプトロンを学びます。
内容は、書籍に任せることにして、Elixirでの実装部分を学習しながら進めたいと思います。
前回の学習内容はこちらです。
2.3 パーセプトロンの実装
- パーセプトロンとは
複数の信号を入力として受け取り、一つの信号を出力します。
と言う事で、論理演算のイメージみたいです。ただし、0と1の重みづけの際に、0と判断するのか、1と判断するのかの閾値を決める必要があり、論理演算の場合は人間があらかじめ決めた閾値の通りに動くのを、機械学習の問題ではこの閾値を決める作業をコンピュータに自動で行わせる作業で、人がやることはパーセプトロンの構造を考えることにあると思います。
2.3.1 簡単な実装
準び
$ mix new perceptron
ANDゲートの実装
defmodule Perceptron do
def _and(x1, x2) do
[w1, w2, theta] = [0.5, 0.5, 0.7]
tmp = x1 * w1 + x2 * w2
case tmp do
tmp when tmp > theta -> 1
tmp when tmp <= theta -> 0
end
end
end
ANDゲートの実行結果
iex()> Perceptron._and(1,0)
0
iex()> Perceptron._and(0,0)
0
iex()> Perceptron._and(0,1)
0
iex()> Perceptron._and(1,1)
1
NANDゲートの実装
NANDゲートはANDゲートの実装のパラメータを反転する事で実現できるので、反転してみました。
def _nand(x1, x2) do
[w1, w2, theta] = [-0.5, -0.5, -0.7]
tmp = x1 * w1 + x2 * w2
case tmp do
tmp when tmp > theta -> 1
tmp when tmp <= theta -> 0
end
end
NANDゲートの実行結果
iex()> Perceptron._nand(1,1)
0
iex()> Perceptron._nand(0,1)
1
iex()> Perceptron._nand(0,0)
1
iex()> Perceptron._nand(1,0)
1
ORゲートの実装
ORゲートは、どちらか1つが1であれば1になり0になるのはどちらも0の場合のみなのでパラメータのthetaを0にすればOKです。
def _or(x1, x2) do
[w1, w2, theta] = [0.5, 0.5, 0.0]
tmp = x1 * w1 + x2 * w2
case tmp do
tmp when tmp > theta -> 1
tmp when tmp <= theta -> 0
end
end
実は、ここまで書くまでに最初、少しだけ、上記のプログラムを変更して書いてました。
例えば、NANDゲートに関しては、こんな感じで、ここでこのプログラムは同じ構造であることが重要なんだと気がつきました。要するにパラメータを変えるだけで挙動が変わる、構造は同じモデルである事が重要なのだと。そこで、Enum.map(& &1 * -1)
の余計な処理を削除して、パラメータを変更する形に変えました。
def _nand(x1, x2) do
[w1, w2, theta] = [0.5, 0.5, 0.7] |> Enum.map(& &1 * -1)
tmp = x1 * w1 + x2 * w2
case tmp do
tmp when tmp > theta -> 1
tmp when tmp <= theta -> 0
end
end
2.3.2 重みとバイアスの導入
Nxを使うので、Nxモジュールをインストールします。
defp deps do
[
{:nx, "~> 0.4.1"}
]
end
$ mix deps.get
wを重み、bをバイアスとしてθを-bにして式変換してANDを実装する、まずはインタラクティブモードで実行しながら確認します。
iex()> x = Nx.tensor([0,1])
#Nx.Tensor<
s64[2]
[0, 1]
>
iex()> w = Nx.tensor([0.5,0.5])
#Nx.Tensor<
f32[2]
[0.5, 0.5]
>
iex()> b = -0.7
-0.7
iex()> Nx.multiply(x, w)
#Nx.Tensor<
f32[2]
[0.0, 0.5]
>
iex()> Nx.multiply(x, w)|> Nx.sum
#Nx.Tensor<
f32
0.5
>
iex()> Nx.multiply(x, w)|> Nx.sum |> Nx.add(b)
#Nx.Tensor<
f32
-0.19999998807907104
>
2.3.3 重みとバイアスによる実装
ANDゲートの実装
def nx_and(x1, x2) do
x = Nx.tensor([x1, x2])
w = Nx.tensor([0.5, 0.5])
b = -0.7
[tmp] = Nx.multiply(x, w)
|> Nx.sum
|> Nx.add(b)
|> Nx.to_flat_list()
case tmp do
tmp when tmp <= 0 -> 0
tmp when tmp > 0 -> 1
end
end
wの重みづけと違い、bのバイアスは1の出力する度合いの調整をする役割を持っている。
NANDゲートの実装
ANDのバイアスと重みのパラメータを反転するだけで実装できます。
def nx_nand(x1, x2) do
x = Nx.tensor([x1, x2])
w = Nx.tensor([-0.5, -0.5])
b = 0.7
[tmp] = Nx.multiply(x, w)
|> Nx.sum
|> Nx.add(b)
|> Nx.to_flat_list()
case tmp do
tmp when tmp <= 0 -> 0
tmp when tmp > 0 -> 1
end
end
ORゲートの実装
バイアスの重みづけを0にするだけで変更できます。
def nx_or(x1, x2) do
x = Nx.tensor([x1, x2])
w = Nx.tensor([0.5, 0.5])
b = 0
[tmp] = Nx.multiply(x, w)
|> Nx.sum
|> Nx.add(b)
|> Nx.to_flat_list()
case tmp do
tmp when tmp <= 0 -> 0
tmp when tmp > 0 -> 1
end
end
2.4 パーセプトロンの限界
パーセプトロンを用いる事で、AND, OR, NANDを同じ構造で実装できました。そこで、次はXORについてみて行きます。
2.4.1 XORゲート
x1 | x2 | y |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
ここでの学習メモ:
AND, OR, NANDは線形な領域で直線で0と1の領域を分けられたが、XORの場合は線形では分けられず、
非線形な領域での分別が必要な為、パーセプトロンでは分けられない。
しかし、層を重ねるというアイデアを駆使すればXORも分別可能になる。
x1 | x2 | s1(NAND) | s2(OR) | y(AND) |
---|---|---|---|---|
0 | 0 | 1 | 0 | 0 |
1 | 0 | 1 | 1 | 1 |
0 | 1 | 1 | 1 | 1 |
1 | 1 | 0 | 1 | 0 |
という事で、ここまで書いたら実装は簡単ですね。
def nx_xor(x1, x2) do
s1 = nx_nand(x1, x2)
s2 = nx_or(x1, x2)
nx_and(s1,s2)
end
XORは2つのパーセプトロンを介して解を出しているので、2層のパーセプトロンというらしい。
そして、この多重の層を重ねたパーセプトロンならもっと複雑な事も可能になるのと、次章のニューラルネットワークの基礎になるので重要との事でした。
よし、いよいよ次章、ニューラルネットワークに入っていける。楽しみだ!