誤差逆伝播法

誤差逆伝播

前回までは、勾配の計算は数値微分で行いました。数値微分は、実行に時間がかかるため、より早い誤差逆伝播法を使います。

加算ノード

定義

入力x,yで出力zのとき、順伝播は
 { z=x+y }
逆伝播は
 { \frac{∂z}{∂x}=1 }
 { \frac{∂z}{∂y}=1 }
なので、上位層からの出力をEとすると、
 { E * \frac{∂z}{∂x} = E * 1 }
 { E * \frac{∂z}{∂y} = E * 1 }

コード

class AddLayer:
    def __init__(self):
        pass
    
    def forward(self, x, y):
        out = x + y
        return out
    
    def backward(self, dout):
        dx = dout*1
        dy = dout*1
        return dx,dy

乗算ノード

定義

順伝播
 { z=xy }
逆伝播
 { \frac{∂z}{∂x}=y }
 { \frac{∂z}{∂y}=x }
ひっくり返る。
出力をEとすると
 { E * \frac{∂z}{∂x}=E * y }
 { E * \frac{∂z}{∂y}=E * x }

コード

class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None
    
    def forward(self, x, y):
        self.x = x
        self.y = y
        out = x * y
        return out
    
    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x
        return dx,dy

Affineノード

定義

順伝播
 { y=XW+b }
逆伝播
 { \frac{∂L}{∂X} = \frac{∂L}{∂Y} * {W^{T}} }
 { \frac{∂L}{∂W} = {X^{T}} * \frac{∂L}{∂Y} }
 { \frac{∂L}{∂b} = \frac{∂L}{∂Y} }
出力をEとすると
 { E * \frac{∂L}{∂X} = E * \frac{∂L}{∂Y} * {W^{T}} }
 { E * \frac{∂L}{∂W} = E * {X^{T}} * \frac{∂L}{∂Y} }
 { E * \frac{∂L}{∂b} = E * \frac{∂L}{∂Y} }

コード

class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b
        self.x = None
        self.dW = None
        self.db = None
    
    def forward(self, x):
        self.x = x
        out = np.dot(x, self.W) + self.b
        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        return dx #必要なのは入力xの逆伝播のみ

ReLU

定義

順伝播
y = x (x > 0)
y = 0 ( x <= 0)
逆伝播
∂y/∂x = 1 (x > 0)
∂y/∂x = 0 (x <= 0)

コード

class Relu:
    def __init__(self):
        self.mask
    
    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0
        return out
    
    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout
        return dx

SoftmaxWithLoss

コード

class SofmaxWithLoss:
    def __init__(self):
        self.y = None
        self.t = None

    def forward(self, x, t):
        self.y = sofmax(x)
        self.t = t
        loss = cross_entropy_error(self.y, self.t)
        return loss
    
    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size #データ1個当たりの誤差
        return dx

レイヤの生成

from collections import OrderedDict
self.layers = OrderedDict()
self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = Relu()
self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
self.lastLayer = SoftmaxWithLoss()

順伝播

def predict(self, x):
    for layer in self.layers.values():
        x = layer.forward(x)  #それぞれのレイヤの順伝播を実行
    return x

def loss(self, x):
    y = self.predict(x)
    return self.lastLayer.forward(y, t)

def gradient(self, x, t):
    #forward
    self.loss(x, t)

逆伝播を追加

def gradient(self, x, t):
    #forward
    self.loss(x, t)
    
    #backward
    dout = 1
    dout = self.lastLayer.backward(dout)

    layers = list(self.layers.values())
    layers.reverse()
    for layer in layers:
        dout = layers.backward(dout)
    
    grads = {}
    grads['W1'] = self.layers['Affine1'].dW
    grads['b1'] = self.layers['Affine1'].db
    grads['W2'] = self.layers['Affine2'].dW
    grads['b2'] = self.layers['Affine2'].db
    return grads

重みの初期値

重みの初期値

