• 2022搜狐校园NLP算法大赛情感分析第一名方案理解和复现


    目录

    一、比赛和方案理解

    baseline的缺陷

    第一名的方案

    数据维度变化

    二、代码实现

    第一名代码

    swa——平均权重

    baseline代码

    三、效果展示

    第一名的方案:

    a、adamW + swa

    b、sgd + swa 

    baseline的方案


            在知乎上看到2022搜狐校园NLP算法大赛情感分析第一名方案分享分享,觉得方案非常简单优雅,同时又有点prompt learning的意味在里面(严格来说不是prompt learning),并且效果非常好。虽然在他们的方案分享中也给出了比较详细的思路和基于pytorch-lightning的代码,但是有一些细节的地方还不够清楚,同时代码也不太容易理解,因此在博客中做更加清晰的说明和分享更加简洁(更好理解基于torch)的代码。

    一、比赛和方案理解

            这次比赛的任务是——面向实体对象的文本描述情感极性及色彩强度分析。情感极性和强度分为五种情况:极正向、正向、中立、负向、极负向。选手需要针对给定的每一个实体对象,从文本描述的角度,分析出对该实体的情感极性和强度。

    数据如下:

    {"id": 7410, "content": "如此战斗力惊人的篮网,球迷和专家对他的季后赛期待值能不高吗?因此整个赛季,大家的预测还是有道理的,今年的季后赛东部分区决赛应该还是去年的老样式,篮网和雄鹿估计是还是如约相见。今天的篮网三巨头其实还不是究极体,但也轻松“庖丁解牛”,公牛的路应该还是很漫长的,NBA终究还是那个超级巨星说话的舞台!", "entity": {"篮网": 1, "季后赛": 0}}
    
    {"id": 88679, "content": "2014.09 海南省委常委、儋州市委书记,兼洋浦经济开发区工委副书记2014.10 海南省委常委、三亚市委书记2016.11 海南省委常委、海口市委书记2019年9月被查。", "entity": {"市委书记": 0, "海南省委": 0}}

    针对上面数据中的content文本和给出的entity,分析出它们分别在content中包含的情感色彩。很明显这是一个分类任务,我当时看见这个赛题的时候,头脑中闪现出的解决方案就是和他们给出的baseline一模一样:

    [CLS]content[SEP]entity_0[SEP]

    [CLS]content[SEP]entity_1[SEP]

    [CLS]content[SEP]entity_2[SEP]

    ......

    [CLS]content[SEP]entity_n[SEP]

    按照上面把content和每个entity拼接起来后,送入bert模型提取句向量,然后过分类器,这样就完成了这个任务,这个方案在比赛中也有人使用据说效果不是很理想。下面来看看比赛第一名的方案:

    baseline的缺陷

    如下图(引用比赛作者方案分享中的图)

     因为每条数据的实体数据不相等,所以如同baseline那样的拼接方案,会导致模型见到content文本次数不一样,对最终的效果可能会有影响;同时把每一条数据复制了entity数量次,导致训练数据过多,效率比较低下。还有一个问题就是,模型得到的句向量的选择也会有一定的误差,baseline的方案中最后要么去cls或者所有token的embedding做meanPooling,这样也会对最后的结果产生一定的影响;最后就是那每个实体单独拼接,感觉有点弱化了每个实体间的联系,对最后的结果会产生一定的影响。

    第一名的方案

    如上图(引用比赛作者方案分享中的图),把每条数据中的实体用[MASK]拼接起来,然后和content文本使用[SEP]拼接起来,这样就可以高效的在一条数据中构建一个分类任务,而不需要如果baseline那样对每一条数据重复多次。同时这里也避免了最后句向量的取舍问题,直接把[MASK]处对应的embedding作为每个实体情感的分类embedding。这个方案中[MASK]的引入,也有一点prompt learning的意味在里面,效果上作者说比较好。另一方面,它又不是严格的prompt learning,它不需要预测出[Mask]处具体的token是什么,然后做类别映射,也就是不需要做Prompt 答案空间映射(Verbalizer)的构造,只是做了一个Prompt 模版(Template)的构造。

    总体行来说,这个方案确实比较优雅,当然效果也比较好,让人一看就有点耳目一新的感觉。当然看论文(prompt learning)比较多的话,应该也能想到类似的方案。代码上实现的一些细节——矩阵的维度变换,给一个更加清晰的说明,理解整个方案就更加的容易了。

    数据维度变化

    一个batch的数据

    [CLS]content_0[SEP]entity_0_0[MASK]entity_0_1[MASK]entity_0_2[MASK][SEP]

    [CLS]content_1[SEP]entity_1_0[MASK]entity_1_1[MASK][SEP]

    [CLS]content_2[SEP]entity_2_0[MASK][SEP]

    [CLS]content_3[SEP]entity_3_0[MASK]entity_3_1[MASK][SEP]

    [CLS]content_4[SEP]entity_4_0[MASK]entity_4_1[MASK]entity_4_2[MASK][SEP]

    ......

    [CLS]content_(batch_size-1)[SEP]entity_(batch_size-1)_0[MASK]entity_(batch_size-1)_1[MASK][SEP]

     经过tokenizer后,就把token映射到词典对应的id上,需要记录每条数据的input_ids、attention_mask、mask_tokens、entity_count、label,对应的维度变化如下

    input_ids:[batch,seq_length]

    [

    [101,******,102,**,103,**,103,102,,0,0,0,0,0],

    [101,******,102,**,103,**,103,102],

    [101,******,102,**,103,102,0,0,0,0,0],

    ......

    [101,******,102,**,103,**,103,**,103,**,103,0,0]

    ]

    attention_mask:[batch,seq_length]

    [

    [1,******,1,**,1,**,1,1,,0,0,0,0,0],

    [1,******,1,**,1,**,1,1],

    [1,******,1,**,1,1,0,0,0,0,0],

    ......

    [1,******,1,**,1,**,1,**,1,**,1,0,0]

    ]

    mask_tokens:[batch,seq_length]

    [

    [0,******,0,**,1,**,1,0,,0,0,0,0,0],

    [0,******,0,**,1,**,1,0],

    [0,******,0,**,1,0,0,0,0,0,0],

    ......

    [0,******,0,**,1,**,1,**,1,**,1,0,0]

    ]

    label用list维护

    [

    [-2,2],

    [1,2],

    [-2],

    ......

    [2,-2,0,-1]

    ]

    如果batch内的实体为m个那么label的矩阵就是[m]

    [-2,2,1,2,......,2,-2,0,-1]

    input_ids+attention_mask经过bert后得到的结果:

    # m表示batch内有m个实体
    is_masked = inputs['is_masked'].bool()
    inputs = {k: v for k, v in inputs.items() if k in ["input_ids", "attention_mask"]}
    outputs = self.bert(**inputs,return_dict=True, output_hidden_states=True)
    # [batch, seq_length, 768]
    outputs = outputs.last_hidden_state
    # [m,768]
    masked_outputs = outputs[is_masked]
    # [m,5]
    logits = self.classifier(masked_outputs)

    二、代码实现

    第一名代码

    作者给出了基于pytorch-lightning的代码,我认为封装的比较高了,不太容易理解,在此基础上,我实现了一版基于torch的代码:

    模型代码

    1. from transformers import BertPreTrainedModel,BertModel
    2. import torch.nn as nn
    3. class SentiClassifyBertPrompt(BertPreTrainedModel):
    4. def __init__(self,config):
    5. super(SentiClassifyBertPrompt,self).__init__(config)
    6. self.bert = BertModel(config=config)
    7. self.classifier = nn.Sequential(
    8. nn.Linear(config.hidden_size, config.hidden_size),
    9. nn.LayerNorm(config.hidden_size),
    10. nn.LeakyReLU(),
    11. nn.Dropout(p=config.dropout),
    12. nn.Linear(config.hidden_size, config.output_dim),
    13. )
    14. def forward(self,inputs):
    15. # m表示batch内有m个实体
    16. is_masked = inputs['is_masked'].bool()
    17. inputs = {k: v for k, v in inputs.items() if k in ["input_ids", "attention_mask"]}
    18. outputs = self.bert(**inputs,return_dict=True, output_hidden_states=True)
    19. # [batch, seq_length, 768]
    20. outputs = outputs.last_hidden_state
    21. # [m,768]
    22. masked_outputs = outputs[is_masked]
    23. # [m,5]
    24. logits = self.classifier(masked_outputs)
    25. return logits

    数据加载代码

    1. import torch
    2. from torch.utils.data import Dataset
    3. from tqdm import tqdm
    4. import json
    5. class DataReader(Dataset):
    6. def __init__(self,file_path,tokenizer,max_langth):
    7. self.file_path = file_path
    8. self.tokenizer = tokenizer
    9. self.max_length = max_langth
    10. self.data_list = self.texts_tokeniztion()
    11. self.allLength = len(self.data_list)
    12. def texts_tokeniztion(self):
    13. with open(self.file_path,'r',encoding='utf-8') as f:
    14. lines = f.readlines()
    15. res = []
    16. for line in tqdm(lines,desc='texts tokenization'):
    17. line_dic = json.loads(line.strip('\n'))
    18. content = line_dic['content']
    19. entity = line_dic['entity']
    20. prompt_length = 0
    21. prompts = ""
    22. label = []
    23. en_count = len(entity)
    24. for k,v in entity.items():
    25. prompt_length += len(k) + 1
    26. #标签化为 0-4的整数
    27. label.append(v+2)
    28. prompts += k +"[MASK]"
    29. #直接最大长度拼接
    30. content = content[0:self.max_length-prompt_length-1-10]
    31. text = content + "[SEP]" + prompts
    32. input_ids,attention_mask,masks = self.text2ids(text)
    33. input_ids = torch.tensor(input_ids,dtype=torch.long)
    34. attention_mask = torch.tensor(attention_mask,dtype=torch.long)
    35. masks = torch.tensor(masks, dtype=torch.long)
    36. #记录每条数据有多少个实体,方便推理的时候batch推理
    37. en_count = torch.tensor(en_count,dtype=torch.long)
    38. temp = []
    39. temp.append(input_ids)
    40. temp.append(attention_mask)
    41. temp.append(masks)
    42. temp.append(label)
    43. temp.append(en_count)
    44. res.append(temp)
    45. return res
    46. def text2ids(self,text):
    47. inputs = self.tokenizer(text)
    48. input_ids = inputs['input_ids']
    49. attention_mask = inputs['attention_mask']
    50. masks = [ int(id==self.tokenizer.mask_token_id) for id in input_ids]
    51. return input_ids, attention_mask, masks
    52. def __getitem__(self, item):
    53. input_ids = self.data_list[item][0]
    54. attention_mask = self.data_list[item][1]
    55. masks = self.data_list[item][2]
    56. label = self.data_list[item][3]
    57. en_count = self.data_list[item][4]
    58. return input_ids, attention_mask, masks, label, en_count
    59. def __len__(self):
    60. return self.allLength

    模型训练代码

    1. from data_reader.reader import DataReader
    2. import torch
    3. from torch.utils.data import DataLoader
    4. from transformers import BertTokenizer,BertConfig
    5. from torch.optim import AdamW
    6. from model import SentiClassifyBertPrompt
    7. from torch.optim.swa_utils import AveragedModel, SWALR
    8. from torch.nn.utils.rnn import pad_sequence
    9. from log.log import Logger
    10. from tqdm import tqdm
    11. import torch.nn.functional as F
    12. import os
    13. os.environ['CUDA_VISIBLE_DEVICES'] = "1"
    14. def collate_fn(batch):
    15. input_ids, attention_mask, masks, label, en_count = zip(*batch)
    16. input_ids = pad_sequence(input_ids,batch_first=True,padding_value=0)
    17. attention_mask = pad_sequence(attention_mask,batch_first=True,padding_value=0)
    18. masks = pad_sequence(masks, batch_first=True, padding_value=0)
    19. labels = []
    20. for ele in label:
    21. labels.extend(ele)
    22. labels = torch.tensor(labels,dtype=torch.long)
    23. en_count = torch.stack(en_count,dim=0)
    24. return input_ids, attention_mask, masks, labels, en_count
    25. def dev_validation(dev_loader,device,model):
    26. total_correct = 0
    27. total = 0
    28. model.eval()
    29. with torch.no_grad():
    30. for step, batch in enumerate(tqdm(dev_loader, desc="dev_validation")):
    31. batch = [t.to(device) for t in batch]
    32. inputs = {"input_ids": batch[0], "attention_mask": batch[1], "is_masked": batch[2]}
    33. label = batch[3]
    34. logits = model(inputs)
    35. preds = torch.argmax(logits,dim=1)
    36. correct = (preds==label).sum()
    37. total_correct += correct
    38. total += label.size()[0]
    39. acc = total_correct/total
    40. return acc
    41. def set_seed(seed = 1):
    42. torch.cuda.manual_seed_all(seed)
    43. torch.manual_seed(seed)
    44. torch.backends.cudnn.deterministic = True
    45. if __name__ == '__main__':
    46. set_seed()
    47. log_level = 10
    48. log_path = "logs/train_bert_prompt_AdamW_swa.log"
    49. logger = Logger(log_name='train_bert_prompt', log_level=log_level, log_path=log_path).logger
    50. pretrain_model_path = "./pretrained_models/chinese-bert-wwm-ext"
    51. batch_size = 16
    52. epochs = 10
    53. tokenizer = BertTokenizer.from_pretrained(pretrain_model_path)
    54. config = BertConfig.from_pretrained(pretrain_model_path)
    55. config.dropout = 0.2
    56. config.output_dim = 5
    57. config.batch_size = batch_size
    58. device = "cuda" if torch.cuda.is_available() else "cpu"
    59. model = SentiClassifyBertPrompt.from_pretrained(config=config,pretrained_model_name_or_path = pretrain_model_path)
    60. model.to(device)
    61. optimizer = AdamW(params=model.parameters(),lr=1e-6)
    62. # 随机权重平均SWA,实现更好的泛化
    63. swa_model = AveragedModel(model=model,device=device)
    64. # SWA调整学习率
    65. swa_scheduler = SWALR(optimizer, swa_lr=1e-6)
    66. train_dataset = DataReader(tokenizer=tokenizer, max_langth=512, file_path='./data/train_split.txt')
    67. train_loader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=batch_size, collate_fn=collate_fn)
    68. dev_dataset = DataReader(tokenizer=tokenizer, max_langth=512, file_path='./data/dev_split.txt')
    69. dev_loader = DataLoader(dataset=dev_dataset, shuffle=True, batch_size=batch_size, collate_fn=collate_fn)
    70. for epoch in range(epochs):
    71. model.train()
    72. for step,batch in enumerate(tqdm(train_loader,desc="training")):
    73. batch = [ t.to(device) for t in batch]
    74. inputs = {"input_ids":batch[0],"attention_mask":batch[1],"is_masked":batch[2]}
    75. label = batch[3]
    76. logits = model(inputs)
    77. loss = F.cross_entropy(logits,label)
    78. loss.backward()
    79. optimizer.step()
    80. optimizer.zero_grad()
    81. swa_model.update_parameters(model)
    82. swa_scheduler.step()
    83. acc = dev_validation(dev_loader,device,model)
    84. swa_acc = dev_validation(dev_loader,device,swa_model)
    85. logger.info('Epoch %d acc is %.6f'%(epoch,acc))
    86. logger.info('Epoch %d swa_acc is %.6f' % (epoch, swa_acc))

    工程目录如下

    swa——平均权重

    以上训练代码中有一个训练的trick——swa——平均权重是我之前没有见过和使用过的,有必要提一提,其核心思想,是训练的过程中最后保留的模型,并不是验证集上效果最好的模型,而是所有epoch训练后的模型的权重平均值,这样训练出来的模型最具更好的泛化能力和最优的效果。我们也不用自己去实现怎么计算权重的平均,torch也已经有了规范化的流程和代码了,具体的效果怎么样,需要实验去验证(有人说过sgd+swa才有效)。

    1. ......
    2. optimizer = AdamW(params=model.parameters(),lr=1e-6)
    3. # 随机权重平均SWA,实现更好的泛化
    4. swa_model = AveragedModel(model=model,device=device)
    5. # SWA调整学习率
    6. swa_scheduler = SWALR(optimizer, swa_lr=1e-6)
    7. for epoch in range(epochs):
    8. model.train()
    9. for step,batch in enumerate(tqdm(train_loader,desc="training")):
    10. ......
    11. #正常训练
    12. logits = model(inputs)
    13. loss = F.cross_entropy(logits,label)
    14. loss.backward()
    15. optimizer.step()
    16. optimizer.zero_grad()
    17. #每个epoch后swa_model模型更新参数
    18. swa_model.update_parameters(model)
    19. #调整学习率
    20. swa_scheduler.step()

    baseline代码

    为了简单验证一下效果怎么样,我也把baseline的方案跑出来了,代码如下:

    1. import torch
    2. from torch.utils.data import Dataset
    3. from tqdm import tqdm
    4. import json
    5. from transformers import BertPreTrainedModel,BertModel
    6. import torch.nn as nn
    7. class SentiClassifyBert(BertPreTrainedModel):
    8. def __init__(self,config):
    9. super(SentiClassifyBert,self).__init__(config)
    10. self.bert = BertModel(config=config)
    11. self.classifier = nn.Sequential(
    12. nn.Linear(config.hidden_size, config.hidden_size),
    13. nn.LayerNorm(config.hidden_size),
    14. nn.LeakyReLU(),
    15. nn.Dropout(p=config.dropout),
    16. nn.Linear(config.hidden_size, config.output_dim),
    17. )
    18. def forward(self,inputs):
    19. inputs = {k: v for k, v in inputs.items() if k in ["input_ids", "attention_mask"]}
    20. outputs = self.bert(**inputs,return_dict=True, output_hidden_states=True)
    21. outputs = outputs.last_hidden_state
    22. cls_output = outputs[:,0:1,:].squeeze()
    23. logits = self.classifier(cls_output)
    24. return logits
    25. class DataReader(Dataset):
    26. def __init__(self,file_path,tokenizer,max_langth):
    27. self.file_path = file_path
    28. self.tokenizer = tokenizer
    29. self.max_length = max_langth
    30. self.data_list = self.texts_tokeniztion()
    31. self.allLength = len(self.data_list)
    32. def texts_tokeniztion(self):
    33. with open(self.file_path,'r',encoding='utf-8') as f:
    34. lines = f.readlines()
    35. res = []
    36. for line in tqdm(lines,desc='texts tokenization'):
    37. line_dic = json.loads(line.strip('\n'))
    38. content = line_dic['content']
    39. entity = line_dic['entity']
    40. for k,v in entity.items():
    41. # 直接最大长度拼接
    42. content = content[0:self.max_length - len(k) - 1 - 10]
    43. text = content + "[SEP]" + k
    44. input_ids, attention_mask, masks = self.text2ids(text)
    45. input_ids = torch.tensor(input_ids, dtype=torch.long)
    46. attention_mask = torch.tensor(attention_mask, dtype=torch.long)
    47. label = torch.tensor(v+2, dtype=torch.long)
    48. temp = []
    49. temp.append(input_ids)
    50. temp.append(attention_mask)
    51. temp.append(label)
    52. res.append(temp)
    53. return res
    54. def text2ids(self,text):
    55. inputs = self.tokenizer(text)
    56. input_ids = inputs['input_ids']
    57. attention_mask = inputs['attention_mask']
    58. masks = [ int(id==self.tokenizer.mask_token_id) for id in input_ids]
    59. return input_ids, attention_mask, masks
    60. def __getitem__(self, item):
    61. input_ids = self.data_list[item][0]
    62. attention_mask = self.data_list[item][1]
    63. label = self.data_list[item][2]
    64. return input_ids, attention_mask, label
    65. from data_reader.reader import DataReader
    66. import torch
    67. from torch.utils.data import DataLoader
    68. from transformers import BertTokenizer,BertConfig
    69. from torch.optim import AdamW,SGD
    70. from model import SentiClassifyBert
    71. from torch.optim.swa_utils import AveragedModel, SWALR
    72. from torch.nn.utils.rnn import pad_sequence
    73. from log.log import Logger
    74. from tqdm import tqdm
    75. import torch.nn.functional as F
    76. import os
    77. os.environ['CUDA_VISIBLE_DEVICES'] = "0"
    78. def collate_fn(batch):
    79. input_ids, attention_mask, label = zip(*batch)
    80. input_ids = pad_sequence(input_ids,batch_first=True,padding_value=0)
    81. attention_mask = pad_sequence(attention_mask,batch_first=True,padding_value=0)
    82. label = torch.stack(label,dim=0)
    83. return input_ids, attention_mask, label
    84. def dev_validation(dev_loader,device,model):
    85. total_correct = 0
    86. total = 0
    87. model.eval()
    88. with torch.no_grad():
    89. for step, batch in enumerate(tqdm(dev_loader, desc="dev_validation")):
    90. batch = [t.to(device) for t in batch]
    91. inputs = {"input_ids": batch[0], "attention_mask": batch[1]}
    92. label = batch[2]
    93. logits = model(inputs)
    94. preds = torch.argmax(logits,dim=1)
    95. correct = (preds==label).sum()
    96. total_correct += correct
    97. total += label.size()[0]
    98. acc = total_correct/total
    99. return acc
    100. def set_seed(seed = 1):
    101. torch.cuda.manual_seed_all(seed)
    102. torch.manual_seed(seed)
    103. torch.backends.cudnn.deterministic = True
    104. if __name__ == '__main__':
    105. set_seed()
    106. log_level = 10
    107. log_path = "logs/train_bert_adamW_swa_20220718.log"
    108. logger = Logger(log_name='train_bert', log_level=log_level, log_path=log_path).logger
    109. pretrain_model_path = "./pretrained_models/chinese-bert-wwm-ext"
    110. batch_size = 16
    111. epochs = 20
    112. tokenizer = BertTokenizer.from_pretrained(pretrain_model_path)
    113. config = BertConfig.from_pretrained(pretrain_model_path)
    114. config.dropout = 0.2
    115. config.output_dim = 5
    116. config.batch_size = batch_size
    117. device = "cuda" if torch.cuda.is_available() else "cpu"
    118. model = SentiClassifyBert.from_pretrained(config=config,pretrained_model_name_or_path = pretrain_model_path)
    119. model.to(device)
    120. optimizer = AdamW(params=model.parameters(),lr=1e-6)
    121. # optimizer = SGD(params=model.parameters(), lr=1e-5,momentum=0.9)
    122. # 随机权重平均SWA,实现更好的泛化
    123. swa_model = AveragedModel(model=model,device=device)
    124. # SWA调整学习率
    125. swa_scheduler = SWALR(optimizer, swa_lr=1e-6)
    126. train_dataset = DataReader(tokenizer=tokenizer, max_langth=512, file_path='./data/train_split.txt')
    127. train_loader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=batch_size, collate_fn=collate_fn)
    128. dev_dataset = DataReader(tokenizer=tokenizer, max_langth=512, file_path='./data/dev_split.txt')
    129. dev_loader = DataLoader(dataset=dev_dataset, shuffle=True, batch_size=batch_size, collate_fn=collate_fn)
    130. for epoch in range(epochs):
    131. model.train()
    132. for step,batch in enumerate(tqdm(train_loader,desc="training")):
    133. batch = [ t.to(device) for t in batch]
    134. inputs = {"input_ids":batch[0],"attention_mask":batch[1]}
    135. label = batch[2]
    136. logits = model(inputs)
    137. loss = F.cross_entropy(logits,label)
    138. loss.backward()
    139. optimizer.step()
    140. optimizer.zero_grad()
    141. swa_model.update_parameters(model)
    142. swa_scheduler.step()
    143. acc = dev_validation(dev_loader,device,model)
    144. swa_acc = dev_validation(dev_loader,device,swa_model)
    145. logger.info('Epoch %d acc is %.6f'%(epoch,acc))
    146. logger.info('Epoch %d swa_acc is %.6f' % (epoch, swa_acc))

    训练中把约9W条数据的训练集切分出1W条数据作为验证集,使用chinese-bert-wwm-ext作为预训练模型,训练20个epochs;对比了SGD和AdamW优化器的效果;同时也对比了baseline和第一名方案的效果;当然swa的效果好不好不能给出一个结论,因为没有测试集。

    三、效果展示

    第一名的方案:

    a、adamW + swa

     验证集上的准确率使用AdamW优化器20个epochs内最高准确率是0.929579;swa则是0.928673——它在测试集上表现如何就不太清楚了了。

    b、sgd + swa 

    从准确率上来看sgd收敛的比较慢,再低19个epcoh准确率才到达最高值,而且准确率也没有AdamW高,才89.7,不过看来还没有完全收敛,继续训练还可以提升,不过要花很长时间;看来AdamW这种智能优化器还是比较适合我这种不太会调优化器参数的人呀。 

    baseline的方案

    对比来看baseline的效果差的有点多,第一名的方案确实有效,主要的还是有两点,一是没有重复拼接,造成数据分布的改变,同时模型可能对于学习实体直接的关系更加擅长;二是句向量的选择更加的合适,没有选取cls也没有选取meanPooling的embedding,而是选取[MASK]对应的embedding更加的精确,这种本质上是把prompt learning做了改变应用到这里来了,预训练和微调的gap更小了,提取到的embedding更加准确,所有效果才好。

    方案优雅,值得学习和借鉴!

    参考文章

    2022搜狐校园NLP算法大赛情感分析第一名方案分享

    2022搜狐校园 情感分析 算法大赛

  • 相关阅读:
    编辑距离算法
    我开源了一个加密算法仓库,支持18种算法!登录注册业务可用!
    海康威视-下载的录像视频浏览器播放问题
    openGauss学习笔记-84 openGauss 数据库管理-内存优化表MOT管理-内存表特性-MOT部署服务器优化:x86
    RabbitMQ:简单模式(Hello World)
    Linux 中使普通用户使用Sudo不需要输入密码
    华为云鲲鹏架构docker部署2048小游戏
    React+TypeScript 声明组件的属性和默认值
    什么款式的蓝牙耳机跑步不容易掉?推荐几款很不错的运动耳机
    SAP Client Copy 参数文件
  • 原文地址:https://blog.csdn.net/HUSTHY/article/details/125809156