最近参加了一次kaggle竞赛Jigsaw Unintended Bias in Toxicity Classification,经过一个多月的努力探索,从5月20日左右到6月26日提交最终的两个kernel,在public dataset上最终排名为4%(115/3167),说实话以前也并没有怎么接触过NLP方面的东西,对深度学习的理解也不是特别深刻。
BERT是目前非常火的NLP模型,采用两段式的训练方式,分为pretrain和fine-tune两部分,pretrain部分由谷歌在TPU集群上训练完成,并给出部分模型供免费下载使用。其中包括’cased_l-24_h-1024_a-16’, ‘chinese_l-12_h-768_a-12’, ‘uncased_l-12_h-768_a-12’, ‘uncased_l-24_h-1024_a-16’, ‘multi_cased_l-12_h-768_a-12’, ‘cased_l-12_h-768_a-12’
github上的下载地址为
https://github.com/google-research/bert
在本次比赛中主要采用的是BERT-Base,uncased的这个model,训练全数据180万左右的样本,样本长度设为220,在四卡Titan的GPU卡上训练的时间接近6小时,BERT-Large,uncased的训练时间大概是33小时左右。uncased和cased的区别在于uncased将全部样本变为小写,而cased则要区分大小写,将cased和uncased的模型训练结果进行融合也会有一定程度的提升。
比赛的主要代码是在一个以色列大佬的kernrl上进行修改,比赛结束时已经被fork了将近1595,代码地址
https://www.kaggle.com/yuval6967/toxic-bert-plain-vanila,非常感谢这位无私的大佬
def convert_lines(example, max_seq_length,tokenizer):
max_seq_length -=2
all_tokens = []
longer = 0
for text in tqdm_notebook(example):
tokens_a = tokenizer.tokenize(text)
if len(tokens_a)>max_seq_length:
tokens_a = tokens_a[:max_seq_length]
longer += 1
one_token = tokenizer.convert_tokens_to_ids(["[CLS]"]+tokens_a+["[SEP]"])+[0] * (max_seq_length - len(tokens_a))
all_tokens.append(one_token)
print(longer)
return np.array(all_tokens)
bert_config = BertConfig('../input/bert-pretrained-models/uncased_l-12_h-768_a-12/uncased_L-12_H-768_A-12/'+'bert_config.json')
tokenizer = BertTokenizer.from_pretrained(BERT_MODEL_PATH, cache_dir=None,do_lower_case=True)
train_df = pd.read_csv(os.path.join(Data_dir,"train.csv")).sample(num_to_load+valid_size,random_state=SEED)
train_df['comment_text'] = train_df['comment_text'].astype(str)
sequences = convert_lines(train_df["comment_text"].fillna("DUMMY_VALUE"),MAX_SEQUENCE_LENGTH,tokenizer)
#此处可以对其进行保存,下次可以直接调用npy文件
#np.save('sequences.npy',sequences)
#sequences = np.load('../wql/wql_base_sequences.npy')
通过uncased_L-12_H-768_A-12文件下的bert_model.ckpt和bert_config.json文件,在working目录下生成pytorch_model.bin文件,并copy其中的bert_config.json到working目录下
BERT_MODEL_PATH = '../input/bert-pretrained-models/uncased_l-12_h-768_a-12/uncased_L-12_H-768_A-12/'
convert_tf_checkpoint_to_pytorch.convert_tf_checkpoint_to_pytorch(
BERT_MODEL_PATH + 'bert_model.ckpt',
BERT_MODEL_PATH + 'bert_config.json',
WORK_DIR + 'pytorch_model.bin')
shutil.copyfile(BERT_MODEL_PATH + 'bert_config.json', WORK_DIR + 'bert_config.json')
train_dataset = torch.utils.data.TensorDataset(torch.tensor(X,dtype=torch.long), torch.tensor(y,dtype=torch.float))
#实例化模型,确保working目录下有bert_config.json文件和pytorch_model.bin文件,
model = BertForSequenceClassification.from_pretrained('./working/',cache_dir=None,num_labels=1)
#四卡并行计算的代码
devices = [0,1,2,3]
if len(devices) > 1:
print('use multi gpus')
model = nn.DataParallel(model, device_ids=devices)
model = model.cuda(devices[0])
#定义权重衰减的参数
no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
{'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
{'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
]
model=model.train()
lr=2e-5
#若batch_size大可能导致cuda memory out的问题,可以通过减低batchsize来解决,在训练large时,batchsize设过4
batch_size = 100
#用于参数更新的加速,设为2,参数更新的次数为1的1/2
accumulation_steps=2
save_steps = 1000
checkpoint = None
# 全数据训练的epoch次数
EPOCHS = 1
num_train_optimization_steps = int(EPOCHS*len(train) / batch_size / accumulation_steps)
optimizer = BertAdam(optimizer_grouped_parameters,
lr=lr, #在epoch2时可适当降低lr,例如取其1/2
warmup=0.05, #当lr下降的时候,也可以降低warmup,例如epoch2时设为0
t_total=num_train_optimization_steps)
tq = tqdm_notebook(range(EPOCHS))
for epoch in tq:
#将avg_loss和avg_accuracy写入txt文件
file_name = 'loss_log_' + 'epoch' + str(epoch) + '.txt'
file = open(file_name, 'w', encoding='utf-8')
train_loader = torch.utils.data.DataLoader(train, batch_size=batch_size, shuffle=True)
avg_loss = 0.
avg_accuracy = 0.
optimizer.zero_grad()
for (x_batch, y_batch) in tqdm(train_loader):
y_pred = model(x_batch.cuda(), attention_mask=(x_batch>0).cuda(), labels=None)
loss = nn.随意一个loss-function()(y_pred, y_batch.cuda())
loss.backward()
optimizer.step() # Now we can do an optimizer step
optimizer.zero_grad()
avg_loss += loss.item() / len(train_loader)
avg_accuracy += torch.mean(((torch.sigmoid(y_pred[:,0])>0.5) == (y_batch[:,0]>0.5).cuda()).to(torch.float) ).item()/len(train_loader)
i += 1
file.write('batch' + str(i) + '\t' + 'avg_loss' + '=' + str(avg_loss) + '\t' + 'avg_accuracy' + '=' + str(avg_accuracy) + '\n')
file.close()
file_path = output_model_file + str(epoch) +'.bin'
#保存bert model用于迁移学习或测试
torch.save(model.state_dict(), file_path)
print(f'loss:{avg_loss} accuracy:{avg_accuracy}')
就我目前对bert的理解和在比赛中调参的过程来浅谈一点经验,大佬贴出了bert模型的模板,几乎所有选手都采用这位大佬的模型作为原本进行改写,有几个重要点可以进行改写来提高预测的准确率
首先是loss-function,这决定了这次比赛的成败,如果没法写出一个适合比赛的lossfunction,那么比赛最终成绩也就是可以fork到的最高分。虽然大佬在bert fine-tune中只给出了F.binary_cross_entropy_with_logits,但是在其他LSTM的kernel上却给出了下面这样比较优秀的loss
def custom_loss(data, targets):
bce_loss_1 = nn.BCEWithLogitsLoss(weight=targets[:,1:2])(data[:,:1],targets[:,:1])
bce_loss_2 = nn.BCEWithLogitsLoss()(data[:,2:],targets[:,2:])
return (bce_loss_1 * loss_weight) + bce_loss_2
可以通过一定的补充,应用到bert模型中去,仅仅是这样一步就使得bert的预测准确率提高了很多
在对全样本数据进行fine-tune的过程中发现,训练到第三个epoch的时候,bert模型就发生了过拟合,通过小样本数据多次进行lr的调整之后也得到同样的结果,因此epoch的数量设置为2,比赛结束后一些高分单个bert的epoch数量也确实是2。
在我看来fine-tune过程中可以调节的参数应该有以下几个,lr、warmup、dropout、accumulation_steps、batch_size、weight_decay,MAX_SEQUENCE_LENGTH
hidden_dropout_prob: The dropout probabilitiy for all fully connected layers in the embeddings, encoder, and pooler.
attention_probs_dropout_prob: The dropout ratio for the attention probabilities.
accumulation_steps减小从实践效果来说可以略微提升训练效果。
weight_decay 调整之后并没有收到准确率提升的效果
lr的话,第一个epoch为2e-5,第二个epoch为1e-5是相对较好的lr参数
batch_size对于结果影响并不大,越大的batchsize计算速度越快,但是对于gpu的显存要求更高
MAX_SEQUENCE_LENGTH 规定了sequence的长度,长度越长对显存要求也越高
warmup 一个高分的bert模型中,epoch0 lr为2e-5,warmup为0.05,epoch1 lr为1e-5,warmup为0,对结果有一定的提升
给出一高分的bert模型地址 https://www.kaggle.com/hanyaopeng/single-bert-base-with-0-94376
其实我对bert理解也只停留在模型的应用上,如有了解的大佬看到觉得不对的地方,希望指出错误。