NLP竞赛参与打卡记录:汽车领域多语种迁移学习挑战赛
本博客为Coggle 30 Days of ML(22年7月)竞赛打卡活动记录页面,会记录本人的打卡内容。活动链接为:活动链接
任务1:比赛报名
步骤1:报名比赛
前往竞赛地址,完成账号注册和比赛的报名。
报名成果截图:
步骤2:下载比赛数据
在竞赛页面点击数据下载:
步骤3:解压比赛数据,并使用pandas进行读取
将下载下来的内容进行解压,然后调用pandas进行读取,展示
读取的代码如下:
import pandas as pd
train_cn = pd.read_excel("data/1/中文_train.xlsx")
train_en = pd.read_excel("data/1/英文_train.xlsx")
train_jp = pd.read_excel("data/1/日语_train.xlsx")
test_a = pd.read_excel("data/testA.xlsx")
要展示的话,你可以选择print或者在jupoyter notebook上直接用变量名
步骤4:查看数据类型
可以用pandas的info()来查看训练集和测试集的相关字段信息
print("cn train")
print(train_cn.info())
print("---"*10)
print("en train")
print(train_en.info())
print("---"*10)
print("jp train")
print(train_jp.info())
print("---"*10)
print("testa")
print(test_a.info())
任务2:文本分析与文本分词
步骤1:使用jieba对中文进行分词;
jieba是常用的处理中文字符的第三库,关于它的用法可自行搜索。
先用pip来安装它
pip install jieba
然后,我们可以利用jieba进行简单的中文分词
import jieba
def cutword(txt):
return jieba.lcut(txt)
train_cn['phase'] = train_cn['原始文本'].apply(cutword)
步骤2:使用negisa对日语进行分词
对于日文的数据,我们可以使用negisa这个库来进行分词操作;具体的用法可见官方文档negisa;直接pip安装需要一定时间(视网络条件而定,这个包20m左右大小;一般还会安装其他依赖)
类似的,分词代码如下
import nagisa
def cutword_jp(txt):
words = nagisa.tagging(txt)
return words.words
train_jp['phase'] = train_jp['原始文本'].apply(cutword_jp
任务3:TFIDF与文本分类
任务3总的来说可以参考Coggle 给的例子。
步骤1:学习TFIDF的使用,提取语料的TFIDF特征;步骤2:使用逻辑回归结合TFIDF进行训练(所有的语言语料),并对测试集的意图进行分类
这里,两个步骤是可以合并在一起完成的,因为可以把提取特征和逻辑回归一起构建成一个pipeline,一起进行训练。
第一部分,数据预处理代码如下:
import pandas as pd
import numpy as np
import nagisa
from sklearn.feature_extraction.text import TfidfVectorizer # 文本特征提取
from sklearn.linear_model import LogisticRegression # 逻辑回归
from sklearn.pipeline import make_pipeline # 组合流水线
# 读取数据
train_cn = pd.read_excel("data/1/中文_train.xlsx") # 因为test仅包含英文和中文,所以本次没有使用cn的数据来加强特征学习
train_en = pd.read_excel("data/1/英文_train.xlsx")
train_jp = pd.read_excel("data/1/日语_train.xlsx")
test_jp = pd.read_excel('data/testA.xlsx', sheet_name='日语_testA')
test_en = pd.read_excel('data/testA.xlsx', sheet_name='英文_testA')
# 文本分词
train_jp['words'] = train_jp['原始文本'].apply(lambda x: ' '.join(nagisa.tagging(x).words))
train_en['words'] = train_en['原始文本'].apply(lambda x: x.lower())
test_jp['words'] = test_jp['原始文本'].apply(lambda x: ' '.join(nagisa.tagging(x).words))
test_en['words'] = test_en['原始文本'].apply(lambda x: x.lower())
第二部分,就是简单划分一下train数据集,然后进行模型的训练;代码如下:
from sklearn.model_selection import train_test_split #y用于切分数据集
# 以8:2的比例来划分训练集和验证集
train_en1, dev_en = train_test_split(train_en, test_size=.2, random_state=2)
train_jp1, dev_jp = train_test_split(train_jp, test_size=.2, random_state=2)
# 训练TFIDF和逻辑回归
pipline = make_pipeline(
TfidfVectorizer(),
LogisticRegression()
)
pipline.fit(
train_jp1['words'].tolist() + train_en1['words'].tolist(),
train_jp1['意图'].tolist() + train_en1['意图'].tolist()
)
dev_a = dev_en['words'].tolist() + dev_jp['words'].tolist()
output_dev = pipline.predict(dev_a)
num_dev = len(dev_a)
tmp = 0
for i in range(num_dev):
if(output_dev[i] == y_dev[i]):
tmp += 1
acc = tmp / num_dev
print(acc)
train1 = train_en1['words'].tolist() + train_jp1['words'].tolist()
y_train = train_en1['意图'].tolist() + train_jp1['意图'].tolist()
output_train = pipline.predict(train1)
num_train = len(train1)
tmp = 0
for i in range(num_train):
if(output_train[i] == y_train[i]):
tmp += 1
acc_train = tmp / num_train
print(acc_train)
两个acc的结果可见截图:
额外思考
其实TfidfVectorizer()和LogisticRegression()是有很多参数提供使用的;但是在简单的测试参数的时候,发现貌似默认的情况会好一点?(当然,没有认真去调参,只是简单加几个参数,看看acc的差别)
代码如下:
# 训练TFIDF和逻辑回归
pipline = make_pipeline(
TfidfVectorizer(ngram_range=(1,3)),
LogisticRegression(tol=1e-5)
)
只是简单给个例子,因为参数组合比较多,不放全部组合的例子了
结果见图:
所以,目前来看,默认情况貌似会更好一丢丢~
步骤3:将步骤2预测的结果文件提交到比赛,截图分数
最终输出的不仅需要意图,很多情况还有具体的参数,比如空调调到多少度。但是这里,先完成打卡,先把这个结果提交一下(从学习的角度来看是可以的,但是如果你想要分数高一点,毕竟是竞赛,可以等任务4,再具体得到而外的输出再一起提交)
任务4:正则表达式
任务4是使用正则表达式来提取字符串中的槽值,但是这个在不同语言是不一样的,而且对于不同的意图,还要单独处理,相对来说,是不推荐使用的,但是学习是ok的。所以本博客会进行简单的正则使用, 但不会将其用于竞赛中,槽值的提取留到后面几个任务再慢慢调优。
问题
不同的文本,里面的指标是不一样的,比如数值,中文是阿拉伯数字,日语则是中文字符的数字(“一”、“二”等),英文则是one、two……
所以是比较难找到一种通用的字符串处理来应付所有的文本,所以这就是不推荐在竞赛中使用正则表达式来提取关键字的原因。
步骤1:提取连续数字
这里展示两个,一个是中文的阿拉伯数字,这个比较简单;另一个是日语中使用的“一”、“二”……;英文就不处理了(按我的写法的话,太多了苦笑😹)
import re
def get_num_last(txt):
return re.findall("\d+\.?\d*", txt)
train_cn['num_last'] = train_cn['原始文本'].apply(get_num_last)
print(train_cn)
import re
def get_num_last(txt):
return re.findall("[一|二|三|四|五|六|七|八|九|十]+", txt)
train_jp['num_last'] = train_jp['原始文本'].apply(get_num_last)
print(train_jp)
当然这省略了上面的数据处理部分,但是正则部分已经在这里了,python的话一般使用re这个库来处理正则表达式。
步骤2:使用正则表达式进行槽值匹配(基于历史的槽值字符串)
虽然这里会有我的简单代码,但是还是要说一句,不推荐这么做;因为后面会有任务来处理槽值的,更准确且没那么麻烦(个人是怎么觉得的,虽然要搭建模型来训练也不是简洁的事)
def getcaozhi_offset(txt):
num_l = get_num_last(txt)
if len(num_l) > 0:
return "offset:" + num_l[-1]
else:
return np.nan
train_cn['槽值_test'] = np.nan
for i in range(len(train_cn)):
#以意图作为条件,分别进行正则,来提取槽值
if train_cn.iloc[i, 1] == "adjust_ac_temperature_to_number":
train_cn['槽值_test'][i] = getcaozhi_offset(train_cn.iloc[i, 0])
print(train_cn)
思路就是根据意图(train里本来就有的,或者是你用模型的预测输出也可以),来分别对待。比如是调温度的话,就调用步骤1中的提取数字的函数,因为往往最后一个数字是我们的目标温度,所以使用最后一个数字作为温度调节的槽值。
其他的有点繁琐,就不细写了……
任务5:BERT模型入门
步骤1:学习transformers库中pipline和加载模型的过程
使用Hugging Face的transformers库可以让我们快速上手nlp的模型。这个库包含了大量的目前流行的预训练模型,我们只需几行代码,便可下载并应用,后面再根据实际数据做下游任务的微调。
首先是下载模型,去Hugging Face的官网找到你要的模型,hugging face
在这里搜索你想要使用的预训练模型,比如bert,然后选一个点进去
在右上角,点击use in transformers,里面会有简单的使用案例;但是因为网络的原因,很可能你直接复制粘贴下来,会报错(因为transformers会先在服务器上找到你想要的模型,然后下载到你电脑的缓存中,再进行加载);网络问题导致不能下载的,你可以手动下载;使用如下代码:
from huggingface_hub import snapshot_download
snapshot_download(repo_id="hfl/chinese-roberta-wwm-ext") #id填你想要下载的模型名称,要和官网上的一致
然后他就会下载到你的缓存目录了(如果库没安装,可以pip安装一下)
最后它会提示你下载到你电脑的地址,你去找,然后复制到你自己项目的地址,给他新建个文件夹,取个短点的名字(方便你后续加载使用)
然后,就可以根据要求,加载模型了
import transformers
from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_path = "../model/bert-chinese"
model = AutoModelForSequenceClassification.from_pretrained(
model_path, # 小写的 12 层预训练模型
num_labels=17, # 分类数 --2 表示二分类
# 你可以改变这个数字,用于多分类任务
# output_attentions=False, # 模型是否返回 attentions weights.
# output_hidden_states=False, # 模型是否返回所有隐层状态.
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
pipeline = transformers.TextClassificationPipeline(model=model, tokenizer = tokenizer)
print(pipeline(train_cn['原始文本'][0]))
步骤2:学习transformers库的使用:包括定义数据集,定义模型和训练模型
这部分我不打算在这里书写,可见任务6和7;下面有详细的过程;这里不重复了(已经很长了😂)
任务6:BERT文本分类
步骤1:使用BERT完成意图识别(文本分类)
先导入数据
import pandas as pd
import numpy as np
# 读取数据
train_cn = pd.read_excel("data/1/中文_train.xlsx") # 因为test仅包含英文和中文,所以本次没有使用cn的数据来加强特征学习
train_en = pd.read_excel("data/1/英文_train.xlsx")
train_jp = pd.read_excel("data/1/日语_train.xlsx")
test_jp = pd.read_excel('data/testA.xlsx', sheet_name='日语_testA')
test_en = pd.read_excel('data/testA.xlsx', sheet_name='英文_testA')
数据拼接,因为英文和日文的数据每个只有1000条左右,所以可以使用中文的数据,抽取10000w条中文数据放在一起,进行训练(当然也可以分开训练单独的模型,最后再融合;这里只是展示第一个想法)
train_df = pd.concat([
train_jp[['原始文本', '意图', '槽值1', '槽值2']],
train_cn[['原始文本', '意图', '槽值1', '槽值2']].sample(10000),
train_en[['原始文本', '意图', '槽值1', '槽值2']],
],axis = 0)
对label进行数值化
train_df = train_df.sample(frac=1.0) # 对原来的数据进行随机
train_df['意图_encode'], lbl_ecode = pd.factorize(train_df['意图'])
print(train_df['原始文本'].str.len().max())# 查看一共多少个分类
定义模型和tokenizer(使用本地先下载好的模型,怕网络不通导致加载出错)
from torch.utils.data import Dataset, DataLoader, TensorDataset
import torch
from torch import nn
tokenizer = AutoTokenizer.from_pretrained("../model/bert-base-multilingual-cased")
config = AutoConfig.from_pretrained("../model/bert-base-multilingual-cased")
# 数据集读取
class MyDataset(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['labels'] = torch.tensor(int(self.labels[idx]))
return item
def __len__(self):
return len(self.labels)
#在Bert的基础上定义我们的分类模型
class MyModel(nn.Module):
def __init__(self, num_labels):
super(MyModel,self).__init__()
self.model = model = AutoModel.from_pretrained("../model/bert-base-multilingual-cased")
self.dropout = nn.Dropout(0.1)
self.classifier = nn.Linear(768, num_labels)
def forward(self, input_ids=None, attention_mask=None,labels=None):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
sequence_output = self.dropout(outputs[0]) #outputs[0]=last hidden state
logits = self.classifier(sequence_output[:,0,:].view(-1,768))
return logits
train_encoding = tokenizer(train_df['原始文本'].tolist()[:-500], truncation=True, padding=True, max_length=70)
val_encoding = tokenizer(train_df['原始文本'].tolist()[-500:], truncation=True, padding=True, max_length=70)
train_dataset = MyDataset(train_encoding, train_df['意图_encode'].tolist()[:-500])
val_dataset = MyDataset(val_encoding, train_df['意图_encode'].tolist()[-500:])
训练
# 单个读取到批量读取
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_dataloader = DataLoader(val_dataset, batch_size=32, shuffle=False)
model = MyModel(18)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# device = 'cpu'
model = model.to(device)
from torch.nn import CrossEntropyLoss
from torch.optim import AdamW
loss_fn = CrossEntropyLoss() # ingore index = -1
optim = AdamW(model.parameters(), lr=5e-5)
def train():
model.train()
total_train_loss = 0
iter_num = 0
total_iter = len(train_loader)
for batch in train_loader:
# 正向传播
optim.zero_grad()
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
label = batch['labels'].to(device)
pred = model(
input_ids,
attention_mask
)
# 计算损失
loss = loss_fn(pred, label)
# 反向梯度信息
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) #梯度裁剪
# 参数更新
optim.step()
iter_num += 1
if(iter_num % 100 == 0):
print("iter_num: %d, loss: %.4f, %.2f%% %.4f" % (
iter_num, loss.item(), iter_num/total_iter*100,
(pred.argmax(1) == label).float().data.cpu().numpy().mean(),
))
def validation():
model.eval()
label_acc = 0
for batch in val_dataloader:
with torch.no_grad():
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
label = batch['labels'].to(device)
pred = model(
input_ids,
attention_mask
)
label_acc += (pred.argmax(1) == label).float().sum().item()
label_acc = label_acc / len(val_dataloader.dataset)
print("-------------------------------")
print("Accuracy: %.4f" % (label_acc))
print("-------------------------------")
for epoch in range(4):
train()
validation()
步骤2:将步骤1预测的结果文件提交到比赛,截图分数;
用这个模型预测test然后保存成excel
def prediction():
model.eval()
test_label = []
for batch in test_dataloader:
with torch.no_grad():
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
pred = model(input_ids, attention_mask)
test_label += list(pred.argmax(1).data.cpu().numpy())
return test_label
test_encoding = tokenizer(test_en['原始文本'].tolist(), truncation=True, padding=True, max_length=70)
test_dataset = MyDataset(test_encoding, [0] * len(test_en))
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=False)
test_en_intent = prediction()
test_encoding = tokenizer(test_jp['原始文本'].tolist(), truncation=True, padding=True, max_length=70)
test_dataset = MyDataset(test_encoding, [0] * len(test_jp))
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=False)
test_ja_intent = prediction()
test_jp['意图'] = [lbl_ecode[x] for x in test_ja_intent]
test_en['意图'] = [lbl_ecode[x] for x in test_en_intent]
test_en['槽值1'] = np.nan
test_en['槽值2'] = np.nan
test_jp['槽值1'] = np.nan
test_jp['槽值2'] = np.nan
writer = pd.ExcelWriter('submit_0705_not_entity.xlsx')
test_en[['意图', '槽值1', '槽值2']].to_excel(writer, sheet_name='英文_testA', index=None)
test_jp[['意图', '槽值1', '槽值2']].to_excel(writer, sheet_name='日语_testA', index=None)
writer.save()
writer.close()
评分截图:
任务7:BER实体抽取
步骤1:使用BERT完成实体抽取(槽位识别)
用Bert来进行实体抽取,根据我所查阅和参考的资料,思量就是对每一个token进行分类;类别数就是实体的数量。具体参考来源:赛题baselne
首先查看一共有多少类不同的实体:
# 统计slot标签
SLOT_LIST = set()
SLOT_LIST.add('O')
for idx,rows in train_jp.iterrows():
if rows['槽值1'] is not np.NaN:
slot_name = (rows["意图"] +"-"+ rows["槽值1"].split(":")[0])
SLOT_LIST.add(slot_name)
if rows['槽值2'] is not np.NaN:
slot_name = (rows["意图"] +"-"+ rows["槽值2"].split(":")[0])
SLOT_LIST.add(slot_name)
for idx,rows in train_en.iterrows():
if rows['槽值1'] is not np.NaN:
slot_name = (rows["意图"] +"-"+ rows["槽值1"].split(":")[0])
SLOT_LIST.add(slot_name)
if rows['槽值2'] is not np.NaN:
slot_name = (rows["意图"] +"-"+ rows["槽值2"].split(":")[0])
SLOT_LIST.add(slot_name)
for idx,rows in train_cn.iterrows():
if rows['槽值1'] is not np.NaN:
slot_name = (rows["意图"] +"-"+ rows["槽值1"].split(":")[0])
SLOT_LIST.add(slot_name)
if rows['槽值2'] is not np.NaN:
slot_name = (rows["意图"] +"-"+ rows["槽值2"].split(":")[0])
SLOT_LIST.add(slot_name)
SLOT_LIST = list(set(SLOT_LIST))
SLOT_LIST = sorted(SLOT_LIST)
print(f"intent-slot标签共{len(SLOT_LIST)}个\n分别是{SLOT_LIST}")
然后是转换(字符和对应的数字进行转换):
# 给出的标签均为文本,创建label_map构建映射关系
def get_label_map(label_list):
id2label = dict([(idx, label) for idx, label in enumerate(label_list)])
label2id = dict([(label, idx) for idx, label in enumerate(label_list)])
return id2label, label2id
# 槽位类型、意图类型和隐藏意图类型处理成字典
id2slot, slot2id = get_label_map(SLOT_LIST)
print(slot2id)
加载tokenizer:
tokenizer = AutoTokenizer.from_pretrained("../model/bert-base-multilingual-cased")
config = AutoConfig.from_pretrained("../model/bert-base-multilingual-cased")
在构建样本label,参考了上面的baseline。简单来说就是根据输入的token来构建对应位置的实体label。
代码:
def en_slot_tokenizer_gen(texts,label=None,slot_label=None,slot_value=None,modeSlot=True):
# 使用tokenizer编码原始文本和slot值
new_texts = list(tokenizer.convert_ids_to_tokens(tokenizer(texts)['input_ids']))
new_slots = np.full(shape=len(new_texts),fill_value='O',dtype='object')
# 如果是无slot的文本直接返回编码后的全O标签,有slot则使用新的序列进行搜索生成新slot序列
if modeSlot is False:
return new_slots
else:
new_slot_value = tokenizer.convert_ids_to_tokens(tokenizer(slot_value)['input_ids'])[1:-1]
s = new_texts.index(new_slot_value[0])
e = s + len(new_slot_value)
new_slots[s:e] = [(label+"-"+slot_label)] * len(new_slot_value)
return new_slots
# 根据本地文件格式和上述方式定义数据读取生成器
def change2slot(df,istrain=True):
# 是否是训练集,否则仅返回文本,是则分会文本和标签
text_l = []
intent_l = []
slot_l = []
if istrain:
for idx,rows in tqdm(df.iterrows(),total=df.shape[0]):
text = rows['原始文本']
intents = rows['意图']
# 使用langid判定文本属于哪一种语言
language = langid.classify(text)[0]
if language == 'en':
texts = text
slots = en_slot_tokenizer_gen(texts,modeSlot=False)
else:
textsl = [i for i in text]
texts = text
slots = np.full(shape=len(textsl),fill_value='O',dtype='object')
# 排除槽值为空的情况
slot_cols = ['槽值1','槽值2']
if rows['槽值1'] is np.NaN:
slot_cols.remove('槽值1')
if rows['槽值2'] is np.NaN:
slot_cols.remove('槽值2')
# 根据槽值字段列表存在的情况对槽值进行处理
if len(slot_cols) > 0:
for solt_col in slot_cols:
# 获取 槽值标签 和 槽值内容
slot_value = rows[solt_col].split(":")[1]
slot_label = rows[solt_col].split(":")[0]
# 若是英语则调用tokenizer进行处理,非英文则搜索对应槽值找到起始位置和结束为止生成序列标记
if language == 'en':
slots = en_slot_tokenizer_gen(texts,intents,slot_label,slot_value,modeSlot=True)
else:
s = text.index(slot_value)
e = s + len(slot_value)
slots_name = (intents+"-"+slot_label)
slots[s:e] = [slots_name] * len(slot_value)
# 对于非英文语言,需要在首尾为[CLS][SEP]补充添加"O"
if language != 'en':
slots = ['O'] + slots.tolist() + ['O']
# yield {
# "words":texts,
# "intents":intents,
# "slots":slots,
# }
text_l.append(texts)
intent_l.append(intents)
slot_l.append(slots)
else:
for idx,rows in df.iterrows():
text_l.append(txt)
# yield {
# 'words': rows['原始文本'],
# }
return text_l, intent_l, slot_l
数据还是和任务6一样train_df;使用change2slot这个函数进行label的标记和数据转换:
tl, il, sl = change2slot(train_df)
sl_n = []
for i in range(len(sl)):
tmo = []
for j in range(len(sl[i])):
tmo.append(slot2id[sl[i][j]])
sl_n.append(tmo)
剩下的和任务6差别不大了,定义模型,训练即可。
在这里,我采用的是直接用transfomers库的bert,暂时不添加其他层(这个留给后面改进吧,如果有时间的话😂)
from torch.utils.data import Dataset, DataLoader, TensorDataset
import torch
from torch import nn
# 数据集读取
class MyDataset(Dataset):
def __init__(self, encodings, slot):
self.encodings = encodings
self.slot = slot
# 读取单个样本
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
item['label'] = torch.tensor(self.slot[idx] + [0] * (len(item['input_ids'])-len(self.slot[idx])))
return item
def __len__(self):
return len(self.slot)
train_encoding = tokenizer(tl[:-500], truncation=True, padding='max_length', max_length=70)
val_encoding = tokenizer(tl[-500:], truncation=True, padding='max_length', max_length=70)
train_dataset = MyDataset(train_encoding, sl_n[:-500])
val_dataset = MyDataset(val_encoding, sl_n[-500:])
import torch
from transformers import BertForTokenClassification, AdamW, get_linear_schedule_with_warmup
model = BertForTokenClassification.from_pretrained('../model/bert-base-multilingual-cased', num_labels=11)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)
optim = AdamW(model.parameters(), lr=5e-5)
total_steps = len(train_loader) * 1
scheduler = get_linear_schedule_with_warmup(optim,
num_warmup_steps = 0, # Default value in run_glue.py
num_training_steps = total_steps)
def train():
model.train()
total_train_loss = 0
iter_num = 0
total_iter = len(train_loader)
for idx, batch in enumerate(train_loader):
optim.zero_grad()
input_ids = batch['input_ids'].to(device)
# print(input_ids.shape)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
# print(outputs.shape)
# loss = outputs[0]
loss = outputs.loss
if idx % 20 == 0:
with torch.no_grad():
# 64 * 7
print((outputs[1].argmax(2).data == labels.data).float().mean().item(), loss.item())
total_train_loss += loss.item()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optim.step()
scheduler.step()
iter_num += 1
if(iter_num % 100==0):
print("epoth: %d, iter_num: %d, loss: %.4f, %.2f%%" % (epoch, iter_num, loss.item(), iter_num/total_iter*100))
print("Epoch: %d, Average training loss: %.4f"%(epoch, total_train_loss/len(train_loader)))
def validation():
model.eval()
total_eval_accuracy = 0
total_eval_loss = 0
for idx, batch in enumerate(val_dataloader):
with torch.no_grad():
input_ids = batch['input_ids'].to(device)
# print(input_ids.shape)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
# print(input_ids)
outputs = model(input_ids, attention_mask=attention_mask,labels=labels)
loss = outputs.loss
logits = outputs[1]
total_eval_loss += loss.item()
logits = logits.detach().cpu().numpy()
label_ids = labels.to('cpu').numpy()
total_eval_accuracy += (outputs[1].argmax(2).data == labels.data).float().mean().item()
avg_val_accuracy = total_eval_accuracy / len(val_dataloader)
print("Accuracy: %.4f" % (avg_val_accuracy))
print("Average testing loss: %.4f"%(total_eval_loss/len(val_dataloader)))
for epoch in range(4):
print("------------Epoch: %d ----------------" % epoch)
train()
validation()
训练好后,就是预测数据了。
预测的代码:
pred_slots1 = []
pred_slots2 = []
def predcit(s):
item = tokenizer([s], truncation=True, padding="max_length", max_length=70) # 加一个list
with torch.no_grad():
input_ids = torch.tensor(item['input_ids']).to(device).reshape(1, -1)
attention_mask = torch.tensor(item['attention_mask']).to(device).reshape(1, -1)
labels = torch.tensor([0] * attention_mask.shape[1]).to(device).reshape(1, -1)
length_labels = [len([i for i in i_input if i != tokenizer.pad_token_id]) for i_input in input_ids][0]
outputs = model(input_ids, attention_mask, labels)
outputs = outputs[0].data.cpu().numpy()
# print(length_labels)
outputs = outputs[0].argmax(1)[1:-1]
# print(outputs)
ner_result = ''
ner_flag = ''
if outputs.sum() == 0:
pred_slots1.append(np.NAN)
pred_slots2.append(np.NAN)
else:
# 使用langid判定文本属于哪一种语言
language = langid.classify(s)[0]
# if language != 'en':
text_input_ids = input_ids.cpu().numpy()[0][2:length_labels]
# print(text_input_ids)
slot_label = outputs[1:length_labels-1]
slot_name = [id2slot[i] for i in slot_label]
slot_dict = {slot:[] for slot in set(slot_name) if slot!='O'}
for word_id,tag in zip(text_input_ids,slot_name):
if tag != 'O':
slot_dict[tag].append(word_id)
for slot in slot_dict.keys():
slot_dict[slot] = tokenizer.convert_tokens_to_string(tokenizer.convert_ids_to_tokens(slot_dict[slot]))
results = ["{}:{}".format(k.split("-")[1],v) for k,v in slot_dict.items()]
# print(slot_dict)
pred_slots1.append(results[0])
if len(results) > 1:
pred_slots2.append(results[1])
else:
pred_slots2.append(np.NAN)
# predcit(train_df['原始文本'].tolist()[-450])
for i in range(len(test_jp)):
predcit(test_jp["原始文本"][i])
pred_slots1
这里写得可能比较繁琐一点,简单来说就是得到输出,然后根据tokenizer的顺序来解析出原本的槽值。
得到槽值后,直接拼接任务6 的结果即可。
步骤2:将步骤1预测的结果文件提交到比赛,截图分数
将结果提交后:
从auc从65%提升到74%,结果还可以。当然还有很大的优化空间了。
后记
到这里,其实这篇博客算结束了,后面的改进,如果我真的有时间去做并且有效果的话,到时我再回来添加点内容吧;但是打卡学习的角度来看,应该已经完成了。