赞
踩
cluener下载链接:数据下载
{"text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,", "label": {"name": {"叶老桂": [[9, 11]]}, "company": {"浙商银行": [[0, 3]]}}}
{"text": "生生不息CSOL生化狂潮让你填弹狂扫", "label": {"game": {"CSOL": [[4, 7]]}}}
展开看就是:
{ "text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,", "label": { "name": { "叶老桂": [ [9, 11], [32, 34] ] }, "company": { "浙商银行": [ [0, 3] ] } } }
数据字段解释:
以train.json为例,数据分为两列:text & label,其中text列代表文本,label列代表文本中出现的所有包含在10个类别中的实体。例如:
text: "北京勘察设计协会副会长兼秘书长周荫如"
label: {"organization": {"北京勘察设计协会": [[0, 7]]}, "name": {"周荫如": [[15, 17]]}, "position": {"副会长": [[8, 10]], "秘书长": [[12, 14]]}}
其中,organization,name,position代表实体类别,
"organization": {"北京勘察设计协会": [[0, 7]]}:表示原text中,"北京勘察设计协会" 是类别为 "组织机构(organization)" 的实体, 并且start_index为0,end_index为7 (注:下标从0开始计数)
"name": {"周荫如": [[15, 17]]}:表示原text中,"周荫如" 是类别为 "姓名(name)" 的实体, 并且start_index为15,end_index为17
"position": {"副会长": [[8, 10]], "秘书长": [[12, 14]]}:表示原text中,"副会长" 是类别为 "职位(position)" 的实体, 并且start_index为8,end_index为10,同时,"秘书长" 也是类别为 "职位(position)" 的实体,
并且start_index为12,end_index为14
数据分为10个标签类别,分别为: 地址(address),书名(book),公司(company),游戏(game),政府(goverment),电影(movie),姓名(name),组织机构(organization),职位(position),景点(scene)
地址(address): **省**市**区**街**号,**路,**街道,**村等(如单独出现也标记),注意:地址需要标记完全, 标记到最细。
书名(book): 小说,杂志,习题集,教科书,教辅,地图册,食谱,书店里能买到的一类书籍,包含电子书。
公司(company): **公司,**集团,**银行(央行,中国人民银行除外,二者属于政府机构), 如:新东方,包含新华网/中国军网等。
游戏(game): 常见的游戏,注意有一些从小说,电视剧改编的游戏,要分析具体场景到底是不是游戏。
政府(goverment): 包括中央行政机关和地方行政机关两级。 中央行政机关有国务院、国务院组成部门(包括各部、委员会、中国人民银行和审计署)、国务院直属机构(如海关、税务、工商、环保总局等),军队等。
电影(movie): 电影,也包括拍的一些在电影院上映的纪录片,如果是根据书名改编成电影,要根据场景上下文着重区分下是电影名字还是书名。
姓名(name): 一般指人名,也包括小说里面的人物,宋江,武松,郭靖,小说里面的人物绰号:及时雨,花和尚,著名人物的别称,通过这个别称能对应到某个具体人物。
组织机构(organization): 篮球队,足球队,乐团,社团等,另外包含小说里面的帮派如:少林寺,丐帮,铁掌帮,武当,峨眉等。
职位(position): 古时候的职称:巡抚,知州,国师等。现代的总经理,记者,总裁,艺术家,收藏家等。
景点(scene): 常见旅游景点如:长沙公园,深圳动物园,海洋馆,植物园,黄河,长江等。
训练集:10748 验证集:1343
按照不同标签类别统计,训练集数据分布如下(注:一条数据中出现的所有实体都进行标注,如果一条数据出现两个地址(address)实体,那么统计地址(address)类别数据的时候,算两条数据):
【训练集】标签数据分布如下:
地址(address):2829 书名(book):1131 公司(company):2897 游戏(game):2325 政府(government):1797 电影(movie):1109 姓名(name):3661 组织机构(organization):3075 职位(position):3052 景点(scene):1462 【验证集】标签数据分布如下: 地址(address):364 书名(book):152 公司(company):366 游戏(game):287 政府(government):244 电影(movie):150 姓名(name):451 组织机构(organization):344 职位(position):425 景点(scene):199
平台测试结果:
Roberta指的chinese_roberta_wwm_large模型。(roberta-wwm-large-ext)
模型 BiLSTM+CRF bert-base-chinese Roberta+Softmax Roberta+CRF Roberta+BiLSTM+CRF
overall 70/67 78.82 75.90 80.4/79.3 79.64
可见,Roberta+lstm和Roberta模型差别不大。
官方处理方法:softmax、crf和span,模型本体和运行代码见:CLUENER2020/pytorch_version/models/albert_for_ner.py | run_ner_crf.py。
为什么使用CRF提升这么大呢? softmax最终分类,只能通过输入判断输出,但是 CRF 可以通过学习转移矩阵,看前后的输出来判断当前的输出。这样就能学到一些规律(比如“O 后面不能直接接 I”“B-brand 后面不可能接 I-color”),这些规律在有时会起到至关重要的作用。
例如下面的例子,A 是没加 CRF 的输出结果,B 是加了 CRF 的输出结果,一看就懂不细说了
本文选取Roberta+lstm+lstm,标注方法选择BIOS。
“B”:(实体开始的token)前缀
“I” :(实体中间的token)前缀
“O”:无特别实体(no special entity)
“S”: 即Single,“S-X”表示该字单独标记为X标签
另外还有BIO、BIOE(“E-X”表示该字是标签X的词片段末尾的终止字)等。
NER作为序列标注任务,输出需要确定实体边界和类型。如果预先进行了分词处理,由于分词工具原本就无法保证绝对正确的分词方案,势必会产生错误的分词结果,而这将进一步影响序列标注结果。因此,我们不进行分词,在字层面进行BIOS标注。
我们采用BIOS标注对原始标签进行转换。范例:
{"text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,",
"label": {"name": {"叶老桂": [[9, 11],[32, 34]]}, "company": {"浙商银行": [[0, 3]]}}}
转换结果为:
['B-company', 'I-company', 'I-company', 'I-company', 'O', 'O', 'O', 'O', 'O', 'B-name', 'I-name', 'I-name', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'B-name', 'I-name', 'I-name', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']
这部分处理代码参考《hemingkx/CLUENER2020》
import os import torch data_dir='./data/' train_dir=data_dir+'train.npz' test_dir=data_dir+'test.npz' files=['train','test'] bert_model='bert-base-chinese' roberta_model='hfl/chinese-roberta-wwm-ext-large' case_dir=os.getcwd()+'/case/bad_case.txt' # 训练集、验证集划分比例 dev_split_size=0.1 # 是否加载训练好的NER模型 load_before = False # 是否对整个BERT进行fine tuning full_fine_tuning = True # hyper-parameter learning_rate = 3e-5 weight_decay = 0.01 clip_grad = 5 batch_size = 32 epoch_num = 50 min_epoch_num = 5 patience = 0.0002 patience_num = 10 gpu = '1' if gpu != '': device = torch.device(f"cuda:{gpu}") else: device = torch.device("cpu") labels = ['address', 'book', 'company', 'game', 'government', 'movie', 'name', 'organization', 'position', 'scene'] label2id = { "O": 0, "B-address": 1, "B-book": 2, "B-company": 3, 'B-game': 4, 'B-government': 5, 'B-movie': 6, 'B-name': 7, 'B-organization': 8, 'B-position': 9, 'B-scene': 10, "I-address": 11, "I-book": 12, "I-company": 13, 'I-game': 14, 'I-government': 15, 'I-movie': 16, 'I-name': 17, 'I-organization': 18, 'I-position': 19, 'I-scene': 20, "S-address": 21, "S-book": 22, "S-company": 23, 'S-game': 24, 'S-government': 25, 'S-movie': 26, 'S-name': 27, 'S-organization': 28, 'S-position': 29, 'S-scene': 30 } id2label = {_id: _label for _label, _id in list(label2id.items())}
import os
import json
import logging
import numpy as np
import pandas as pd
import config
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader
# 定义process函数,将原始标签替换为BIOS标签,并与token(字)一一对应 # 处理完的word_list BIOS_label_list存储为npz格式 def process(mode): data_dir='./data/' input_dir=data_dir+str(mode)+'.json' output_dir=data_dir+str(mode)+'.npz' if os.path.exists(output_dir) is True: return word_list=[] label_list=[] with open(input_dir,'r',encoding='utf-8') as f: # 先读取到内存中,然后逐行处理 for line in f.readlines(): # loads():用于处理内存中的json对象,strip去除可能存在的空格 json_line = json.loads(line.strip()) text=json_line['text'] words=list(text) # 如果没有label,则返回None label_entities = json_line.get('label', None) labels=['O']*len(words) if label_entities is not None: for key, value in label_entities.items(): for sub_name, sub_index in value.items(): for start_index, end_index in sub_index: assert ''.join(words[start_index:end_index+1]) == sub_name if start_index == end_index: labels[start_index]='S-'+ key else: labels[start_index]='B-'+ key labels[start_index+1:end_index+1]=['I-'+key]*(len(sub_name)-1) word_list.append(words) label_list.append(labels) # 保存成二进制文件 np.savez_compressed(output_dir,words=word_list,labels=label_list) logging.info("--------{} data process DONE!--------".format(mode))
#处理训练集和验证集数据
mode1,mode2,mode3='train','dev','test'
train_data=process(mode1)#45min处理时间
dev_data=process(mode2)
test_data=process(mode3)
#加载处理完的npz数据集
#不加allow_pickle=True会报错Object arrays cannot be loaded when allow_pickle=False,numpy新版本中默认为False。
train_data=np.load('./data/train.npz',allow_pickle=True)
val_data=np.load('./data/dev.npz',allow_pickle=True)
test_data=np.load('./data/test.npz',allow_pickle=True)
#转换为dataframe格式 import pandas as pd #补个随机frac train_df=pd.concat([pd.DataFrame(train_data['words'],columns=['words']), pd.DataFrame(train_data['labels'],columns=['labels'])],axis=1) val_df=pd.concat([pd.DataFrame(val_data['words'],columns=['words']), pd.DataFrame(val_data['labels'],columns=['labels'])],axis=1) test_df=pd.concat([pd.DataFrame(test_data['words'],columns=['words']), pd.DataFrame(test_data['labels'],columns=['labels'])],axis=1) #将训练验证集的BIOS标签转换为数字索引,此时word和labels已经对齐了 def trans(labels): labels=list(labels) nums=[] for label in labels: nums.append(config.label2id[label]) return nums train_df['labels']=train_df['labels'].map(lambda x: trans(x)) val_df['labels']=val_df['labels'].map(lambda x: trans(x)) test_df['labels']=test_df['labels'].map(lambda x: trans(x)) val_df
words labels
0 [彭, 小, 军, 认, 为, ,, 国, 内, 银, 行, 现, 在, 走, 的, 是, ... [7, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
1 [温, 格, 的, 球, 队, 终, 于, 又, 踢, 了, 一, 场, 经, 典, 的, ... [7, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
2 [突, 袭, 黑, 暗, 雅, 典, 娜, 》, 中, R, i, d, d, i, c, ... [4, 14, 14, 14, 14, 14, 14, 14, 0, 7, 17, 17, ...
3 [郑, 阿, 姨, 就, 赶, 到, 文, 汇, 路, 排, 队, 拿, 钱, ,, 希, ... [0, 0, 0, 0, 0, 0, 1, 11, 11, 0, 0, 0, 0, 0, 0...
4 [我, 想, 站, 在, 雪, 山, 脚, 下, 你, 会, 被, 那, 巍, 峨, 的, ... [0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0...
... ... ...
1338 [在, 这, 个, 非, 常, 喜, 庆, 的, 日, 子, 里, ,, 我, 们, 首, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
1339 [姜, 哲, 中, :, 公, 共, 之, 敌, 1, -, 1, 》, 、, 《, 神, ... [6, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16...
1340 [目, 前, ,, 日, 本, 松, 山, 海, 上, 保, 安, 部, 正, 在, 就, ... [0, 0, 0, 5, 15, 15, 15, 15, 15, 15, 15, 15, 0...
1341 [也, 就, 是, 说, 英, 国, 人, 在, 世, 博, 会, 上, 的, 英, 国, ... [0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 20, 0, 0, 0, ...
1342 [另, 外, 意, 大, 利, 的, P, l, a, y, G, e, n, e, r, ... [0, 0, 0, 0, 0, 0, 2, 12, 12, 12, 12, 12, 12, ...
1343 rows × 2 columns
关于labels的长度不一致,也可以在整理函数里面写填充。一开始没写出来,后来pandas已经处理完了可以跑了,就没有再试。应该是涉及到pytorch的张量运算。
from datasets import Dataset
from transformers import AutoTokenizer
#这里一定要选AutoTokenizer,如果是BertTokenizer,会提示bertbase没有word_ids方法。结果没用到
trains_ds=Dataset.from_pandas(train_df)
val_ds=Dataset.from_pandas(val_df)
test_ds=Dataset.from_pandas(test_df)
tokenizer=AutoTokenizer.from_pretrained(config.roberta_model,do_lower_case=True)
#tokenized_inputs=tokenizer(trains_ds["words"],padding=True,truncation=True,is_split_into_words=True)为啥这种是错的
tokenized_trains_ds=trains_ds.map(lambda examples:tokenizer(examples['words'],is_split_into_words=True,truncation=True,padding=True),batched=True)
tokenized_val_ds=val_ds.map(lambda examples:tokenizer(examples['words'],is_split_into_words=True,truncation=True,padding=True),batched=True)
tokenized_test_ds=test_ds.map(lambda examples:tokenizer(examples['words'],is_split_into_words=True,truncation=True,padding=True),batched=True)
#在编码之后的datasets里面操作,得到的结果无法写入datasets,所以只好写到pandas文件里。 #将labels用-100来填充到和input_ids一样长(最长句子52,所以其实全部都填充到52) def padding(data): pad_labels=[] for ds in data: labels=ds['labels'] mask=ds['attention_mask'] label_ids=[-100] pad_length=len(mask) label_length=len(labels) label_ids=label_ids+labels+[-100]*(pad_length-label_length-1) pad_labels.append(label_ids) return pad_labels #tokenized_trains_ds["pad_labels"]=pad_labels# Column 2 named labels expected length 10748 but got length 1000 train_df['pad_labels']=padding(tokenized_trains_ds)# val_df['pad_labels']=padding(tokenized_val_ds) test_df['pad_labels']=padding(tokenized_test_ds) test_df
测试训练集句子长度
%pylab inline
#最大句子长度50
train_df['text_len'] = train_df['words'].apply(lambda x: len(x))
print(train_df['text_len'].describe())
Populating the interactive namespace from numpy and matplotlib
count 10748.000000
mean 37.380350
std 10.709827
min 2.000000
25% 32.000000
50% 41.000000
75% 46.000000
max 50.000000
Name: text_len, dtype: float64
#每个句子都被pad到52的长度
train_df['label_len'] = train_df['pad_labels'].apply(lambda x: len(x))
print(train_df['label_len'].describe())
count 10748.0
mean 52.0
std 0.0
min 52.0
25% 52.0
50% 52.0
75% 52.0
max 52.0
Name: label_len, dtype: float64
batch_size=32 #划分训练验证集 from sklearn.model_selection import train_test_split from datasets import Dataset from torch.nn.utils.rnn import pad_sequence train_data,val_data,train_label,val_label=train_test_split( train_df['words'].iloc[:], train_df['pad_labels'].iloc[:], test_size=0.15,shuffle=True) test_data,test_label=(test_df['words'].iloc[:],test_df['pad_labels'].iloc[:]) validation_data,validation_label=(val_df['words'].iloc[:],val_df['pad_labels'].iloc[:]) #stratify=train_df['label'].iloc[:]报错:The least populated class in y has only 1 member,which is too few. #The minimum number of groups for any class cannot be less than 2.估计是样本太少,分层抽取不可行。 #数据预处理 tokenizer=AutoTokenizer.from_pretrained(config.roberta_model,do_lower_case=True) train_encoding=tokenizer(list(train_data),is_split_into_words=True,truncation=True,padding=True,return_tensors='pt')#训练集中划分的训练集 val_encoding=tokenizer(list(val_data),is_split_into_words=True,truncation=True,padding=True,return_tensors='pt')#训练集中划分的验证集 test_encoding=tokenizer(list(test_data),is_split_into_words=True,truncation=True,padding=True,return_tensors='pt')#测试集 validation_econding=tokenizer(list(validation_data),is_split_into_words=True,truncation=True,padding=True,return_tensors='pt')#原本的验证集
中间test_loader数据被我照抄的时候shuffle,结果预测结果完全不对,坑。
#加载到datalodar并预处理 #数据集读取 from torch.utils.data import Dataset, DataLoader,TensorDataset import torch class XFeiDataset(Dataset): def __init__(self,encodings,labels): self.encodings=encodings self.labels=labels # 读取单个样本 def __getitem__(self,idx): item={key:torch.tensor(val[idx]) for key,val in self.encodings.items()} item['pad_labels']=torch.tensor((self.labels[idx])) item['mask']=(item['pad_labels']!=-100) return item def __len__(self): return len(self.labels) #def collate_fn train_dataset=XFeiDataset(train_encoding,list(train_label)) val_dataset=XFeiDataset(val_encoding,list(val_label)) test_dataset=XFeiDataset(test_encoding,list(test_label)) validation_dataset=XFeiDataset(validation_econding,list(validation_label)) from torch.utils.data import Dataset,DataLoader,TensorDataset train_loader=DataLoader(train_dataset,batch_size=batch_size,shuffle=True) val_loader=DataLoader(val_dataset,batch_size=batch_size,shuffle=True) test_loader=DataLoader(test_dataset,batch_size=batch_size,shuffle=False)#test数据不能shuffle啊,真坑死我了 validation_loader=DataLoader(validation_dataset,batch_size=batch_size,shuffle=False)#test数据不能shuffle啊,真坑死我了
可以选取数据打印看看
for examples in validation_loader:
print(examples,len(examples))#输出是一个五元字典,mask矩阵可以用来帅选有效的词
本来想用bert+lstm+crf来做。结果装的crf一直import不了,用softmax做的。crf还没试
from transformers import BertModel from torch.nn.utils.rnn import pad_sequence #初始化bert模型 from transformers import BertConfig import torch.nn as nn from torch.nn import LSTM from torch.nn import functional as F #from torchcrf import CRF num_labels=31 dropout=0.1 class Bert_LSTM(nn.Module): def __init__(self): super(Bert_LSTM,self).__init__() self.num_labels=num_labels self.dropout=nn.Dropout(dropout) self.bert=BertModel.from_pretrained(config.roberta_model) for param in self.bert.parameters(): param.requires_grad=True self.classifier=nn.Linear(1024,self.num_labels) #self.crf=CRF(num_labels,batch_first=True) from torch.nn import functional as F self.bilstm=nn.LSTM( input_size=1024, hidden_size=512, batch_first=True, num_layers=2, dropout=0.5, bidirectional=True) def forward(self,batch_seqs,batch_seq_masks,batch_seq_segments): output=self.bert(input_ids=batch_seqs,attention_mask=batch_seq_masks,token_type_ids=batch_seq_segments) #pooler_output=output.pooler_output last_hidden_state=output.last_hidden_state if model.train(): last_hidden_state=self.dropout(last_hidden_state) #只有这种写法不会报错,如果是sequence_output,pooler_output=self.bert(**kwags)这种,sequence_output会报错str没有xxx属性。 #貌似是bert输出有很多,直接用output.last_hidden_state来调用结果(估计是版本问题,坑),关键是输出要打印出来 lstm_output,(hn,cn)=self.bilstm(last_hidden_state) #output为输出序列的隐藏层,hn为最后一个时刻的隐藏层,cn为最后一个时刻的隐藏细胞 if model.train(): lstm_output=self.dropout(lstm_output) # 得到判别值 logits=self.classifier(lstm_output) log_probs = F.log_softmax(logits,dim=-1) return log_probs
加载模型
model=Bert_LSTM()
#model.load_state_dict(torch.load("best_bert_model_3epoch"))
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
epoch=10 lr=3e-5 from transformers import AdamW,get_scheduler train_steps_per_epoch=len(train_loader) num_training_steps=train_steps_per_epoch*epoch #定义各模块参数 bert_parameters=list(model.bert.named_parameters()) lstm_parameters=list(model.bilstm.named_parameters()) classifier_parameters=list(model.classifier.named_parameters()) no_decay=['bias','LayerNorm.weight'] #bert模型、lstm模型、nn.linear的学习率分离,后两个是bert的3倍 optimizer_grouped_parameters=[ {'params':[p for n,p in bert_parameters if not any(nd in n for nd in no_decay)], 'lr':lr,'weight_decay':0.01}, {'params':[p for n,p in bert_parameters if any(nd in n for nd in no_decay)], 'lr':lr,'weight_decay':0.0}, {'params':[p for n,p in lstm_parameters if not any(nd in n for nd in no_decay)], 'lr':lr*3,'weight_decay':0.01}, {'params':[p for n,p in lstm_parameters if any(nd in n for nd in no_decay)], 'lr':lr*3,'weight_decay': 0.0}, {'params':[p for n,p in classifier_parameters if not any(nd in n for nd in no_decay)], 'lr':lr*3,'weight_decay':0.01}, {'params':[p for n,p in classifier_parameters if any(nd in n for nd in no_decay)], 'lr':lr*3,'weight_decay':0.0}] optimizer=AdamW(optimizer_grouped_parameters,lr=lr,eps=1e-8) lr_scheduler=get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps)
import time import numpy as np from sklearn.metrics import f1_score,precision_score,recall_score,accuracy_score from torch.nn import functional as F #加载进度条 from tqdm.auto import tqdm num_training_steps=train_steps_per_epoch*epoch progress_bar=tqdm(range(num_training_steps)) def train_and_eval(epoch): best_acc=0.0 #criterion=nn.CrossEntropyLoss() criterion=nn.NLLLoss()#不带softmax的损失函数 for i in range(epoch): """训练模型""" start=time.time() model.train() print("***** Running training epoch {} *****".format(i+1)) train_loss_sum=0.0 for idx,batch in enumerate(train_loader): input_ids=batch['input_ids'].to(device) attention_mask=batch['attention_mask'].to(device) token_type_ids=batch['token_type_ids'].to(device) pad_labels=batch['pad_labels'].to(device) mask=batch['mask'].to(device) #计算输出和loss logits=model(input_ids,attention_mask,token_type_ids) loss=criterion(logits[mask],pad_labels[mask]) loss.backward() optimizer.step() lr_scheduler.step() optimizer.zero_grad() progress_bar.update(1) train_loss_sum+=loss.item() if (idx+1)%(len(train_loader)//5)==0: # 只打印五次结果 print("Epoch {:03d} | Step {:04d}/{:04d} | Loss {:.4f} | Time {:.4f} | Learning rate = {} \n".format( i+1,idx+1,len(train_loader),train_loss_sum/(idx+1),time.time()-start,optimizer.state_dict()['param_groups'][0]['lr'])) #验证模型 model.eval() total=0#每个batch要预测的token总数 acc=0#每个batch的acc total_eval_accuracy=0 total_eval_loss=0 for batch in val_loader: with torch.no_grad():#只有这一块是不需要求导的 input_ids=batch['input_ids'].to(device) attention_mask=batch['attention_mask'].to(device) token_type_ids=batch['token_type_ids'].to(device) pad_labels=batch['pad_labels'].to(device) mask=batch['mask'].to(device) logits=model(input_ids,attention_mask,token_type_ids) #logits[mask]从句子矩阵变被拉平,且只含有真实token的logtis。和bertfortoken分类任务头的view效果是一样的。 loss=criterion(logits[mask],pad_labels[mask])#只计算没有mask的部分单词的loss和准确率 total_eval_loss+=loss.item() acc+=(logits.argmax(dim=-1)==pad_labels)[mask].sum().item()#只计算没有mask的单词的准确率 total+=mask.sum().item() total_eval_accuracy=acc/total #avg_val_accuracy=total_eval_accuracy/len(val_loader) if total_eval_accuracy>best_acc: best_acc=total_eval_accuracy torch.save(model.state_dict(),"after_test_bert_lstm_softmax_model") print("val_accuracy:%.4f" % (total_eval_accuracy)) print("Average val loss: %.4f"%(total_eval_loss)) print("time costed={}s \n".format(round(time.time()-start,5))) print("-------------------------------")
开始训练
train_and_eval(epoch)
验证集准确率0.934,老感觉哪里不对,是不是准确率没有舍掉pad部分,但是不应该啊。
#编写predict函数 def predict(model,data_loader):#参数名为data时加载训练好的模型来预测报错,原模型不报错 model.eval() test_pred = [] for batch in data_loader: with torch.no_grad(): input_ids=batch['input_ids'].to(device) attention_mask=batch['attention_mask'].to(device) token_type_ids=batch['token_type_ids'].to(device) mask=batch['mask'].to(device) logits=model(input_ids,attention_mask,token_type_ids) pad_logits=logits[mask] y_pred=torch.argmax(logits,dim=-1).detach().cpu().numpy()#为啥最后拉平的又变回矩阵了,看不懂啊 test_pred.extend(y_pred) return test_pred
试了一下,用mask矩阵,由于过滤之后句子长度不一致,二维token矩阵会被拉成一维,不知道为啥最后预测的结果还可以是长52的labels矩阵。这一点没想明白。
import torch
from torch import tensor
a=torch.randn(2,4)
b=tensor([[False,True,True,False],
[True,False,True,True]])
c=a[b]
print(a,a.shape)
print(c,c.shape)
tensor([[-0.2196, 0.1262, -0.6929, -1.7824],
[-0.4014, -0.5301, -0.6155, 0.6116]]) torch.Size([2, 4])
tensor([ 0.1262, -0.6929, -0.4014, -0.6155, 0.6116]) torch.Size([5])
#用trainer预测验证集结果并保存
#torch.save(model.state_dict(),"best_lstm_whole_4epoch")
#model.load_state_dict(torch.load("after_test_bert_lstm_softmax_model"))
#model.to(device)
predictions=predict(model,validation_loader)
val_df['pre_labels']=pd.Series(predictions)
from datasets import Dataset
val_datasets=Dataset.from_pandas(val_df)
#将预测的结果直接加到val_df。如果存入csv读取出来再加入,读取的labels数据就是文本数据,坑了好久才发现。而且还有换行符,醉了
懒得写pandas列间运算,这里抄前面datasets的填充处理,将填充的无效部分labels去掉
def unpadding(data):
unpad_labels=[]
for ds in data:#直接这样迭代读取报错。pandas数据不能这样读取每一行。datasets是dict格式,可以用datasets['train'][0]这样的方式读取
pad_labels=ds['pre_labels'] #这里是pre_labels,又他妈写错了
words=ds['words']
length=len(words)
label_ids=pad_labels[1:(length+1)]
unpad_labels.append(label_ids)
return unpad_labels
#tokenized_trains_ds["pad_labels"]=pad_labels# Column 2 named labels expected length 10748 but got length 1000
val_df['unpad_labels']=unpadding(val_datasets)
val_df
val_df.drop(columns=(['pad_labels','pre_labels',]),inplace=True)
val_df.to_csv('bert_lstm_validation_1113.csv')
val_df
words labels unpad_labels
0 [彭, 小, 军, 认, 为, ,, 国, 内, 银, 行, 现, 在, 走, 的, 是, ... [7, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0... [7, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
1 [温, 格, 的, 球, 队, 终, 于, 又, 踢, 了, 一, 场, 经, 典, 的, ... [7, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... [7, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
2 [突, 袭, 黑, 暗, 雅, 典, 娜, 》, 中, R, i, d, d, i, c, ... [4, 14, 14, 14, 14, 14, 14, 14, 0, 7, 17, 17, ... [4, 14, 14, 14, 14, 14, 14, 14, 0, 7, 17, 17, ...
3 [郑, 阿, 姨, 就, 赶, 到, 文, 汇, 路, 排, 队, 拿, 钱, ,, 希, ... [0, 0, 0, 0, 0, 0, 1, 11, 11, 0, 0, 0, 0, 0, 0... [0, 0, 0, 0, 0, 0, 1, 11, 11, 0, 0, 0, 0, 0, 0...
4 [我, 想, 站, 在, 雪, 山, 脚, 下, 你, 会, 被, 那, 巍, 峨, 的, ... [0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0... [0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0...
... ... ... ...
1338 [在, 这, 个, 非, 常, 喜, 庆, 的, 日, 子, 里, ,, 我, 们, 首, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
1339 [姜, 哲, 中, :, 公, 共, 之, 敌, 1, -, 1, 》, 、, 《, 神, ... [6, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16... [7, 17, 17, 0, 6, 16, 16, 16, 16, 16, 16, 16, ...
1340 [目, 前, ,, 日, 本, 松, 山, 海, 上, 保, 安, 部, 正, 在, 就, ... [0, 0, 0, 5, 15, 15, 15, 15, 15, 15, 15, 15, 0... [0, 0, 0, 5, 15, 15, 15, 15, 15, 15, 15, 15, 0...
1341 [也, 就, 是, 说, 英, 国, 人, 在, 世, 博, 会, 上, 的, 英, 国, ... [0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 20, 0, 0, 0, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20...
1342 [另, 外, 意, 大, 利, 的, P, l, a, y, G, e, n, e, r, ... [0, 0, 0, 0, 0, 0, 2, 12, 12, 12, 12, 12, 12, ... [0, 0, 1, 0, 0, 0, 2, 12, 12, 12, 12, 12, 12, ...
1343 rows × 3 columns
5.6 生成test数据集结果
test_predictions=predict(model,test_loader)
test_df['pre_labels']=pd.Series(test_predictions)
test_datasets=Dataset.from_pandas(test_df)
test_df['unpad_labels']=unpadding(test_datasets)
test_df.drop(columns=(['labels','pad_labels','pre_labels']),inplace=True)
test_df
words labels unpad_labels
0 [彭, 小, 军, 认, 为, ,, 国, 内, 银, 行, 现, 在, 走, 的, 是, ... [7, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0... [7, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
1 [温, 格, 的, 球, 队, 终, 于, 又, 踢, 了, 一, 场, 经, 典, 的, ... [7, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,... [7, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
2 [突, 袭, 黑, 暗, 雅, 典, 娜, 》, 中, R, i, d, d, i, c, ... [4, 14, 14, 14, 14, 14, 14, 14, 0, 7, 17, 17, ... [4, 14, 14, 14, 14, 14, 14, 14, 0, 7, 17, 17, ...
3 [郑, 阿, 姨, 就, 赶, 到, 文, 汇, 路, 排, 队, 拿, 钱, ,, 希, ... [0, 0, 0, 0, 0, 0, 1, 11, 11, 0, 0, 0, 0, 0, 0... [0, 0, 0, 0, 0, 0, 1, 11, 11, 0, 0, 0, 0, 0, 0...
4 [我, 想, 站, 在, 雪, 山, 脚, 下, 你, 会, 被, 那, 巍, 峨, 的, ... [0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0... [0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0...
... ... ... ...
1338 [在, 这, 个, 非, 常, 喜, 庆, 的, 日, 子, 里, ,, 我, 们, 首, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
1339 [姜, 哲, 中, :, 公, 共, 之, 敌, 1, -, 1, 》, 、, 《, 神, ... [6, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16... [7, 17, 17, 0, 6, 16, 16, 16, 16, 16, 16, 16, ...
1340 [目, 前, ,, 日, 本, 松, 山, 海, 上, 保, 安, 部, 正, 在, 就, ... [0, 0, 0, 5, 15, 15, 15, 15, 15, 15, 15, 15, 0... [0, 0, 0, 5, 15, 15, 15, 15, 15, 15, 15, 15, 0...
1341 [也, 就, 是, 说, 英, 国, 人, 在, 世, 博, 会, 上, 的, 英, 国, ... [0, 0, 0, 0, 0, 0, 0, 0, 10, 20, 20, 0, 0, 0, ... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 20...
1342 [另, 外, 意, 大, 利, 的, P, l, a, y, G, e, n, e, r, ... [0, 0, 0, 0, 0, 0, 2, 12, 12, 12, 12, 12, 12, ... [0, 0, 1, 0, 0, 0, 2, 12, 12, 12, 12, 12, 12, ...
1343 rows × 3 columns
最后部分索引转成BIOS再转成json文件还没有做。那个平台登录不上去,无法提交结果。
一开始纠结于bert的特殊字符处有没有输出最终的词向量,lstm的处理中有没有特殊字符eos等等。打印输出就折腾了好久,最终是读取一个batch数据进行测试。并对比bert模型和bertfortoken分类模型的输出,后者就是过了一个nn.linear层。
总感觉特殊字符输出的bert词向量,输入lstm后对最终结果有影响。上次做句子分类没有处理。这次准备bert输出的last_hidden_state,根据实际tokens去掉特殊字符处的值。然后变长序列进过pad_sentences后,再经过pack_padded_sequence打包压缩,去掉pad_sentences函数pad的字符。最后pad_packed_sequence将序列恢复到原来的长度。
最后是pytorch的dataset和dataloader不熟悉,结果吃了大亏。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。