这一部分就是完成
然后利用gpu训练和计算不同任务的准确度。
利用预训练的BERT模型的嵌入,来解决三个不同的自然语言处理(NLP)任务:情感分析、释义检测(paraphrase detection)、和语义文本相似度(semantic textual similarity)。这个项目的目标是探索如何构建健壮的嵌入,使其能够在广泛的不同任务中表现良好
任务概述
-
情感分析:
- 使用 SST (Stanford Sentiment Treebank) 数据集 来评估模型在情感分析方面的表现。
- 这个任务涉及将文本分类为正面或负面情绪。
-
释义检测:
- 使用 Quora 数据集,该数据集包含404,298对问题,标注了这些问题对是否彼此释义。
- 这个任务的目的是识别两个句子是否表达相同的意思。
-
语义文本相似度:
- 使用 SemEval STS Benchmark 数据集,该数据集包含8,628对句子,这些句子按照从0(无关)到5(完全相同的意思)的标度进行相似性标注。
- 这个任务需要评估两个句子的语义相似度,并使用皮尔逊相关系数来评价预测相似度值与真实相似度值之间的相关性。
数据集和评估
-
Quora 数据集:
- 训练集:283,010个例子
- 开发集:40,429个例子
- 测试集:80,859个例子
- 评估标准:准确率
-
SemEval STS Benchmark 数据集:
- 训练集:6,040个例子
- 开发集:863个例子
- 测试集:1,725个例子
- 评估标准:皮尔逊相关系数
init:
class MultitaskBERT(nn.Module):
'''
This module should use BERT for 3 tasks:
- Sentiment classification (predict_sentiment)
- Paraphrase detection (predict_paraphrase)
- Semantic Textual Similarity (predict_similarity)
'''
def __init__(self, config):
super(MultitaskBERT, self).__init__()
self.bert = BertModel.from_pretrained('bert-base-uncased')
# Pretrain mode does not require updating BERT paramters.
for param in self.bert.parameters():
if config.option == 'pretrain':
param.requires_grad = False
elif config.option == 'finetune':
param.requires_grad = True
# You will want to add layers here to perform the downstream tasks.
### TODO
self.linear_sentiment = torch.nn.Linear(config.hidden_size, len(config.num_labels))
self.dropout = torch.nn.Dropout(config.hidden_dropout_prob)
#Works for paraphrase detection (double the hidden size since we cat two examples outputs, and out values either 0 or 1 on whether paraphrase)
self.linear_paraphrase = torch.nn.Linear(2 * config.hidden_size, 1)
#Works for similarity prediction detection (out values 0 to 5 on how similar)
self.linear_similarity = torch.nn.Linear(2 * config.hidden_size, 1)
#raise NotImplementedError
forward:
def forward(self, input_ids, attention_mask):
'Takes a batch of sentences and produces embeddings for them.'
# The final BERT embedding is the hidden state of [CLS] token (the first token)
# Here, you can start by just returning the embeddings straight from BERT.
# When thinking of improvements, you can later try modifying this
# (e.g., by adding other layers).
### TODO
minbert_out = self.bert(input_ids, attention_mask)['pooler_output']
return minbert_out
这部分很好理解,来自于bert.py的forward部分(具体见完成minibert的分析)
def predict_sentiment(self, input_ids, attention_mask):
这个部分是情感分类任务的编写,前向传播后,dropout减少过拟合,再直接用init里定义的关于情感分类的线性层
def predict_sentiment(self, input_ids, attention_mask):
'''Given a batch of sentences, outputs logits for classifying sentiment.
There are 5 sentiment classes:
(0 - negative, 1- somewhat negative, 2- neutral, 3- somewhat positive, 4- positive)
Thus, your output should contain 5 logits for each sentence.
'''
### TODO
#Initial baseline- just use existing BERT embeddings to attempt downstream tasks
minbert_out = self.forward(input_ids, attention_mask)
dropout_out = self.dropout(minbert_out)
logits = self.linear_sentiment(dropout_out)
return logits
def predict_paraphrase(self,
input_ids_1, attention_mask_1,
input_ids_2, attention_mask_2):
这部分逻辑一致,略微修改参数的序号标志,然后要用combined_minbert = torch.cat((dropout_out_1, dropout_out_2), dim=1)
:将两批句子的dropout后输出沿特征维度合并。这一操作将两个句子的表示拼接起来,为接下来的比较提供了一个统一的特征集。dim=1
:这指定了连接操作应该沿哪个维度进行。在PyTorch中,维度0是批处理维度(即不同的数据样本),而维度1是特征维度。设置 dim=1
意味着我们要沿特征维度将两个张量连接起来。连接操作后的结果 combined_minbert
是一个新的张量,它包含了两个输入张量的特征信息。
def predict_paraphrase(self,
input_ids_1, attention_mask_1,
input_ids_2, attention_mask_2):
'''Given a batch of pairs of sentences, outputs a single logit for predicting whether they are paraphrases.
Note that your output should be unnormalized (a logit); it will be passed to the sigmoid function
during evaluation.
'''
### TODO
minbert_out_1 = self.forward(input_ids_1, attention_mask_1)
dropout_out_1 = self.dropout(minbert_out_1)
minbert_out_2 = self.forward(input_ids_2, attention_mask_2)
dropout_out_2 = self.dropout(minbert_out_2)
combined_minbert= torch.cat((dropout_out_1, dropout_out_2), dim=1)
logits = self.linear_paraphrase(combined_minbert)
return logits
def predict_similarity(self,
input_ids_1, attention_mask_1,
input_ids_2, attention_mask_2):
逻辑和上一个函数一致,无论是释义检测还是语义相似度评估,处理流程都涉及将两个句子的表示合并为一个单一的表示,这可以通过拼接、相减、元素乘等方式实现。这种合并的表示随后用于预测句子之间的关系或相似度。对于这两个任务,输出是一个逻辑值,它在释义检测中表示是否释义,在相似度预测中表示相似程度。这种输出处理方式的相似性导致了架构上的一致性。
def predict_similarity(self,
input_ids_1, attention_mask_1,
input_ids_2, attention_mask_2):
'''Given a batch of pairs of sentences, outputs a single logit corresponding to how similar they are.
Note that your output should be unnormalized (a logit).
'''
### TODO
minbert_out_1 = self.forward(input_ids_1, attention_mask_1)
dropout_out_1 = self.dropout(minbert_out_1)
minbert_out_2 = self.forward(input_ids_2, attention_mask_2)
dropout_out_2 = self.dropout(minbert_out_2)
combined_minbert= torch.cat((dropout_out_1, dropout_out_2), dim=1)
logits = self.linear_similarity(combined_minbert)
return logits
最后运行python3 multitask_classifier.py,这将会按照默认参数执行三个任务的训练和准确度输出:
可以发现训练集的平均损失是0.967,训练集准确率为29.9%,开发集准确率为28.2%。
可见模型能够同时处理多个任务,但其性能还有提升的空间,后续会对单独的任务进行特别的优化来提高准确率。