ハイパラメータとして他にも重要なのが、重みの初期値です。ここでは、XavierとHeの初期値を使ってXOR回路のニューラルネットワークを実行したいと思います。

Xavier

定義

ランダムで選んだ初期値に、前層のノードの個数nの平方で割ります。
 {\boldsymbol{W}*\frac{1}{\sqrt{n}}}

コード

def __init__(self, input_size, hidden_size, output_size):
    self.params = {}
    self.params['W1'] = np.random.randn(input_size, hidden_size) / np.sqrt(input_size)
    self.params['b1'] = np.zeros(hidden_size)
    self.params['W2'] = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_size)
    self.params['b2'] = np.zeros(output_size)

He

定義

Xavierの初期値に {\sqrt{2}}を掛けます。
 {\boldsymbol{W}*\sqrt{\frac{2}{n}}}

コード

def __init__(self, input_size, hidden_size, output_size):
    self.params = {}
    self.params['W1'] = np.random.randn(input_size, hidden_size) / np.sqrt(input_size) * np.sqrt{2}
    self.params['b1'] = np.zeros(hidden_size)
    self.params['W2'] = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_size) * np.sqrt{2}
    self.params['b2'] = np.zeros(output_size)

他にも

標準偏差0.01

def __init__(self, input_size, hidden_size, output_size):
    self.params = {}
    self.params['W1'] = 0.01 * np.random.randn(input_size, hidden_size)
    self.params['b1'] = np.zeros(hidden_size)
    self.params['W2'] = 0.01 * np.random.randn(hidden_size, output_size)
    self.params['b2'] = np.zeros(output_size)

間違ったら良くなった初期値

def __init__(self, input_size, hidden_size, output_size):
    self.params = {}
    self.params['W1'] = 1.1 ** np.random.randn(input_size, hidden_size)
    self.params['b1'] = np.zeros(hidden_size)
    self.params['W2'] = 1.1 * np.random.randn(hidden_size, output_size)
    self.params['b2'] = np.zeros(output_size)

出力

Xavier

('ans:', 15, '/ 100')
('ans:', 10, '/ 100')
('ans:', 22, '/ 100')

He

('ans:', 18, '/ 100')
('ans:', 17, '/ 100')
('ans:', 13, '/ 100')

標準偏差0.01

('ans:', 15, '/ 100')
('ans:', 16, '/ 100')
('ans:', 14, '/ 100')

間違ったら良くなった初期値

('ans:', 44, '/ 100')
('ans:', 40, '/ 100')
('ans:', 40, '/ 100')

結論

XavierもHeもあまりよくならなかった。ハイパラメータを自動で決めることで、良い結果が得られるかも。 ちなみに一番良かった初期値(間違ったやつ)にMomentumを使うと、

('ans:', 76, '/ 100')
('ans:', 71, '/ 100')
('ans:', 64, '/ 100')

なかなかすばらしい。

MomentumとAdaGrad

MomentumとAdaGrad

前回まではパラメータの更新に確率的勾配降下法(SGD)を使いました。精度は30%ぐらいだったので、今回はMomentumとAdaGradを使ってどの位変わるか試してみます。

SGD

定義

 {x←x-\frac{∂L}{∂W}}

コード

SGDクラスを作成する。

class SGD:
    def __init__(self, lr=0.5):
        self.lr = lr
    
    def update(self, paramas, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key]

Momentum

定義

 {v←αv-η\frac{∂L}{∂W}}

コード

Momentumクラスを作成する。

class Momentum:
    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None

    def update(self, params, grads):
        if self.v is None:
            self.v ={}
            for key, val in params.items():
                self.v[key] = np.zeros_like(val)

        for key in params.keys():
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
            params[key] += self.v[key]

AdaGrad

定義

[tex: {h←h+\frac{∂L}{∂W}\frac{∂L}{∂W}}]
[tex: {W←W-η\frac{1}{\sqrt{h}}
\frac{∂L}{∂W}}]
学習回数が増えるにしたがい、パラメータ更新が小さくなる。

