目录

  • 相关链接
  • 1 引言
  • 2 NEZHA方案
    • 2.1 预训练
    • 2.2 微调
  • 3 Bert 方案
    • 3.1 预训练
    • 3.2 微调
  • 3 模型融合和TTA测试集数据增强
  • 4 总结和反思
  • 5 参考资料

相关链接

【2021 第五届“达观杯” 基于大规模预训练模型的风险事件标签识别】1 初赛Rank12的总结与分析
【2021 第五届“达观杯” 基于大规模预训练模型的风险事件标签识别】2 DPCNN、HAN、RCNN等传统深度学习方案
【2021 第五届“达观杯” 基于大规模预训练模型的风险事件标签识别】3 Bert和Nezha方案

1 引言

2 NEZHA方案

(1)代码结构

完整源码下载Github

├── Bert_pytorch # Bert 方案
│   ├── bert-base-chinese # 初始权重,下载地址https://huggingface.co/bert-base-chinese#
│   ├── bert_finetuning # Bert微调
│   │   ├── Config.py # Bert配置文件
│   │   ├── ensemble_10fold.py # 10折checkpoint融合
│   │   ├── ensemble_single.py #每种模型不划分验证集只生成的一个模型,用这些模型进行checkpoint融合
│   │   ├── generate_pseudo_label.py # 利用做高分模型 给无标注数据做伪标签
│   │   ├── main_bert_10fold.py # 划分10折的Bert,这种会存储10个模型,每一个fold一个模型
│   │   ├── main_bert_all.py # 不划分验证集的Bert,这种只会存储一个模型
│   │   ├── model.py # 17种魔改Bert,和其他网络的具体实现部分
│   │   ├── models
│   │   ├── NEZHA # 网络结构实现文件,来源于官网
│   │   │   ├── configuration_nezha.py
│   │   │   └── modeling_nezha.py
│   │   ├── predict.py # 用模型模型进行预测测试集
│   │   ├── predict_tta.py # 用模型进行预测测试集,并使用TTA 测试集增强
│   │   ├── stacking.py # Stacking集成方法
│   │   └── utils.py # 工具函数
│   ├── bert_model_1000 # 存储预训练模型,下载地址https://drive.google.com/file/d/1rpWe5ec_buORvu8-ezvvAk9jrUZkOsIr/view?usp=sharing
│   ├── Data_analysis.ipynb # 数据分析
│   ├── Generate_TTA.ipynb # 生成TTA测试集增强的文件
│   └── pretrain # Bert预训练
│       ├── bert_model
│       │   ├── vocab_100w.txt # 100W未标注数据语料的词典,有18544个词
│       │   ├── vocab_3462.txt # 整个训练集和测试集的词典,不包括未标注数据
│       │   └── vocab.txt
│       ├── NLP_Utils.py
│       ├── train_bert.py # Bert预训练主函数
│       └── transformers1.zip # transformes较高的版本
├── data
│   ├── datagrand_2021_test.csv # 测试集
│   └── datagrand_2021_train.csv # 训练集
├── Nezha_pytorch #NEZHA预训练方案
│   ├── finetuning #  Nezha微调
│   │   ├── Config.py 
│   │   ├── model.py #模型实现文件
│   │   ├── models
│   │   ├── NEZHA
│   │   │   ├── configuration_nezha.py
│   │   │   └── modeling_nezha.py
│   │   ├── NEZHA_main.py #微调主函数
│   │   ├── predict.py # 10折模型预测
│   │   ├── submit
│   │   │   └── submit_bert_5epoch-10fold-first.csv
│   │   └── utils.py
│   ├── nezha-cn-base #nezha-base初始权重,下载地址https://github.com/lonePatient/NeZha_Chinese_PyTorch
│   ├── nezha_model #存放预训练生成的模型
│   ├── NEZHA_models
│   ├── nezha_output #预训练的checkpoint
│   ├── pretrain #nezha预训练
│   │   ├── __init__.py
│   │   ├── NEZHA
│   │   │   ├── configuration_nezha.py
│   │   │   ├── modeling_nezha.py
│   │   ├── nezha_model
│   │   │   └── vocab.txt # 预训练时,所需要的训练集的词典
│   │   ├── NLP_Utils.py
│   │   ├── train_nezha.py #预训练NEZHA的主函数
│   │   └── transformers1.zip # 更高版本的transformers
│   └── submit

2.1 预训练

nezha-base-chinese 初始权重下载

nezha-large效果并不如nezha-base,区别只在于初始加载的权重不同以及预训练的网络层数不同。其他NEZHA-base和NEZHA-large一样。以下只针对NEZHA-base详解。

