読者です 読者をやめる 読者になる 読者になる

俵言

しがない社会人が書く、勉強とかゆるふわプロコンのこと

【jupyterで学ぶ】 ゼロから作るDeep Learning - 第1回:パーセプトロン

はじめに

このブログ、一応勉強するためと銘打っているものの、すっかりたまに競プロの話を書くだけの場になってしまいました。 かつては本を読むモチベーションにしようとしてたんですけどねえ..(ただし一日坊主)

これはアカンということで、久々に本を勉強しながらブログ書くことにしました。また自然消滅するって?いや、今度こそはやり遂げてみせる..!

と、いうわけで題材はコイツです。実は一月ほど前に買ったのに積んだままなんでいい加減勉強したい。

今まで深層学習に興味があって「勉強したい!」と思って本を買ったりもしたんですが、その前のニューラルネット自体の知識がイマイチで二の足踏んでました。
「伝播していくとかイメージはわかるけど、実際にどう動くんかとか学習とかするんかよくわからん!」って状態がずっと続いていた感じ。そんなときに出たこの本は、僕の好きなpythonで本当に初歩の初歩から実装して学んでいくということで、絶対いい勉強になるってことで読むことにしました。
理論的に深いところまでは行かないらしいですが、そこは有名な青色のアレとか濃青色のコレでいずれカバーするということで..

これに加えて、最近はまり出した jupyter notebook をもっと使って習熟するのも目的です。まあタイトルには入れてるけど記事に出てくるかは微妙かも..。伝わりにくいですが可視化の練習をjupyter notebook上でやりつつ勉強しています。

目次は以下の通り。

見ての通り、実はdeep learningが出てくるのは一番最後の章です。ただそこに至るまでを順を追ってやっていくので、ニューラルネットワークを勉強するのにはちょうど良いですよね!

2章 パーセプトロン

「あれ?1章は?」pythonの話書いてるだけなのでパス。pythonの基本の基本に加えて、Numpy(数値計算ライブラリ)やmatplotlib(可視化ライブラリ)が紹介されてました。windowspython入れるなら本にも書かれているAnacondaがめっちゃ楽ですね。

さて、2章はパーセプトロンの話。ぶっちゃけ本当に初歩的なところなので飛ばしても良いところです。概要を言うと、以下のような流れ。

  1. パーセプトロンで単純な論理回路を作る
  2. パーセプトロンの限界
  3. 多層にすることでの表現力アップ

1. パーセプトロンで単純な論理回路を作る

パーセプトロンの仕組みは、入力の線形和が閾値を超えれば発火する(ここでは出力が1となる)という単純な仕組みです。(以下は入力が二変数の場合)


y =
\begin{cases}
0 \ \ (w_1 x_1 + w_2 x_2 \leq \theta) \\
1 \ \ (w_1 x_1 + w_2 x_2 \gt \theta) \\
\end{cases}

あるいは、閾値( \theta)の代わりにバイアス項( b)を使って以下のようにも書ける。


y =
\begin{cases}
0 \ \ (w_1 x_1 + w_2 x_2 + b \leq 0) \\
1 \ \ (w_1 x_1 + w_2 x_2 + b \gt 0) \\
\end{cases}

というわけでコレをなぞって実際に論理関数を作る。本では一個一個関数を作ってますが、せっかくなので二入力の単層Perceptronクラスを作ります。yとか要らんけど見やすさのためにこう書いてみました。

#-*- coding:utf-8 -*-#
import numpy as np
### w,xはベクトル(np.array)、bはスカラ
class Perceptron:
    def __init__(bias,weight):
        self.b = bias
        self.w = weight
    def is_ignition(x):
        if np.sum(w*x) + b < 0:
            y = 0
        else:
            y = 1
        return y

さて、このクラスでAND、OR、NANDを作ります。重みとバイアス項を与えれば完成!

import numpy as np
from my_functions import Perceptoron as Perc
## 重みとバイアス項を与えて初期化
AND = Perc(1.5, np.array([1,1]))
OR = Perc(-0.75, np.array([1,1]))
NAND = Perc(1.5,np.array([-1,-1]))

## 結果を見たいのでpandasのDataFrameを使って(x1,x2)の組を表現
x_df = DataFrame(
{"x1":[0,1,0,1],"x2":[0,0,1,1]},
columns = ["x1","x2"]
)
print exsample_df
>>    x1  x2
>> 0   0   0
>> 1   1   0
>> 2   0   1
>> 3   1   1

## 各論理関数を適用
x_df["AND"] = x_df.apply(lambda x:AND.ignition(x[0:2]), axis = 1)
x_df["OR"] = x_df.apply(lambda x:OR.ignition(x[0:2]), axis = 1)
x_df["NAND"] = x_df.apply(lambda x:NAND.ignition(x[0:2]), axis = 1)
print x_df
>>    x1  x2  AND  OR  NAND
>> 0   0   0    0   0     1
>> 1   1   0    0   1     1
>> 2   0   1    0   1     1
>> 3   1   1    1   1     0

確かにちゃんと結果が出てます。(applyってまとめてできないんですかね?)

2. パーセプトロンの限界

これはSVMとか習ったことあるなら何遍も聞いた話だと思うんですが、パーセプトロンは線形和が閾値より高いか低いか、すなわち座標平面(空間)上で直線(平面)の上にあるか下にあるか、で発火が決まります。なので直線(平面)で分割できないような場合はうまく行きません。
で、論理演算の中だとXORは表現できないよねって話になります。

練習も兼ねて、実際に平面上に描画してみます。

