神经网络推理
(x_1,x_2)表示输入层的数据,w_{11}、w_{21}表示权重,b_1表示偏置。
第一个隐藏神经元的结果可以表示为:
隐藏层的神经元是基于加权和计算出来的。
python实现mini版全连接
代码语言:javascript复制import numpy as np
W1 = np.random.randn(2, 4) # 2*4
b1 = np.random.randn(4)
x = np.random.randn(10,2) # 10*2
h = np.dot(x, W1) b1
print("W1:n",W1)
print("b1:n",b1)
print("x:n",x)
print("h:n",h)
代码语言:javascript复制W1:
[[ 1.2395529 1.36244947 -0.36417116 1.37209539]
[ 0.79381936 0.9539435 0.10809906 1.20005854]]
b1:
[ 0.90337564 -0.37367157 -0.65477654 -0.53367142]
x:
[[-1.87632050e-01 -1.56664439e-01]
[-1.77483807e 00 1.14098853e 00]
[ 1.74651236e 00 -9.99758195e-01]
[-3.18839224e-01 -9.41541718e-06]
[-9.05030740e-01 -4.27663837e-01]
[ 5.08077914e-01 9.23716486e-01]
[-1.13378280e 00 1.71743576e 00]
[-1.07155261e 00 -4.37605821e-01]
[-9.05201730e-02 -3.37833720e-01]
[ 9.45202380e-01 -5.44267793e-01]]
h:
[[ 0.54643252 -0.77875978 -0.60338164 -0.97912699]
[-0.39089126 -1.70336018 0.11490808 -1.59966553]
[ 2.2746427 1.05215044 -1.39887889 0.66294178]
[ 0.50815008 -0.80808289 -0.53866551 -0.97116055]
[-0.55794567 -2.01469736 -0.37142051 -2.28868157]
[ 2.26642912 1.19973225 -0.73995098 1.2719738 ]
[ 0.86132563 -0.28005668 -0.05623236 -0.02830612]
[-0.77225049 -2.25105909 -0.31185276 -2.52909643]
[ 0.52299215 -0.81927501 -0.65833121 -1.06329398]
[ 1.64295368 0.39491819 -1.05782682 0.11008319]]
上面的例子中有10笔样本数据:x[0]、x[1]…,对应10个隐藏层的神经元h[0]、h[1]…
使用sigmoid激活函数
全连接层的变换是线性变换,激活函数能够赋予它"非线性"的效果。使用激活函数能够增加神经网络的表现力。
代码语言:javascript复制def sigmoid(x):
return 1 / (1 np.exp(-x))
代码语言:javascript复制a = sigmoid(h) # 对隐层单元的内容使用激活函数
a
代码语言:javascript复制array([[0.63330751, 0.31458724, 0.35357041, 0.27306504],
[0.40350277, 0.15402692, 0.52869545, 0.16802837],
[0.90675507, 0.74118763, 0.19799407, 0.65992091],
[0.62437271, 0.30829917, 0.36849807, 0.27464924],
[0.36402292, 0.11766841, 0.40819782, 0.0920647 ],
[0.90605829, 0.76847715, 0.32301486, 0.78108044],
[0.70293754, 0.43043988, 0.48594561, 0.49292394],
[0.31599248, 0.09525815, 0.42266257, 0.07384342],
[0.62784716, 0.30591758, 0.34111458, 0.25668048],
[0.83793645, 0.59746609, 0.25772497, 0.52749304]])
用另一个全连接层来变换这个激活函数的输出a。
隐藏层有4个神经元,输出层有3个神经元,所以全连接层使用的权重矩阵的形状设置成4*3。
完整代码
整体的完整代码为:
代码语言:javascript复制import numpy as np
def sigmoid(x):
return 1 / (1 np.exp(-x))
x = np.random.randn(10,2) # 10*2
W1 = np.random.randn(2, 4) # 2*4 # 4表示隐藏神经元个数;2是和输入x的x.shape[1]相匹配
b1 = np.random.randn(4) # 10*4;偏置必须为4
W2 = np.random.randn(4, 3) # 4*3 # 3个输出神经元个数;4个第一个隐藏神经元的shape[1]
b2 = np.random.randn(3) # # 10*3;偏置必须为3
h = np.dot(x, W1) b1
a = sigmoid(h)
s = np.dot(a, W2) b2
s
代码语言:javascript复制array([[ 2.1993881 , -0.66932037, 0.73650529],
[ 2.10594575, -0.44002399, 0.67983363],
[ 1.54631603, 0.46590836, 0.48583189],
[ 1.91380546, 0.09817995, 0.50751596],
[ 1.34782105, 0.98687129, 0.37225228],
[ 2.18438808, -0.63015213, 0.7306245 ],
[ 1.8960649 , 0.13953076, 0.49930039],
[ 1.36793674, 1.02409436, 0.37293796],
[ 1.90386465, 0.00908374, 0.5740634 ],
[ 1.16277154, 1.05478025, 0.30068793]])
python实现层的类及正向传播
- sigmoid函数的变换:Sigmoid层
- 全连接层的变换相当于几何学领域的放射变换:Affine层
代码规范:
- 所有的层都使用forward()方法和backward()方法
- 所有的层都使用params 和 grads实例变量
Sigmoid层
代码语言:javascript复制import numpy as np
class Sigmoid:
def __init__(self):
self.params = [] # 没有学习的参数,使用空列表
def forward(self,x):
return 1 / (1 np.exp(-x))
Affine层
代码语言:javascript复制class Affine:
def __init__(self, W, b):
# 初始化时接收权重和偏置
self.params = [W,b] # 参数列表保存
def forward(self, x):
W,b = self.params
out = np.dot(x,W) b
return out
TwoLayerNet网络
代码语言:javascript复制class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size):
I,H,O = input_size, hidden_size, output_size
W1 = np.random.randn(I,H)
b1 = np.random.randn(H)
W2 = np.random.randn(H,O)
b2 = np.random.randn(O)
# 生成层
self.layers = [
Affine(W1, b1),
Sigmoid(),
Affine(W2,b2)
]
# 将所有的权重整理到列表中
self.params = []
for layer in self.layers: # 循环每个层;权重参数放到列表params中
self.params = layer.params
def predict(self, x):
for layer in self.layers:
x = layer.forward(x) # 对每个层使用forward的更新方法,输出Out
return x
应用
代码语言:javascript复制x = np.random.randn(10,2)
model = TwoLayerNet(2,4,3)
s = model.predict(x)
s
代码语言:javascript复制array([[-0.89091485, 0.3790137 , -0.69215058],
[ 0.31672348, 0.33206884, -1.32879037],
[-1.04059946, 0.69280576, -0.71941753],
[ 2.07021878, -0.74625453, -2.63285994],
[ 0.8618358 , 0.24588236, -1.69390253],
[-0.36771902, 0.68768811, -0.99495204],
[-0.26414073, 0.39642317, -1.00626304],
[-0.08078445, 0.15941385, -1.14579999],
[-0.66130715, 0.48855694, -0.8163898 ],
[ 0.74677525, 0.32622056, -1.6045171 ]])
神经网络的学习
损失函数loss function
基于监督学习或者神经网络的预测结果,与实际结果之间差异程度。也就说将模型的恶劣程度作为标量值计算出来,得到的就是损失。
多类别分类问题中,通常使用的交叉熵误差cross entropy
作为损失函数。
Softmax函数的表达式:
Softmax函数输出的各个元素是0.0~1.0的实数。如果将这些元素全部加起来,则和为1.因此,Softmax的输出可以解释为概率。之后这个概率被输入交叉熵误差中。
交叉熵误差表示为:
- t_k对应于第k个类别的监督标签
- log是以e为底数的对数
在考虑了mini-batch处理的情况下,交叉熵误差可以表示为:
假设有N笔数据,t_{nk}表示第n笔数据的第k维元素的值;y_{nk}表示神经网络的输出,t_{nk}表示监督标签
MatMul层的实现
代码语言:javascript复制class MatMul:
def __init__(self, W):
self.params = [W] # 保存学习的参数
self.grads = [np.zeros_like(W)] # 梯度保存在grads
self.x = None
# 前向传播
def forward(self, x):
W, = self.params # 参数
out = np.dot(x,W) # 输出
self.x = x
return out
# 后向传播
def backword(self, dout):
W, = self.params
dx = np.dot(dout, W.T)
dW = np.dot(self.x.T, dout)
# grads[0][...] 使用了省略号:可以固定Numpy数组的内存地址,覆盖Numpy数组的元素
self.grads[0][...] = dW # 实例变量grads中设置权重的梯度
return dx
关于Numpy的[…]复制问题
代码语言:javascript复制a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
代码语言:javascript复制id(a),id(b)
代码语言:javascript复制(1549258733968, 1549258615664)
代码语言:javascript复制a = b # 将b赋值给a;
a
代码语言:javascript复制array([4, 5, 6])
可以看到a和b的内存地址是完全相同的;
代码语言:javascript复制id(a),id(b)
代码语言:javascript复制(1549258615664, 1549258615664)
代码语言:javascript复制a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
代码语言:javascript复制id(a),id(b)
代码语言:javascript复制(1549258733392, 1549258809392)
代码语言:javascript复制a[...] = b # 赋值
代码语言:javascript复制id(a),id(b)
代码语言:javascript复制(1549258733392, 1549258809392)
可以看到a和b的内存地址是不同的;且a的地址还是赋值前的地址。
总结规律:使用省略号时数据会被覆盖,变量指向的内存地址不变。
梯度的推导和反向传播实现
Sigmoid层
代码语言:javascript复制class Sigmoid:
def __init__(self):
self.params, self.grads = [], [] # 参数和梯度的保存
self.out = None
def forward(self, x):
# 前向传播过程
out = 1 / (1 np.exp(-x)) # sigmoid函数
self.out = out # 保存输出out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
Affine层
通过y = np.dot(x,W) b实现了Affine层的正向传播
代码语言:javascript复制class Affine:
def __init__(self, W, b):
self.params = [W,b] # 保存参数
self.grads = [np.zeros_like(W), np.zeros_like(W)] # 梯度初始化
self.x = None
def forward(self, x):
W,b = self.params
out = np.dot(x,W) b # 前向输出
self.x = x
return out
def backword(self, dout):
W, b = self.params
dx = np.dot(dout, W.T) #
dW = np.dot(self.x.T, dout)
db = np.sum(dout, axis=0)
self.grads[0][...] = dW
self.grads[1][...] = db
return dx
权重更新
代码语言:javascript复制class SGD:
def __init__(self, lr=0.01):
self.lr = lr # 学习率设置
def update(self, params, grads):
for i in range(len(params)):
params[i] -= self.lr * grads[i] # 参数更新
使用SGD类更新神经网络的参数:
代码语言:javascript复制# 伪代码
model = TwoLayerNet(...)
optimizer = SGD()
for i in range(10000):
x_batch, t_batch = get_mini_batch()
loss = model.farward(x_batch, t_batch)
model.backward()
optimizer.update(model.params, model.grads)
代码语言:javascript复制