(1)重要方法

  • Mask策略
    动态mask:可以每次迭代都随机生成新的mask文本,增强模型泛化能力
    N-gram Mask:以掩码概率mask_p的概率选中token,为增加训练难度,选中部分以70%、20%、10%的概率进行1-gram、2-gram、3-gram片段的mask(选中token使用[MASK]、随机词、自身替换的概率和原版Bert一致)
    长度自适应:考虑到对短文本进行过较长gram的mask对语义有较大破坏,长度小于7的文本不进行3-gram mask,小于4的文本不进行2-gram mask(这一点在是参考原作者代码的,并没有进行修改,虽然已经在代码中已经实现,但是在该赛题中,并没有长度低于7的句子。所以并没有起任何作用,也没有任何影响)
    防止小概率的连续Mask:已经mask了的文本片段,强制跳过下一个token的mask,防止一长串连续的mask
  • 掩码概率: mask_p,原本是0.15,我们通过增加了掩码概率为0.5增大预训练的难度,能够一定程度防止微调过拟合。
  • 截断长度: 根据数据分析,发现句子的平均词数是54左右,随机选择了100的截断长度,这一点并没有进行调参
  • 截断方式: 首尾截断,还有首部截断和尾部截断并没有进行对比,一直使用的首尾截断。实现过程就是计算大于截断长度的数,首部截断一半,尾部截断一半。
  • Epoch: 设置为480时,NEZHA单模效果最佳。
  • 只训练word_embedding和position_emebedding
    加快训练。在打印查看model的position_embedding的时候,并没有找到,实现时就只训练了word_embedding。能缩短两倍的训练时间
model = NeZhaForMaskedLM.from_pretrained("./nezha-cn-base/")
model.resize_token_embeddings(len(tokenizer))
# 只训练word_embedding。能缩短两倍的训练时间
for name, p in model.named_parameters():if name != 'bert.embeddings.word_embeddings.weight':p.requires_grad = False
  • Warmup学习率和权重衰退: 采用transformers的有预训练函数,参数设置如下
from transformers import Trainer, TrainingArguments,BertTokenizer
training_args = TrainingArguments(output_dir='Nezha_pytorch/pretrain/nezha_output',# 此处必须是绝对路径overwrite_output_dir=True,num_train_epochs=1000,per_device_train_batch_size=32,save_steps=10000,#每10000step就 save一次save_total_limit=3,logging_steps=len(dl),#每个epoch log一次seed=2021,learning_rate=5e-5,weight_decay=0.01,#权重衰退warmup_steps=int(450000*150/batch_size*0.03)# warmup
)
  • 分块shuffle: 原源代码作者实现,我们并未修改这块
    分块shuffle将长度差不多的样本组成batch快,块间shuffle,减少padding部分运算量,预训练耗时减少了约40%
#sortBsNum:原序列按多少个bs块为单位排序,可用来增强随机性
#比如如果每次打乱后都全体一起排序,那每次都是一样的
def blockShuffle(data:list,bs:int,sortBsNum,key):random.shuffle(data)#先打乱tail=len(data)%bs#计算碎片长度tail=[] if tail==0 else data[-tail:]data=data[:len(data)-len(tail)]assert len(data)%bs==0#剩下的一定能被bs整除sortBsNum=len(data)//bs if sortBsNum is None else sortBsNum#为None就是整体排序data=splitList(data,sortBsNum*bs)data=[sorted(i,key=key,reverse=True) for i in data]#每个大块进行降排序data=unionList(data)data=splitList(data,bs)#最后,按bs分块random.shuffle(data)#块间打乱data=unionList(data)+tailreturn data
from torch.utils.data.dataloader import _SingleProcessDataLoaderIter,_MultiProcessingDataLoaderIter
#每轮迭代重新分块shuffle数据的DataLoader
class blockShuffleDataLoader(DataLoader):def __init__(self, dataset: Dataset,sortBsNum,key,**kwargs):assert isinstance(dataset.data,list)#需要有list类型的data属性super().__init__(dataset,**kwargs)#父类的参数传过去self.sortBsNum=sortBsNumself.key=keydef __iter__(self):#分块shuffleself.dataset.data=blockShuffle(self.dataset.data,self.batch_size,self.sortBsNum,self.key)if self.num_workers == 0:return _SingleProcessDataLoaderIter(self)else:return _MultiProcessingDataLoaderIter(self)

(2)掩码策略实现

