作者:解琛
时间:2021 年 2 月 2 日
一、基本神经网络
Machine Learning for Beginners: An Introduction to Neural Networks
1.1 神经元
神经元是神经网络的基本单元。
神经元先获得输入,在执行特定的数学运算后,产生一个输出。
在神经元里,输入总共经历了 3 步数学运算。
- 将输入乘以权重(weight);
- x 1 ← x 1 ω 1 x_1 \leftarrow x_1 \omega_1 x1←x1ω1
- x 2 ← x 2 ω 2 x_2 \leftarrow x_2 \omega_2 x2←x2ω2
- 将结果相加在加上偏置(bias);
- x 1 ω 1 + x 2 ω 2 + b x_1 \omega_1 + x_2 \omega_2 + b x1ω1+x2ω2+b
- 经过激活函数(activation function)处理得到输出;
- y = f ( x 1 ω 1 + x 2 ω 2 + b ) y = f(x_1 \omega_1 + x_2 \omega_2 + b) y=f(x1ω1+x2ω2+b)
激活函数的作用是将无限制的输入转换为可预测形式的输出。
一种常用的激活函数是 s i g m o i d sigmoid sigmoid 函数,其图形用 python 代码绘制如下。
# 作者:解琛;
# 功能:绘制 sigmoid 图形;
# 时间:2020 年 2 月 1 日;
# 备注:无。
import matplotlib.pyplot as plt
import numpy as np
# 指定启动参数,设置字体;
# plt.rcParams['font.sans-serif'] = 'SimHei'
# plt.rcParams['axes.unicode_minus'] = False
def sigmoid(z):
return 1 / (1 + np.exp(-z))
z = np.arange(-9.5, 10, 0.1)
phi_z = sigmoid(z)
plt.figure(figsize=(9, 6))
plt.plot(z, phi_z)
plt.axvline(0, c='black')
plt.axhspan(.0, 1.0, facecolor='0.93', alpha=1.0, ls=':', edgecolor='0.4')
plt.axhline(y=.5, color='.3', alpha=1.0, ls=':')
plt.yticks([.0, .5, 1.0])
plt.yticks([.0, .5, 1.0])
plt.ylim(-.1, 1.1)
plt.title('sigmoid', fontsize=23)
plt.xlabel('z', fontsize=19)
plt.ylabel('$\phi(z)$', fontsize=13)
plt.show()
s i g m o i d sigmoid sigmoid 函数的输出介于 0 和 1。
我们可以理解为它把 ( − ∞ , + ∞ ) (−∞,+∞) (−∞,+∞) 范围内的数压缩到 ( 0 , 1 ) (0, 1) (0,1) 以内。
正值越大输出越接近 1,负向数值越大输出越接近 0。
我们来写一个神经元,向里面传递 2 个输入参数:[2, 3]。
# 作者:解琛;
# 功能:基本神经元;
# 时间:2020 年 1 月 31 日;
# 备注:无。
import numpy as np
def sigmoid(x):
'''
定义激活函数:sigmoid
f(x) = 1 / (1 + e^(-x));
'''
return 1 / (1 + np.exp(-x))
class Neuron():
def __init__(self, weights, bias):
'''构造函数;'''
self.weights = weights # 权重;
self.bias = bias # 误差(偏置);
def feedforward(self, inputs):
'''向前传递;'''
# weights 点乘 inputs + bias;
total = np.dot(self.weights, inputs) + self.bias
# 使用 sigmoid 函数包裹;
return sigmoid(total)
# 构造一个神经元 nn;
weights = np.array([1, 0])
bias = 4
nn = Neuron(weights, bias)
# 将输入的数据传入神经元;
x = np.array([2, 3])
output = nn.feedforward(x)
print(output)
神经元在初始化时,权重和偏置取如下数值。
w = [0, 1]
b = 4
w = [ 0 , 1 ] w = [0, 1] w=[0,1] 是 ω 1 = 0 \omega_1 = 0 ω1=0 和 ω 2 = 1 \omega_2 = 1 ω2=1 的向量形式写法。
给神经元一个输入 x = [ 2 , 3 ] x = [2, 3] x=[2,3]。
可以用向量点积的形式把神经元的输出计算出来。
ω x + b = x 1 ω 1 + x 2 ω 2 + b { \omega x + b = x_1 \omega_1 + x_2 \omega_2 + b } ωx+b=x1ω1+x2ω2+b
= 0 × 2 + 1 × 3 + 4 = 7 { = 0 \times 2 + 1 \times 3 + 4 = 7 } =0×2+1×3+4=7
∴ y = f ( ω X + b ) { \therefore y = f(\omega X + b) } ∴y=f(ωX+b)
f ( 7 ) = 0.999 { f(7) = 0.999 } f(7)=0.999
1.2 神经网络
神经网络就是把一堆神经元连接在一起。
我们搭建一个神经网络,含有 2 个输入、一个包含 2 个神经元的隐藏层(h1 和 h2)、包含 1 个神经元的输出层 o1。
隐藏层是夹在输入输入层和输出层之间的部分,一个神经网络可以有多个隐藏层。
把神经元的输入向前传递获得输出的过程称为 前馈 (feedforward)。
实现的代码如下。
# 作者:解琛;
# 功能:基本神经网络;
# 时间:2020 年 1 月 31 日;
# 备注:神经网络就是把一堆神经元连接在一起。
import numpy as np
class Neuron():
'''基本神经元;'''
def __init__(self, weights, bias):
'''构造函数;'''
self.weights = weights # 权重;
self.bias = bias # 误差(偏置);
def sigmoid(self, x):
'''
定义激活函数:sigmoid
f(x) = 1 / (1 + e^(-x));
'''
return 1 / (1 + np.exp(-x))
def feedforward(self, inputs):
'''向前传递;'''
# weights 点乘 inputs + bias;
total = np.dot(self.weights, inputs) + self.bias
# 使用 sigmoid 函数包裹;
return self.sigmoid(total)
class NeuralNetworks():
'''
神经网络;
2 个输入;
1 个包含 2 个神经元的隐藏层;
1 个输出层。
'''
def __init__(self):
''' 构造函数,在对象被创建时执行;'''
weights = np.array([0, 1]) # 权重初始化,这里是 2 个输入,所以是 [0, 1];
bias = 0 # 偏置初始化为 0;
# 定义 1 个包含 2 个神经元的隐藏层;
self.hide1 = Neuron(weights, bias)
self.hide2 = Neuron(weights, bias)
# 定义一个神经元的输出层;
self.output = Neuron(weights, bias)
def feedforward(self, x):
'''向前传递;'''
# 将输入传入 2 个隐藏层的神经元中;
out_hide1 = self.hide1.feedforward(x)
out_hide2 = self.hide1.feedforward(x)
# 将 2 个隐藏层的输出传入输出层中,得到传递结果;
out_output = self.output.feedforward(np.array([out_hide1, out_hide2]))
return out_output
# 定义并初始化一个神经网络;
nn = NeuralNetworks()
# 定义输入的数据;
x = np.array([2, 3])
# 将输入传入神经网络,获取神经网络的输出结果;
output = nn.feedforward(x)
print(output) # 0.7216325609518421
假设上面的网络里所有神经元都具有相同的权重 w = [ 0 , 1 ] {w=[0, 1]} w=[0,1] 和偏置 b = 0 { b = 0} b=0,激活函数使用的是 s i g m o i d sigmoid sigmoid,则输出如下。
h 1 = h 2 = f ( ω x + b ) { h_1 = h_2 = f(\omega x + b) } h1=h2=f(ωx+b)
= f ( 0 × 2 + 1 × 3 + 0 ) = f ( 3 ) = 0.9526 { = f(0 \times 2 + 1 \times 3 + 0) = f(3) = 0.9526 } =f(0×2+1×3+0)=f(3)=0.9526
o 1 = f ( ω [ h 1 , h 2 ] + b ) { o_1 = f(\omega [h_1, h_2] + b) } o1=f(ω[h1,h2]+b)
= f ( 0 × h 1 + 1 × h 2 + 0 ) = f ( 0.9526 ) = 0.7216 { = f(0 \times h_1 + 1 \times h_2 + 0) = f(0.9526) = 0.7216 } =f(0×h1+1×h2+0)=f(0.9526)=0.7216
1.3 训练
1.3.1 模型及数据
训练神经网络,实质上是一个模型参数优化的过程。
假设有一个数据集,包含 4 个人的身高、体重和性别,。
Name | Weight | Height | Gender(1:男、0:女) |
---|---|---|---|
Alice | 133 | 65 | 1 |
Bob | 160 | 72 | 0 |
Charlie | 152 | 70 | 0 |
Diana | 120 | 60 | 1 |
为了简便起见,我们将每个人的身高(-135)、体重(-66)减去一个固定数值。
现在我们的目标是训练一个网络,根据体重和身高来推测某人的性别。
搭建的神经网络如下。
在训练神经网络之前,我们需要有一个标准来评估结果到底好不好,以便我们进行改进,称其为损失(loss)。
这里用均方误差(mean squared error, MSE)来作为损失函数。
M S E = 1 n ∑ i = 1 n ( y t r u e − y p r e d ) 2 MSE = \frac {1} {n} \sum_{i = 1}^{n} (y_{true} - y_{pred})^2 MSE=n1i=1∑n(ytrue−ypred)2
- n n n 是样本的数量,在上面的数据集中的样本数量是 4;
- y y y 代表人的性别,男性是 1,女性是 0;
- y t r u e y_{true} ytrue 是变量的真实值;
- y p r e d y_{pred} ypred 是变量的预测值。
均方误差就是所有数据方差的平均值,我们将它定义为损失函数。
预测结果越好,损失就越低,训练神经网络就是将损失最小化的过程。
如果上面网络的输出一直是 0,也就是预测所有人都是男性,那么损失的计算结果如下。
Name | y t r u e y_{true} ytrue | y p r e d y_{pred} ypred | ( y t r u e − y p r e d ) 2 (y_{true} - y_{pred})^2 (ytrue−ypred)2 |
---|---|---|---|
Alice | 1 | 0 | 1 |
Bob | 0 | 0 | 0 |
Charlie | 0 | 0 | 0 |
Diana | 1 | 0 | 1 |
M S E = 1 4 ( 1 + 0 + 0 + 1 ) = 0.5 {MSE = \frac {1} {4} (1 + 0 + 0 + 1) = 0.5} MSE=41(1+0+0+1)=0.5
计算的方法如下。
def mse_loss(y_true, y_pred):
# ** 代表乘方运算;
return ((y_true - y_pred) ** 2).mean()
y_true = np.array([1, 0, 0, 1])
y_pred = np.array([0, 0, 0, 0])
print(mse_loss(y_true, y_pred)) # 0.5;
1.3.2 链式求导
为了简化问题,现在只关注 Alice 的数据。
Name | Weight(-135) | Height(-66) | Gender(1:男、0:女) |
---|---|---|---|
Alice | -2 | -1 | 1 |
所以损失函数 MSE 计算得到的数值如下。
M S E = 1 1 ∑ i = 1 1 ( y t r u e − y p r e d ) 2 { MSE = \frac{1}{1} \sum^{1}_{i = 1} (y_{true} - y_{pred})^2 } MSE=11i=1∑1(ytrue−ypred)2
= ( y t r u e − y p r e d ) 2 { = (y_{true} - y_{pred})^2 } =(ytrue−ypred)2
= ( 1 − y p r e d ) 2 { = (1 - y_{pred})^2 } =(1−ypred)2
y p r e d y_{pred} ypred 预测值是由网络中各个神经元的权重和偏置依次计算得到的。
所以损失函数实质上是包含多个权重、偏置的多元函数。
L ( ω 1 , ω 2 , ω 3 , ω 4 , ω 5 , ω 6 , b 1 , b 2 , b 3 ) L(\omega_1, \omega_2, \omega_3, \omega_4, \omega_5, \omega_6, b_1, b_2, b_3) L(ω1,ω2,ω3,ω4,ω5,ω6,b1,b2,b3)
现在关注一个问题:如果调整一下 w 1 w_1 w1,损失函数是会变大还是变小?
我们需要知道偏导数 ∂ L ∂ w 1 \frac{\partial L}{\partial w_1} ∂w1∂L 是正是负才能回答这个问题。
根据链式求导法则。
∂ L ∂ w 1 = ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ w 1 \frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_{pred}} \cdot \frac{\partial y_{pred}}{\partial w_1} ∂w1∂L=∂ypred∂L⋅∂w1∂ypred
其中第一项展开如下。
∂ L ∂ y p r e d = ∂ ( 1 − y p r e d ) 2 ∂ y p r e d = − 2 ( 1 − y p r e d ) \frac{\partial L}{\partial y_{pred}} = \frac{\partial (1 - y_{pred})^2}{\partial y_{pred}} = -2(1 - y_{pred}) ∂ypred∂L=∂ypred∂(1−ypred)2=−2(1−ypred)
神经元 h 1 h_1 h1、 h 2 h_2 h2 和 o 1 o_1 o1 的关系如下。
y p r e d = o 1 = f ( ω 5 h 1 + ω 6 h 2 + b 3 ) y_{pred} = o_1 = f(\omega_5 h_1 + \omega_6 h_2 + b_3) ypred=o1=f(ω5h1+ω6h2+b3)
实际只有神经元 h 1 h_1 h1 包含 ω 1 \omega_1 ω1,再次运用链式求导法则。
∂ y p r e d ∂ ω 1 = ∂ y p r e d ∂ h 1 ⋅ ∂ h 1 ∂ ω 1 \frac{\partial y_{pred}}{{\partial \omega_1}} = \frac{\partial y_{pred}}{\partial h_1} \cdot \frac{\partial h_1}{\partial \omega_1} ∂ω1∂ypred=∂h1∂ypred⋅∂ω1∂h1
∂ y p r e d ∂ h 1 = ω 5 f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) \frac{\partial y_{pred}}{\partial h_1} = \omega_5 f'(\omega_5 h_1 + \omega_6 h_2 + b_3) ∂h1∂ypred=ω5f′(ω5h1+ω6h2+b3)
h 1 = f ( ω 1 x 1 + ω 2 x 2 + b 1 ) h_1 = f(\omega_1 x_1 + \omega_2 x_2 + b_1) h1=f(ω1x1+ω2x2+b1)
∂ h 1 ω 1 = x 1 f ′ ( ω 1 x 1 + ω 2 x 2 + b 1 ) \frac{\partial h_1}{\omega_1} = x_1 f'(\omega_1 x_1 + \omega_2 x_2 + b_1) ω1∂h1=x1f′(ω1x1+ω2x2+b1)
这部分推算中遇到了 2 次 s i g m o i d sigmoid sigmoid 激活函数的导数 f ′ ( x ) f'(x) f′(x)。
∴ ∂ y p r e d ∂ ω 1 = ω 5 x 1 f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) f ′ ( ω 1 x 1 + ω 2 x 2 + h 1 ) { \therefore \frac {\partial y_{pred}} {\partial \omega_1} = \omega_5 x_1 f'(\omega_5 h_1 + \omega_6 h_2 + b_3) f'(\omega_1 x_1 + \omega_2 x_2 + h_1) } ∴∂ω1∂ypred=ω5x1f′(ω5h1+ω6h2+b3)f′(ω1x1+ω2x2+h1)
s i g m o i d sigmoid sigmoid 函数如下。
f ( x ) = 1 1 + e − x f(x) = \frac{1}{1 + e^{-x}} f(x)=1+e−x1
其导数如下。
f ′ ( x ) = e x ( 1 + e − x ) 2 = f ( x ) ( 1 − f ( x ) ) f'(x) = \frac{e^x}{(1 + e^{-x})^2} = f(x)(1 - f(x)) f′(x)=(1+e−x)2ex=f(x)(1−f(x))
所以总的链式求导公式如下。
∂ L ∂ ω 1 { \frac{\partial L}{\partial \omega_1} } ∂ω1∂L
= ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ ω 1 { = \frac{\partial L}{\partial y_{pred}} \cdot \frac{\partial y_{pred}}{\partial \omega_1} } =∂ypred∂L⋅∂ω1∂ypred
= ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ h 1 ⋅ ∂ h 1 ∂ ω 1 { = \frac{\partial L}{\partial y_{pred}} \cdot \frac{\partial y_{pred}}{\partial h_1} \cdot \frac{\partial h_1}{\partial \omega_1} } =∂ypred∂L⋅∂h1∂ypred⋅∂ω1∂h1
= − 2 ω 5 x 1 ( 1 − f ( ω 5 h 1 + ω 6 h 2 + b 3 ) ) f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) f ′ ( ω 1 x 1 + ω 2 x 2 + h 1 ) { = -2 \omega_5 x_1 (1 - f(\omega_5 h_1 + \omega_6 h_2 + b_3)) f'(\omega_5 h_1 + \omega_6 h_2 + b_3) f'(\omega_1 x_1 + \omega_2 x_2 + h_1) } =−2ω5x1(1−f(ω5h1+ω6h2+b3))f′(ω5h1+ω6h2+b3)f′(ω1x1+ω2x2+h1)
这种向后计算偏导数的方式称为反向传播(backpropagation)。
代入实际数据进行计算。
h 1 = f ( x 1 ω 1 + x 2 ω 2 + b 1 ) = f ( − 2 + ( − 1 ) + 0 ) = 0.0474 h_1 = f(x_1 \omega_1 + x_2 \omega_2 + b_1) = f(-2 + (-1) + 0)= 0.0474 h1=f(x1ω1+x2ω2+b1)=f(−2+(−1)+0)=0.0474
h 2 = f ( x 3 ω 3 + x 4 ω 4 + b 2 ) = 0.0474 h_2 = f(x_3 \omega_3 + x_4 \omega_4 + b_2) = 0.0474 h2=f(x3ω3+x4ω4+b2)=0.0474
o 1 = f ( h 1 ω 5 + h 2 ω 6 + b 3 ) = f ( 0.0474 + 0.0474 + 0 ) = 0.524 o_1 = f(h_1 \omega_5 + h_2 \omega_6 + b_3) = f(0.0474 + 0.0474 + 0) = 0.524 o1=f(h1ω5+h2ω6+b3)=f(0.0474+0.0474+0)=0.524
最终神经网络的输出 y = 0.524 y = 0.524 y=0.524,这个输出无法判断出是男还是女,目前预测效果不好。
现在进行反向传播的运算。
∂ L ∂ ω 1 = ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ h 1 ⋅ ∂ h 1 ∂ ω 1 { \frac {\partial L} {\partial \omega_1} = \frac {\partial L} {\partial y_{pred}} \cdot \frac {\partial y_{pred}} {\partial h_1} \cdot \frac {\partial h_1} {\partial \omega_1} } ∂ω1∂L=∂ypred∂L⋅∂h1∂ypred⋅∂ω1∂h1
∂ L ∂ y p r e d = ∂ ( 1 − y p r e d ) 2 ∂ y p r e d = − 2 ( 1 − y p r e d ) { \frac {\partial L} {\partial y_{pred}} = \frac {\partial (1 - y_{pred})^2} {\partial y_{pred}} = -2(1 - y_{pred}) } ∂ypred∂L=∂ypred∂(1−ypred)2=−2(1−ypred)
= − 2 ( 1 − 0.524 ) = − 0.952 { = -2(1 - 0.524) = -0.952 } =−2(1−0.524)=−0.952
∂ y p r e d ∂ h 1 = ω 5 f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) { \frac {\partial y_{pred}} {\partial h_1} = \omega_5 f'(\omega_5 h_1 + \omega_6 h_2 + b_3) } ∂h1∂ypred=ω5f′(ω5h1+ω6h2+b3)
= 1 × f ′ ( 0.0474 + 0.0474 + 0 ) = f ( 0.0948 ) f ( 1 − 0.0948 ) = 0.249 { = 1 \times f'(0.0474 + 0.0474 + 0) = f(0.0948)f(1 - 0.0948) = 0.249 } =1×f′(0.0474+0.0474+0)=f(0.0948)f(1−0.0948)=0.249
∂ h 1 ∂ ω 1 = x 1 f ′ ( ω 1 x 1 + ω 2 x 2 + b 1 ) { \frac {\partial h_1} {\partial \omega_1} = x_1 f'(\omega_1 x_1 + \omega_2 x_2 + b_1) } ∂ω1∂h1=x1f′(ω1x1+ω2x2+b1)
= − 2 × f ′ ( − 2 + ( − 1 ) + 0 ) = − 2 × f ( − 3 ) f ( 1 − ( − 3 ) ) = − 0.0904 { = -2 \times f'(-2 + (-1) + 0) = -2 \times f(-3)f(1 - (-3)) = -0.0904 } =−2×f′(−2+(−1)+0)=−2×f(−3)f(1−(−3))=−0.0904
所以。
∂ L ∂ ω 1 { \frac {\partial L} {\partial \omega_1} } ∂ω1∂L
= − 0.952 × 0.249 × ( − 0.0904 ) = 0.0214 { = -0.952 \times 0.249 \times (-0.0904) = 0.0214 } =−0.952×0.249×(−0.0904)=0.0214
∵ ∂ L ∂ ω 1 > 0 { \because \frac {\partial L} {\partial \omega_1} > 0 } ∵∂ω1∂L>0
所以,当 ω 1 \omega_1 ω1 增大时,损失函数 L L L 会有一个较小的增长。
1.3.3 随机梯度下降
我们使用随机梯度下降(stochastic gradient descent, SGD)优化算法,来训练网络。
经过前面的运算,我们已经有了训练神经网络所有数据,但是该如何操作?
SGD 定义了改变权重和偏置的方法。
ω 1 ← ω 1 − η ∂ L ∂ ω 1 \omega_1 \leftarrow \omega_1 - \eta \frac {\partial L} {\partial \omega_1} ω1←ω1−η∂ω1∂L
η \eta η 是一个常数,称为学习率(learning rate),它决定了我们训练网络速率的快慢。
将 ω 1 \omega_1 ω1 减去 η ∂ L ∂ ω 1 \eta \frac {\partial L} {\partial \omega_1} η∂ω1∂L 就得到了新的权重 ω 1 \omega_1 ω1。
-
若 ∂ L ∂ ω 1 {\frac {\partial L} {\partial \omega_1}} ∂ω1∂L 大于 0, ω 1 {\omega_1} ω1 的值减小,则 L {L} L 的值减小;
-
若 ∂ L ∂ ω 1 {\frac {\partial L} {\partial \omega_1}} ∂ω1∂L 小于 0, ω 1 {\omega_1} ω1 的值增加,则 L {L} L 的值减小。
如果我们用这种方法去逐步改变网络的权重 ω \omega ω 和偏置 b b b,损失函数会缓慢地降低,从而改进我们的神经网络。
1.3.4 训练神经网络
整个训练的流程如下。
数据集如下。
Name | Weight | Height | Gender(1:男、0:女) |
---|---|---|---|
Alice | -2 | -1 | 1 |
Bob | 25 | 6 | 0 |
Charlie | 17 | 4 | 0 |
Diana | -15 | -6 | 1 |
神经网络的结构如下。
实现过程如下。
# 作者:解琛;
# 功能:训练神经网络;
# 时间:2020 年 1 月 31 日;
# 备注:
# 训练神经网络其实就是优化的过程;
#
# 假设有一个数据集,包含 4 个人的身高、体重和性别;
#
# Name Weight (lb) Height (in) Gender
# Alice 133 65 0
# Bob 160 72 1
# Charlie 152 70 1
# Diana 120 60 0
#
# 目标是训练一个网络,根据体重和身高来推测某人的性别。
#
# 在训练神经网络之前,需要有一个标准定义它到底好不好,以便我们进行改进,这就是损失(loss)。
#
# 这里用均方误差(MSE)来定义损失,即所有数据方差的平均值。
# 预测结果越好,结果越准确,损失就越低,训练神经网络的过程就是将损失最小化的过程。
import numpy as np
class NeuralNetwork():
def __init__(self):
'''初始化构造函数,在实例被创建时运行一次;'''
# 权重 weight;
self.w1 = np.random.normal()
self.w2 = np.random.normal()
self.w3 = np.random.normal()
self.w4 = np.random.normal()
self.w5 = np.random.normal()
self.w6 = np.random.normal()
# 偏置 biases;
self.b1 = np.random.normal()
self.b2 = np.random.normal()
self.b3 = np.random.normal()
def sigmoid(self, x):
'''激活函数;'''
# f(x) = 1 / (1 + e^(-x));
return 1 / (1 + np.exp(-x))
def deriv_sigmoid(self, x):
'''激活函数的导数;'''
# f'(x) = f(x) * (1 - f(x))
fx = self.sigmoid(x)
return fx * (1 - fx)
def feedforward(self, x):
'''神经网络前向传播;'''
# x 是一个 numpy 数组,包含 2 个元素,即 [输入 1, 输入 2];
h1 = self.sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
h2 = self.sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
o1 = self.sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
return o1
def mse_loss(self, y_true, y_pred):
'''MSE 损失函数;'''
# 真实的输出值和预测输出值应保持相同的数据结构;
return ((y_true - y_pred) ** 2).mean()
def train(self, data, all_y_trues):
'''训练神经网络;'''
learn_rate = 0.1 # 学习率;
epochs = 1000 # 训练次数;
for epoch in range(epochs):
for x, y_true in zip(data, all_y_trues):
# 1. 进行输入的前向传递;
# 获取隐藏层 1 的输出;
sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
h1 = self.sigmoid(sum_h1)
# 获取隐藏层 2 的输出;
sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
h2 = self.sigmoid(sum_h2)
# 获取输出层的输出;
sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
o1 = self.sigmoid(sum_o1)
# 最终的预测值,就是神经网络输出层输出的结果;
y_pred = o1
# 2. 计算偏导数;
#
# 变量命名规则:
#
# d_L_d_w1
#
# 代表: ∂L
# ——
# ∂w1
# 2. 进行反向传递;
# 损失函数对最终输出求偏导;
d_L_d_ypred = -2 * (y_true - y_pred)
# 计算输出层 o1 反向传播的权重和偏置结果;
d_ypred_d_w5 = h1 * self.deriv_sigmoid(sum_o1)
d_ypred_d_w6 = h2 * self.deriv_sigmoid(sum_o1)
d_ypred_d_b3 = self.deriv_sigmoid(sum_o1)
# 计算隐藏层 h1 和隐藏层 h2 的反向输出结果;
d_ypred_d_h1 = self.w5 * self.deriv_sigmoid(sum_o1)
d_ypred_d_h2 = self.w6 * self.deriv_sigmoid(sum_o1)
# 计算隐藏层 h1 反向传播的权重和偏置结果;
d_h1_d_w1 = x[0] * self.deriv_sigmoid(sum_h1)
d_h1_d_w2 = x[1] * self.deriv_sigmoid(sum_h1)
d_h1_d_b1 = self.deriv_sigmoid(sum_h1)
# 计算隐藏层 h2 反向传播的权重和偏置结果;
d_h2_d_w3 = x[0] * self.deriv_sigmoid(sum_h2)
d_h2_d_w4 = x[1] * self.deriv_sigmoid(sum_h2)
d_h2_d_b2 = self.deriv_sigmoid(sum_h2)
# 3. 通过随机梯度下降的方式(SMG)更新权重和偏移;
# 更新输出层 o1 的权重和偏置;
self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3
# 更新隐藏层 h1 的权重和偏置;
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
# 更新隐藏层 h2 的权重和偏置;
self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
# 3. 每隔 10 次通过 MSE 损失函数计算一次损失值;
# 注意:损失值只是用来评估神经网络的好坏,没有参与优化运算;
if epoch % 10 == 0:
# 将 data 里面的每一个行数据传入 feedforward,计算其结果;
y_preds = np.apply_along_axis(self.feedforward, 1, data)
# 求真实值和预测值之间的损失;
loss = self.mse_loss(all_y_trues, y_preds)
# 输出损失值;
print("Epoch %d loss: %.3f" % (epoch, loss))
# 定义数据集输入的参数;
data = np.array([
[-2, -1], # Alice
[25, 6], # Bob
[17, 4], # Charlie
[-15, -6], # Diana
])
# 定义数据集对应的结果;
all_y_trues = np.array([
1, # Alice
0, # Bob
0, # Charlie
1, # Diana
])
# 进行神经网络的训练;
network = NeuralNetwork()
network.train(data, all_y_trues)
1.4 使用神经网络进行预测
接着我们使用训练好的神经网络进行预测。
所谓的神经网络已经训练完成,即损失值已经足够低,意味着输入传入神经网络后得到的预测输出值,已经和真实的输出值之间差异很小。
实质上就是完成了神经网络内部各个节点内权重和偏置的调整。
创建 2 个新的数据集传入神经网络,成功预测它们的性别。
# Make some predictions
emily = np.array([-7, -3]) # 128 pounds, 63 inches
frank = np.array([20, 2]) # 155 pounds, 68 inches
print("Emily: %.3f" % network.feedforward(emily)) # 0.951 - F
print("Frank: %.3f" % network.feedforward(frank)) # 0.039 - M