NLP (自然语言处理)攻略 - Intent Classification

任务简介

意图分析, 表示 ⇒ 输入文字叙述, 要分类文字属于何种意图

sample:

"i dont like my current insurance plan and want a new one" ⇒ insurance_change

"when will my american express credit card expire" ⇒ expiration_date

“how would i get to city hall via bus” ⇒ directions

大纲

系统分析概念统整简单解法(利用 transformer 完成)困难解法(从头利用 pytorch 手刻)

系统分析

题意: 输入文字叙述, 分类文字属于何种意图

要完成一个程式, 我想第一步应该是釐清这支程式的抽象, 先定义清楚我们究竟要完成什么

所以我画了下面这张图

http://img2.58codes.com/2024/20131164dbYXbvSuOn.png

可以得知我们的目标是训练出这样的 AI 模型

训练资料连结:

https://github.com/leon123858/ADL2022/tree/main/HW1/data/intent

接着就可以回头看看我们的训练资料来从头想想, 我们该如何完成训练

打开训练资料如下

[  {    "text": "how long should i cook steak for",    "intent": "cook_time",    "id": "eval-0"  },  {    "text": "please tell me how much money i have in my bank accounts",    "intent": "balance",    "id": "eval-1"  },  {    "text": "what is the gas level in my gas tank",    "intent": "gas",    "id": "eval-2"  },...]

可以看见每个句子都被分类到对应的某种意图(即为分类)

此时我们可以透过简单的 python 脚本大概去了解总共有几种意图

"""load json and check intent count"""import jsonwith open('./train.json', encoding='utf8') as f:    data = json.load(f)    intent_set = set()    for item in data:        intent_set.add(item['intent'])    print(len(intent_set)) # 150

自此, 对整个模型的样貌, 我们有了更清晰明确的认识, 如下图, 其中句子在150个分类中各自的机率可以用长度为150的数字阵列来表示

http://img2.58codes.com/2024/20131164C0n6cK76ug.png

因为实务上文字无法直接输入模型, 所以我们要用 text encoding 这类的演算法把文字转换成数字, 毕竟 AI 的本质就是一个超大型“矩阵”, 模型的推测其实就是矩阵运算的结果, 模型的训练其实就是矩阵参数的调适, 基于以上理解, 画出以下更明确设计图

http://img2.58codes.com/2024/20131164QHOTmfeHf7.png

该有的流程都清楚后我们就来开始写程式一步一步把这个 AI 任务完成吧!

概念统整

提出如何手刻模型前我想先试着以我自己的理解来聊聊这项任务的本质, 希望可以协助读者后续的理解, 解释过程做了大量的简化, 只是为了协助理解, 有些细节不是特别合理

首先要有一个概念 AI 模型的本质其实就是连续互相乘法加法的多个超大型矩阵, 所谓的参数数量其实就是这些矩阵内部的元素, 所以不管要完成的任务有多困难, 本质上我们都是在试着创建出一个函数, 在给定特定的输入后取得我们想要的输出, 因为参数的数量极大(函数极为複杂), 所以这个函数理论上可以解决所有问题。

回到实务面来说, 我们必须要有一种标準化的逻辑来调整函数内的参数, 这就是我们所说的训练模型

最简单的训练方法就是把输入丢到函数中查看输出, 如果输出不对, 那就随机调整函数(模型)内的参数, 调整到某一组随机参数可以在所有已知的输入下输出对应的输出, 以下举一个简单的例子

假设我们想要训练一个简单的模型可以输入两个整数, 输出对应的加法结果, 那我们可以创建一个模型(矩阵)如下, 仅有 2 个参数

http://img2.58codes.com/2024/20131164O5qbBegCN1.png

当输入两个整数 5,7 答案明显是错的

http://img2.58codes.com/2024/20131164kb2IIBnPrk.png

但此时如果好死不死, 刚好随机出 1, 1 这组参数

http://img2.58codes.com/2024/20131164xCDDNsoV64.png

之后这种两数字加法的 task 都可以正确地完成了

当然以上的方法有很大的问题就是当参数足够多时, 随机出正确参数的可能性极低, 所以我们必须要有一种方法可以协助我们微调参数, 可以使我们每一次的训练都能更接近正确的参数一点

这边就用到 Loss function 和 optimizer 的概念

loss function 有很多变形, 但最基本的概念就是评价当前的答案和正确答案有多少落差, 以上方的範例来说, 我们可以定义 loss function 是 真实答案-输出 的值, 如此当答案正确时 loss 为 0, 答案差很多时 loss 很大

optimizer 可以说成是一种调参的工具, 可以根据 loss 值来决定各个参数要怎么调整

回到上面的例子, 我们可以定义 optimizer 是把 loss * 0.1 随机加到上下任一个参数

所以就会有以下的训练过程:

