用Python从零开始构建一个神经网络
2023年西安电子科技大学认知计算作业
下面的内容旨在帮助你完成从零构建一个神经网络,内容基础来自于victorzhou的一篇blog:Machine Learning for Beginners: An Introduction to Neural Networks
在上文基础上对内容做了优化
1. 构建基本的神经元
神经元往往是一个神经网络最基础的组成部分,一个神经元的任务是,它应该接受一个输入,然后根据内部的权重来做一个输出,本质上是一个数学模型,下面的内容会展示一个非常简单的神经元,它接受一个二维输入,然后输出一维结果
该神经元内部发生的计算过程如下:
首先每一维的特征需要与对应的权重值相乘,这里的权重就指
w
1
w_1
w1
x
1
→
x
1
∗
w
1
x_1 \rightarrow x_1 * w_1
x1→x1∗w1
x
2
→
x
2
∗
w
2
x_2 \rightarrow x_2 * w_2
x2→x2∗w2
接下来当权重计算完成后,我们需要加入一个偏置值
b
i
a
s
bias
bias 在这里写为
b
b
b :
(
x
1
∗
w
1
)
+
(
x
2
∗
w
2
)
+
b
(x_1 * w_1) + (x_2 * w_2) + b
(x1∗w1)+(x2∗w2)+b
最后得到的结果将经过一个激活函数
f
f
f ,在后面的演示中,激活函数我们将采用
s
i
g
m
o
d
sigmod
sigmod:
y
=
f
(
x
1
∗
w
1
+
x
2
∗
w
2
+
b
)
y = f(x_1 * w_1 + x_2 * w_2 + b)
y=f(x1∗w1+x2∗w2+b)
sigmod 函数的图像如下,它的输出范围为 ( 0 , 1 ) (0, 1) (0,1),可以将 ( − ∞ , + ∞ ) (-\infty, +\infty) (−∞,+∞) 的输出范围 缩小到 ( 0 , 1 ) (0, 1) (0,1) 非常适合完成二分类任务
一个简单示例
假设按照我们的设计存在这样一个神经元,权重为 [0, 1] ,偏置值为 4
w
=
[
0
,
1
]
w = [0, 1]
w=[0,1]
b
=
4
b = 4
b=4
当输入为
x
=
[
2
,
3
]
x = [2, 3]
x=[2,3]. 它会进行如下的计算:
(
w
⋅
x
)
+
b
=
(
(
w
1
∗
x
1
)
+
(
w
2
∗
x
2
)
)
+
b
=
0
∗
2
+
1
∗
3
+
4
=
7
\begin{aligned} (w \cdot x) + b &= ((w_1 * x_1) + (w_2 * x_2)) + b \\ &= 0 * 2 + 1 * 3 + 4 \\ &= 7 \\ \end{aligned}
(w⋅x)+b=((w1∗x1)+(w2∗x2))+b=0∗2+1∗3+4=7
y
=
f
(
w
⋅
x
+
b
)
=
f
(
7
)
=
0.999
y = f(w \cdot x + b) = f(7) = \boxed{0.999}
y=f(w⋅x+b)=f(7)=0.999
最后我们得到的结果是 0.999 0.999 0.999 ,这样的一个完整的过程,我们称它为前向计算。
### Python代码实现一个简单的神经元
import numpy as np
def sigmoid(x):
# 激活函数的表达式: 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):
# 与权重相乘,再加上偏置值,最后经过激活函数
total = np.dot(self.weights, inputs) + self.bias
return sigmoid(total)
weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4 # b = 4
n = Neuron(weights, bias)
x = np.array([2, 3]) # x1 = 2, x2 = 3
print(n.feedforward(x)) # 0.9990889488055994
0.9990889488055994
2. 将神经元组装为一个神经网络
一个复杂的神经网络往往由许多个神经元连接组成,而这些神经元往往可以看成一层一层的结构,一个简单的神经网络结构如下:
该网络有 2 个输入,一个带有 2 个神经元的隐藏层 ( h 1 h_1 h1 and h 2 h_2 h2), 以及一个具有 1 个神经元的输出层 ( o 1 o_1 o1).
隐藏层是输入(第一)层和输出(最后)层之间的任何层。可以有多个隐藏层
神经网络中的前向计算
让我们使用上图所示的网络并假设所有神经元具有相同的权重 w = [ 0 , 1 ] w = [0, 1] w=[0,1] , 偏置值 b = 0 b = 0 b=0 , 并且同样使用sigmod来作为激活函数.
来看看输入 x = [ 2 , 3 ] x = [2, 3] x=[2,3] 会发生什么
h
1
=
h
2
=
f
(
w
⋅
x
+
b
)
=
f
(
(
0
∗
2
)
+
(
1
∗
3
)
+
0
)
=
f
(
3
)
=
0.9526
\begin{aligned} h_1 = h_2 &= f(w \cdot x + b) \\ &= f((0 * 2) + (1 * 3) + 0) \\ &= f(3) \\ &= 0.9526 \\ \end{aligned}
h1=h2=f(w⋅x+b)=f((0∗2)+(1∗3)+0)=f(3)=0.9526
o
1
=
f
(
w
⋅
[
h
1
,
h
2
]
+
b
)
=
f
(
(
0
∗
h
1
)
+
(
1
∗
h
2
)
+
0
)
=
f
(
0.9526
)
=
0.7216
\begin{aligned} o_1 &= f(w \cdot [h_1, h_2] + b) \\ &= f((0 * h_1) + (1 * h_2) + 0) \\ &= f(0.9526) \\ &= \boxed{0.7216} \\ \end{aligned}
o1=f(w⋅[h1,h2]+b)=f((0∗h1)+(1∗h2)+0)=f(0.9526)=0.7216
我们输入了 x = [ 2 , 3 ] x = [2, 3] x=[2,3] 得到了神经网络的计算值为 0.7216 0.7216 0.7216.
神经网络可以有任意数量的层,这些层中可以有任意数量的神经元。基本思想保持不变:通过网络中的神经元向前馈送输入,以最终获得输出。为简单起见,我们将在本文的其余部分继续使用上图所示的网络。
Python代码实现实现简单的神经网络
结构参考如下:
class OurNeuralNetwork:
'''
A neural network with:
- 2 inputs
- a hidden layer with 2 neurons (h1, h2)
- an output layer with 1 neuron (o1)
Each neuron has the same weights and bias:
- w = [0, 1]
- b = 0
'''
def __init__(self):
weights = np.array([0, 1])
bias = 0
# 这里的神经元我们使用上一小节定义的神经元
self.h1 = Neuron(weights, bias)
self.h2 = Neuron(weights, bias)
self.o1 = Neuron(weights, bias)
def feedforward(self, x):
out_h1 = self.h1.feedforward(x)
out_h2 = self.h2.feedforward(x)
out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))
return out_o1
network = OurNeuralNetwork()
x = np.array([2, 3])
print(network.feedforward(x)) # 0.7216325609518421
0.7216325609518421
3. 训练我们设计的神经网络, Part 1 Loss
现在我们有如下的数据信息:
Name | Weight (lb) | Height (in) | Gender |
---|---|---|---|
Alice | 133 | 65 | F |
Bob | 160 | 72 | M |
Charlie | 152 | 70 | M |
Diana | 120 | 60 | F |
我们现在希望能够训练一个模型,根据用户输入的特征,比如 180身高80公斤的体重 来得出一个人的性别
结构大概如下图:
为了方便训练,我们将女性性别表示为 1 1 1 ,将男性性别表示为 0 0 0,
Name | Weight (minus 135) | Height (minus 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
Bob | 25 | 6 | 0 |
Charlie | 17 | 4 | 0 |
Diana | -15 | -6 | 1 |
这里不会过多的介绍为什么这样处理训练数据,而且这里的数据处理非常简单,如果您有更多的兴趣,可以在网络上获取更多关于数据预处理操作的信息
损失
在训练网络之前,我们首先需要一种方法来量化它的好程度,以便它可以尝试做得更好。这就是损失函数。
我们将使用均方误差(MSE)损失::
MSE = 1 n ∑ i = 1 n ( y t r u e − y p r e d ) 2 \text{MSE} = \frac{1}{n} \sum_{i=1}^n (y_{true} - y_{pred})^2 MSE=n1i=1∑n(ytrue−ypred)2
Let’s break this down:
- n n n 是样本数, w为 4 4 4 (Alice, Bob, Charlie, Diana)。
- y y y 代表被预测的变量, 也就是性别(0,1)。
- y t r u e y_{true} ytrue 是变量的真实值(“正确答案”)。也就是说 y t r u e y_{true} ytrue 当输入 Alice 的特征时,结果应该是 1 1 1 (Female)。
- 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 称为均方误差。我们的损失函数只是取所有平方误差的平均值(因此称为均方误差)。我们的预测越好,我们的损失就越低
通常越小的loss值就代表模型现在的能力越强(不考虑过拟合)
训练模型的过程就是希望最小化它的损失
Loss计算的示例
假设我们的网络总是输出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 |
MSE = 1 4 ( 1 + 0 + 0 + 1 ) = 0.5 \text{MSE} = \frac{1}{4} (1 + 0 + 0 + 1) = \boxed{0.5} MSE=41(1+0+0+1)=0.5
Python代码实现 MSE Loss
import numpy as np
def mse_loss(y_true, y_pred):
# y_true and y_pred are numpy arrays of the same length.
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
4. 训练我们设计的神经网络, Part 2 反向传播, 优化神经网络
我们现在有一个明确的目标:最小化神经网络的损失。我们知道我们可以改变网络的权重和偏差来影响其预测,但网络权重与偏差的改变不可能由我们随心所欲地来进行,我们希望整个过程损失是不断减少的,那如何做到这一点呢?
为了简单起见,我们假设数据集中只有 Alice:
Name | Weight (minus 135) | Height (minus 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
那么均方误差损失就是 Alice 的平方误差:
MSE = 1 1 ∑ i = 1 1 ( y t r u e − y p r e d ) 2 = ( y t r u e − y p r e d ) 2 = ( 1 − y p r e d ) 2 \begin{aligned} \text{MSE} &= \frac{1}{1} \sum_{i=1}^1 (y_{true} - y_{pred})^2 \\ &= (y_{true} - y_{pred})^2 \\ &= (1 - y_{pred})^2 \\ \end{aligned} MSE=11i=1∑1(ytrue−ypred)2=(ytrue−ypred)2=(1−ypred)2
考虑损失的另一种方法是作为权重和偏差的函数。让我们标记网络中的每个权重和偏差:
然后,我们可以将损失写成多变量函数:
L ( w 1 , w 2 , w 3 , w 4 , w 5 , w 6 , b 1 , b 2 , b 3 ) L(w_1, w_2, w_3, w_4, w_5, w_6, b_1, b_2, b_3) L(w1,w2,w3,w4,w5,w6,b1,b2,b3)
想象一下我们想要调整 w 1 w_1 w1. 那当我们调整 w 1 w_1 w1时, L L L 会如何变化呢? 我们可以用偏导数来观察 L L L的变化 ∂ L ∂ w 1 \frac{\partial L}{\partial w_1} ∂w1∂L
利用链式法则,我们可以将 ∂ y p r e d ∂ w 1 \frac{\partial y_{pred}}{\partial w_1} ∂w1∂ypred 重写为:
∂ 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}} * \frac{\partial y_{pred}}{\partial w_1} ∂w1∂L=∂ypred∂L∗∂w1∂ypred
我们可以计算 ∂ L ∂ y p r e d \frac{\partial L}{\partial y_{pred}} ∂ypred∂L 因为我们已经计算过 L = ( 1 − y p r e d ) 2 L = (1 - y_{pred})^2 L=(1−ypred)2 :
∂ 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}} = \boxed{-2(1 - y_{pred})} ∂ypred∂L=∂ypred∂(1−ypred)2=−2(1−ypred)
接下来我们研究如何计算 ∂ y p r e d ∂ w 1 \frac{\partial y_{pred}}{\partial w_1} ∂w1∂ypred. 我们用 h 1 , h 2 , o 1 h_1, h_2, o_1 h1,h2,o1 来代表神经网络每层的输出
y p r e d = o 1 = f ( w 5 h 1 + w 6 h 2 + b 3 ) y_{pred} = o_1 = f(w_5h_1 + w_6h_2 + b_3) ypred=o1=f(w5h1+w6h2+b3)
参数 w 1 w_1 w1 只影响 h 1 h_1 h1 (not h 2 h_2 h2), 所以我们可以将计算式写成
∂
y
p
r
e
d
∂
w
1
=
∂
y
p
r
e
d
∂
h
1
∗
∂
h
1
∂
w
1
\frac{\partial y_{pred}}{\partial w_1} = \frac{\partial y_{pred}}{\partial h_1} * \frac{\partial h_1}{\partial w_1}
∂w1∂ypred=∂h1∂ypred∗∂w1∂h1
∂
y
p
r
e
d
∂
h
1
=
w
5
∗
f
′
(
w
5
h
1
+
w
6
h
2
+
b
3
)
\frac{\partial y_{pred}}{\partial h_1} = \boxed{w_5 * f'(w_5h_1 + w_6h_2 + b_3)}
∂h1∂ypred=w5∗f′(w5h1+w6h2+b3)
同样的,我们也可以对 ∂ h 1 ∂ w 1 \frac{\partial h_1}{\partial w_1} ∂w1∂h1 使用链式法则:
h
1
=
f
(
w
1
x
1
+
w
2
x
2
+
b
1
)
h_1 = f(w_1x_1 + w_2x_2 + b_1)
h1=f(w1x1+w2x2+b1)
∂
h
1
∂
w
1
=
x
1
∗
f
′
(
w
1
x
1
+
w
2
x
2
+
b
1
)
\frac{\partial h_1}{\partial w_1} = \boxed{x_1 * f'(w_1x_1 + w_2x_2 + b_1)}
∂w1∂h1=x1∗f′(w1x1+w2x2+b1)
x 1 x_1 x1 代表体重, and x 2 x_2 x2 代表身高. 接下来我们来观察sigmod函数的导数
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)2e−x=f(x)∗(1−f(x))
现在我们已经将最开始的 ∂ 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 ∂ h 1 ∗ ∂ h 1 ∂ w 1 \boxed{\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_{pred}} * \frac{\partial y_{pred}}{\partial h_1} * \frac{\partial h_1}{\partial w_1}} ∂w1∂L=∂ypred∂L∗∂h1∂ypred∗∂w1∂h1
这种通过向后计算偏导数的系统称为反向传播
如何计算偏导数
我们将继续假设只有 Alice 在我们的数据集中
Name | Weight (minus 135) | Height (minus 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
让我们将所有权重初始化为 1 1 1 所有的偏置设置为 0 0 0. 当我们进行前向计算,我们会得到下面的结果:
h
1
=
f
(
w
1
x
1
+
w
2
x
2
+
b
1
)
=
f
(
−
2
+
−
1
+
0
)
=
0.0474
\begin{aligned} h_1 &= f(w_1x_1 + w_2x_2 + b_1) \\ &= f(-2 + -1 + 0) \\ &= 0.0474 \\ \end{aligned}
h1=f(w1x1+w2x2+b1)=f(−2+−1+0)=0.0474
h
2
=
f
(
w
3
x
1
+
w
4
x
2
+
b
2
)
=
0.0474
h_2 = f(w_3x_1 + w_4x_2 + b_2) = 0.0474
h2=f(w3x1+w4x2+b2)=0.0474
o
1
=
f
(
w
5
h
1
+
w
6
h
2
+
b
3
)
=
f
(
0.0474
+
0.0474
+
0
)
=
0.524
\begin{aligned} o_1 &= f(w_5h_1 + w_6h_2 + b_3) \\ &= f(0.0474 + 0.0474 + 0) \\ &= 0.524 \\ \end{aligned}
o1=f(w5h1+w6h2+b3)=f(0.0474+0.0474+0)=0.524
输出的结果为 y p r e d = 0.524 y_{pred} = 0.524 ypred=0.524, 这不能够帮助我们准确的分辨出输入的是男性特征( 0 0 0)还是女性特征( 1 1 1) 。接下来让我们计算偏导 ∂ 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
∂
h
1
∗
∂
h
1
∂
w
1
\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_{pred}} * \frac{\partial y_{pred}}{\partial h_1} * \frac{\partial h_1}{\partial w_1}
∂w1∂L=∂ypred∂L∗∂h1∂ypred∗∂w1∂h1
∂
L
∂
y
p
r
e
d
=
−
2
(
1
−
y
p
r
e
d
)
=
−
2
(
1
−
0.524
)
=
−
0.952
\begin{aligned} \frac{\partial L}{\partial y_{pred}} &= -2(1 - y_{pred}) \\ &= -2(1 - 0.524) \\ &= -0.952 \\ \end{aligned}
∂ypred∂L=−2(1−ypred)=−2(1−0.524)=−0.952
∂
y
p
r
e
d
∂
h
1
=
w
5
∗
f
′
(
w
5
h
1
+
w
6
h
2
+
b
3
)
=
1
∗
f
′
(
0.0474
+
0.0474
+
0
)
=
f
(
0.0948
)
∗
(
1
−
f
(
0.0948
)
)
=
0.249
\begin{aligned} \frac{\partial y_{pred}}{\partial h_1} &= w_5 * f'(w_5h_1 + w_6h_2 + b_3) \\ &= 1 * f'(0.0474 + 0.0474 + 0) \\ &= f(0.0948) * (1 - f(0.0948)) \\ &= 0.249 \\ \end{aligned}
∂h1∂ypred=w5∗f′(w5h1+w6h2+b3)=1∗f′(0.0474+0.0474+0)=f(0.0948)∗(1−f(0.0948))=0.249
∂
h
1
∂
w
1
=
x
1
∗
f
′
(
w
1
x
1
+
w
2
x
2
+
b
1
)
=
−
2
∗
f
′
(
−
2
+
−
1
+
0
)
=
−
2
∗
f
(
−
3
)
∗
(
1
−
f
(
−
3
)
)
=
−
0.0904
\begin{aligned} \frac{\partial h_1}{\partial w_1} &= x_1 * f'(w_1x_1 + w_2x_2 + b_1) \\ &= -2 * f'(-2 + -1 + 0) \\ &= -2 * f(-3) * (1 - f(-3)) \\ &= -0.0904 \\ \end{aligned}
∂w1∂h1=x1∗f′(w1x1+w2x2+b1)=−2∗f′(−2+−1+0)=−2∗f(−3)∗(1−f(−3))=−0.0904
∂
L
∂
w
1
=
−
0.952
∗
0.249
∗
−
0.0904
=
0.0214
\begin{aligned} \frac{\partial L}{\partial w_1} &= -0.952 * 0.249 * -0.0904 \\ &= \boxed{0.0214} \\ \end{aligned}
∂w1∂L=−0.952∗0.249∗−0.0904=0.0214
计算的偏导大于0,这告诉我们 增大 w 1 w_1 w1, L L L 也会增大,所以我们需要减小 w 1 w_1 w1
随机梯度下降
我们现在基本已经完成了训练的所有准备! 我们将使用一种称为随机梯度下降(SGD)的优化算法,它告诉我们如何改变权重和偏差以最小化损失。基本上就是这个更新方程:
w 1 ← w 1 − η ∂ L ∂ w 1 w_1 \leftarrow w_1 - \eta \frac{\partial L}{\partial w_1} w1←w1−η∂w1∂L
η \eta η 是一个称为学习率的常数,它控制我们训练的速度。我们所做的只是让 w 1 w_1 w1 减去 η ∂ L ∂ w 1 \eta \frac{\partial L}{\partial w_1} η∂w1∂L :
- 如果 ∂ L ∂ w 1 \frac{\partial L}{\partial w_1} ∂w1∂L 大于0, w 1 w_1 w1 将会减小, 使得 L L L 下降.
- 如果 ∂ L ∂ w 1 \frac{\partial L}{\partial w_1} ∂w1∂L 小于0, w 1 w_1 w1 将会增大, 使得 L L L 下降.
如果我们对网络中的每个权重和偏置都这样做,损失就会慢慢减少,我们的网络也会得到改善。
我们的训练过程将如下所示:
- 从数据集中随机采样,这里我们只随机采取一个样本。
- 计算损失相对于权重或偏差的所有偏导数 (e.g. ∂ L ∂ w 1 \frac{\partial L}{\partial w_1} ∂w1∂L, ∂ L ∂ w 2 \frac{\partial L}{\partial w_2} ∂w2∂L, etc).
- 使用SGD来更新每个权重和偏差
- 重复步骤1
实现完整的BP神经网络
训练数据如下:
Name | Weight (minus 135) | Height (minus 66) | Gender |
---|---|---|---|
Alice | -2 | -1 | 1 |
Bob | 25 | 6 | 0 |
Charlie | 17 | 4 | 0 |
Diana | -15 | -6 | 1 |
import numpy as np
def sigmoid(x):
# Sigmoid activation function: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
def deriv_sigmoid(x):
# Derivative of sigmoid: f'(x) = f(x) * (1 - f(x))
fx = sigmoid(x)
return fx * (1 - fx)
def mse_loss(y_true, y_pred):
# y_true and y_pred are numpy arrays of the same length.
return ((y_true - y_pred) ** 2).mean()
class OurNeuralNetwork:
'''
A neural network with:
- 2 inputs
- a hidden layer with 2 neurons (h1, h2)
- an output layer with 1 neuron (o1)
*** DISCLAIMER ***:
The code below is intended to be simple and educational, NOT optimal.
Real neural net code looks nothing like this. DO NOT use this code.
Instead, read/run it to understand how this specific network works.
'''
def __init__(self):
# Weights
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 feedforward(self, x):
# x is a numpy array with 2 elements.
h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
return o1
def train(self, data, all_y_trues):
'''
- data is a (n x 2) numpy array, n = # of samples in the dataset.
- all_y_trues is a numpy array with n elements.
Elements in all_y_trues correspond to those in data.
'''
learn_rate = 0.1
epochs = 1000 # number of times to loop through the entire dataset
for epoch in range(epochs):
for x, y_true in zip(data, all_y_trues):
# --- Do a feedforward (we'll need these values later)
sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
h1 = sigmoid(sum_h1)
sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
h2 = sigmoid(sum_h2)
sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
o1 = sigmoid(sum_o1)
y_pred = o1
# --- Calculate partial derivatives.
# --- Naming: d_L_d_w1 represents "partial L / partial w1"
d_L_d_ypred = -2 * (y_true - y_pred)
# Neuron o1
d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)
d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)
d_ypred_d_b3 = deriv_sigmoid(sum_o1)
d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)
d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)
# Neuron h1
d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)
d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)
d_h1_d_b1 = deriv_sigmoid(sum_h1)
# Neuron h2
d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)
d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)
d_h2_d_b2 = deriv_sigmoid(sum_h2)
# --- Update weights and biases
# Neuron 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
# Neuron 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
# Neuron 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
# --- Calculate total loss at the end of each epoch
if epoch % 10 == 0:
y_preds = np.apply_along_axis(self.feedforward, 1, data)
loss = mse_loss(all_y_trues, y_preds)
print("Epoch %d loss: %.3f" % (epoch, loss))
# Define dataset
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
])
# Train our neural network!
network = OurNeuralNetwork()
network.train(data, all_y_trues)
随着网络的不断学习,损失不断下降:
下面我们就能看到预测的结果:
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