赞
踩
文本分类是将文本分成不同类别的过程。SpaCy通过textcat组件能够方便地进行自定义文本结构化分类。
文本分类通常用于对影评、酒店服务评论、新闻数据、文本主题等进行分类,或根据投诉类型对客户支持电子邮件进行分类等情况。对于实际案例,训练自定义文本分类模型能够提高分类准确率。本文将向您展示如何使用spaCy库构建自定义文本分类器。本文的案例是基于《火电厂辅机运行规程》中的小标题进行分类的。
我们在从亚马逊买了一本书之后会填写一份评价反馈。这些评论会帮助商家分析问题并改进服务。我们来看看这个过程:有数百万条评论是由客户填写的,有没有可能手动浏览每一条评论,看看是赞赏还是否定?
当然,不!第一步是将所有审查分为积极和消极两类。然后,你可以很容易地分析有多少人不满意以及为什么不满意。将文本分类为不同 组/标签 的过程称为文本分类。
文本分类的实现方法多种多样。我们将使用spaCy对文本进行分类。spaCy是一个非常流行的NLP库,它提供了最先进(state-of-the-art)的组件。对于实际应用,最好使用经过训练的自定义模型进行分类。下一节将首先介绍什么是定制模型,以及为什么我们需要定制。
##1 什么是自定义文本分类器模型?
假设你有一大堆电影评论/客户评论。你希望把每一个评论都分为正面的或负面的。如果使用默认的spaCy分类器,结果可能不是很好。但是,如果自己收集电影/客户评论的标记数据集,并在此基础上培训自己的模型呢?
结果会更好更准确!你可以通过训练自定义文本分类器来实现。首先在标记好数据集上对它进行训练,并为我们在类似上下文中的使用它做好准备。这非常有用,尤其是在数据量大的情况下,效果更好。
在接下来的部分中,我们将逐步探索如何在spaCy中训练自定义文本分类模型。
spaCy是一个用于执行NLP任务(如分类)的高级库。spaCy之所以备受青睐,一个重要原因是它允许轻松地构建或扩展文本分类模型。我们将使用这个功能进行文本分类。
接下来,我将用一个真实的例子演示如何训练文本分类器。假设你有文本数据,其中包含文本和分类标记。我们的任务是使用这些数据并训练我们的模型。最后,模型应该能够将一个新的没见过的文本上给出正确的分类标记。
将数据集读入CSV并查看内容。对于我们的任务,我们只需要2列。包含文本和分类标签栏。让我们将这两列提取到一个Dataframe中。
# Import pandas & read csv file
import pandas as pd
cat_set=pd.read_csv("./data/cat_set.csv")
# 提取列,并查看dataframe
cat_set = cat_set[['TEXT','CAT']]
cat_set.head(10)
- | TEXT | CAT |
---|---|---|
0 | 润滑油净化装置的停运 | S |
1 | 汽动给水泵组事故处理 | E |
2 | 启动锅炉运行调整的主要任务 | M |
3 | 湿式搅拌机的启动 | R |
4 | 一次风机并列 | O |
5 | 引风机启动 | R |
6 | 高压流化风机启动前的检查 | B |
7 | 主机冷油器切换 | O |
8 | 真空系统投运 | R |
9 | 一次风机停止(两台风机运行停止一台) | S |
CAT标记说明:
标记 | 英文说明 | 中文说明 |
---|---|---|
A | After | 启动前 |
B | Before | 停运后 |
C | Common | 通用规定 |
E | Emergency | 事故处理 |
M | Maintain | 运行维护 |
O | Opertation | 操作 |
R | Run | 启动 |
S | Stop | 停运 |
通过上面的数据可以看到每个文本对应相应的标签。我们需要训练自定义模型,让其新的在训练数据中未出现过的文本进行分类。
我们从训练集中得到的原始数据。当我们使用spaCy时,为了训练我们的模型,我们导入spaCy包。导入后,可以加载一个预先训练好的模型,如“zh_core_web_sm”。稍后我们将向这个模型添加/修改文本分类器。对于任何spaCy模型,都可以通过 pipe_names 方法查看当前管道中存在的管道组件。
import spacy
nlp = spacy.load('zh_core_web_sm')
nlp.pipe_names
输出:
[‘tagger’, ‘parser’, ‘ner’]
可以看出它没有文本分类器。所以,我们需要将spaCy的内置textcat管道组件添加到管道中,用于文本分类。使用add_pipe()方法
text_cat=nlp.create_pipe( "textcat", config={"exclusive_classes": True, "architecture": "simple_cnn"})
nlp.add_pipe(text_cat, last=True)
nlp.pipe_names
这样当前管道就变为:
[‘tagger’, ‘parser’, ‘ner’, ‘textcat’]
现在,我们将用我们的数据集训练textcat。
首先,需要向管道组件添加所需的标签。使用add_label函数将这些标签添加到textcat。
for label in ["A","B","C","E","M","O","R","S"]:
textcat.add_label(label)
默认textcat模型已经就绪,接下来只需要准备所需格式的数据。
可以编写一个load_data()函数,它将元组列表作为输入。每个元组包含文本和标签值。下面的代码演示了如何将我们的训练数据集转换成所需的格式:
cat_set['tuples'] = cat_set.apply(lambda row: (row['TEXT'],row['CAT']), axis=1)
train =cat_set['tuples'].tolist()
train[:10]
结果:
[(‘配料’, ‘O’), (‘当锅筒压力到0.3~0.4MPa时,进行如下工作:’, ‘O’), (‘扇区冷却三角手动充水’, ‘O’), (‘转速自动控制方式’, ‘O’), (‘扇区冷却三角程控充水步序:’, ‘O’), (‘尿素溶液输送和储存’, ‘O’), (‘扇区冷却三角程控充水’, ‘O’), (‘油罐油温控制’, ‘O’), (‘空预器事故停机’, ‘O’), (‘小机抽真空与主机同时进行,小机冲转前送轴封:’, ‘O’)]
接下来,您可以将train数据作为输入传递给load_data()函数。
load_data()函数执行以下函数:
使用随机。随机()此函数防止任何基于示例顺序的训练。
对于输入数据中的每个元组,根据标签值指定相应类别,并存储在cats中
80%的输入数据将用于培训,20%用于评估。可以使用split参数更改此比例。
定义函数后,将元组列表传递给上述函数。该函数将返回文本和CAT,用于训练和评估。可以使用load_data()函数来获得最终的训练数据,如下面的代码所示。
def load_data(train, split=0.8): import random import pandas as pd train_data =cat_set['tuples'].tolist() # Shuffle the data random.shuffle(train_data) texts, labels = zip(*train_data) # get the categories for each review categories = ["A","B","C","E","M","O","R","S"] cats = [] for y in labels: cat = {category: 0 for category in categories} cat[y] = 1 cats.append(cat) # Splitting the training and evaluation data split = int(len(train_data) * split) return (texts[:split], cats[:split]), (texts[split:], cats[split:]) # Calling the load_data() function (train_texts, train_cats), (dev_texts, dev_cats) = load_data(train) # Processing the final format of training data train_data = list(zip(train_texts,[{'cats': cats} for cats in train_cats])) train_data[:2]
我们得到最终期望的训练数据格式:
[(‘冲转、升速注意事项:’, {‘cats’: {‘A’: 0, ‘B’: 0, ‘C’: 1, ‘E’: 0, ‘M’: 0, ‘O’: 0, ‘R’: 0, ‘S’: 0}}), (‘炉前燃油系统启动’, {‘cats’: {‘A’: 0, ‘B’: 0, ‘C’: 0, ‘E’: 0, ‘M’: 0, ‘O’: 0, ‘R’: 1, ‘S’: 0}})]
至此,我们已经准备好了所需格式的训练数据,并将其存储在train_data变量中。同时,我们在textcat中得到了模型的文本分类器组件。所以,可以继续在我们的train_data上训练textcat。但是,是不是还少点什么?
对于我们要训练的模型,检查其是否符合我们的预期是很重要的。这一步即是评估模型。这是一个可选的步骤,但强烈建议为选择这一步。load_data()函数会将原始数据的大约20%分割出来进行模型评估。我们将用这个来测试模型的训练效果。
所以,让我们来编写一个函数evaluate(),它可以执行这个评估过程。稍后我们将在模型训练中调用这个evaluate()函数来查看模型的性能。
此函数以textcat和评估数据作为输入。对于评估数据中的每个文本,它从模型预测结果中读取score。代码如下:
def evaluate(tokenizer, textcat, texts, cats):
docs = (tokenizer(text) for text in texts)
tp = 0
for i, doc in enumerate(textcat.pipe(docs)):
#获取最大值对应的key
gold = max(cats[i], key=cats[i].get)
ds = doc.cats.items()
h = {}
[h.update({k:v}) for k,v in ds]
predict = max(h, key=h.get)
if gold == predict:
tp += 1
precision = tp / len(texts)
return precision
训练数据存储在train_data变量中,组件存储在 texcat 中。
在开始训练之前,需要禁用除 textcat 之外的其他管道组件。这是为了防止其他组件在训练时受到影响。通过disable_pipes()方法完成。接下来,使用begin_training()函数返回优化器。
使用n_iter参数定义在训练数据上迭代模型的次数。在每一次迭代中,我们循环训练数据,并使用spaCy的minibatch和composition helpers将数据划分为多个batch。
spaCy的minibatch()函数将批量返回训练数据。它使用size参数来表示批量大小。您可以使用函数composition()生成size。
函数接受三个输入,分别是start(起始值)、stop(可以生成的最大值)和增长率。size会随着调用次数从start–>stop。
对于每个迭代,通过nlp.update()命令更新。参数如下:
docs:使用一个batch文本作为输入。将每个batch传递给zip方法,该方法将返回一个batch的文本和分类。
drop:这表示drop out率。
losses:字典,保存每个管道组件的损失。创建一个空字典并传递给它。
模型训练完成后,您可以通过调用我们在上一节中定义的evaluate()函数来评估模型所做的预测。
# coding=utf-8 def load_data(split=0.8): import random import pandas as pd cat_set=pd.read_csv("./data/cat_set.csv") cat_set['tuples'] = cat_set.apply(lambda row: (row['TEXT'],row['CAT']), axis=1) train_data =cat_set['tuples'].tolist() random.shuffle(train_data) texts, labels = zip(*train_data) # 为每个样本格式化分类字典 categories = ["A","B","C","E","M","O","R","S"] cats = [] for y in labels: cat = {category: 0 for category in categories} cat[y] = 1 cats.append(cat) # 划分数据集 split = int(len(train_data) * split) return (texts[:split], cats[:split]), (texts[split:], cats[split:]) # 装载数据 (train_texts, train_cats), (dev_texts, dev_cats) = load_data() train_data = list(zip(train_texts,[{'cats': cats} for cats in train_cats])) from spacy.util import minibatch, compounding import spacy nlp = spacy.load('zh_core_web_sm') text_cat=nlp.create_pipe( "textcat", config={"exclusive_classes": True, "architecture": "simple_cnn"}) nlp.add_pipe(text_cat, last=True) for label in ["A","B","C","E","M","O","R","S"]: text_cat.add_label(label) n_iter = 10 # 禁用其他组件 other_pipes = [pipe for pipe in nlp.pipe_names if pipe != 'textcat'] with nlp.disable_pipes(*other_pipes): # 只训练 textcat optimizer = nlp.begin_training() print("Training the model...") print('{:^5}\t{:^5}'.format('LOSS', 'PRECISION')) # 开始训练 for i in range(n_iter): losses = {} batches = minibatch(train_data, size=compounding(4., 32., 1.001)) for batch in batches: texts, annotations = zip(*batch) nlp.update(texts, annotations, sgd=optimizer, drop=0.2, losses=losses) with text_cat.model.use_params(optimizer.averages): score = evaluate(nlp.tokenizer, text_cat, dev_texts, dev_cats) print('{0:.3f}\t{1:.3f}'.format(losses['textcat'], score))
结果如下:
Training the model...
LOSS PRECISION
3.979 0.800
1.143 0.832
0.618 0.853
0.424 0.842
0.293 0.863
0.186 0.842
0.226 0.863
0.187 0.842
0.139 0.832
0.101 0.842
最终这个模型现在可以使用了。
我们来为全新未见过的文本创建一个spaCy doc。它的分类或预测结果将存储在Doc.cats属性。这个Doc.cats属性存储将标签映射到文本类别的评分字典。
#测试模型
texts = ['变频装置操作原则','变频装置送电启动前检查项目','凝泵变频器检修转热备用']
docs = nlp.pipe(texts)
for doc in docs:
print(doc.text)
print(doc.cats)
测试结果:
变频装置操作原则
{'A': 1.4584960581487394e-06, 'B': 4.261386834514269e-07, 'C': 0.9997310042381287, 'E': 1.8897288782682153e-06, 'M': 1.6282596959626972e-07, 'O': 0.00022265917505137622, 'R': 5.221944476829776e-08, 'S': 4.2241339542670175e-05}
变频装置送电启动前检查项目
{'A': 9.86464146990329e-05, 'B': 0.9877699613571167, 'C': 0.009538334794342518, 'E': 0.0017646290361881256, 'M': 1.1881843420269433e-05, 'O': 5.8820318372454494e-05, 'R': 0.0003737462102435529, 'S': 0.0003839542914647609}
凝泵变频器检修转热备用
{'A': 0.568670928478241, 'B': 0.07395660877227783, 'C': 0.0025626164861023426, 'E': 0.21869421005249023, 'M': 0.003099241992458701, 'O': 0.11320023983716965, 'R': 0.0005361264338716865, 'S': 0.019280044361948967}
这三个句子对应的标记模型给出的结果:‘C’, ‘B’ , ‘A’
我们期望的是:‘C’ , ‘B’ , ‘O’
希望通过上述一个简单的例子,你已经了解了如何使用spaCy训练自定义文本分类模型。使用spaCy训练模型在各个领域都有广泛的应用。与此类似,spaCy还可以训练自定义NER模型,这部分将另开新贴进行论述,敬请关注!
P.s.
很遗憾,这个例子是在spaCy 2.3.0下运行的。如果可能,将来我会将其移植到spaCy V3.0下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。