コード

AdaGradクラスを作成する。

class AdaGrad:
    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None

    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in pramas.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]+1e-7))

mainプログラム

最適化手法を選べるようにプログラムを修正する。

ans_label = np.argmax(t_train, axis=1)
ans = 0
for i in range(100):
    network = LayerNet(2, 2, 2)
    optimizer = Momentum()       #最適化手法を選択
    for step in range(1001):
        batch_mask = np.random.choice([0,1,2,3], 1)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]

        grads = network.training(x_batch, t_batch)
        params = network.params
        optimizer.update(params, grads)

        if step == 1000:
            y = np.argmax(network.predict(x_train), axis=1)
            print("y:", y)
            a = (y == ans_label)
            if a.all():
                ans += 1
print("ans:", ans, "/ 100")

出力

Momentum

学習率0.01 運動量0.9

('ans:', 52, '/ 100')
('ans:', 53, '/ 100')
('ans:', 51, '/ 100')

学習率0.5 運動量0.9

('ans:', 0, '/ 100')
('ans:', 0, '/ 100')

学習率0.05 運動量0.9

('ans:', 18, '/ 100')

学習率0.01 運動量0.5

('ans:', 39, '/ 100')

AdaGrad

学習率0.01

('ans:', 0, '/ 100')
('ans:', 0, '/ 100')

学習率0.5

('ans:', 7, '/ 100')
('ans:', 6, '/ 100')

結論

学習率と運動量をうまく設定すれば、Momentumを使用することで精度があがる。 AdaGradはXOR回路ではうまくいかない。 ハイパーパラメータ(学習率,運動量など)はランダムに選んで、良い結果を導けるプログラムに修正すると尚よい。

NumPyだけでXOR回路

XOR回路

XOR回路は入力x1,x2に対して、出力yを行います。 | x1| x2| y | |:-:|:-:|:-:| | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 |

多層パーセプトロン

隠れ層をもつニューラルネットワークのことです。 単純パーセプトロンを何層にも重ねたので、多層パーセプトロンといいます。 XOR回路は線形分離可能ではないため、多層パーセプトロンで解きます。

ニューラルネットワークを作成する

基本AND回路と同じで、 1.パラメータの宣言 2.学習 2.1.訓練データの推論 2.2.損失の計算 2.3.勾配の計算 2.4.パラメータの更新 3.テストデータの推論 です。

パラメータの宣言

2層で行います。

def __init__(self, input_size, hidden_size, output_size):
    self.params = {}
    self.params['W1'] = 1.5**np.random.randn(input_size, hidden_size)
    self.params['b1'] = np.zeros(hidden_size)
    self.params['W2'] = 1.5*np.random.randn(hidden_size, output_size)
    self.params['b2'] = np.zeros(output_size)

学習

def training(self, x, t):
    loss_W = lambda W: self.loss(x, t)

    grads = {}
    grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
    grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
    grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
    grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

    for key in ('W1', 'b1', 'W2', 'b2'):
        self.params[key] -= 0.5*grads[key]
    #return grads

推論

def predict(self, x):
    W1, W2 = self.params['W1'], self.params['W2']
    b1, b2 = self.params['b1'], self.params['b2']

    h1 = np.dot(x, W1) + b1
    z1 = relu(h1)
    h2 = np.dot(z1, W2) + b2
    y = softmax(h2)
    return y

損失の計算

def loss(self, x, t):
    y = self.predict(x)
    loss = mean_squared_error(y, t)
    return loss

勾配の計算

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished: #全ての組み合わせを計算
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)

        x[idx] = float(tmp_val) - h
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val # 値を元に戻す
        it.iternext()

    return grad

パラメータの更新

for key in ('W1', 'b1', 'W2', 'b2'):
    self.params[key] -= 0.5*grads[key]

推論

for i in range(x_train.shape[0]):
    print(x_train[i], "answer", network.predict(x_train[i]))

ソースコード

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#たまにうまくいく

import numpy as np

class LayerNet:
    def __init__(self, input_size, hidden_size, output_size):
        self.params = {}
        self.params['W1'] = 1.5**np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = 1.5*np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)

    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']

        h1 = np.dot(x, W1) + b1
        z1 = relu(h1)
        h2 = np.dot(z1, W2) + b2
        y = softmax(h2)
        return y

    def loss(self, x, t):
        y = self.predict(x)
        loss = mean_squared_error(y, t)
        return loss


    def training(self, x, t):
        loss_W = lambda W: self.loss(x, t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        for key in ('W1', 'b1', 'W2', 'b2'):
            self.params[key] -= 0.5*grads[key]
        #return grads


def relu(x):
    return np.maximum(x, 0)

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

def mean_squared_error(y, t):
    loss = np.sum(0.5*(y-t)**2)
    return loss

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished: #全ての組み合わせを計算
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)

        x[idx] = float(tmp_val) - h
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val # 値を元に戻す
        it.iternext()

    return grad

x_train = np.array([[0,0], [0,1], [1,0], [1,1]])
t_train = np.array([[0,1],[1,0],[1,0],[0,1]]) #[1,1]のとき[0,0]と間違っていた

network = LayerNet(2, 2, 2)
for step in range(10001):
    batch_mask = np.random.choice([0,1,2,3], 1)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    network.training(x_batch, t_batch)
    #for key in ('W1', 'b1', 'W2', 'b2'):
    #    network.params[key] -= 0.5*grad[key]

    if step % 1000 == 0:
        print("step", step)
        for i in range(x_train.shape[0]):
           print(x_train[i], "answer", network.predict(x_train[i]))

出力

たまに正しく出力されます。

('step', 10000)
(array([0, 0]), 'answer', array([ 0.01287935,  0.98712065]))
(array([0, 1]), 'answer', array([ 0.99375572,  0.00624428]))
(array([1, 0]), 'answer', array([ 0.99372009,  0.00627991]))
(array([1, 1]), 'answer', array([ 0.00540053,  0.99459947]))

100回中何回成功するかを計測するプログラム

#network = LayerNet(2, 2, 2)
ans_label = np.argmax(t_train, axis=1)
ans = 0
for i in range(100):
    network = LayerNet(2, 2, 2)
    for step in range(1001):
        batch_mask = np.random.choice([0,1,2,3], 1)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]

        network.training(x_batch, t_batch)

        if step == 1000:
            y = np.argmax(network.predict(x_train), axis=1)
            print("y:", y)
            a = (y == ans_label)
            if a.all():
                ans += 1
print("ans:", ans, "/ 100") 

学習率0.5のプログラムを3回実行した結果。

('ans:', 26, '/ 100')
('ans:', 30, '/ 100')
('ans:', 27, '/ 100')

およそ30/100でしか正解しない。

学習率0.01

('ans:', 15, '/ 100')
('ans:', 29, '/ 100')

改良点

パラメータを最適化していく。
1.確率的勾配降下法ではなく、Momentum、AdaGradを使ってみる。
2.重みの初期値を変える。Xavierの初期値か、ReLUをつかっているためHeの初期値を使う

NumPyだけでAND回路

AND回路

AND回路は入力x1,x2に対して、出力yを行います。 | x1| x2| y | |:-:|:-:|:-:| | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 |

入力は2つ、出力は1つですが、one-hot表現以外のやり方ができなかったので、出力も2つにします。

単純パーセプトロン

単純パーセプトロンは、隠れそうのないニューラルネットワークのことです。 AND回路の線形分離可能であるため、単純パーセプトロンニューラルネットワークで作成することができます。

ニューラルネットワークを作成する

