目录
题目概述
本文主要是对代码的解读,优化效果甚微。
这次的任务是使用 Transformer 进行深度学习。
具体思路和RNN的相同,从关键函数——训练函数 train 来理清代码的逻辑。
# 训练
def train():
## super param
N = 10 # 批次数量 int / int(len(dataset) * 1) # 或者你可以设置为数据集大小的一定比例,如 int(len(dataset) * 0.1)
INPUT_DIM = 292 # src length,即输入数据的维度。对于嵌入层来说,这是词汇表的大小或输入序列的长度
D_MODEL = 512 # 嵌入向量的维度。这是输入数据在嵌入后的表示大小,即模型中的特征维度
NUM_HEADS = 4 # 多头注意力机制中的头数。多头注意力机制可以捕捉到不同子空间的关系
FNN_DIM = 1024 # 前馈神经网络(FNN)层的隐藏层维度。它决定了前馈网络中的中间表示大小
NUM_LAYERS = 4 # Transformer 编码器的层数。每一层包含一个多头注意力子层和一个前馈神经网络子层
DROPOUT = 0.2 # Dropout 的比率,用于在训练过程中随机丢弃部分神经元,防止过拟合
CLIP = 1 # CLIP value,即梯度裁剪的阈值。用于防止梯度爆炸现象,确保梯度在反向传播过程中不会过大
N_EPOCHS = 40 # 训练的轮数
LR = 1e-4 # 学习率
start_time = time.time() # 开始计时
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# device = 'cpu'
data = read_data("../dataset/round1_train_data.csv")
dataset = ReactionDataset(data)
subset_indices = list(range(N))
subset_dataset = Subset(dataset, subset_indices)
train_loader = DataLoader(dataset, batch_size=128, shuffle=True, collate_fn=collate_fn)
model = TransformerEncoderModel(INPUT_DIM, D_MODEL, NUM_HEADS, FNN_DIM, NUM_LAYERS, DROPOUT)
model = model.to(device)
model.train()
optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=0.01)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10)
# 这里默认采用MSE损失函数
criterion = nn.MSELoss()
best_valid_loss = 10
for epoch in range(N_EPOCHS):
epoch_loss = 0
# adjust_learning_rate(optimizer, epoch, LR) # 动态调整学习率
for i, (src, y) in enumerate(train_loader):
src, y = src.to(device), y.to(device)
optimizer.zero_grad()
output = model(src)
loss = criterion(output, y)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP)
optimizer.step()
epoch_loss += loss.detach().item()
if i % 50 == 0:
print(f'Step: {i} | Train Loss: {epoch_loss:.4f}')
loss_in_a_epoch = epoch_loss / len(train_loader)
scheduler.step(loss_in_a_epoch)
print(f'Epoch: {epoch+1:02} | Train Loss: {loss_in_a_epoch:.3f}')
if loss_in_a_epoch < best_valid_loss:
best_valid_loss = loss_in_a_epoch
# 在训练循环结束后保存模型
torch.save(model.state_dict(), '../model/transformer.pth')
end_time = time.time() # 结束计时
# 计算并打印运行时间
elapsed_time_minute = (end_time - start_time)/60
print(f"Total running time: {elapsed_time_minute:.2f} minutes")
可以看到,整体的思路和 RNN 模型大同小异,区别主要在于模型的转变和相应参数的变化,更改为使用 TransformerEncoderModel 训练。
下面是一些功能函数/类的介绍。
-
adjust_learning_rate:动态调整学习率
# 每 3 个 epoch 将学习率衰减 10 倍,从而控制学习率的变化 def adjust_learning_rate(optimizer, epoch, start_lr): """Sets the learning rate to the initial LR decayed by 10 every 3 epochs""" # 根据当前的 epoch 计算新的学习率 lr = start_lr * (0.1 ** (epoch // 3)) # 遍历优化器的参数组,并将每个参数组的学习率设置为新计算的学习率 lr for param_group in optimizer.param_groups: param_group['lr'] = lr
- TransformerEncoderModel:transformer模型
# 模型 ''' 直接采用一个transformer encoder model就好了 ''' class TransformerEncoderModel(nn.Module): # 初始化模型 def __init__(self, input_dim, d_model, num_heads, fnn_dim, num_layers, dropout): super().__init__() # 将输入的词嵌入到 d_model 维的向量空间 self.embedding = nn.Embedding(input_dim, d_model) # Layer Normalization 层,标准化嵌入向量 self.layerNorm = nn.LayerNorm(d_model) # 单个 Transformer 编码器层,包括多头注意力机制和前馈神经网络 self.encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=num_heads, dim_feedforward=fnn_dim, dropout=dropout, batch_first=True, norm_first=True # pre-layernorm ) # 堆叠多个 Transformer 编码器层 self.transformer_encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=num_layers, norm=self.layerNorm) # Dropout 层,防止过拟合 self.dropout = nn.Dropout(dropout) # 多层感知机(MLP),用于将编码器的输出变换为最终的输出 self.lc = nn.Sequential(nn.Linear(d_model, 256), nn.Sigmoid(), nn.Linear(256, 96), nn.Sigmoid(), nn.Linear(96, 1)) # 前向传播,主要作用是通过模型计算输入数据的预测结果,包括接收输入数据、嵌入层处理、通过编码器层、生成输出几个步骤 def forward(self, src): # src shape: [batch_size, src_len] # 对嵌入向量进行 Dropout embedded = self.dropout(self.embedding(src)) # embedded shape: [batch_size, src_len, d_model] # 通过 Transformer 编码器层处理嵌入向量 outputs = self.transformer_encoder(embedded) # outputs shape: [batch_size, src_len, d_model] # fisrt # 取出每个序列的第一个标记(通常是特殊的分类标记)的输出 z = outputs[:,0,:] # z = torch.sum(outputs, dim=1) # print(z) # z shape: [bs, d_model] # 通过全连接层(MLP)处理第一个标记的输出,得到最终的标量输出 outputs = self.lc(z) # print(outputs) # outputs shape: [bs, 1] # 返回形状为 [batch_size] 的输出 return outputs.squeeze(-1)
至于其他函数,大体可以参考 task2 文章中的介绍。
主要思路
其实优化的主要思路和 RNN 也是类似的,包括调参、修改模型。在进行了一系列调整之后,模型只有0.15左右的表现,并不理想。
总结
第三期夏令营的必做任务基本已经完成,最好的成绩还是 task1 的0.35(后面又有一些优化),排名也就是几十名吧,成绩不算太好,尤其对于深度学习的优化效果不佳。作为软件专业的学生,人工智能在之后都可能没有机会专门学习了,但是我很乐意将它作为自己的兴趣,继续探究下去。