# パーセプトロンの限界を確かめるためグラフを描いてみる
## DataFrameにXORを追加
x_df["XOR"] = x_df["x1"] ^ x_df["x2"]

## グラフ描くための準備
plt.rcParams["font.size"] = 18
fig,(ax_and,ax_or,ax_xor) = plt.subplots(1,3,figsize=(15,5), sharex=True, sharey = True)
x1 = np.arange(-3,3,0.1)

## AND
### 分離直線の描画
x2 = -x1 + 1.5
ax_and.plot(x1,x2)

### 発火(y=1)を赤〇、それ以外を青×でプロット
c_t = x_df[x_df["AND"] == 1] # y = 1 のものだけ選ぶ 
c_f = x_df[x_df["AND"] == 0] # y = 0 のものだけ選ぶ
ax_and.scatter(c_t["x1"],c_t["x2"], c= "red", marker = "o", label = "y = 1")
ax_and.scatter(c_f["x1"],c_f["x2"], c = "blue", marker = "x", label = "y = 0")

### ラベル設定など
ax_and.set_title("AND")
ax_and.set_xlabel("x1"); ax_and.set_ylabel("x2")
ax_and.set_xlim(-0.2,1.2); ax_and.set_ylim(-0.2,1.4)
ax_and.legend() #凡例の表示

## OR
### 直線の描画
x2 = -x1 + 0.75
ax_or.plot(x1,x2)

### 点のプロット
c_t = x_df[x_df["OR"] == 1]; c_f = x_df[x_df["OR"] == 0]
ax_or.scatter(c_t["x1"],c_t["x2"], c= "red", marker = "o", label = "y = 1")
ax_or.scatter(c_f["x1"],c_f["x2"], c = "blue", marker = "x", label = "y = 0")

### ラベル設定など
ax_or.set_title("OR")
ax_or.set_xlabel("x1"); ax_or.set_ylabel("x2")
ax_or.legend()

## XOR
### 点のプロット
c_t = x_df[x_df["XOR"] == 1]; c_f = x_df[x_df["XOR"] == 0]
ax_xor.scatter(c_t["x1"],c_t["x2"], c= "red", marker = "o", label = "y = 1")
ax_xor.scatter(c_f["x1"],c_f["x2"], c = "blue", marker = "x", label = "y = 0")

### ラベル設定など
ax_xor.set_title("XOR")
ax_xor.set_xlabel("x1"); ax_xor.set_ylabel("x2")
ax_xor.legend()

##保存
figure.savefig("and_or_xor.png")

で、and_or_xor.pngの中身がこちら。

f:id:Tawara:20161024004800p:plain

AND,ORについては 1.で作った直線で赤〇 y = 1)と青× y = 0)を分離できていることがわかります。しかしXORについては、どう直線を引っ張っても赤〇と青×を分離できない。これらを分離するためには非線形に分離する必要があるので、パーセプトロンは早速限界を迎えちゃいました。

3. 多層にすることでの表現力アップ

2.で早くも限界を迎えたパーセプトロンですが、実は層を増やすことでXORを認識できます。この本ではその一例として、これまでに作った AND,OR,NANDを組み合わせてXORを作っています。

## XORをAND,NAND,ORで表現
def XOR(x):
    s1 = OR.ignition(x)
    s2 = NAND.ignition(x)
    y = AND.ignition(np.array([s1,s2]))
    return y

## もっかいDataFrameを作成
x_df2 = DataFrame({"x1":[0,1,0,1],"x2":[0,0,1,1]},columns = ["x1","x2"])

## 関数を適用(見やすくするためにs1,s2も作っておく)
x_df2["s1"] = x_df2.apply(lambda x:OR.ignition(x[0:2]), axis = 1)
x_df2["s2"] = x_df2.apply(lambda x:NAND.ignition(x[0:2]), axis = 1)
x_df2["XOR"] = x_df2.apply(lambda x:XOR(x[0:2]), axis = 1)

print x_df2
>>    x1  x2  s1  s2  XOR
>> 0   0   0   0   1    0
>> 1   1   0   1   1    1
>> 2   0   1   1   1    1
>> 3   1   1   1   0    0

確かに表現できました!..とは言うものの、これだけじゃなんなので何故出来るのかってのを考えます。

2.で見たように、二次元座標平面上でXOR(x1,x2)が1になるものと0になるものを直線で分離することはできません。ですが、上に示した(s1,s2)の組み合わせなら直線で分離が可能です。実際に図に描画すると以下の通り。(コードは省略)

f:id:Tawara:20161024004822p:plain

つまるところ第一層のORとNANDによって(x1,x2)を変換し、直線(第二層のAND)で分離できる(s1,s2)にしてます。線形分離できないのを分離できるようにするって聞くとカーネルトリックを思い出しますね。現在の形で識別できないのなら識別できる形に変換してすりゃいいって話。

ただいたずらに層や入力数を増やすのも駄目だと思うので、それについては色々研究されているのでしょう(多分)。

おわりに

2章さくっと終わらせるつもりが、描画してみたりして遊んでたらやたら時間かかりました。途中からパーセプトロンというより matplotlib.pyplot と pandas.DataFrame の使い方の記事になってた気がする...先が思いやられますが頑張ります。
実装がメインなのでコードとかたくさん載せるんですけど、それをするとやたらと文章が長くなるしどうしよっかなあという感じです。

さて、次回は3章 ニューラルネットワーク に入ります。僕が一番知りたいのは学習の話なので、できるだけさらっと行きたいのですが結構長いです。もしかしたら分割するかもしれないですが、とりあえず今週中には絶対第二回を上げようと思います。

ではまた。

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装