class MLM_Data(Dataset):def __init__(self,textLs:list,maxLen:int,tk:BertTokenizer):super().__init__()self.data=textLsself.maxLen=maxLenself.tk=tkself.spNum=len(tk.all_special_tokens)self.tkNum=tk.vocab_sizedef __len__(self):return len(self.data)def random_mask(self,text_ids):input_ids, output_ids = [], []rands = np.random.random(len(text_ids))idx=0mask_p = 0.5 # 原始是0.15,加大mask_p就会加大预训练难度while idx<len(rands):if rands[idx]<mask_p:#需要mask# n-gram 动态mask策略ngram=np.random.choice([1,2,3], p=[0.7,0.2,0.1])#若要mask,进行x_gram mask的概率if ngram==3 and len(rands)<7:#太大的gram不要应用于过短文本ngram=2if ngram==2 and len(rands)<4:ngram=1L=idx+1R=idx+ngram#最终需要mask的右边界(开)while L<R and L<len(rands):rands[L]=np.random.random()*0.15#强制maskL+=1idx=Rif idx<len(rands):rands[idx]=1#禁止mask片段的下一个token被mask,防止一大片连续maskidx+=1for r, i in zip(rands, text_ids):if r < mask_p * 0.8:input_ids.append(self.tk.mask_token_id)output_ids.append(i)#mask预测自己elif r < mask_p * 0.9:input_ids.append(i)output_ids.append(i)#自己预测自己elif r < mask_p:input_ids.append(np.random.randint(self.spNum,self.tkNum))output_ids.append(i)#随机的一个词预测自己,随机词不会从特殊符号中选取,有小概率抽到自己else:input_ids.append(i)output_ids.append(-100)#保持原样不预测return input_ids, output_ids#耗时操作在此进行,可用上多进程def __getitem__(self, item):text1,_=self.data[item]#预处理,mask等操作text1=truncate(text1,self.maxLen)text1_ids = self.tk.convert_tokens_to_ids(text1)text1_ids, out1_ids = self.random_mask(text1_ids)#添加mask预测input_ids = [self.tk.cls_token_id] + text1_ids + [self.tk.sep_token_id]#拼接token_type_ids=[0]*(len(text1_ids)+2)labels = [-100] + out1_ids + [-100] assert len(input_ids)==len(token_type_ids)==len(labels)return {'input_ids':input_ids,'token_type_ids':token_type_ids,'labels':labels}@classmethoddef collate(cls,batch):input_ids=[i['input_ids'] for i in batch]token_type_ids=[i['token_type_ids'] for i in batch]labels=[i['labels'] for i in batch]input_ids=paddingList(input_ids,0,returnTensor=True)token_type_ids=paddingList(token_type_ids,0,returnTensor=True)labels=paddingList(labels,-100,returnTensor=True)attention_mask=(input_ids!=0)return {'input_ids':input_ids,'token_type_ids':token_type_ids,'attention_mask':attention_mask,'labels':labels}

(3)预训练好的模型下载

nezha_model

2.2 微调

(1)重要方法

  • 最大截断长度: 根据数据分析,训练集和测试集的平均每个句子的词的个数是54,在传统DL上进行过调参,100最佳,在这里就选择100
  • Dropout: 调参决定0.2和0.1接近,最终选择0.2
  • scheduler学习率: 对比过多种学习率,最终选择余弦退火学习率
    • get_constant_schedule:保持固定学习率不变
    • get_constant_schedule_with_warmup:在每一个 step 中线性调整学习率
    • get_linear_schedule_with_warmup:两段式调整学习率
    • get_cosine_schedule_with_warmup:和两段式调整类似,只不过采用的是三角函数式的曲线调整
    • get_cosine_with_hard_restarts_schedule_with_warmup:训练中将上面get_cosine_schedule_with_warmup 的调整重复 n 次
    • get_polynomial_decay_schedule_with_warmup:按指数曲线进行两段式调整

使用schduler的作用是:在训练初期使用较小的学习率(从 0 开始),在一定步数(比如 1000 步)内逐渐提高到正常大小(比如上面的 2e-5),避免模型过早进入局部最优而过拟合;在训练后期再慢慢将学习率降低到 0,避免后期训练还出现较大的参数变化

  • 数据预处理: 在类似情感分析这种文本分类任务中,标点符号是很重要的标志,在此的数据处理就并没有采用删除的方法,而是替换为不在数据集中的词。
def preprocess_text(document):# 将符号替换为不在脱敏文本的词典中的词# 删除逗号, 脱敏数据中最大值为30357text = str(document)text = text.replace(',', '35001')text = text.replace('!', '35002')text = text.replace('?', '35003')text = text.replace('。', '35004')# text = text.replace('17281', '')# 用单个空格替换多个空格text = re.sub(r'\s+', ' ', text, flags=re.I)return text
  • 优化器: 对比Lookahead和 AdamW两种,AdamW最佳。 Lookahead需要源码使用,具体代码见utils.py
from transformers import AdamW
if config.optimizer == "AdamW":optimizer = AdamW(optimizer_parameters, lr=config.learn_rate)
elif config.optimizer == "lookahead":optimizer = AdamW(optimizer_parameters,lr=config.learn_rate, eps=adam_epsilon)optimizer = Lookahead(optimizer=optimizer, la_steps=5, la_alpha=0.6)
  • 交叉验证分层划分: 对比过使用 Kfold和StratifiedKFold。后者更加
  • 混合精度训练: 虽然NEZHA模型本身就是加入了混合精度训练的,但是我们在跑模型的时候,还是去配置了使用FP16,未对比我们加入自定义的FP16是否会与NEZHA本身FP16冲突以及是否会影响精度。
  • Epoch: 加大Epoch能够训练充分,考虑到训练时间和预训练的数据集只有1W多的数据,在微调就加大了Epoch,选择了50Epoch。但是一般情况下,如果预训练语料足够大,微调的Epoch设置为个位数即可。
  • 对抗训练: 对比了FGM和PGD 的两种方法,FGM较快,且加入对抗能提高两个点。
  • 训练时间: 显卡3090,大概13个小时
  • 占用显存: 大约7G