ニューラルネットワークの基本は、 1.パラメータの宣言 2.学習 2.1.訓練データの推論 2.2.損失の計算 2.3.勾配の計算 2.4.パラメータの更新 4.テストデータの推論 です。

パラメータの宣言

隠れそうはありません。

def __init__(self,input_size, output_size):
    self.params = {}
    self.params['W'] = np.zeros([input_size,output_size])
    self.params['b'] = np.zeros([output_size])

学習

学習データの推論

行列積とソフトマックス関数で出力を計算します。

def predict(self, x):
    W = self.params['W']
    b = self.params['b']
    z = np.dot(x, W)+b
    y = softmax(z)
    return y

損失の計算

推論で得た出力をもとに、損失の大きさを計算します。

def loss(self, x, t):
    y = self.predict(x)
    return mean_squared_error(y,t) #損失を返す

パラメータの更新

損失をもとにパラメータを更新します。損失が正ならば負の向きに更新します。損失が大きいほど大きい値で更新します。

self.params['W'] -= 0.5*grads['dW']
self.params['b'] -= 0.5*grads['db']

テストデータの推論

for i in range(x_train.shape[0]):
    print(x_train[i], "answer", network.predict(x_train[i]))

ソースコード

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np

class LayerNet:
    def __init__(self,input_size, output_size):
        self.params = {}
        self.params['W'] = np.zeros([input_size,output_size])
        self.params['b'] = np.zeros([output_size])

    def predict(self, x):
        W = self.params['W']
        b = self.params['b']
        z = np.dot(x, W)+b
        y = softmax(z)
        return y

    def loss(self, x, t):
        y = self.predict(x)
        return mean_squared_error(y,t) #損失を返す

    def training(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {}
        grads['dW'] = numerical_gradient(loss_W, self.params['W']) #勾配を代入
        grads['db'] = numerical_gradient(loss_W, self.params['b']) #勾配を代入
        self.params['W'] -= 0.5*grads['dW']
        self.params['b'] -= 0.5*grads['db']
        #return grads

def mean_squared_error(y, t):
    return np.sum(0.5*(y-t)**2)

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished: #全ての組み合わせを計算
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)

        x[idx] = float(tmp_val) - h
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val # 値を元に戻す
        it.iternext()

    return grad

x_train = np.array([[0,0], [0,1], [1,0], [1,1]])
t_train = np.array([[0,1],[0,1],[0,1],[1,0]])

network = LayerNet(x_train.shape[1], t_train.shape[1])
for step in range(1001):
    batch_mask = np.random.choice([0,1,2,3], 1)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    network.training(x_batch, t_batch)
    #network.params['W'] -= 0.5*grad['dW']
    #network.params['b'] -= 0.5*grad['db']
    if step % 100 == 0:
        print("step", step)
        for i in range(x_train.shape[0]):
           print(x_train[i], "answer", network.predict(x_train[i]))

出力

正しく出力されました。

('step', 1000)
(array([0, 0]), 'answer', array([  7.61342093e-04,   9.99238658e-01]))
(array([0, 1]), 'answer', array([ 0.08064322,  0.91935678]))
(array([1, 0]), 'answer', array([ 0.0666248,  0.9333752]))
(array([1, 1]), 'answer', array([ 0.89151362,  0.10848638]))

NumPyの基本

NumPy

NumPyは数値計算を簡単に行うためのライブラリです。

特に強力なのが行列計算です。ディープラーニングの計算はほとんどが行列計算です。NumPyでは多次元の行列計算も簡単に行うことができます。

NumPyのインポート

NumPyは外部ライブラリです。そのため、初めにインポートする必要があります。 インポートするときは便宜上、次からnpと省略して使えるようにします。

import numpy as np

NumPy配列の作成

それでは、NumPy配列を作成したいと思います。 NumPy配列は以下のように作成します。比較のため普通のリストも作成しましょう。

np_array1 = np.array([1,2,3])
np_array2 = np.array([4,5,6])

