第五章
节点的正向传播和反向传播
简单加法乘法层的实现
# 乘法层的实现
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
# 加法层的实现
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
# 简单层的实现
apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1
# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_layer = AddLayer()
mul_tax_layer = MulLayer()
# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
orange_price = mul_orange_layer.forward(orange, orange_num)
all_price = add_layer.forward(apple_price, orange_price)
price = mul_tax_layer.forward(all_price, tax)
# backward
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice)
dapple_price, dorange_price = add_layer.backward(dall_price)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(price)
print(dapple_num, dapple, dorange, dorange_num, dtax)
神经网络的实现
激活函数Relu函数和Sigmoid函数
Relu层
- x≤0时输出0,x>0时输出x
- 因此导函数为:x≤0时输出0,x>0时输出1
– 当输入的x>0时 反向传播回来的dout[对应索引]会被原样向左传播
– 而当输入的x≤0时 反向传播回来的dout[对应索引]会被赋值为0
class Relu:
def __init__(self):
self.mask = None
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
Sigmoid层
- y = 1 / ( 1+exp(-x) )
- 反向传播时: 值y(1-y) ← sigmoid层 ← 值
- 即 sigmoid层可以只根据正向传播的输出 就能计算反向传播
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * self.out * (1.0 - self.out)
return dx
Affine/Softmax层的实现
Affine层
- 神经网络的正向传播中进行的矩阵乘积运算在几何领域被称为“放射变换”
- 几何中放射变化包括一次线性变换和一次平移,分别对应神经网络的加权和运算与加偏置运算
- 因此这里将进行仿射变换的处理实现为Affine层
批版本的Affine层:
- 由dot(X*W)+b正向传播
- 反向传播时:(注意公式的左右位置不要颠倒)
– dx = dout * W的转置
– dW = X的转置 * dout
– db = dout
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
softmax-with-Loss层的实现
- softmax是输出层的激活函数
- softmax层将输入值正规化(将输出值的和调整为1)后再输出为y;同时也考虑到了损失函数,让y和t作交叉熵误差;加上这一部分后又称为Softmax-with-Loss层
- eg:(y1, y2, y3)是Softmax层的输出,(t1, t2, t3)是监督数据,此时(y1-t1, y2-t2, y3-t3)—Softmax层的输出和监督标签的差分—即为Softmax-with-Loss层这一大块的反向传播的数据
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
def softmax(a):
c = np.maximum(a)
exp_a = np.exp(a - c)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
class SoftMaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
注意:反向传播时不要忘记除以批的大小,因为传递给前面层的应该是单个数据的误差
对应误差反向传播法的神经网络的实现
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
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
class Relu:
def __init__(self):
self.mask = None
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
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
def softmax(a):
c = np.maximum(a)
exp_a = np.exp(a - c) # 溢出对策
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
class SoftMaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# 初始化权重
self.params={}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b1'] = weight_init_std * np.zeros(hidden_size)
self.params['b2'] = weight_init_std * np.zeros(output_size)
# 初始化生成层
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.layers['Relu2'] = Relu()
self.lastLayer = SoftMaxWithLoss()
def predict(self, x):
for layer in self.layers.values:
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(x)
loss = self.lastLayer.forward(y, t)
return loss
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
if t.ndim != 1:
t = np.argmax(t, axis=1)
accuracy = np .sum(y==t) / float(x.shape[0])
return accuracy
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 self.layers:
layer.backward(dout)
# 得到梯度(更新后的参数权重)
grads = {}
grads['W1'] = self.layers['Affine1'].dW
grads['b1'] = self.layers['Relu1'].db
grads['W2'] = self.layers['Affine2'].dW
grads['b2'] = self.layers['Relu2'].db
return grads
PS:梯度确认
比较数值微分的结果和误差反向传播法的结果,以确认误差反向确认法的实现是否正确。
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
# 求各个权重的绝对误差的平均值
for key in grad_backprop.keys():
diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
print(key + ":" + str(diff))