参数 0,0 ⇒ loss 12 ⇒ new 参数 1.2 , 0参数 1.2,0 ⇒ loss 6 ⇒ new 参数 1.2, 0.6参数 1.2, 0.6 ⇒ loss 1.8 ⇒ new 参数 1.2, 0.78参数 1.2, 0.78 ⇒ loss -0.54 ⇒ new 参数 1.146, 0.78

可以发现 loss 很明确地不断下降, 要是多几组训练资料, 很快就可以产生一组类似 1, 1 的参数了

接着观察上面的训练过程, 可以发现随着 loss 的下降, optimizer 的策略好像开始不合时宜, 当 loss 已经很接近 0 时, 直接把 loss 乘 0.1 随机加好像太多了, 这时就有了 scheduler 的用武之地

scheduler 可以在给定的条件下微调 optimizer 的优化参数, 例如当训练 10 次后改成 loss * 0.05 之类

以上我们描述了几个重要的概念 包含: model , loss function , optimizer, scheduler

他们都是每一笔资料训练过程中的重要组成, 接着人类开始思考一次一笔资料训练会不会太慢, 于是衍伸出了 dataset , dataLoader 的概念

dataset 把资料整理成一笔一笔的形式, dataLoader 把资料多笔结合成一组, 用一组一组的形式来训练, 这边一组资料, 我们喜欢用 batch 称呼, 用上面做範例

dataset 可以整理出这样的东西

输入: [ 1,8 ] 输出: [9]输入: [ 2,6 ] 输出: [8]输入: [ 9,6 ] 输出: [15]输入: [ 22,18 ] 输出: [40]

dataLoader 若把一组 (batch) 设为 2 笔资料, 可以整理出这样的输出

batch1输入: [ 1,8 ] 输出: [9]输入: [ 2,6 ] 输出: [8]batch2输入: [ 9,6 ] 输出: [15]输入: [ 22,18 ] 输出: [40]

如此一来 optimizer 可以计算一个 batch 的 loss 总和, 再统一调整, 如此一来因为可以平行化, 也减少调整次数, 训练速度自然提升

最终, 我们利用上方提到的概念, 撰写一个伪代码来完成这个 Intent Classification 的任务

把资料整理进 dataset把 dataset 放入 dataLoader 且设定 batch 相关参数设计模型架构, 创建模型(model)设定 loss function, optimizer, scheduler 作为训练过程的工具训练 (Loop)从 dataLoader 加载一个 batch 的数据一笔一笔放入模型中取得输出后与正确答案比较, 计算 loss (利用 loss function)整合所有 loss利用 optimizer 微调参数检测, 如果有给定情况, 利用 scheduler 调整 optimizer 参数预测把一笔输入编码放入训练好的模型产生输出根据输出的阵列推论结果

简单解法(利用 transformer 完成)

src code:

https://github.com/leon123858/ADL2022/blob/main/HW2/src/bonus/train_intent.ipynb

可以把上方程式码贴至 google colab 即可顺利运行, 记得把压缩过后的资料放在相同目录中喔

以下一格格解释做了什么

首先下载相依套件

!pip3 install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl!pip3 install torchvision!pip3 install pickle!pip3 install datasets!pip3 install transformers

其中 torch 是来自 pytorch https://pytorch.org/

是完成 AI 运算的底层模型

datasets 则是专门拿来加载资料的组件

transformers 则是对 pytorch 的封装 ,让我们可以直接从抽象层完成模型的训练, 不用理解模型底层的架构

接着解压缩训练资料, 上方连结有提供, 可以压缩后上传 colab

from zipfile import ZipFilefile_name = "intent.zip"with ZipFile(file_name, 'r') as zip:  zip.extractall()  print('Done')

利用 datasets 加载训练用资料

from datasets import load_datasetdata_files = {}extension = ""data_files["train"] = "intent/train.json"data_files["eval"] = "intent/eval.json"extension = "intent/train.json".split(".")[-1]datasets = load_dataset(    extension,    data_files=data_files)

把资料整理成要的格式存好

# 训练时输入的句子train_texts = [item["text"] for item in datasets["train"]]# 训练时输入的句子对应的意图train_labels = [item["intent"] for item in datasets["train"]]dev_texts = [item["text"] for item in datasets["eval"]]dev_labels = [item["intent"] for item in datasets["eval"]]# 所有可能的句子分类(意图)labels = list(set(train_labels))len(labels) # 150 种意图# 意图的编号与返身idx2label = labelslabel2idx = {k:idx for idx,k in enumerate(labels)}

创建资料集提供训练

以下是 pytorch 的标準用法, 需要继承以及 overwrite 下面这三种方法

以下分别描述个要做什么

import torch# 训练时从中取出训练用资料以及对应的答案class ClassificationDataset(torch.utils.data.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['label'] = label2idx[self.labels[idx]]        return item# 类别个数    def __len__(self):        return len(self.labels)

正确率计算函数

训练时透过模型可以推算在 150 个分类中的机率, 取出最高的比较是否和答案相符合

from sklearn.metrics import accuracy_scoredef compute_metrics(pred):    labels = pred.label_ids    preds = pred.predictions.argmax(-1)    acc = accuracy_score(labels, preds)    return {        'accuracy': acc}

