NLP竞赛参与打卡记录:汽车领域多语种迁移学习挑战赛

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上直接用变量名
展示图1

步骤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

任务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)

展示3

步骤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

展示4

任务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用于切分数据集

# 以82的比例来划分训练集和验证集
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的结果可见截图:
展示5

额外思考

其实TfidfVectorizer()和LogisticRegression()是有很多参数提供使用的;但是在简单的测试参数的时候,发现貌似默认的情况会好一点?(当然,没有认真去调参,只是简单加几个参数,看看acc的差别)
代码如下:

# 训练TFIDF和逻辑回归
pipline = make_pipeline(
    TfidfVectorizer(ngram_range=(1,3)),
    LogisticRegression(tol=1e-5)
)

只是简单给个例子,因为参数组合比较多,不放全部组合的例子了
结果见图:
展示6
所以,目前来看,默认情况貌似会更好一丢丢~

步骤3:将步骤2预测的结果文件提交到比赛,截图分数

最终输出的不仅需要意图,很多情况还有具体的参数,比如空调调到多少度。但是这里,先完成打卡,先把这个结果提交一下(从学习的角度来看是可以的,但是如果你想要分数高一点,毕竟是竞赛,可以等任务4,再具体得到而外的输出再一起提交)
展示7

任务4:正则表达式

任务4是使用正则表达式来提取字符串中的槽值,但是这个在不同语言是不一样的,而且对于不同的意图,还要单独处理,相对来说,是不推荐使用的,但是学习是ok的。所以本博客会进行简单的正则使用, 但不会将其用于竞赛中,槽值的提取留到后面几个任务再慢慢调优。

问题

不同的文本,里面的指标是不一样的,比如数值,中文是阿拉伯数字,日语则是中文字符的数字(“一”、“二”等),英文则是one、two……
展示8
展示8
展示9
所以是比较难找到一种通用的字符串处理来应付所有的文本,所以这就是不推荐在竞赛中使用正则表达式来提取关键字的原因。

步骤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

展示10
在这里搜索你想要使用的预训练模型,比如bert,然后选一个点进去
展示11
在右上角,点击use in transformers,里面会有简单的使用案例;但是因为网络的原因,很可能你直接复制粘贴下来,会报错(因为transformers会先在服务器上找到你想要的模型,然后下载到你电脑的缓存中,再进行加载);网络问题导致不能下载的,你可以手动下载;使用如下代码:

from huggingface_hub import snapshot_download 
snapshot_download(repo_id="hfl/chinese-roberta-wwm-ext") #id填你想要下载的模型名称,要和官网上的一致

然后他就会下载到你的缓存目录了(如果库没安装,可以pip安装一下)
展示12
最后它会提示你下载到你电脑的地址,你去找,然后复制到你自己项目的地址,给他新建个文件夹,取个短点的名字(方便你后续加载使用)

然后,就可以根据要求,加载模型了

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]))

展示13

步骤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()

评分截图:
评分0705

任务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%,结果还可以。当然还有很大的优化空间了。

后记

到这里,其实这篇博客算结束了,后面的改进,如果我真的有时间去做并且有效果的话,到时我再回来添加点内容吧;但是打卡学习的角度来看,应该已经完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值