<jupyter_start><jupyter_text>学習するニューラルネットワーク学習可能なニューラルネットワークを実装します。  「Iris dataset」という多数の花のデータが格納されたデータセットを使用し、品種の分類ができるようにニューラルネットワークを訓練します。    Iris datasetの導入scikit-learnというライブラリからIris datasetを導入します。  Iris datasetは、150個、3品種のIrisの花のサイズからなるデータセットです。  今回は、この中の2品種、SetosaとVersicolorのがく（Sepal）の長さと幅を使います。<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

# Irisデータの読み込み
iris = datasets.load_iris()

# 各花のサイズ
iris_data = iris.data
# print(iris_data)
# print(iris_data.shape)  # 形状

# 散布図で表示
st_data = iris_data[:50]  # Setosa
vc_data = iris_data[50:100]  # Versicolor
plt.scatter(st_data[:, 0], st_data[:, 1], label="Setosa")  # Sepal lengthとSepal width
plt.scatter(vc_data[:, 0], vc_data[:, 1], label="Versicolor")  # Sepal lengthとSepal width
plt.legend()

plt.xlabel("Sepal length (cm)")
plt.ylabel("Sepal width (cm)")
plt.show()<jupyter_output><empty_output><jupyter_text>出力層の実装出力層をクラスとして実装します。  誤差には二乗和誤差を、活性化関数にはシグモイド関数を使います。  重みとバイアスの更新には以下の式を使います。  $$ w_i \leftarrow w_i-\eta x_i\delta $$$$ b \leftarrow b-\eta \delta $$学習のためには、以下の式で各ニューロンごとの$\delta$を求める必要があります。  $$ \delta = \frac{\partial E}{\partial u} = \frac{\partial E}{\partial y}\frac{\partial y}{\partial u} $$ 今回は二乗和誤差を誤差として使用するので、$\frac{\partial E}{\partial y}$は以下の形になります。  $$ \begin{aligned} \\\frac{\partial E}{\partial y} & = \frac{\partial}{\partial y}(\frac{1}{2} \sum_{k=1}^n(y_k-t_k)^2) \\& = \frac{\partial}{\partial y}(\frac{1}{2}(y_0-t_0)^2+\frac{1}{2}(y_1-t_1)^2+\cdots+\frac{1}{2}(y-t)^2+\cdots+\frac{1}{2}(y_n-t_n)^2) \\& = y-t\end{aligned} $$また、活性化関数にはシグモイド関数を使うので、$\frac{\partial y}{\partial u} $はシグモイド関数の導関数の形になります。  $$\frac{\partial y}{\partial u} = (1-y)y$$これらを使って$\delta$を求め、重みとバイアスを更新します。    そして、前の層（1つ入力に近い層）に渡すために、以下の値を計算しておきます。  $$ \frac{\partial E}{\partial x_i} = \sum_{k=1}^n\delta_kw_{ik} $$<jupyter_code>import numpy as np
import matplotlib.pyplot as plt  # グラフの表示に使用

class OutputLayer():
    def __init__(self, n, W, B):  # 初期化
        self.params = [n, W, B]  # 各パラメータをまとめる

    def neuron(self, x, w, b):  # ニューロンを表す関数
        u = np.sum(x*w) + b
        return 1/(1+np.exp(-u))

    def delta(self, t):  # δを計算する関数
        return (1-self.y)*self.y * (self.y-t)

    def __call__(self, x):  # 順伝播
        n, W, B = self.params  # 各パラメータを取り出す
        self.x = x  # 他のメソッドで使用

        self.y= np.zeros(n)  # 出力を格納する配列
        for i in range(n):  # 各ニューロンごとに
            w = W[i]  # 重み
            b = B[i]  # バイアス
            self.y[i] = self.neuron(x, w, b)
        return self.y  # この層の出力

    def backward(self, t, eta):  # 逆伝播 t: 正解 eta: 学習係数
        n, W, B = self.params  # 各パラメータを取り出す
        delta = self.delta(t)

        grad_x = np.zeros_like(self.x)  # 1つ前の層に渡す値
        for i in range(n):  # 各ニューロンごとに
            grad_x += delta[i] * W[i]
            W[i] -= eta * self.x * delta[i]  # 重みの更新
            B[i] -= eta * delta[i]  # バイアスの更新

        return grad_x<jupyter_output><empty_output><jupyter_text>中間層の実装中間層をクラスとして実装します。  活性化関数にはシグモイド関数を使います。  出力層と同様に、重みとバイアスの更新には以下の式を使います。    $$ w_i \leftarrow w_i-\eta x_i\delta $$$$ b \leftarrow b-\eta \delta $$学習のためには、以下の式で各ニューロンごとの$\delta$を求める必要があります。  $$ \delta = \frac{\partial E}{\partial u} = \frac{\partial E}{\partial y}\frac{\partial y}{\partial u} $$ 活性化関数にはシグモイド関数を使うので、$\frac{\partial y}{\partial u} $はシグモイド関数の導関数の形になります。  $$\frac{\partial y}{\partial u} = (1-y)y$$$\frac{\partial E}{\partial y}$は次の層（1つ出力に近い層）から受け取ります。  これらを使って$\delta$を求め、重みとバイアスを更新します。    出力層と同様に、前の層（1つ入力に近い層）に渡すために、以下の値を計算しておきます。  $$ \frac{\partial E}{\partial x_i} = \sum_{k=1}^n\delta_kw_{ik} $$<jupyter_code>class MiddleLayer():
    def __init__(self, n, W, B):  # 初期化
        self.params = [n, W, B]  # 各パラメータをまとめる

    def neuron(self, x, w, b):  # ニューロンを表す関数
        u = np.sum(x*w) + b
        return 1/(1+np.exp(-u))

    def delta(self, grad_y):  # δを計算する関数
        return (1-self.y)*self.y * grad_y

    def __call__(self, x):  # 順伝播
        n, W, B = self.params  # 各パラメータを取り出す
        self.x = x  # 他のメソッドで使用

        self.y= np.zeros(n)  # 出力を格納する配列
        for i in range(n):  # 各ニューロンごとに
            w = W[i]  # 重み
            b = B[i]  # バイアス
            self.y[i] = self.neuron(x, w, b)
        return self.y  # この層の出力

    def backward(self, grad_y, eta):  # 逆伝播 grad_y: ∂E/∂y eta: 学習係数
        n, W, B = self.params  # 各パラメータを取り出す
        delta = self.delta(grad_y)

        grad_x = np.zeros_like(self.x)  # 1つ前の層に渡す値
        for i in range(n):  # 各ニューロンごとに
            grad_x += delta[i] * W[i]
            W[i] -= eta * self.x * delta[i]  # 重みの更新
            B[i] -= eta * delta[i]  # バイアスの更新

        return grad_x<jupyter_output><empty_output><jupyter_text>ニューラルネットワークの訓練`OutputLayer`クラスと`MiddleLayer`クラスを使い、入力層、中間層、中間層、出力層が並んだニューラルネットワークを構築します。  そして、パラメータの更新を何度も繰り返し、ニューラルネットワークを訓練します。  学習が進むとともに、次第に正しくIrisの品種分類ができるようになることを確認します。<jupyter_code>import random

