技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

自作ニューラルネット(分類型)を作って、色々やってみる

今度は分類問題に対応した NN 実装を考える。
とはいえ、テンプレは 前回記事 で作ってるので、さっくり定義する

import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

wb_width = 0.01 # 重みバイアスの広がり方
epoch    = 101  # 学習データ数
eta      = 0.1  # 学習係数

# 各層のニューロン数
n_in  = 2
n_mid = 6
n_out = 2

class Neuron:
    def __init__(self, n_upper, n, activation_function, differential_function):
        self.w = wb_width * np.random.randn(n_upper, n)
        self.b = wb_width * np.random.randn(n)
        self.grad_w = np.zeros((n_upper, n))
        self.grad_b = np.zeros((n))
        self.activation_function = activation_function
        self.differential_function = differential_function

    def update(self, eta):
        self.w -= eta * self.grad_w
        self.b -= eta * self.grad_b
    
    def forward(self, x):
        self.x = x
        u = x.dot(self.w) + self.b
        self.y = self.activation_function(u)
        return self.y

    def backword(self, t):
        delta = self.differential_function(self.y, t)
        self.grad_w = self.x.T.dot(delta)
        self.grad_b = np.sum(delta, axis=0)
        self.grad_x = delta.dot(self.w.T)
        return self.grad_x


class Output(Neuron):
    pass


class Middle(Neuron):
    def backword(self, grad_y):
        delta = self.differential_function(grad_y, self.y)
        self.grad_w = self.x.T.dot(delta)
        self.grad_b = delta.sum(axis = 0)
        self.grad_x = delta.dot(self.w.T)
        return self.grad_x

判定の場合、出力層が異なってくる

シグモイド関数はこんな式なので


f(x) = \frac{1}{1 + e^{-x}} \\
\delta_j = \partial y_j (1 - y_j)y_j
def sigmoid(u):
    """シグモイド関数"""
    return 1 / (1 + np.exp(-u))


def differential_sigmod(grad_y, y):
    """シグモイド関数の勾配関数"""
    return grad_y * (1 - y) * y

出力層のソフトマックス関数 + 交差エントロピー過去計算 した式を拝借して


E(t,y) = - \sum_x t_x log(y_x) ... 交差エントロピー誤差  \\
y = \frac{exp(x)}{\sum^n_{k=1} exp(k)} .. ソフトマックス関数

E = - \sum_k t_k log( \frac{exp(u_k)}{ \sum_k exp(u_k) } ) \\
\delta_k = -t_k + y_k
def soft_max(u):
    """ ソフトマックス関数 """
    return np.exp(u) / np.sum(np.exp(u), axis = 1, keepdims=True)


def differential_softmax(y, t):
    """ソフトマックス + 交差エントロピー の勾配関数"""
    return y - t
# 座標系
X = np.arange(-1.0, 1.1, 0.1)
Y = np.arange(-1.0, 1.1, 0.1)

# 学習データ
input_data   = []
correct_data = []
for x in X:
    for y in Y:
        input_data.append([x, y])
        if y < np.sin(np.pi * x):
            correct_data.append([0, 1])
        else:
            correct_data.append([1, 0])

n_data       = len(correct_data)
input_data   = np.array(input_data)
correct_data = np.array(correct_data)


# ニューロン生成
middle = Middle(n_in, n_mid, sigmoid, differential_sigmod)
output = Output(n_mid, n_out, soft_max, differential_softmax)

# 中間出力
interval = 10

# 表示用
sin_data = np.sin(np.pi * X)

for i in range(epoch):
    # ランダムなインデックス値生成
    rand_idx = np.arange(n_data)
    np.random.shuffle(rand_idx)

    # 中間結果表示用データ
    total_err = 0
    x_1 = []
    y_1 = []
    x_2 = []
    y_2 = []

    for idx in rand_idx:
        # テストデータと、教師データ
        x = input_data[idx]
        t = correct_data[idx]

        # 順伝播
        y = output.forward(middle.forward(x.reshape(1, 2)))

        # 逆伝播
        middle.backword(output.backword(t.reshape(1, 2)))

        # 学習
        middle.update(eta)
        output.update(eta)

        if i % interval == 0:
            y = output.y.reshape(-1)
            total_err += - np.sum(t * np.log(y + 1e-7))

            if y[0] > y[1]:
                x_1.append(x[0])
                y_1.append(x[1])
            else:
                x_2.append(x[0])
                y_2.append(x[1])

    if i % interval == 0:
        plt.plot(X, sin_data)
        plt.scatter(x_1, y_1, marker='*')
        plt.scatter(x_2, y_2, marker='o')
        plt.show()
        print(f"Epoch:{i} / Error: {total_err/n_data}")