array1 = [1,2,3]
array2 = [4,5,6]

型を見ると、それぞれの型が違うことがわかります。

print(type(np_array1)) #numpy.ndarray
print(type(array1)) #list

NumPy配列はshapeメソッドを使うことで、形を知ることができます。

np_array1 = np.array([[1,2,3],[4,5,6]])
np_array2 = np.array([[1,2],[3,4],[5,6]])
print(np_array1.shape) #(2, 3)
print(np_array2.shape) #(3, 2)

NumPyの加減乗除

NumPy配列の足し算について、見ていきたいと思います。 NumPy配列の足し算を以下のコードで行います。

print(np_array1+np_array2) #[5,7,9]
print(array1+array2) #[1,2,3,4,5,6]

結果から分かるように、NumPy配列の場合には2つの配列の要素ごとに足し算が行われ、要素数は変わりません。 それに対し、リストの足し算の場合には2つのリストの連結が行われています。

同様にかけ算についても、以下のように行います。

print(np_array1*np_array2) #[4,10,18]
print(array1*array2) #エラー

NumPy配列は各要素ごとのかけ算であったのに対し、リストではエラーとなります。 引き算、割り算も同様の結果になります。

NumPyの行列積

NumPyでは行列積の計算を簡単に行うことができます。 まずは、多次元配列を作成します。

np_array2_3 = np.array([[1,2],[3,4],[5,6]])
np_array3_2 = np.array([[1,2,3],[4,5,6]])

array2_3 = [[1,2],[3,4],[5,6]]
array3_2 = [[1,2,3],[4,5,6]]

リストを使っての行列積の計算の場合には、2×3の行列と3×2の行列をfor文のループを使って行い、2×2の行列を作成すると思います。

sum_array = [[0,0],[0,0]]
for i in range(len(array2_3)):
    for j in range(len(array2_3)):
        for k in range(len(array2_3[0])):
            sum_array[i][j] += (array2_3[i][k] * array3_2[k][j])
print(sum_array) #[[22, 28], [49, 64]]

NumPyを使うと、簡単に多次元配列の計算が可能です。 NumPyで作成した配列の場合には以下のようにできます。

print(np.dot(np_array2_3, np_array3_2)) #[[22, 28], [49 64]] 多少表記は異なる

NumPyのライブラリ

行列積の計算では、NumPyのdotというメソッドが使われました。NumPyには他にも便利なメソッドが多くあるので、後で使うものも含めて見ていきたいと思います。

三角関数

np_array = np.array([3.141,-3.141])
print(np.sin(np_array)) #[ 0.00059265 -0.00059265]
print(np.cos(np_array)) #[-0.99999982 -0.99999982]
print(np.tan(np_array)) #[-0.00059265  0.00059265]

指数関数

np_array = np.array([0, 1])
print(np.exp(np_array)) #[ 1.          2.71828183]

対数関数

np_array = np.array([1, 2.71828])
print(np.log(np_array)) #[ 0.          0.99999933]

最大値

np_array = np.array([-1,-2,1,2,0])
print(np.max(np_array)) #2
print(np.min(np_array)) #-2

NumPy配列の行ごと(列ごと)の合計

行を軸として合計をとる

np_array1 = np.array([[1,2,3],[4,5,6]])
np_array2 = np.array([[1,2],[3,4],[5,6]])
print(np.sum(np_array1, axis=0)) #[5 7 9]
print(np.sum(np_array2, axis=0)) #[ 9 12]

列を軸として合計をとる

np_array1 = np.array([[1,2,3],[4,5,6]])
np_array2 = np.array([[1,2],[3,4],[5,6]])
print(np.sum(np_array1, axis=1)) #[ 6 15]
print(np.sum(np_array2, axis=1)) #[ 3  7 11]

ランダム値の生成

x~yの範囲の中から1つ取り出す。np.random.uniform(x,y)