(2)NEZHA模型实现

完整代码见源码Github

class NEZHA(nn.Module):def __init__(self, config):super(NEZHA, self).__init__()self.n_classes = config.num_classconfig_json = 'bert_config.json' if os.path.exists(config.model_path + 'bert_config.json') else 'config.json'self.bert_config = CONFIGS[config.model].from_pretrained(config.model_path + config_json)#self.bert_model = MODELS[config.model](config=self.bert_config)self.bert_model = MODELS[config.model].from_pretrained(config.model_path, config=self.bert_config)# NEZHA init#torch_init_model(self.bert_model, os.path.join(config.model_path, 'pytorch_model.bin'))self.isDropout = True if 0 < config.dropout < 1 else Falseself.dropout = nn.Dropout(p=config.dropout)self.classifier = nn.Linear(self.bert_config.hidden_size * 2, self.n_classes)def forward(self, input_ids, input_masks, segment_ids):sequence_output, pooler_output = self.bert_model(input_ids=input_ids, token_type_ids=segment_ids,attention_mask=input_masks)seq_avg = torch.mean(sequence_output, dim=1)concat_out = torch.cat((seq_avg, pooler_output), dim=1)if self.isDropout:concat_out = self.dropout(concat_out)logit = self.classifier(concat_out)return logit

3 Bert 方案

3.1 预训练

初始权重下载

预训练和NEZHA不同的有三个地方

  • 掩码概率mask_p为0.15:因为把NEZHA的预训练方案应用在Bert 上的预训练后,实验对比发现,效果不佳。
  • 预训练模型全部层都训练了,并没有冻结word_embedding以外的所有层去训练。
  • 预处理是删除掉标点符号,但是未来得及做其他的预处理预训练,本应该与NEZHA的预处理保持一致的
def preprocess_text(document):# 删除逗号text = str(document)text = text.replace(',', '')text = text.replace('!', '')text = text.replace('17281', '')# 用单个空格替换多个空格text = re.sub(r'\s+', ' ', text, flags=re.I)return text

(2)预训练好的模型下载

bert_model_1000

3.2 微调

(1)注意

除了以下四个不同的点,其他与NEZHA一致

  • 对抗训练:FGM和PGD效果都不佳,就没有加入对抗训练
  • Dropout:设置为0.1 调参选择出来的
  • 不划分验证集:全部训练集都作为训练集,不验证,当然这是在对不部分调参完毕后,做的实验,比交叉验证效果更佳
  • 数据预处理:和预训练的一样

(2)网络结构

并不是使用的传统Bert,而是使用的魔改Bert

  • Bert+LSTM
class BertLstm(nn.Module):def __init__(self, config):super(BertLstm, self).__init__()self.n_classes = config.num_classconfig_json = 'bert_config.json' if os.path.exists(config.model_path + 'bert_config.json') else 'config.json'self.bert_config = CONFIGS[config.model].from_pretrained(config.model_path + config_json,output_hidden_states=True)self.bert_model = MODELS[config.model].from_pretrained(config.model_path, config=self.bert_config)self.isDropout = True if 0 < config.dropout < 1 else Falseself.dropout = nn.Dropout(p=config.dropout)self.classifier = nn.Linear(self.bert_config.hidden_size * 2, self.n_classes)self.bilstm = nn.LSTM(input_size=self.bert_config.hidden_size,hidden_size=self.bert_config.hidden_size, batch_first=True, bidirectional=True)def forward(self, input_ids, input_masks, segment_ids):output = self.bert_model(input_ids=input_ids, token_type_ids=segment_ids, attention_mask=input_masks)sequence_output = output[0]pooler_output = output[1]output_hidden, _ = self.bilstm(sequence_output)  # [10, 300, 768]concat_out = torch.mean(output_hidden, dim=1)if self.isDropout:concat_out = self.dropout(concat_out)logit = self.classifier(concat_out)return logit
  • Bert+CLS
    最后一层向量取平均后与最后一层cls拼接
class BertForClass(nn.Module):def __init__(self, config):super(BertForClass, self).__init__()self.n_classes = config.num_classconfig_json = 'bert_config.json' if os.path.exists(config.model_path + 'bert_config.json') else 'config.json'self.bert_config = CONFIGS[config.model].from_pretrained(config.model_path + config_json,output_hidden_states=True)self.bert_model = MODELS[config.model].from_pretrained(config.model_path, config=self.bert_config)self.isDropout = True if 0 < config.dropout < 1 else Falseself.dropout = nn.Dropout(p=config.dropout)self.classifier = nn.Linear(self.bert_config.hidden_size * 2, self.n_classes)self.bilstm = nn.LSTM(input_size=self.bert_config.hidden_size,hidden_size=self.bert_config.hidden_size, batch_first=True, bidirectional=True)def forward(self, input_ids, input_masks, segment_ids):output = self.bert_model(input_ids=input_ids, token_type_ids=segment_ids,attention_mask=input_masks)sequence_output = output[0]pooler_output = output[1]hidden_states = output[2]seq_avg = torch.mean(sequence_output, dim=1)concat_out = torch.cat((seq_avg, pooler_output), dim=1)if self.isDropout:concat_out = self.dropout(concat_out)logit = self.classifier(concat_out)return logit