開始時

f:id:white-azalea:20210309215117p:plain

30 エポック学習時点

f:id:white-azalea:20210309215153p:plain

100 エポック学習時点

f:id:white-azalea:20210309215223p:plain

精度がガンガン上がっていくことが分かる。

Iris データセットを学習させてみる

scikitlearn にアヤメデータセットが存在する。
詳しくはこれ

qiita.com

データセットの 7 割を学習用、3割を検証用に分離する。

from sklearn.datasets import load_iris
import pandas as pd
from sklearn.model_selection import train_test_split

iris = load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris['data'], iris['target'], random_state=0)

で、どんなデータかというと

X_train = pd.DataFrame(X_train)
y_train = pd.DataFrame(y_train)
X_test = pd.DataFrame(X_test)
y_test = pd.DataFrame(y_test)
X_train.head()

f:id:white-azalea:20210309215711p:plain

y_train.head()

f:id:white-azalea:20210309215741p:plain

このままだと形状が合わないので、加工していく。

def conv(x):
    if x == 0:
        return np.array([1, 0, 0])
    elif x == 1:
        return np.array([0, 1, 0])
    else:
        return np.array([0, 0, 1])

y_train[0] = y_train[0].apply(conv)
y_test[0] = y_test[0].apply(conv)
y_train.head()

f:id:white-azalea:20210309215824p:plain

入力も 0-1 にしてしまおうか

X_train = X_train.applymap(lambda x: x / 10.0)
X_test = X_test.applymap(lambda x: x / 10.0)
X_train.head()

f:id:white-azalea:20210309215934p:plain

さぁ、準備は整った、学習を開始しようか

wb_width = 0.01 # 重みバイアスの広がり方
epoch    = 101  # 学習データ数
eta      = 0.1  # 学習係数

# 各層のニューロン数
n_in  = 4   # 1 行 4 データあるよ
n_mid = 8  # ニューロン数は適当
n_out = 3   # 結果は 3 通りだよ

# 学習データ
input_data   = np.array(X_train.values)
correct_data = np.array(y_train.values)
n_data = len(correct_data)

middle = Middle(n_in, n_mid, sigmoid, differential_sigmod)
output = Output(n_mid, n_out, soft_max, differential_softmax)

for i in range(epoch):
    # ランダムなインデックス値生成
    rand_idx = np.arange(n_data)
    np.random.shuffle(rand_idx)

    # 中間結果表示用データ
    total_err = 0

    for idx in rand_idx:
        # テストデータと、教師データ
        x = input_data[idx]
        t = correct_data[idx][0]

        # 順伝播
        y = output.forward(middle.forward(x.reshape(1, 4)))

        # 逆伝播
        middle.backword(output.backword(np.array(t)))

        # 学習
        middle.update(eta)
        output.update(eta)

        if i % interval == 0:
            total_err += - np.sum(t * np.log(y + 1e-7))
    if i % interval == 0:
        print(f"Epoch:{i} / Error: {total_err/n_data}")

学習を開始すると、回を重ねる度にエラー率が下がっていくのが分かる。

f:id:white-azalea:20210309220232p:plain

では、検証用に残していた残りの 30% のデータで検証しよう

input_data   = np.array(X_test.values)
correct_data = np.array(y_test.values)


def is_match(y, t):
    """[a, b, c] から最も大きい値を 1 として考える"""
    a, b, c = t
    if a > b and a > c:
        return t[0] == 1
    elif b > a and b > c:
        return t[1] == 1
    else:
        return t[2] == 1

correct_count = 0
for idx in range(len(X_test)):
    x = input_data[idx]
    t = correct_data[idx][0]

    # 順伝播してみる
    y = output.forward(middle.forward(x.reshape(1, 4)))
    y = y.reshape(-1)
    
    # 結果比較
    if is_match(y, t):
        correct_count += 1

print(f'Correct rate: {correct_count} / {len(X_test)}')

f:id:white-azalea:20210309220329p:plain

うぇーい (゚∀゚)