Datawhale AI 夏令营——AI+物质科学 Task2 学习笔记
一、学习目标
目标
- 了解AI4Science相关知识
- 了解用深度学习的方法进行建模
期望结果
- 跑通baseline
- 在成功跑通baseline的基础上调节参数,寻找更优方案
二、学习到的新概念
AI4Science
将AI技术应用于物理学、化学、生物学、医学等领域的科学研究,利用AI技术对复杂系统进行建模以及预测结果可以加速科学研究中的数据分析并且优化实验过程。
深度学习
机器学习的一个分支,建立和训练复杂的神经网络模型来处理和分析数据。
相比传统机器学习的优点
-
特征工程
与机器学习的手动特征工程不同,深度学习可以自动学习数据中的特征并且提取这些特征。 -
模型容量
深度学习具有更大的模型容量可以处理更复杂且更大规模的数据集。
RNN——循环神经网络(在该Task中主要运用到)
用于处理序列数据,具有内部状态(记忆),允许网络在处理当前输入时,考虑到之前的输入。
- 结构
输入层:接收当前时间步的输入数据
隐藏层:计算当前时间步的隐藏状态,传递给下一个时间步。隐藏状态在时间步之间传递,带有记忆功能。
输出层:产生当前时间步的输出结果。 - 常见变体
长短期记忆网络(LSTM)、门控循环单元(GRU)、双向 RNN - 会遇到的问题
梯度消失问题:在反向传播过程,由梯度值逐渐变小而导致较早层的梯度更新更新缓慢,甚至停止,使得模型在长序列数据中难以捕捉长程依赖关系。
梯度爆炸问题:在反向传播过程中,梯度值迅速增大,导致权重更新过大,数值不稳定,训练无法正常进行。
三、详细笔记
定义RNN模型
因为之前没有接触过深度学习,所以在这一块的理解上有些困难。以下是我对baseline中定义模型部分的一些理解。
# 定义RNN模型
class RNNModel(nn.Module):
def __init__(self, num_embed, input_size, hidden_size, output_size, num_layers, dropout, device):
super(RNNModel, self).__init__()
self.embed = nn.Embedding(num_embed, input_size)
self.rnn = nn.RNN(input_size, hidden_size, num_layers=num_layers,
batch_first=True, dropout=dropout, bidirectional=True)
self.fc = nn.Sequential(nn.Linear(2 * num_layers * hidden_size, output_size),
nn.Sigmoid(),
nn.Linear(output_size, 1),
nn.Sigmoid())
def forward(self, x):
# x : [bs, seq_len]
x = self.embed(x)
# x : [bs, seq_len, input_size]
_, hn = self.rnn(x) # hn : [2*num_layers, bs, h_dim]
hn = hn.transpose(0,1)
z = hn.reshape(hn.shape[0], -1) # z shape: [bs, 2*num_layers*h_dim]
output = self.fc(z).squeeze(-1) # output shape: [bs, 1]
return output
定义rnn模型需要的参数
- num_embed:嵌入层的词汇量大小
- input_size:每个词嵌入向量维度
- hidden_size:隐藏层维度,该参数偏大时,模型可以捕捉更多复杂的模式和特征,但是计算开销和训练时间随之增加,且数据量不足时会有过拟合风险。
- output_size:全连接层的输出维度
- num_layers:模型层数,该参数越大模型能够捕捉更复杂的时间序列模式和长期依赖关系,但是训练时间和内存需求也会随之增加,还会有梯度消失或梯度爆炸问题。
- dropout:dropout率,显著减少模型过拟合
- device:模型所运行的设备
嵌入层
将整数索引映射到高维向量空间
self.embed = nn.Embedding(num_embed, input_size)
RNN层
设置batch_first=True
,输入和输出都为(batch_size, sequence_length, feature_dimension)的格式。
设置bidirectional=True
,使用双向RNN
全连接层
self.fc = nn.Sequential(nn.Linear(2 * num_layers * hidden_size, output_size),
nn.Sigmoid(),
nn.Linear(output_size, 1),
nn.Sigmoid())
将RNN输出维度(2 * num_layers * hidden_size)映射到output_size,第一个Sigmoid激活函数用于非线性变换,第二个线性层将output_size映射到1,第二个激活函数将输出限制到[0,1]。
前向传播
def forward(self, x):
# x : [bs, seq_len]
x = self.embed(x)
# x : [bs, seq_len, input_size]
_, hn = self.rnn(x) # hn : [2*num_layers, bs, h_dim]
hn = hn.transpose(0,1)
z = hn.reshape(hn.shape[0], -1) # z shape: [bs, 2*num_layers*h_dim]
output = self.fc(z).squeeze(-1) # output shape: [bs, 1]
return output
x是形状为[bs, seq_len]的输入张量,经过嵌入层后变为形状为[bs, seq_len, input_size]的嵌入向量,再经过RNN层得到最后一个时间步的隐藏状态,最后通过全连接层,得到最终输出。
寻找最优超参
和上一个Task一样,用初始参数跑完的分数非常低,根据上一次的经验用贝叶斯优化寻找最优超参。
def objective(trial):
hidden_size = trial.suggest_int('hidden_size', 512, 1000)
dropout = trial.suggest_float('dropout', 0.1, 0.5)
lr = trial.suggest_loguniform('lr', 1e-5, 1e-1)
num_layers = trial.suggest_int('num_layers', 10, 50)
N = 10 #int / int(len(dataset) * 1) # 或者你可以设置为数据集大小的一定比例,如 int(len(dataset) * 0.1)
NUM_EMBED = 294 # nn.Embedding()
INPUT_SIZE = 300 # src length
OUTPUT_SIZE = 512
CLIP = 1 # CLIP value
N_EPOCHS = 100
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)
test_data = read_data("../dataset/round1_test_data.csv", train=False)
test_dataset = ReactionDataset(test_data)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, collate_fn=collate_fn)
model = RNNModel(NUM_EMBED, INPUT_SIZE, hidden_size, OUTPUT_SIZE, num_layers, dropout, device).to(device)
model.train()
optimizer = optim.Adam(model.parameters(), lr=lr)
# criterion = nn.MSELoss() # MSE
criterion = nn.L1Loss() # MAE
best_loss = 10
for epoch in range(N_EPOCHS):
epoch_loss = 0
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.item()
loss_in_a_epoch = epoch_loss / len(train_loader)
print(f'Epoch: {epoch+1:02} | Train Loss: {loss_in_a_epoch:.3f}')
if loss_in_a_epoch < best_loss:
# 在训练循环结束后保存模型
torch.save(model.state_dict(), '../model/RNN.pth')
end_time = time.time() # 结束计时
# 计算并打印运行时间
elapsed_time_minute = (end_time - start_time)/60
print(f"Total running time: {elapsed_time_minute:.2f} minutes")
model.eval()
output_list = []
for i, (src, y) in enumerate(test_loader):
src, y = src.to(device), y.to(device)
with torch.no_grad():
output = model(src)
output_list += output.detach().tolist()
ans_str_lst = ['rxnid,Yield']
for idx,y in enumerate(output_list):
ans_str_lst.append(f'test{idx+1},{y:.4f}')
print("done!!!")
study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=50)
print("Best parameters:", study.best_params)
print("Best score:", study.best_value)
将训练函数与预测函数结合构造成贝叶斯优化的目标函数,最后输出最优参数以及最佳准确率。
四、总结
感觉这一次任务的难度提升了很多,对于rnn的学习扔不够透彻,还需要在这一方面强化