print(np.random.uniform(-3,3)) #-3~3の範囲の数字を出力
print(np.random.uniform(-3,3,[2,3])) #-3~3の範囲の数字を[2,3]で出力

0~xの範囲の中からy個取り出す。np.random.choice(x,y)

print(np.random.choice(10,5)) #0~10の範囲の数字を5個取り出した配列を出力

標準正規分布から値を取り出し、行数x,列数yの配列を作成する。np.random.randn(x,y)

print(np.random.randn(2,3)) #2×3の行列を出力

平均x,分散yに従う正規分布から値を1個取り出す。np.random.normal(x,y)

print(np.random.normal(50,10)) #平均50,分散10の正規分布に従う

コインを投げるという試行が、試行回数n,表の出る確率pとき、コインの表が出る確率は二項分布に従う。np.random.binomial(n,p)

print(np.random.binomial(n=100,p=0.3)) #試行回数50,確率0.3  nを増やすほど3割に近づく

Pythonの基本

Pythonの基本

データ型

今のディープラーニングフレームワークのほとんどが、Pythonで書かれています。ここでは、必要最低限の知識を書いていきたいと思います。

Pythonでは、変数への代入の際に型変換を自動で行ってくれます。そのため、変数宣言は不要となります。 以下のコードで変数に代入を行います。下のコードを書いたら、Shit+Enterを押しましょう。

x = 5
y = 3.14
z = "Hello"

それでは、それぞれの型を見てみたいと思います。 型の表示には、typeメソッドを使います。

print(type(x)) #int
print(type(y)) #float
print(type(z)) #str

すると、それぞれint, Float, strと出たと思います。

リスト

次に重要なのが、リストです。これはC言語の配列と同じです。宣言するには要素を {[x_1, x_2, …} ]と書いていきます。

a = [0,1,2,3,4]


リストの型と、全要素を表示するには次のようにします。

 print(type(a)) #list
 print(a) #[0,1,2,3,4]


また、3番目の要素だけを抜き出したいときには次のようにします。リストの番号は0から始まることに注意です。

print(a[2]) #2


次にすべての要素を一つずつ取り出しましょう。 このとき、for文を使いたいと思います。

for i in a:
   print(i) #0~4までを出力

すべての要素がひとつずつ取り出され、出力されていることがわかると思います。



次に多次元リストも作成しましょう。

a_2 = [[1,2,3],[4,5,6]]



1行2列の取り出し方は次のようになります。

print(a_2[0][1]) #2

また、1行目だけを取り出すには次のようにします。

print(a_2[0]) #[1,2,3]

スライシング

そして、すごく重要なのがスライシングです。これは、リストに含まれる必要な要素だけを取り出すことができます。 例えば、さきほどのリストを使いましょう。

a = [0,1,2,3,4]

このとき、左から0番目の要素と名付けたとします。 0~2番目の要素(1,2,3)を取り出したいときは次のようにします。

print(a[0:3]) #[0,1,2]

1~2番目の要素を取り出したいときは次のようにします。

print(a[1:3]) #[1,2]

また、0~2番目まで、3~4番目までの要素を取り出したいときは次のようになります。

print(a[:3]) #[0,1,2]
print(a[3:]) #[3,4]

次に,0~4番目の要素を2ステップごとに取り出したいときは次のように書きます。

print(a[0:5:2]) #[0,2,4]
print(a[::2]) #[0,2,4]

ディクショナリ

ディクショナリはキーと値のペアを作成します。C++だとMAPに相当します。
ディクショナリは { {key_1:value_1, key_2, value_2} } のように書いていきます。

dic = {'key1': 1, 'key2': 2, 'key3': 3}

出力をするときはキーを指定すると、値が返されます。

print(dic['key1']) #1

また、値を変更する際には次のように行います。

dic['key1'] = 10
print(dic['key1']) #10

他にもタプルなどのデータ型がありますが、あまり使用しないため、説明しません。

次はnumpyの説明をしたいと思います。