3 模型融合和TTA测试集数据增强

模型融合提升了0.1,TTA能提高0.003,模型融合必须要保证模型线上差异不是特别大,比如NEZAH模型最高达到0.62+,Bert方案只有0.59+,两者融合反而会低于0.62。

(1)模型融合

本质上就是多个模型预测测试集后,会得到6004行35列(测试集是6004行,训练集类别有35类),将多个6004×35的矩阵按每行每列相加,得到一个求和后的6004×35的矩阵。再计算标签。具体实现如下,完整代码见predict.py

def build_data():train_clean = 'data/datagrand_2021_train.csv'test_clean = 'data/datagrand_2021_test.csv'train = pd.read_csv(train_clean)test = pd.read_csv(test_clean)train["text"].progress_apply(lambda x: preprocess_text(x))test["text"].progress_apply(lambda x: preprocess_text(x))id2label = list(train['label'].unique())test_dataset = []for i in tqdm(range(len(test))):test_dict = {}test_dict['text'] = test.loc[i, 'text']test_dict['label'] = [-1]*35test_dataset.append(test_dict)return test_dataset, test, id2labeldef pre_ensemble(model_li_1, model_li_2, test_dataset, test_dataset2):config = Config()config_2 = Config2()test_prelist = []test_D = data_generator(test_dataset, config)test_D_2 = data_generator(test_dataset, config_2)for i,path in enumerate(model_li_1):# 每个模型的print("正在测试{}".format(path))PATH = './ensemble_model/{}.pth'.format(path)model = torch.load(PATH)model.eval()n = 0with torch.no_grad():train_logit = Nonefor input_ids, input_masks, segment_ids, labels in tqdm(test_D, disable=True):print(n)n += 1y_pred = model(input_ids, input_masks, segment_ids)y_pred = F.softmax(y_pred, dim=1)y_pred = y_pred.detach().to("cpu").numpy()if train_logit is None:train_logit = y_predelse:train_logit = np.vstack((train_logit, y_pred))test_prelist.append(train_logit)for i, path in enumerate(model_li_2):# 每个模型的print("正在测试{}".format(path))PATH = './ensemble_model/{}.pth'.format(path)model = torch.load(PATH)model.eval()n = 0with torch.no_grad():train_logit = Nonefor input_ids, input_masks, segment_ids, labels in tqdm(test_D_2, disable=True):print(n)n += 1y_pred = model(input_ids, input_masks, segment_ids)y_pred = F.softmax(y_pred, dim=1)y_pred = y_pred.detach().to("cpu").numpy()if train_logit is None:train_logit = y_predelse:train_logit = np.vstack((train_logit, y_pred))test_prelist.append(train_logit)test_prelist.append(train_logit)return test_prelist
def submit(pred,test_df, id2label,Name):test_preds_merge = np.sum(pred, axis=0) / (pred.shape[0])test_pre_tensor = torch.tensor(test_preds_merge)test_pre = torch.max(test_pre_tensor, 1)[1]pred_labels = [id2label[i] for i in test_pre]SUBMISSION_DIR = "submit"if not os.path.exists(SUBMISSION_DIR):os.makedirs(SUBMISSION_DIR)submit_file = SUBMISSION_DIR+"/submit_{}.csv".format(Name)pd.DataFrame({"id": test_df['id'], "label": pred_labels}).to_csv(submit_file, index=False)
if __name__ == "__main__":# list中存储是每个模型的命名model_li= ["bertfor","bertlstm","model_0", "model_1", "model_2", "model_3"]# 不加TTAtest_dataset, test, id2label = build_data()test_prelist = pre_ensemble(model_li, test_dataset)submit(np.array(test_prelist), test, id2label, "2bert-3nezha-checkpoint-ensemble")print()

(2)TTA

TTA即测试集的数据增强,我们测试了一种按照符号对句子进行shuffle,举例如图所示

shuffle前:7442 27878 9601 ,4004 10636 19121 !28646 227
shuffle后:4004 10636 19121 ,28646 227 ! 7442 27878 9601
  • 生成TTA文件
