からっぽのしょこ

読んだら書く!書いたら読む!読書読読書読書♪同じ事は二度調べ(たく)ない

2章:パーセプトロン【ゼロつく1のノート(実装)】

はじめに

 「プログラミング」学習初手『ゼロから作るDeep Learning』民のための実装攻略ノートです。『ゼロつく1』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。

 関数やクラスとして実装される処理の塊を細かく分解して、1つずつ処理を確認しながらゆっくりと組んでいきます。

 この記事は、2章「パーセプトロン」の内容になります。パーセプトロンによるANDゲート、NANDゲート、ORゲート、XORゲートをPythonで実装します。

【前節の内容】

www.anarchive-beta.com

【他の節の内容】

www.anarchive-beta.com

【この節の内容】

2章 パーセプトロン

 この章では、パーセプトロンによる論理回路を実装しながら仕組みを確認していきます。

 パーセプトロンや論理回路についての説明は本を読んでください。

# 2章で利用するライブラリ
import numpy as np


2.3 パーセプトロンの実装

 パーセプトロンによるANDゲート、NANDゲート、ORゲートを実装していきます。

2.3.1 簡単な実装

 まずはANDゲートを実装していきます。実装に必要な処理を1つずつ確認していきましょう。

# 仮のデータを設定
x1 = 0
x2 = 1
print(x1, x2)
0 1

 これは次のようにまとめて行うことができます。

# 仮のデータを設定
x1, x2 = 0, 1
print(x1, x2)
0 1


 同様に重みの値と閾値$\theta$も設定します。

# 重みと閾値を設定
w1, w2, theta = 0.5, 0.5, 0.7


 値の設定ができたので、入力信号と重みを掛けた合計を計算します。これはthetaとの大小関係を調べるために必要な一時的なデータであるため、変数名をtmp (temporary:一時的)とします。(よく使われる表現です。)

# 重み付き和を計算
tmp = x1 * w1 + x2 * w2
print(tmp)
0.5


 この計算結果tmptheta以下であれば0、thetaより大きければ1を返します。条件によって異なる処理をする場合(条件分岐)にはif文を使います。(ただしreturnはdefの中でしか利用できないので、代わりにprint()を使います。)

# 計算結果によって変化する出力の設定(条件分岐の設定)
if tmp <= theta:
    print(0)
elif tmp > theta:
    print(1)
0

 重み付き和の0.5が閾値の0.7より小さいため0が返ってきました。

 目的通りに動作することが確認できたら、これらの処理をまとめてAND()関数(ANDゲート)を実装しましょう!

 入力信号x1, x2はこの時点では何か分からない(関数を使うときに渡す)値なので、引数として受け取れるように設定します。重みと閾値は固定の値なので、関数内で設定できます。

# ANDゲートを実装
def AND(x1, x2):
    
    # 重みと閾値の設定
    w1, w2, theta = 0.5, 0.5, 0.7
    
    # 重み付き入力の総和を計算
    tmp = x1 * w1 + x2 * w2
    
    # 出力の設定
    if tmp <= theta:
        return 0
    elif tmp > theta:
        return 1


 関数を定義できたら、入力パターンを全て確認してみましょう。

# (x1=0, x2=0)
AND(0, 0)
0
# (x1=1, x2=0)
AND(1, 0)
0
# (x1=0, x2=1)
AND(0, 1)
0
# (x1=1, x2=1)
AND(1, 1)
1

 正しく出力されました!ここまで確認できて実装できたと言えます。

 この方法でも目的通りの実装ができましたが、もっと効率的な実装方法もあります。次は、他の論理回路も含めて効率化した形で実装していきます。

2.3.2 重みとバイアスの導入

 先ほどは、$x_1,\ x_2$に対応する変数をx1x2としました。これをNumPy配列を使用して$\boldsymbol{\mathrm{x}} = (x_1, x_2)$の形式で扱います。重みについても同様です。

# 仮のデータを設定
x = np.array([0, 1])

# 重みを設定
w = np.array([0.5, 0.5])

# バイアスを設定
b = -0.7

 ではこれらの値を使って、重み付き和を計算します。

# (x1 * w1, x2 * w2)
x * w
array([0. , 0.5])

 配列の計算は、1つ目の要素同士、2つ目の要素同士が計算されていることが確認できます。

 対応した要素同士の掛け算の結果をnp.sum()で合計します。

# x1 * w1 + x2 * w2
np.sum(x * w)
0.5

 ここにバイアスも加えた結果をtmpとします。

tmp = np.sum(x * w) + b
print(tmp)
-0.19999999999999996

 -0.2とならないのはコンピュータ上で数値を扱う際に生じる誤差です。この実装では問題にならないのでこのまま進めます。(tmp == 0のように厳密に値をマッチする必要があれば対策が必要になります。)

 先ほどの例ではこの計算結果と$\theta$との大小関係で出力を条件分岐していましたが、この例ではバイアス$b = -\theta$として計算結果に反映されています。これによって0を基準に大小関係を調べればよくなりました。
 0以下でない場合は0より大きい値であるため、2つ目の条件を調べる必要がなくなります。従ってelifではなくelseを使います。

# 出力(条件分岐)を設定
if tmp <= 0:
    print(0)
else:
    print(1)
0


 処理の確認ができたので、関数AND()関数を実装します。
 引数として与えられる2つの値を、NumPy配列のxに変換する処理を加えます。

# ANDゲートを実装
def AND(x1, x2):
    
    # パラメータの設定
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    
    # 重み付き入力の総和を計算
    tmp = np.sum(x * w) + b
    
    # 出力の設定
    if tmp <= 0:
        return 0
    else:
        return 1

 組めたら確認!

# 処理を確認
print(AND(0, 0))
print(AND(1, 0))
print(AND(0, 1))
print(AND(1, 1))
0
0
0
1

 問題ないですね!

 このように目的の操作を実装するとき、様々な方法で実現できます。時間やマシンスペックといった制限の中で、効率的な実装を考えることも重要な視点になってきます(追々ね)。

 続いて、NANDゲートORゲートも実装していきます。どちらもAND()の重みとバイアスの値を変更するだけで実装できます。

# NANDゲートを実装
def NAND(x1, x2):
    
    # パラメータの設定
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    
    # 重み付き入力の総和を計算
    tmp = np.sum(x * w) + b
    
    # 出力の設定
    if tmp <= 0:
        return 0
    else:
        return 1
# 処理を確認
print(NAND(0, 0))
print(NAND(1, 0))
print(NAND(0, 1))
print(NAND(1, 1))
1
1
1
0


# ORゲートを実装
def OR(x1, x2):
    
    # パラメータの設定
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.2
    
    # 重み付き入力の総和を計算
    tmp = np.sum(x * w) + b
    
    # 出力の設定
    if tmp <= 0:
        return 0
    else:
        return 1
# 処理を確認
print(OR(0, 0))
print(OR(1, 0))
print(OR(0, 1))
print(OR(1, 1))
0
1
1
1

 処理の流れは同じなのに、パラメータの違いだけで出力が変化するんですね。

2.4 パーセプトロンの限界

 しかし、パラメータの調整だけでは対応できないこともあります。そんなときは、層を重ねる(処理を組み合わせる)ことでも出力を調整できます。

2.5 多層パーセプトロン

 これまでに実装したAND()NAND()OR()を2層に組み合わせてXORゲートを実装します。まずは処理を1つずつ確認しましょう。

# 第0層(仮のデータを入力)
x1 = 1
x2 = 1

# 第1層
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
print(s1, s2)
0 1

 NAND()の出力をs1OR()の出力をs2とします。

 この2つの値を次の層のAND()に渡します。

# 第2層
y = AND(s1, s2)
print(y)
0


 正しく出力されることを確認出来たら、まとめて関数にします(いつもの)。

# XORゲートを実装
def XOR(x1, x2):
    
    # 第1層
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    
    # 第2層
    y = AND(s1, s2)
    
    # 出力
    return y
# 処理を確認
print(XOR(0, 0))
print(XOR(1, 0))
print(XOR(0, 1))
print(XOR(1, 1))
0
1
1
0

 他の3つよりもむしろシンプルに実装できましたね。

 2章の内容はここまでです。ここで組んだプログラム(関数)自体を今後使う訳ではありませんが、全体では複雑な操作も細かな処理に分けて、それを組み合わせることでシンプルに実装できるという感覚を持つことが重要です。

 おまけで重みの役割というのをもう少し違う視点でも見てみます。(おまけなので飛ばしても問題ないです。)

 入力データ$x_1$を10、$x_2$を5とします。今後はこれを$\boldsymbol{\mathrm{x}} = (10, 5)$と書きます。あるいは$x_1,\ x_2$を明示したいときは$\boldsymbol{\mathrm{x}} = (x_1 = 10, x_2 = 5)$のように表現することもあります。
 重み$w_1$を10、$w_2$を5とします。こちらも$\boldsymbol{\mathrm{w}} = (0.1, 0.9)$あるいは$\boldsymbol{\mathrm{w}} = (w_1 = 0.1, w_2 = 0.9)$と書きます。

# データを設定
x = np.array([10, 5])

# 重みを設定
w = np.array([0.1, 0.9])

 $w_1 * x_1$と$w_2 * x_2$を計算してみましょう。

print(x * w)
[1.  4.5]

 1つ目の値は1、2つ目が4.5となりました。重みによって大小関係が入れ替わっていますね。

 入力データと重みと出力の関係は、例えば「マンガの魅力」として「キャラクター」と「ストーリー」のどちらを重視するのかというイメージです。重視する方の重みが大きくなります。
 キャラクター値を$x_{\mathrm{chara}}$、ストーリー値を$x_\mathrm{story}$とします。$(x_{\mathrm{chara}} = 40, x_{\mathrm{story}} = 60)$のマンガを評価するときに、重みが$(w_{\mathrm{chara}} = 0.5, w_{\mathrm{story}} = 0.5)$であれば

# データを設定
x = np.array([40, 60])

# 重みを設定
w = np.array([0.5, 0.5])

# 重み付き和(魅力)を計算
np.sum(x * w)
50.0

 そのマンガの魅力値は50と計算(出力)されました。

 しかし重みが$(0.3, 0.7)$なら

# 重みを設定
w = np.array([0.3, 0.7])

# 重み付き和(魅力)を計算
np.sum(x * w)
54.0

54となり魅力が高く評価(出力)されました。重視している(重みが大きい)「ストーリー」の値が高いからですね。
 重みが入力をコントロールしているイメージはできましたか。

 重みによって出力が変化すること自体が目的ではなく、出力が正解データに近づくように重みを調整することが目的です。このように(入力と出力)データから、重み(などのパラメータ)を調整することを学習と言います。(4章以降でやっていきます。)

 以上で2章の内容は終わりです。2章では、パーセプトロンというコンピュータの基本的な仕組みから、入力がパラメータの変化と層(演算)を組み合わせることで出力を変化させることを学びました。3章では出力の値を更に多様にできるニューラルネットワークを扱います。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning』オライリー・ジャパン,2016年.

おわりに

 順調です!

【次節の内容】

www.anarchive-beta.com