本文是看深度学习入门(基于python的理论与实现)这本书所做的学习笔记。本文忽略了神经网络的数学原理,着重于代码实现。
神经网络结构
神经网络结构由输入层,中间层,输出层组成。神经网络的基本构架如下图所示:
实现一个简单的神经网络主要实现**函数,负责层与层之间的矩阵运算部分,输出函数这些部分的前向传播和误差后向传播,还有参数更新有损失函数部分。本文中后向传播backward() 函数返回的是参数梯度变化的值,公式的推导使用的是图计算。
**函数
神经网络的**函数主要有Sigmoid,ReLU函数等。
Sigmoid函数为:
ReLU函数为:
下图为几种**函数的比较:
Sigmoid函数的实现代码如下所示:
class Sigmoid:
"""
Sigmoid**函数实现正反向传播
"""
def __init__(self):
self.out = None # 反向传播需要
def forward(self, x):
"""
根据公式计算**函数的值
"""
self.out = out = 1/(1 + np.exp(-x))
return out
def backward(self, dout):
"""
dx = dout * y * (1-y)
"""
return dout*self.out*(1-self.out)
ReLU函数 实现代码如下所示:
class Relu:
"""
Relu函数正反向传递
forward():
backward():
"""
def __init__(self):
self.mask = None
def forward(self, x):
"""
当x>0时,返回x,x<=0时返回0
x:为输入,是一个numpy类型
"""
self.mask = (x <= 0)
out = x.copy() # 产生一个备份
out[self.mask] = 0
return out
def backward(self, dout):
"""
当原输入x > 0时,反向传播直接为dout
当x <= 0 时,反响传播输出0
"""
dout[self.mask] = 0
return dout
矩阵运算Affine层
Affine层的图计算如图所示:
其中(1) 式为: ,(2) 式为:
关于W 的梯度值的矩阵形状,即使是在mini-batch的训练下的大小也是两层节点之间的权重 W的数量,这是因为将多个样本的梯度加到了一起。因此在输出层误差反向传递时,各个节点的误差应该除以batch_size 。
为后面网络的反向传递结果,看作一个已知的值。B是网络的偏置。
Affine层的代码实现如下所示。
class Affine:
def __init__(self, w, b):
self.W = w # 网络之间的权重
self.b = b # 偏置
self.x = None # 输入
self.dw = None # 关于x的梯度
self.db = None # 关于B的梯度
def forward(self, x):
self.x = x
return np.dot(x, self.W) + self.b
def backward1(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
def backward(self, dout):
dx = np.dot(dout, self.W.T)
if self.x.ndim == 1:
self.dw = np.dot(self.x.reshape(len(self.x),1), dout.reshape(1, len(dout)))
self.db = dout
else:
self.dw = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
return dx
输出层设计
一般情况下,回归问题使用恒等函数,分类问题用softmax函数。
softmax函数 的形式为:
与softmax函数一起使用的损失函数是交叉熵误差 。关于softmax函数的实现如下所示。
class SoftmaxWithLoss():
"""softmax-with-loss前向后向传播实现,并计算交叉熵误差"""
def __init__(self):
self.loss = None # 误差
self.y = None # 输出
self.t = None # 监督数据
def forward(self,x, t=None):
"""
x:输入
t:标签,监督数据
"""
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, t)
return self.loss
def backward(self):
return (self.y - self.t)/len(self.y)
梯度参数更新
梯度参数的更新的方法有SGD ,Momentum ,AdaGrad ,Adam 等。本文使用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 params.items():
self.h[key] = np.zeros_like(val) # 给所有的梯度添加一个h,
for key, val in params.items():
self.h[key] += grads[key]**2
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
初始化一个具体的神经网络
本文初始化一个四层的神经网络,初始代码如下所示。
class LayerNet:
def __init__(self, input_size, hide, out_size,lr = 0.001):
"""hide:隐藏层,表示为一个元组()"""
self.params = {}
# 参数初始化,ReLU的权重初始化为标准差为 根号(2/n)。n为前一层的节点数
self.params["W1"] = np.sqrt(2)*np.random.randn(input_size, hide[0])/np.sqrt(input_size)
self.params["b1"] = np.zeros(hide[0])
self.params["W2"] = np.sqrt(2)*np.random.randn(hide[0], hide[1])/np.sqrt(hide[0])
self.params["b2"] = np.zeros(hide[1])
self.params["W3"] = np.sqrt(2)*np.random.randn(hide[1], out_size)/np.sqrt(hide[1])
self.params["b3"] = np.zeros(out_size)
#生成层
self.Layer = OrderedDict() # 有序字典
self.Layer["Affine1"] = Affine(self.params["W1"], self.params["b1"])
self.Layer["Relu1"] = Relu()
self.Layer["Affine2"] = Affine(self.params["W2"], self.params["b2"])
self.Layer["Relu2"] = Relu()
self.Layer["Affine3"] = Affine(self.params["W3"], self.params["b3"])
self.lastLayer = SoftmaxWithLoss()
self.gradchange = AdaGrad(lr) # 梯度更新
def predict(self, x, t):
for layer in self.Layer.values():
x = layer.forward(x)
return x
def loss(self, x,t):
y = self.predict(x, t)
return self.lastLayer.forward(y, t)
def accuracy(self,x, t):
y = self.predict(x,t)
mask = np.argmax(y, axis=1)
t = np.argmax(t, axis = 1)
return np.sum(mask == t)/float(len(t))
def gradient(self, x, t):
# 前向传播
self.loss(x, t)
# 后向传播
dout = self.lastLayer.backward()
layers = list(self.Layer.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
grad = {}
grad["W1"] = self.Layer["Affine1"].dw
grad["b1"] = self.Layer["Affine1"].db
grad["W2"] = self.Layer["Affine2"].dw
grad["b2"] = self.Layer["Affine2"].db
grad["W3"] = self.Layer["Affine3"].dw
grad["b3"] = self.Layer["Affine3"].db
return grad
def update_gradient(self,x, t):
grad = self.gradient(x, t)
self.gradchange.update(self.params, grad)
运行
本文采用的数据为sk-learn的digits dataset,是8X8的格式。图片如下图所示。
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
import numpy as py
data, target = load_digits(return_X_y=True) # 导入数据
target1 = np.array(pd.get_dummies(target)) # 把数字2形式转换为向量形式[0,0,1,0,0,0...]
X_train, X_test, y_train, y_test = train_test_split(data, target1, test_size=0.3, random_state=0)
network = LayerNet(64,(1000, 1000), 10, 0.0001) # 初始化网络
iters_num = 1000 # 迭代次数
train_size = len(X_train)
batch_size = 25 # mini_batch的大小
iters_per_epoch = max(train_size//batch_size, 1) # 每经过一个epoch的迭代次数
train_acc_list = [] # 对于每次epoch迭代,训练数据集的精度
test_acc_list = [] # 对于每次epoch迭代,测试数据集的精度
for i in range(iters_num):
# 分裂数据,提取簇
batch_mask = np.random.choice(train_size, batch_size)
batch_X = X_train[batch_mask]
batch_y = y_train[batch_mask]
network.update_gradient(batch_X, batch_y)
if i % iters_per_epoch == 0:
train_acc_list.append(network.accuracy(X_train, y_train))
test_acc_list.append(network.accuracy(X_test, y_test))
绘制学习曲线
绘制学习曲线的代码如下所示。
# 学习曲线
plt.figure()
plt.title("learn-curves")
plt.xlabel("Training epoch")
plt.ylabel("Score")
train_scores_std = np.std(train_acc_list)
test_scores_std = np.std(test_acc_list)
plt.grid()
plt.plot(np.arange(len(train_acc_list)), train_acc_list, 'o-', color="r",
label="Training score")
plt.plot(np.arange(len(test_acc_list)), test_acc_list, 'o-', color="g",
label="test score")
plt.legend(loc="best")
用上面的参数,学习曲线如下图所示。