import pandas as pd
import numpy as np
from gensim.models import Word2Vec
import pandas as pd
import jieba
import ostest = pd.read_csv('data/datagrand_2021_test.csv')
fuhao=[',','!','。','?']
tmp=test.text.tolist()
totalFuhao=[]
for text in tmp:tF=[]t=text.split()for j in t:if j in fuhao:tF.append(j)# print("ok")totalFuhao.append(tF)
def getClean(document):text = str(document)text = text.replace(',', ',')text = text.replace('!', ',')text = text.replace('?', ',')text = text.replace('。', ',')return text
def suffer(document):text=str(document)t=text.split(',')newT=t[::-1]return " , ".join(newT)
# 数据清洗
train['text']=train['text'].apply(lambda x:getClean(x))
#句子逆序
train['text']=train['text'].apply(lambda x: suffer(x))
#符号还原
def tranform(df):ixd=0totaldx=0ans=[]for text in df:arr=[]dinx=0t=text.split()if ixd==0:print(t)for j in t:if j==',':arr.append(totalFuhao[totaldx][dinx])dinx+=1else :arr.append(j)ixd+=1totaldx+=1if ixd==1 :print(" ".join(arr))ans.append(" ".join(arr))return ans
#将倒序后的句子进行符号还原
newText=train['text'].tolist()
neT=tranform(newText)
test['text'] = neT
test.to_csv("./ttatest.csv",index=False)
  • TTA的实现
    原理是用TTA生成的测试文件用模型预测一遍,得到600435的矩阵,再用原始测试集的文件用模型预测一遍,得到6004×35的矩阵,两个矩阵每行每列求和,得到新的600435的矩阵后再去计算每一行的标签

def submit(pred,pred2,test_df, id2label):# 10个fold先求和test_preds_merge = np.sum(pred, axis=0) / (pred.shape[0])test_pre_tensor = torch.tensor(test_preds_merge)test_preds_merge2 = np.sum(pred2, axis=0) / (pred2.shape[0])test_pre_tensor2 = torch.tensor(test_preds_merge2)Len=len(test_preds_merge)total=[]print(Len)print(len(test_preds_merge2))# 两个矩阵每行求和for i in range(Len):t=test_preds_merge[i]+test_preds_merge2[i]total.append(t)total=np.array(total)test_pre_tensor3=torch.tensor(total) print(test_pre_tensor3[0])  test_pre = torch.max(test_pre_tensor3, 1)[1]pred_labels = [id2label[i] for i in test_pre]SUBMISSION_DIR = "submit"if not os.path.exists(SUBMISSION_DIR):os.makedirs(SUBMISSION_DIR)Name = "tta"submit_file = SUBMISSION_DIR+"/submit_{}.csv".format(Name)pd.DataFrame({"id": test_df['id'], "label": pred_labels}).to_csv(submit_file, index=False)
train_clean = '../data/datagrand_2021_train.csv'
# 原始测试集的模型预测
test_clean = '../data/datagrand_2021_test.csv'
train = pd.read_csv(train_clean)
test = pd.read_csv(test_clean)
test["text"]=test["text"].apply(lambda x: preprocess_text(x))
print(test["text"][0])
id2label = list(train['label'].unique())
label2id = {id2label[i]: i for i in range(len(id2label))}
test_dataset = []
for i in tqdm(range(len(test))):test_dict = {}test_dict['text'] = test.loc[i, 'text']test_dict['label'] = [-1]*35test_dataset.append(test_dict)
print(len(test_dataset))
test_D = data_generator(test_dataset, config)
model_pre = []
model_pre2 =[]
for fold in tqdm(range(config.k_fold)):if fold ==0:continuePATH = './models/model_nezha__{}.pth'.format(fold)model =  torch.load(PATH)model.eval()with torch.no_grad():y_p = []y_l = []val_y = []train_logit = Nonefor input_ids, input_masks, segment_ids, labels in tqdm(test_D, disable=True):y_pred = model(input_ids, input_masks, segment_ids)#print(y_pred.shape)y_pred = F.softmax(y_pred)y_pred = y_pred.detach().to("cpu").numpy()if train_logit is None:train_logit = y_predelse:train_logit = np.vstack((train_logit, y_pred))model_pre.append(train_logit)
# TTA文件的模型预测
test_clean = '../data/ttdtest.csv'
test = pd.read_csv(test_clean)
test["text"]=test["text"].apply(lambda x: preprocess_text(x))
print(test["text"][0])
id2label = list(train['label'].unique())
label2id = {id2label[i]: i for i in range(len(id2label))}
test_dataset = []
for i in tqdm(range(len(test))):test_dict = {}test_dict['text'] = test.loc[i, 'text']test_dict['label'] = [-1]*35test_dataset.append(test_dict)
# 封装数据集
test_D = data_generator(test_dataset, config)
# 依次加载10fold的模型并预测测试集
for fold in tqdm(range(config.k_fold)):PATH = './models/model_nezha__{}.pth'.format(fold)model =  torch.load(PATH)model.eval()with torch.no_grad():y_p = []y_l = []val_y = []train_logit = Nonefor input_ids, input_masks, segment_ids, labels in tqdm(test_D, disable=True):y_pred = model(input_ids, input_masks, segment_ids)#print(y_pred.shape)y_pred = F.softmax(y_pred)#print(len(y_pred))y_pred = y_pred.detach().to("cpu").numpy()if train_logit is None:train_logit = y_predelse:train_logit = np.vstack((train_logit, y_pred))model_pre2.append(train_logit)
submit(np.array(model_pre),np.array(model_pre2), test, id2label)

4 总结和反思