开始训练

from transformers import AutoTokenizer, Trainer, TrainingArguments, AutoModelForSequenceClassification# 要用哪一种模型, 可以到 https://huggingface.co/ 找model_ids = ["prajjwal1/bert-tiny"]accuracies = []for model_id in model_ids:        print(f"*** {model_id} ***")# 创建 encoder    tokenizer = AutoTokenizer.from_pretrained(model_id)    # 创建模型(引用别人已经完成的模型)model = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=len(labels))# 利用 encoder 编码输入文字成为数字阵列(向量)    train_texts_encoded = tokenizer(train_texts, padding=True, truncation=True, return_tensors="pt")    dev_texts_encoded = tokenizer(dev_texts, padding=True, truncation=True, return_tensors="pt")    # 把编码结果作成资料集训练要用    train_dataset = ClassificationDataset(train_texts_encoded, train_labels)    dev_dataset = ClassificationDataset(dev_texts_encoded, dev_labels)    # 设置训练参数, 可查看 https://huggingface.co/docs/transformers/main_classes/trainer#transformers.TrainingArguments    training_args = TrainingArguments(        output_dir='./results',        num_train_epochs=8,        per_device_train_batch_size=16,        per_device_eval_batch_size=64,        warmup_steps=int(len(train_dataset)/16),        weight_decay=0.01,        logging_dir='./logs',        evaluation_strategy="steps",        eval_steps=50,        save_steps=50,        save_total_limit=10,        load_best_model_at_end=True,        no_cuda=False    )# 开始训练    trainer = Trainer(        model=model,        args=training_args,        compute_metrics=compute_metrics,        train_dataset=train_dataset,        eval_dataset=dev_dataset,    )    trainer.train()

如此即可顺利完成系统, 得出语句意图

# 取得预测结果test_results = trainer.evaluate(test_dataset)# 正确率test_results["eval_accuracy"]

按照前方 datasets 的用法, 放入想要预测的资料(假设叫 test_dataset), 用上方这句可以产生预测结果

困难解法(从头利用 pytorch 手刻)

总有些人会觉得全部都调用套件没有办法真正理解自己做了什么, 以下提供给那些想要挑战自我的人

source code:

https://github.com/leon123858/ADL2022/tree/main/HW1

可以先直接试跑

# 下载预训练模型bash download_glove.sh# 準备输入资料的编码以及 encoderpython preprocess_intent.py**#** 训练****python train_intent.py --recover=True --hidden_size=512 --schedule=0.5 --num_epoch=15 --batch_size=64 --lr=1e-5 --dropout=0.1 --num_layers=3**#** 预测****python test_intent.py --test_file './data/intent/test.json' --ckpt_path ckpt/intent/best.pt --pred_file 'pred.intent.csv' --num_layers=3 --hidden_size=512"

在 download_glove 中我下载了来自 https://nlp.stanford.edu/projects/glove/ 的训练结果

详细的原理可以自行查阅

在 preprocess_intent 中我统整了所有输入的句子中出现的词彙以及意图, 最后再把 glove 的下载结果瘦身成刚好我会遇到的词彙并且打包成一个 encoder

在 train_intent 中我引用了自定义的 dataset / model 完成了训练

值得一提的是自定义 modal 的写法

因为是语言模型, 在模型最前面加了一层外部的 encoder 来把文字编码

# 继承 torch.nn.Moduleclass SeqClassifier(torch.nn.Module):    def __init__(        self,        embeddings: torch.tensor,        hidden_size: int,        num_layers: int,        dropout: float,        bidirectional: bool,        num_class: int,    ) -> None:        super(SeqClassifier, self).__init__()        # 加载外部的 encoder 作为模型的其中一层        self.embed = Embedding.from_pretrained(embeddings, freeze=False)        # 利用 RNN 运算每个句子        self.rnn = RNN(input_size=self.embed.embedding_dim, hidden_size=hidden_size, num_layers=num_layers,                       nonlinearity='relu', dropout=dropout, bidirectional=bidirectional, batch_first=True)        # 最后添加线性层, 把输出矩阵转换成长度 150 的向量self.out_layer = Linear(            hidden_size*(2 if bidirectional == True else 1), num_class)# 定义每笔数据的运算, 多个矩阵的乘法, 重点在维度要对好    def forward(self, batch, IS_MPS=False) -> Dict[str, torch.Tensor]:        # TODO: implement model forward        data = batch['data'].clone().to('mps' if IS_MPS else 'cpu')        # dim above: batch_size * string_len        embed_out = self.embed(data)        # dim above: batch_size * string_len * word_vector_len        rnn_out, _ = self.rnn(embed_out)        # dim above: batch_size * string_len * hidden_size * (2 if 双向)        encode_out = torch.mean(rnn_out, 1)        # dim above: batch_size * hidden_size * (2 if 双向)        final_out = self.out_layer(encode_out)# dim above: batch_size * num_class        return final_out

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章