一. 循环神经网络(RNN)
循环神经网络(RNN)是一种神经网络架构,专门设计用于处理序列数据,如时间序列或文本。它的主要特点是在网络内部引入循环连接,使得信息可以在网络中传递。想象一下,你在阅读一段文字。对于一个标准的前馈神经网络(Feedforward Neural Network),它的输入是独立的,每一层的输出只与当前输入有关。RNN 允许信息在网络中传递,以捕捉序列中的上下文和依赖关系。RNN的关键部分是循环连接,允许网络在处理序列时保留过去的信息。每个时间步,RNN都会接收当前输入和前一个时间步的隐藏状态,然后生成新的隐藏状态。这就创造了一个能够记忆先前信息的机制,使得网络可以对序列中的不同部分进行建模。标准的RNN在面对较长序列时可能会面临梯度消失或梯度爆炸等问题,导致难以学习长期依赖关系。为了解决这个问题,出现了一些改进型的循环神经网络,如长短时记忆网络(LSTM)和门控循环单元(GRU),它们引入了特殊的机制来更有效地处理长期依赖。这里先介绍最基本的RNN,之后在详细介绍LSTM与GRU网络。
二. 注意力机制
注意力机制是一种深度学习中的技术,它模拟人类在处理信息时的注意力分配过程。它的目标是使模型能够更加集中地关注输入数据的特定部分,而不是一次性处理所有信息。这种机制在处理序列数据、图像和其他结构化数据时特别有用。在传统的神经网络中,每个输入对最终的输出都有相同的权重,而注意力机制允许网络动态地调整这些权重,以便更关注输入的某些部分。这样可以提高模型对重要信息的感知能力,从而提高整体性能。在序列数据中,比如机器翻译中的句子,注意力机制使模型能够在每个时间步关注源语言句子的不同部分,而不是只关注最终的上下文向量。这有助于处理长句子和捕捉语言中的复杂结构。
注意力机制的核心思想是,对于每个输出(或上下文向量),模型会计算与输入序列中每个位置的相关性分数。这些分数通常通过某种度量(如点积、缩放点积等)计算。然后,利用这些相关性分数,对输入的不同部分进行加权平均,以生成最终的输出或上下文向量。
三. 数据集介绍
这里使用的数据集还是前几期博客当中的温度数据集,因为想让大家可以明显看出网络不通对于预测结果的影响,数据集放下下放网盘当中,需要的自己取一下。
链接:temps.csv
提取码:4i1g
四. 代码详解
class Attention(nn.Module):
def __init__(self, input_size, hidden_size):
super(Attention, self).__init__()
self.linear_in = nn.Linear(input_size, hidden_size, bias=False)
self.linear_out = nn.Linear(hidden_size * 2, hidden_size) # 为了将 RNN 输出和注意力上下文连接在一起
self.tanh = nn.Tanh()
self.softmax = nn.Softmax(dim=1) # 将注意力机制分数归一化为权重
def forward(self, rnn_output, encoder_output):
rnn_output_mapped = self.linear_in(rnn_output)
attn_scores = torch.bmm(rnn_output_mapped, encoder_output.transpose(1, 2)) # 计算注意力机制分数
attn_scores = self.softmax(attn_scores) # 将注意力分数转换为概率分布
context = torch.bmm(attn_scores, encoder_output) # 注意力分数计算编码器输出的加权和 算出上下文向量
output = torch.cat((rnn_output, context), dim=2)
output = self.tanh(self.linear_out(output))
return output
这里构建了一个比较容易的注意力机制类,其用于处理序列数据,将接收rnn_output将其映射到注意力的隐藏空间,计算注意力机制分数,然后将这些分数通过softmax函数归一化为注意力权重,利用这些权重计算编码器输出的加权和,形成注意力上下文向量。将RNN输出和注意力上下文连接在一起,经过线性层和激活函数的处理,得到最终的输出。
class AttentionRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(AttentionRNN, self).__init__()
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True) # ture表示输入数据的形状为 (batch_size, sequence_length, input_size
self.attention = Attention(hidden_size, hidden_size)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
rnn_output, _ = self.rnn(x)
attention_output = self.attention(rnn_output, rnn_output)
output = self.fc(attention_output[:, -1, :])
return output
这里定义一个三层网络结构的RNN模型,与其他RNN模型不同的点在于,这里添加了注意力机制,其通过上述定义的注意力机制类来完成。它接收 RNN 的输出rnn_out作为输入,同时也作为编码器输出,通过注意力机制计算出加权的注意力上下文向量。
五. 完整代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
from torch import nn
from sklearn import preprocessing
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题
# 读取温度数据集
data = pd.read_csv("temps.csv")
# 处理日期信息
data['date'] = pd.to_datetime(data[['year', 'month', 'day']])
data['ordinal_date'] = data['date'].apply(lambda x: x.toordinal())
# 提取特征和标签
features = data[['ordinal_date', 'temp_1', 'temp_2', 'friend']]
labels = data['actual']
# 标准化特征
scaler = preprocessing.StandardScaler()
features = scaler.fit_transform(features)
# 转换为PyTorch张量
x_data = torch.tensor(features, dtype=torch.float32).view(-1, 1, features.shape[1])
y_data = torch.tensor(labels.values, dtype=torch.float32).view(-1, 1)
#将RNN输出和计算得到的上下文向量沿着特征维度(dim=2)拼接起来
# 定义注意力机制
class Attention(nn.Module):
def __init__(self, input_size, hidden_size):
super(Attention, self).__init__()
self.linear_in = nn.Linear(input_size, hidden_size, bias=False)
self.linear_out = nn.Linear(hidden_size * 2, hidden_size) # 为了将 RNN 输出和注意力上下文连接在一起
self.tanh = nn.Tanh()
self.softmax = nn.Softmax(dim=1) # 将注意力机制分数归一化为权重
def forward(self, rnn_output, encoder_output):
rnn_output_mapped = self.linear_in(rnn_output)
attn_scores = torch.bmm(rnn_output_mapped, encoder_output.transpose(1, 2)) # 计算注意力机制分数
attn_scores = self.softmax(attn_scores) # 将注意力分数转换为概率分布
context = torch.bmm(attn_scores, encoder_output) # 注意力分数计算编码器输出的加权和 算出上下文向量
output = torch.cat((rnn_output, context), dim=2)
output = self.tanh(self.linear_out(output))
return output
# 定义带有注意力机制的RNN模型
class AttentionRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(AttentionRNN, self).__init__()
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True) # ture表示输入数据的形状为 (batch_size, sequence_length, input_size
self.attention = Attention(hidden_size, hidden_size)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
rnn_output, _ = self.rnn(x)
attention_output = self.attention(rnn_output, rnn_output)
output = self.fc(attention_output[:, -1, :])
return output
# 实例化带有注意力机制的RNN模型
input_size = features.shape[1]
hidden_size = 128
output_size = 1
attention_rnn = AttentionRNN(input_size, hidden_size, output_size)
# 损失函数和优化器
criterion = nn.MSELoss(reduction='mean')
optimizer = torch.optim.Adam(attention_rnn.parameters(), lr=0.001)
# 训练带有注意力机制的RNN模型
losses = []
for epoch in range(15000):
y_pred = attention_rnn(x_data)
loss = criterion(y_pred, y_data)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 打印损失
if epoch % 100 == 0:
losses.append(loss.item())
print(f'Epoch {epoch}, Loss: {loss.item()}')
# 使用带有注意力机制的RNN模型进行预测
with torch.no_grad():
predictions = attention_rnn(x_data).numpy()
# 绘制训练损失
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练损失曲线')
plt.show()
# 绘制实际值与预测值
dates = data['date']
plt.plot(dates, labels, 'b-', label='实际值')
plt.plot(dates, predictions, 'ro', label='预测值')
plt.xticks(rotation=60)
plt.legend()
plt.xlabel('日期')
plt.ylabel('最高温度(华氏度)')
plt.title('实际 vs 预测')
plt.show()