iris = datasets.load_iris()
iris_data = iris.data
sl_data = iris_data[:100, 0] # SetosaとVersicolor、Sepal length
sw_data = iris_data[:100, 1] # SetosaとVersicolor、Sepal width

# 平均値を0に
sl_ave = np.average(sl_data)  # 平均値
sl_data -= sl_ave  # 平均値を引く
sw_ave = np.average(sw_data)
sw_data -= sw_ave

# 入力をリストに格納
train_data = []
for i in range(100):
    train_data.append([sl_data[i], sw_data[i], iris.target[i]])  # 入力1、入力2、正解

# -- 各層の初期化 --  ニューロン数、重み、バイアスの初期値を設定
layers = [MiddleLayer(2, np.array([[4.0, 4.0], [4.0, 4.0]]), np.array([2.0, -2.0])),
          MiddleLayer(2, np.array([[4.0, 4.0], [4.0, 4.0]]), np.array([2.0, -2.0])),
          OutputLayer(1, np.array([[1.0, -1.0]]), np.array([-0.5]))]

# -- 順伝播 --
def forward_propagation(x):
    for layer in layers:
        x = layer(x)
    return x

# -- 逆伝播 --
def backpropagation(t, eta):
    grad_y = t
    for layer in reversed(layers):  # 逆向き
        grad_y = layer.backward(grad_y, eta)
    return grad_y

# グラフ表示用の関数
def show_graph(epoch):
    print("Epoch:", epoch)
    # 実行
    st_predicted = [[], []]  # Setosa
    vc_predicted = [[], []]  # Versicolor
    for data in train_data:
        x = np.array(data[:2])
        if forward_propagation(x) < 0.5:
            st_predicted[0].append(x[0]+sl_ave)
            st_predicted[1].append(x[1]+sw_ave)
        else:
            vc_predicted[0].append(x[0]+sl_ave)
            vc_predicted[1].append(x[1]+sw_ave)

    # 分類結果をグラフ表示
    plt.scatter(st_predicted[0], st_predicted[1], label="Setosa")
    plt.scatter(vc_predicted[0], vc_predicted[1], label="Versicolor")
    plt.legend()

    plt.xlabel("Sepal length (cm)")
    plt.ylabel("Sepal width (cm)")
    plt.show()

show_graph(0)

# 学習と結果の表示
eta = 0.3  # 学習係数
for t in range(0, 64):  # 64回訓練
    random.shuffle(train_data)
    for data in train_data:
        data = np.array(data)
        forward_propagation(data[:2])  # 順伝播
        backpropagation(data[2], eta)  # 逆伝播
    if t+1 in [1, 2, 4, 8, 16, 32, 64]:  # グラフを表示するタイミング
        show_graph(t+1)

# 比較用に元の分類を散布図で表示
st_data = iris_data[:50]  # Setosa
vc_data = iris_data[50:100]  # Versicolor
plt.scatter(st_data[:, 0], st_data[:, 1], label="Setosa")
plt.scatter(vc_data[:, 0], vc_data[:, 1], label="Versicolor")
plt.legend()

plt.xlabel("Sepal length (cm)")
plt.ylabel("Sepal width (cm)")
plt.title("Original")
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16645928383796488, 'fraction_numerical': 0.02018526199364026, 'mean_word_length': 3.280473372781065, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '203940', 'license': 'MIT', 'n_tokens_mistral': 3911, 'n_tokens_neox': 3794, 'n_words': 541, 'path': 'section_5/04_learn.ipynb', 'repo_name': 'ryo-yama/minnano_dl', 'size': '7233'}