(1)总结

  • 在比赛中,做预训练模型,选用初始设置跑出来一个预训练模型后,再去固定了微调方案,反过来去对预训练方案进行改进和调参。不要着急去做微调,我们这次的比赛中,就犯了这个错误,预训练方案到比赛的最后一天都没有最终确定下来,最后一天还在跑预训练。导致比赛的最后阶段没有去做好微调方案,还有很多微调方案没来得及尝试和对比。
  • 我们团队虽然使用了语雀来维护一个文档,但是代码并没有管理,导致经常出现队友之前代码不一致,沟通和任务安排经常出现偏差。应该使用Git去管我们的代码
  • 队友之间配合还欠缺默契,经常传递信息不够明确,过程中出现了,队友之间跑着一样的程序,占用着两个GPU,或者说用GPU跑着一个没有实验意义的程序。团队中还出现,跑的程序不知道和哪个程序是对比实验,跑出来的结果没有实验对比性,无法判断跑的某个点是否带来增益,白白浪费GPU和时间。

(2)继续提升方向

  • 预训练

    • 参考roberta,将句子复制若干份,让模型见到更多的句子遮罩方法,提高模型见到token的数量,提升预训练模型的鲁棒性
    • 句子数据增广后再预训练
    • TF-IDF Predict Task:提取TFIDF权重,显示的告诉模型权重,让模型学习不同词权重在中的分布关系(来源[2021天池全球人工智能大赛赛道一冠军方案提出)
    • 掩码策略改进(思路来源:https://github.com/nilboy/gaic_track3_pair_sim)
      • WWM 完全掩码
      • 动态Mask
      • n-gram Mask
      • 混合Maks
      • similar ngram mask
    • 加入主办方提供的未标注数据,足足有72G,如果时间允许,设备足够高,预训练充分后,这将会带来巨大的增益。
    • 通过Bert实现同义词替换(思路来源:天池-全球人工智能大赛赛道一-rank2-炼丹术士)

  • 问题优化(思路来源:小布助手问题匹配-冠军方案)

  • 微调

    • EDA 数据增广在脱敏数据上表现不佳,但是AEDA这个方法还未尝试过,就是在句子中随机插入标点符号。(来源资料:https://mp.weixin.qq.com/s/R6uDbn3CqxFkOye73Rqpqg)
  • 模型融合

    • Stacking:我实现过,单个模型都上了0.58+,但是本地验证只有0.55+左右,理论上不应该的,应该是未能正确实现
    • Checkpoint融合:这种方案得到的结果最为稳重,我们在B榜没有经验,提交的文件只是单模的,我们未能提交融合后的方案。
  • 伪标签

    • 由于该任务本身准确率不高,就连A榜第一都只有63%的准确率,做出来的标签不佳,但是如果在其他准确率高的任务中,这将会是一个大杀器。
    • 做伪标签的数据除了是测试集,还可以是未标注的数据,未标注的数据有足够大,足够训练模型。
  • 新方案

    • ELETRA-Pytorch版本,并没有尝试

      • https://github.com/richarddwang/electra_pytorch
      • https://github.com/lucidrains/electra-pytorch
    • 知识蒸馏的预训练模型

      • 训练加速
      • 华为 TinyBert fine-tune阶段采用了数据增强的策略(mask之后预测 并使用余弦相似度来选择对应的N个候选词最后以概率p选择是否替换这个单词,从而产生更多的文本数据)
    • 百ERNIE pytorch
      • https://github.com/649453932/Bert-Chinese-Text-Classification-Pytorch
    • ConVBert
      • https://github.com/codertimo/ConveRT-pytorch

5 参考资料

  • AEDA:随机插入符号的数据增强
  • 天池入门赛-新闻文本分类官网首页
  • 天池-全球人工智能大赛赛道一官网首页
  • 天池-全球人工智能大赛赛道三官网首页
  • 天池-全球人工智能大赛赛道一-rank 4详解博客
  • 天池-全球人工智能大赛赛道一-HAN 和Capsule网络开源方案-github
  • 天池-全球人工智能大赛赛道一NEZHA方案-github
  • 天池-全球人工智能大赛赛道一Seq2Seq方案-github
  • 天池-全球人工智能大赛赛道一-TextCNN +encoder方案

  • 天池-全球人工智能大赛赛道一-Rank3 方案博客详解

  • 天池-全球人工智能大赛赛道一周周星方案1

  • 天池-全球人工智能大赛赛道一周星星方案2

  • 新闻文本分类-Rank 1 博客 代码

  • 新闻文本分类-Rank5 博客 代码

  • 新闻文本分类-Rank4 博客 代码

  • 新闻文本分类-Rank11 代码

  • 新闻文本分类-Rank6 博客 代码

  • 天池-全球人工智能大赛赛道三 RANK1

  • 天池-全球人工智能大赛赛道三 RANK3

  • 天池-全球人工智能大赛赛道三 NEZHA-Pytorch方案

2021 第五届“达观杯” 基于大规模预训练模型的风险事件标签识别】3 Bert和Nezha方案相关推荐

  1. 【2021 第五届“达观杯” 基于大规模预训练模型的风险事件标签识别】1 初赛Rank12的总结与分析

    目录 相关链接 1 赛题分析 2 引言 3 方案 3.1 传统DL方案 3.2 预训练方案 4 提分技巧 5 加快训练 6 总结和反思 7 参考资料 相关链接 [2021 第五届"达观杯&q ...

  2. 达观数据携手CCF举办第五届“达观杯”自然语言处理文本分类竞赛 ,开赛报名中!

    作为国内领先的智能文本处理企业,达观数据主办发起"达观杯"人工智能算法竞赛,每年一届,至今已成功举办四届.2021年,在CCF(中国计算机学会)自然语言处理专业委员会的特别支持下, ...

  3. 第五届“达观杯”自然语言处理文本分类竞赛开启报名,丰厚奖金等你来战!...

    在 CCF(中国计算机学会)自然语言处理专业委员会的特别支持下,第五届"达观杯"正式拉开帷幕.达观杯是由国内文本智能处理领军企业达观数据主办发起的人工智能算法竞赛,每年一届,至今已 ...

  4. 2021智源大会AI TIME|大规模预训练模型离通用人工智能还有多远?

    点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入! 6月1日,人工智能领域内行盛会"北京智源大会"如约而至.当天上午,北京市副市长靳伟.科技部战略规划司司长许倞出席并致 ...

  5. 中文版GPT-3来了?智源研究院发布清源 CPM —— 以中文为核心的大规模预训练模型...

    清源 CPM(Chinese Pretrained Models)是北京智源人工智能研究院和清华大学研究团队合作开展的大规模预训练模型开源计划,清源计划是以中文为核心的大规模预训练模型.首期开源内容包 ...

  6. 中文版GPT-3来了?智源、清华发布清源 CPM——以中文为核心的大规模预训练模型

    2020-11-18 23:43:21 清源 CPM(Chinese Pretrained Models)是北京智源人工智能研究院和清华大学研究团队合作开展的大规模预训练模型开源计划,清源计划是以中文 ...

  7. 还在 Fine-tune 大规模预训练模型? 该了解下最新玩法 Prompt-tuning啦

    写干货不易,点个赞再走吧! 由于预训练模型经过了预训练的学习,因此其本身已经拥有了一定的特征抽取(挖掘)能力,是个"三好学生":而大规模预训练模型在此基础上由于参数量众多,因此结合 ...

  8. 达摩院李雅亮:大规模预训练模型的压缩和蒸馏

    作者 | 李雅亮博士 阿里巴巴 来源 | DataFunTalk 导读:本次分享的主题为大规模预训练模型的压缩和蒸馏,主要是从自动机器学习的角度,介绍大规模预训练模型的压缩和蒸馏.将介绍阿里巴巴达摩院 ...

  9. 【文本分类】基于BERT预训练模型的灾害推文分类方法、基于BERT和RNN的新闻文本分类对比

    ·阅读摘要: 两篇论文,第一篇发表于<图学学报>,<图学学报>是核心期刊:第二篇发表于<北京印刷学院学报>,<北京印刷学院学报>没有任何标签. ·参考文 ...

最新文章

  1. 电子学会青少年编程等级考试四级题目解析07
  2. 不少Java程序员都觉得Lambda表达式很鸡肋,它到底有何用呢?
  3. Python Scipy 科学计算库
  4. 浅析那些你不知道的提升企业网站转化率的SEO优化技巧 !
  5. python程序员在公司都是做什么的-为什么企业很难招聘到好的python程序员?
  6. C++学习笔记第二天:几个知识点
  7. python hash
  8. Git 技术篇 - 同步代码到github失败,提示non-fast-forward、error: failed to push some refs to问题解决方法,git pull的用法
  9. https 页面中引入 http 资源的解决方式
  10. 简述WebService与.NET Remoting的区别及适应场合
  11. J2SE J2EE J2ME的区别
  12. java模型给泛型_【一天一个基础系列】- java之泛型篇
  13. python的动态参数
  14. MySQL Date and Time Types(日期和时间格式)
  15. mysql BDB支持表锁吗_mysql 表锁问题
  16. MySql整理(基础|进阶|运维)【黑马程序员视频】
  17. 手机wap网页制作的认识(有关meta…
  18. 2021第十四届“认证杯”数学建模网络挑战赛C题
  19. 关于在Ubuntu中更新pip时遇到的问题及解决方法
  20. 千牛2015卖家版官方电脑版

热门文章

  1. 0320-复利计算器代码
  2. Windows Terminal 配置GIT
  3. 如何评价文档图片的相似度
  4. 【电子产品】kindle使用心得.满满干货
  5. (1986年 - 2022年)沈先生的回忆篇
  6. 中国地质大学计算机研究所宿舍,在地大,据说一些寝室闪闪发光
  7. 24口光纤配线架 cad块_24口光纤配线架定义
  8. 在Linux服务器上解压rar压缩文件
  9. Androi移动开发基础
  10. JavaScript学习二