I. 前言
在反向传播算法(Backpropagation)----Gradient Descent的推导过程中我简单推导过BP算法,但是只是简单用符号来表示参数间的求导关系,并没有详细介绍求导的每一步具体步骤。因此,本篇文章着重推导反向传播时的求导过程。
系列文章:
- 深入理解PyTorch中LSTM的输入和输出(从input输入到Linear输出)
- PyTorch搭建LSTM实现时间序列预测(负荷预测)
- PyTorch中利用LSTMCell搭建多层LSTM实现时间序列预测
- PyTorch搭建LSTM实现多变量时间序列预测(负荷预测)
- PyTorch搭建双向LSTM实现时间序列预测(负荷预测)
- PyTorch搭建LSTM实现多变量多步长时间序列预测(一):直接多输出
- PyTorch搭建LSTM实现多变量多步长时间序列预测(二):单步滚动预测
- PyTorch搭建LSTM实现多变量多步长时间序列预测(三):多模型单步预测
- PyTorch搭建LSTM实现多变量多步长时间序列预测(四):多模型滚动预测
- PyTorch搭建LSTM实现多变量多步长时间序列预测(五):seq2seq
- PyTorch中实现LSTM多步长时间序列预测的几种方法总结(负荷预测)
- PyTorch-LSTM时间序列预测中如何预测真正的未来值
- PyTorch搭建LSTM实现多变量输入多变量输出时间序列预测(多任务学习)
- PyTorch搭建ANN实现时间序列预测(风速预测)
- PyTorch搭建CNN实现时间序列预测(风速预测)
- PyTorch搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)
- PyTorch搭建Transformer实现多变量多步长时间序列预测(负荷预测)
- PyTorch时间序列预测系列文章总结(代码使用方法)
- TensorFlow搭建LSTM实现时间序列预测(负荷预测)
- TensorFlow搭建LSTM实现多变量时间序列预测(负荷预测)
- TensorFlow搭建双向LSTM实现时间序列预测(负荷预测)
- TensorFlow搭建LSTM实现多变量多步长时间序列预测(一):直接多输出
- TensorFlow搭建LSTM实现多变量多步长时间序列预测(二):单步滚动预测
- TensorFlow搭建LSTM实现多变量多步长时间序列预测(三):多模型单步预测
- TensorFlow搭建LSTM实现多变量多步长时间序列预测(四):多模型滚动预测
- TensorFlow搭建LSTM实现多变量多步长时间序列预测(五):seq2seq
- TensorFlow搭建LSTM实现多变量输入多变量输出时间序列预测(多任务学习)
- TensorFlow搭建ANN实现时间序列预测(风速预测)
- TensorFlow搭建CNN实现时间序列预测(风速预测)
- TensorFlow搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)
- PyG搭建图神经网络实现多变量输入多变量输出时间序列预测
- PyTorch搭建GNN-LSTM和LSTM-GNN模型实现多变量输入多变量输出时间序列预测
- PyG Temporal搭建STGCN实现多变量输入多变量输出时间序列预测
- 时序预测中Attention机制是否真的有效?盘点LSTM/RNN中24种Attention机制+效果对比
- 详解Transformer在时序预测中的Encoder和Decoder过程:以负荷预测为例
- (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
II. 网络结构
本次搭建的神经网络参考了我的另一篇博文:手写神经网络识别MNIST数据集(pytorch读入数据)。神经网络由1个输入层、3个隐藏层以及1个输出层组成,激活函数全部采用Sigmoid函数。
III. 前向传播
网络各层间的运算关系,也就是前向传播过程如下所示:
z
1
=
I
w
1
,
h
1
=
σ
(
z
1
)
z
2
=
h
1
w
2
,
h
2
=
σ
(
z
2
)
z
3
=
h
2
w
3
,
h
3
=
σ
(
z
3
)
z
4
=
h
3
w
4
,
O
=
σ
(
z
4
)
l
o
s
s
=
1
2
(
O
−
y
)
2
z_1=Iw_1,h_1=\sigma(z_1)\\ z_2=h_1w_2,h_2=\sigma(z_2)\\ z_3=h_2w_3,h_3=\sigma(z_3)\\ z_4=h_3w_4,O=\sigma(z_4)\\ loss=\frac{1}{2} (O-y)^2
z1=Iw1,h1=σ(z1)z2=h1w2,h2=σ(z2)z3=h2w3,h3=σ(z3)z4=h3w4,O=σ(z4)loss=21(O−y)2
其中:
- 输入 I I I的shape为 100 × 32 100 \times32 100×32,每一次输入100个样本,每个样本的特征数为32
- w 1 w_1 w1的shape为 32 × 20 32 \times 20 32×20
- z 1 z_1 z1和 h 1 h_1 h1的shape都为 100 × 20 100 \times 20 100×20
- w 2 w_2 w2的shape为 20 × 20 20 \times 20 20×20
- z 2 z_2 z2和 h 2 h_2 h2的shape都为 100 × 20 100 \times 20 100×20
- w 3 w_3 w3的shape为 20 × 20 20 \times 20 20×20
- z 3 z_3 z3和 h 3 h_3 h3的shape都为 100 × 20 100 \times 20 100×20
- w 4 w_4 w4的shape为 20 × 1 20 \times 1 20×1
- z 4 z_4 z4和 O O O的shape都为 100 × 1 100 \times 1 100×1,表示100个样本的输出
- l o s s loss loss为损失函数,shape和输出 O O O一致
代码实现:
- 初始化
class BP:
def __init__(self):
self.input = np.zeros((100, 32)) # 100 samples per round
self.w1 = 2 * np.random.random((32, 20)) - 1 # limit to (-1, 1)
self.z1 = 2 * np.random.random((100, 20)) - 1
self.hidden_layer_1 = np.zeros((100, 20))
self.w2 = 2 * np.random.random((20, 20)) - 1
self.z2 = 2 * np.random.random((100, 20)) - 1
self.hidden_layer_2 = np.zeros((100, 20))
self.w3 = 2 * np.random.random((20, 20)) - 1
self.z3 = 2 * np.random.random((100, 20)) - 1
self.hidden_layer_3 = np.zeros((100, 20))
self.w4 = 2 * np.random.random((20, 1)) - 1
self.z4 = 2 * np.random.random((100, 1)) - 1
self.output_layer = np.zeros((100, 1))
self.loss = np.zeros((100, 1))
self.learning_rate = 0.08
- 前向传播
def forward_prop(self, data, label): # label:100 X 1,data: 100 X 32
self.input = data
self.z1 = np.dot(self.input, self.w1)
self.hidden_layer_1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.hidden_layer_1, self.w2)
self.hidden_layer_2 = self.sigmoid(self.z2)
self.z3 = np.dot(self.hidden_layer_2, self.w3)
self.hidden_layer_3 = self.sigmoid(self.z3)
self.z4 = np.dot(self.hidden_layer_3, self.w4)
self.output_layer = self.sigmoid(self.z4)
# loss
self.loss = 1 / 2 * (label - self.output_layer) ** 2
return self.output_layer
IV. 反向传播
为了实现对4个
w
w
w进行更新,我们需要求出损失函数
l
l
l对4个
w
w
w的导数,然后再对4个
w
w
w分别进行更新:
w
=
w
−
l
r
∗
∂
l
∂
w
w=w-lr* \frac{\partial l}{\partial w}
w=w−lr∗∂w∂l
因此,反向传播的过程中唯一任务就是找出损失函数
l
l
l对4个
w
w
w的导数。
首先分析一下 w 4 w_4 w4:
由
z
4
=
h
3
w
4
z_4=h_3w_4
z4=h3w4
可知:
∂
l
∂
w
4
=
h
3
T
∂
l
∂
z
4
\frac{\partial l}{\partial w_4}=h_3^T \frac{\partial l}{\partial z_4}
∂w4∂l=h3T∂z4∂l
在矩阵的链式求导过程中,若求导对象在前向传播式子中在后,则相应梯度在后;如果求导对象在前,则相应梯度在前。也就是说,如果要算损失函数对
h
3
h_3
h3的导数,
h
3
h_3
h3在前,则有:
∂
l
∂
h
3
=
∂
l
∂
z
4
w
4
T
\frac{\partial l}{\partial h_3}= \frac{\partial l}{\partial z_4}w_4^T
∂h3∂l=∂z4∂lw4T
因此,现在需要寻找
∂
l
∂
z
4
\frac{\partial l}{\partial z_4}
∂z4∂l,而因为又有:
O
=
σ
(
z
4
)
O=\sigma(z_4)
O=σ(z4)
和
l
o
s
s
=
1
2
(
y
−
O
)
2
loss=\frac{1}{2} (y-O)^2
loss=21(y−O)2
所以我们有:
∂
l
∂
z
4
=
∂
l
∂
O
∂
O
∂
z
4
\frac{\partial l}{\partial z_4} = \frac{\partial l}{\partial O}\frac{\partial O}{\partial z_4}
∂z4∂l=∂O∂l∂z4∂O
故:
∂
l
∂
z
4
=
(
O
−
y
)
⊙
σ
′
(
O
)
\frac{\partial l}{\partial z_4}=(O-y)\odot \sigma^{'}(O)
∂z4∂l=(O−y)⊙σ′(O)
因此,最终求得的损失函数对
w
4
w_4
w4的导数就为:
∂
l
∂
w
4
=
h
3
T
(
∂
l
∂
O
⊙
σ
′
(
O
)
)
\frac{\partial l}{\partial w_4} = h_3^T(\frac{\partial l}{\partial O} \odot \sigma^{'}(O))
∂w4∂l=h3T(∂O∂l⊙σ′(O))
相应代码为:
l_deri_out = self.output_layer - label
l_deri_z4 = l_deri_out * self.sigmoid_deri(self.output_layer)
l_deri_w4 = np.dot(self.hidden_layer_3.T, l_deri_z4)
那么同理,对
w
3
w_3
w3我们有:
∂
l
∂
w
4
=
h
2
T
(
∂
l
∂
h
3
⊙
σ
′
(
h
3
)
)
\frac{\partial l}{\partial w_4} = h_2^T(\frac{\partial l}{\partial h_3} \odot \sigma^{'}(h_3))
∂w4∂l=h2T(∂h3∂l⊙σ′(h3))
相应代码为:
l_deri_h3 = np.dot(l_deri_z4, self.w4.T)
l_deri_z3 = l_deri_h3 * self.sigmoid_deri(self.hidden_layer_3)
l_deri_w3 = np.dot(self.hidden_layer_2.T, l_deri_z3)
对
w
2
w_2
w2有:
∂
l
∂
w
2
=
h
1
T
(
∂
l
∂
h
2
⊙
σ
′
(
h
2
)
)
\frac{\partial l}{\partial w_2} = h_1^T(\frac{\partial l}{\partial h_2} \odot \sigma^{'}(h_2))
∂w2∂l=h1T(∂h2∂l⊙σ′(h2))
相应代码为:
l_deri_h2 = np.dot(l_deri_z3, self.w3.T)
l_deri_z2 = l_deri_h2 * self.sigmoid_deri(self.hidden_layer_2)
l_deri_w2 = np.dot(self.hidden_layer_1.T, l_deri_z2)
对
w
1
w_1
w1有:
∂
l
∂
w
1
=
I
T
(
∂
l
∂
h
1
⊙
σ
′
(
h
1
)
)
\frac{\partial l}{\partial w_1} = I^T(\frac{\partial l}{\partial h_1} \odot \sigma^{'}(h_1))
∂w1∂l=IT(∂h1∂l⊙σ′(h1))
相应代码为:
l_deri_h1 = np.dot(l_deri_z2, self.w2.T)
l_deri_z1 = l_deri_h1 * self.sigmoid_deri(self.hidden_layer_1)
l_deri_w1 = np.dot(self.input.T, l_deri_z1)
因此,反向传播的完整代码为:
def backward_prop(self, label):
# w4
l_deri_out = self.output_layer - label
l_deri_z4 = l_deri_out * self.sigmoid_deri(self.output_layer)
l_deri_w4 = np.dot(self.hidden_layer_3.T, l_deri_z4)
# w3
l_deri_h3 = np.dot(l_deri_z4, self.w4.T)
l_deri_z3 = l_deri_h3 * self.sigmoid_deri(self.hidden_layer_3)
l_deri_w3 = np.dot(self.hidden_layer_2.T, l_deri_z3)
# w2
l_deri_h2 = np.dot(l_deri_z3, self.w3.T)
l_deri_z2 = l_deri_h2 * self.sigmoid_deri(self.hidden_layer_2)
l_deri_w2 = np.dot(self.hidden_layer_1.T, l_deri_z2)
# w1
l_deri_h1 = np.dot(l_deri_z2, self.w2.T)
l_deri_z1 = l_deri_h1 * self.sigmoid_deri(self.hidden_layer_1)
l_deri_w1 = np.dot(self.input.T, l_deri_z1)
# update
self.w4 -= self.learning_rate * l_deri_w4
self.w3 -= self.learning_rate * l_deri_w3
self.w2 -= self.learning_rate * l_deri_w2
self.w1 -= self.learning_rate * l_deri_w1
V. 实验
1.数据处理
本次实验拟对负荷进行预测:利用前24个小时的负荷+环境因素来预测下一时刻的负荷值。
def nn_seq(B):
dataset = load_data()
# split
train = dataset[:int(len(dataset) * 0.6)]
val = dataset[int(len(dataset) * 0.6):int(len(dataset) * 0.8)]
test = dataset[int(len(dataset) * 0.8):len(dataset)]
m, n = np.max(train[train.columns[1]]), np.min(train[train.columns[1]])
def process(data):
load = data[data.columns[1]]
data = data.values.tolist()
load = (load - n) / (m - n)
load = load.tolist()
X, Y = [], []
for i in range(len(data) - 24):
train_seq = []
train_label = []
for j in range(i, i + 24):
x = [load[j]]
for c in range(2, 8):
x.append(data[j][c])
train_seq.append(x)
train_label.append(load[i + 24])
# 展平
train_seq = [x for y in train_seq for x in y]
X.append(train_seq)
Y.append(train_label)
X, Y = np.array(X), np.array(Y)
s = int(len(X) / B) * B
X, Y = X[:s], Y[:s]
return X, Y
train_x, train_y = process(train)
val_x, val_y = process(val)
test_x, test_y = process(test)
return [train_x, train_y], [val_x, val_y], [test_x, test_y], m, n
2.训练
def train(nn, Dtr, Val):
print('training...')
train_x, train_y = Dtr[0], Dtr[1]
batch_size = nn.B
min_epochs = 0
best_nn = None
min_val_loss = 5
epochs = 50
batch = int(len(train_x) / batch_size)
train_loss = []
for epoch in tqdm(range(epochs)):
for i in range(batch):
start = i * batch_size
end = start + batch_size
nn.forward_prop(train_x[start:end], train_y[start:end])
nn.backward_prop(train_y[start:end])
train_loss.append(np.mean(nn.loss))
# val
val_loss = get_val_loss(nn, Val)
if epoch + 1 >= min_epochs and val_loss < min_val_loss:
min_val_loss = val_loss
best_nn = copy.deepcopy(nn)
print('epoch {:03d} train_loss {:.8f} val_loss {:.8f}'.format(epoch, np.mean(train_loss), val_loss))
return best_nn
3.测试
def test(nn, Dte, m, n):
test_x, test_y = Dte[0], Dte[1]
pred = []
batch = int(len(test_y) / nn.B)
for i in range(batch):
start = i * nn.B
end = start + nn.B
res = nn.forward_prop(test_x[start:end], test_y[start:end])
res = res.tolist()
res = list(chain.from_iterable(res))
# print('res=', res)
pred.extend(res)
pred = np.array(pred)
test_y = test_y.flatten()
pred = pred * (m - n) + n
test_y = test_y * (m - n) + n
print('mape:', get_mape(test_y, pred))
4.结果
经过200轮训练后,神经网络的mape为6.02%: