BP神经网络

系列文章见: 《回忆AI时代-从图灵机到人工智能》

假设你每天都会根据一些因素决定是否骑车,我们希望训练一个BP神经网络来学习你的决策习惯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import numpy as np

# ==========================================================
# 1. Sigmoid 激活函数
# ==========================================================
# 神经元输出不能直接使用加权和
# 而是需要经过激活函数
#
# Sigmoid 可以把任意数字压缩到 0~1
#
# 输入:-10 -1 0 1 10
# 输出:0.00 0.27 0.50 0.73 1.00
#
def sigmoid(x):
return 1 / (1 + np.exp(-x))


# ==========================================================
# Sigmoid 导数
# ==========================================================
# BP反向传播需要计算梯度
#
# Sigmoid'(x)=Sigmoid(x)*(1-Sigmoid(x))
#
def sigmoid_derivative(y):
return y * (1 - y)


# ==========================================================
# 2. 训练数据
# ==========================================================
#
# 输入:
# 天气(晴=1 雨=0)
# 是否周末(周末=1 工作日=0)
# 身体状态(好=1 差=0)
#
# 输出:
# 是否去骑车
#
X = np.array([
[1, 1, 1], # 晴天 周末 身体好
[1, 0, 1], # 晴天 工作日 身体好
[0, 1, 1], # 雨天 周末 身体好
[1, 1, 0], # 晴天 周末 身体差
[0, 0, 1], # 雨天 工作日 身体好
[0, 1, 0], # 雨天 周末 身体差
[1, 0, 0], # 晴天 工作日 身体差
[0, 0, 0] # 雨天 工作日 身体差
])

# 标签(真实答案)
Y = np.array([
[1],
[1],
[0],
[0],
[0],
[0],
[0],
[0]
])


# ==========================================================
# 3. 初始化网络参数
# ==========================================================

np.random.seed(0)

# 输入层(3) ------> 隐藏层(4)
#
# W1 大小:
#
# 隐藏1 隐藏2 隐藏3 隐藏4
# 天气
# 周末
# 身体
#
W1 = np.random.randn(3, 4)

# 隐藏层偏置
b1 = np.zeros((1, 4))

# 隐藏层(4) ------> 输出层(1)
W2 = np.random.randn(4, 1)

# 输出层偏置
b2 = np.zeros((1, 1))

# 学习率
learning_rate = 0.5


# ==========================================================
# 4. 开始训练
# ==========================================================
#
# BP神经网络训练流程:
#
# 前向传播
# ↓
# 得到预测值
# ↓
# 计算误差
# ↓
# 反向传播
# ↓
# 更新权重
#
for epoch in range(10000):

# ======================================================
# 第一步:前向传播(Forward)
# ======================================================

# 输入层 -> 隐藏层
#
# hidden_input =
# X × W1 + b1
#
hidden_input = np.dot(X, W1) + b1

# 隐藏层经过激活函数
hidden_output = sigmoid(hidden_input)

# 隐藏层 -> 输出层
final_input = np.dot(hidden_output, W2) + b2

# 输出层激活
prediction = sigmoid(final_input)

# ======================================================
# 第二步:计算误差
# ======================================================
#
# prediction:AI预测
# Y:真实答案
#
error = Y - prediction

# 均方误差(MSE)
loss = np.mean(error ** 2)

# ======================================================
# 第三步:反向传播(Back Propagation)
# ======================================================

# -----------------------------
# 输出层梯度
# -----------------------------
#
# 告诉输出层:
# "你应该修改多少"
#
d_output = error * sigmoid_derivative(prediction)

# -----------------------------
# 隐藏层误差
# -----------------------------
#
# 输出层误差
# ↓
# 传播回隐藏层
#
hidden_error = np.dot(d_output, W2.T)

# 隐藏层梯度
d_hidden = hidden_error * sigmoid_derivative(hidden_output)

# ======================================================
# 第四步:更新权重
# ======================================================

# 更新隐藏层 -> 输出层权重
W2 += learning_rate * np.dot(hidden_output.T, d_output)

# 更新输出层偏置
b2 += learning_rate * np.sum(d_output, axis=0, keepdims=True)

# 更新输入层 -> 隐藏层权重
W1 += learning_rate * np.dot(X.T, d_hidden)

# 更新隐藏层偏置
b1 += learning_rate * np.sum(d_hidden, axis=0, keepdims=True)

# 每1000轮打印一次Loss
if epoch % 1000 == 0:
print(f"Epoch {epoch:5d} Loss={loss:.6f}")


# ==========================================================
# 5. 测试
# ==========================================================

print("\n============== 测试结果 ==============\n")

tests = {
"晴天 周末 身体好": [1, 1, 1],
"晴天 周末 身体差": [1, 1, 0],
"雨天 周末 身体好": [0, 1, 1],
"晴天 工作日 身体好": [1, 0, 1],
}

for name, sample in tests.items():

x = np.array([sample])

# 前向传播
hidden = sigmoid(np.dot(x, W1) + b1)
output = sigmoid(np.dot(hidden, W2) + b2)

print(f"{name:<15} -> 去骑车概率:{output[0,0]:.3f}")

整个训练过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
       训练开始


输入训练数据(X)


初始化权重(W1、W2)


┌─────────────┐
│ 前向传播 │
│ Input → Hidden → Output │
└─────────────┘


得到预测值 Prediction


与真实值 Y 比较计算 Loss


┌─────────────┐
│ 反向传播 BP │
│ Output → Hidden │
└─────────────┘


更新权重 W1、W2 和偏置 b1、b2


Loss 是否足够小?
│ │
否 是
│ │
└────继续训练┘

输出训练好的模型