目录
一、文本处理的基本方法
1.1 分词
1.2 命名体实体识别
1.3 词性标注
二、文本张量的表示形式
2.1 one-hot编码
2.2 word2vec 模型
2.2.1 CBOW模式
2.2.2 skipgram模式
2.3 词嵌入word embedding
三、文本数据分析
3.1 标签数量分布
3.2 句子长度分布
3.3 词频统计与关键词词云
四、文本特征处理
4.1 n-gram特征
4.2 文本长度规范
五、文本数据增强
文本语料在输送给模型前一般需要一系列的预处理工作,才能符合模型输入的要求,如:将文本转化成模型需要的张量,规范张量的尺寸等,而且科学的文本预处理环节还将有效指导模型超参数的选择,提升模型的评估指标
一、文本处理的基本方法
1.1 分词
分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符,分词过程就是找到这样分界符的过程
无线电法国别研究
['无线电法', '国别', '研究']
词作为语言语义理解的最小单元,是人类理解文本语言的基础。因此也是AI解决NLP领域高阶任务,如自动问答、机器翻译、文本生成的重要基础环节
- 精确模式分词:试图将句子最精确地切开,适合文本分析
- 全模式分词:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能消除歧义
- 搜索引擎模式分词:在精确模式基础上,对长词再次切分,提高召回率,适合搜索引擎分词
import jieba# 精确模式
def test01():text = '无线电法国别研究'# cut_all默认为Falseobj = jieba.cut(text, cut_all=False)print(obj)# <generator object Tokenizer.cut at 0x0000019CB8CDC148># lcut直接返回分词列表内容words = jieba.lcut(text, cut_all=False)print(words)# ['无线电', '法国', '别', '研究']# 全模式分词
def test02():text = '无线电法国别研究'obj = jieba.cut(text, cut_all=True)print(obj)# <generator object Tokenizer.cut at 0x000001E6304AC148>words = jieba.lcut(text, cut_all=True)print(words)# ['无线', '无线电', '法国', '国别', '研究']# 搜索引擎模式分词
def test03():text = '无线电法国别研究'obj = jieba.cut_for_search(text)print(obj)# <generator object Tokenizer.cut_for_search at 0x000001CCE75DA748>words = jieba.lcut_for_search(text)print(words)# ['无线', '无线电', '法国', '别', '研究']if __name__ == '__main__':# test01()# test02()test03()
使用用户自定义词典
- 添加自定义词典后,jieba能够准确识别词典中出现的词汇,提升整体的识别准确率
- 词典格式:一行分三部分,词语、词频(可略)、词性(可略),空格隔开,顺序不可颠倒
云计算 5 n
李小福 2 nr
easy_install 3 eng
好用 300
韩玉赏鉴 3 nz
八一双鹿 3 nz
import jiebadef main():text = "八一双鹿更名为八一南昌篮球队!"# 精确模式words = jieba.lcut(text)print(words)# ['八', '一双', '鹿', '更名', '为', '八一', '南昌', '篮球队', '!']# 自定义词表jieba.load_userdict("./data/userdict.txt")words = jieba.lcut(text)print(words)# ['八一双鹿', '更名', '为', '八一', '南昌', '篮球队', '!']if __name__ == '__main__':main()
1.2 命名体实体识别
- 命名实体:通常将人名、地名、机构名等专有名词统称命名实体.。如:周杰伦、黑山县、孔子学院、24辊方钢矫直机
- 命名实体识别(Named Entity Recognition,简称NER)就是识别出一段文本中可能存在的命名实体
鲁迅, 浙江绍兴人, 五四新文化运动的重要参与者, 代表作朝花夕拾.
==>
鲁迅(人名) / 浙江绍兴(地名)人 / 五四新文化运动(专有名词) / 重要参与者 / 代表作 / 朝花夕拾(专有名词)
同词汇一样,命名实体也是人类理解文本的基础单元,是AI解决NLP领域高阶任务的重要基础环节
1.3 词性标注
- 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果,常见的词性有14种,如:名词、动词、形容词等
- 词性标注(Part-Of-Speech tagging,简称POS)就是标注出一段文本中每个词汇的词性
我爱自然语言处理
==>
我/rr, 爱/v, 自然语言/n, 处理/vnrr: 人称代词
v: 动词
n: 名词
vn: 动名词
词性标注以分词为基础,是对文本语言的另一个角度的理解,因此也常常成为AI解决NLP领域高阶任务的重要基础环节
import jieba.posseg as psegdef main():text = '我爱北京天安门'words = pseg.lcut(text)print(words)# [pair('我', 'r'), pair('爱', 'v'), pair('北京', 'ns'), pair('天安门', 'ns')]if __name__ == '__main__':main()
二、文本张量的表示形式
将一段文本使用张量进行表示,其中一般将词汇为表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示
["人生", "该", "如何", "起头"]
==>
# 每个词对应矩阵中的一个向量
[[1.32, 4,32, 0,32, 5.2],[3.1, 5.43, 0.34, 3.2],[3.21, 5.32, 2, 4.32],[2.54, 7.32, 5.12, 9.54]]
将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作
2.1 one-hot编码
独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数
["改变", "要", "如何", "起手"]
==>
[[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, 0, 1]]
onehot 编码实现:
import joblib
from keras.preprocessing.text import Tokenizervocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}
# 实例化一个词汇映射器对象
tokenizer = Tokenizer(num_words=None, char_level=False)
# 使用映射器拟合现有文本数据
tokenizer.fit_on_texts(vocab)for token in vocab:zero_list = [0] * len(vocab)# 使用映射器转化现有文本数据, 每个词汇对应从1开始的自然数# 返回样式如: [[2]], 取出其中的数字需要使用[0][0]index = tokenizer.texts_to_sequences([token])[0][0] - 1zero_list[index] = 1print(token, '的one-hot编码是:', zero_list)# 使用joblib工具保存映射器, 以便之后使用
tokenizer_path = "./data/Tokenizer"
joblib.dump(tokenizer, tokenizer_path)# 李宗盛 的one-hot编码是: [1, 0, 0, 0, 0, 0]
# 周杰伦 的one-hot编码是: [0, 1, 0, 0, 0, 0]
# 王力宏 的one-hot编码是: [0, 0, 1, 0, 0, 0]
# 陈奕迅 的one-hot编码是: [0, 0, 0, 1, 0, 0]
# 吴亦凡 的one-hot编码是: [0, 0, 0, 0, 1, 0]
# 鹿晗 的one-hot编码是: [0, 0, 0, 0, 0, 1]
one-hot 编码器的使用:
import joblibvocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}
tokenizer_path = "./data/Tokenizer"
tokenizer = joblib.load(tokenizer_path)token = '李宗盛'
# 使用 token 获取 token_index
index = tokenizer.texts_to_sequences([token])[0][0] - 1
# 得到 one-hot 编码
zero_list = [0] * len(vocab)
zero_list[index] = 1
print(token, '的one-hot编码是:', zero_list)
# 李宗盛 的one-hot编码是: [1, 0, 0, 0, 0, 0]
one-hot 编码的优劣势:
- 优势:操作简单,容易理解
- 劣势:完全割裂了词与词之间的联系,在大语料集下,每个向量的长度过大,占据大量内存
- 因为one-hot编码明显的劣势,这种编码方式被应用的地方越来越少,取而代之的是稠密向量的表示方法 word2vec 和 word embedding
2.2 word2vec 模型
word2vec 是一种流行的将词汇表示成向量的无监督训练方法,该过程将构建神经网络模型,将网络参数作为词汇的向量表示,包含 CBOW 和 skipgram 两种训练模式
2.2.1 CBOW模式
Continuous bag of words
给定一段文本语料,再选定某段长度(窗口)作为研究对象,使用上下文词汇预测目标词汇
上图中窗口大小为9,使用前后4个词汇对目标词汇进行预测
过程详解
假设给定的训练语料只有一句话:Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是CBOW模式,所以将使用Hope和set作为输入,can作为输出。在模型训练时, Hope,can,set 等词汇都使用其 one-hot 编码。输入数据的每个 one-hot 编码(5*1)与各自的变换矩阵(即参数矩阵3*5,这里的 3 是指最后得到的词向量维度)相乘之后再相加,得到上下文表示矩阵(3*1)
将上下文表示矩阵与变换矩阵(参数矩阵 5*3,所有的变换矩阵共享参数)相乘,得到 5*1 的结果矩阵,结果矩阵与目标矩阵即 can 的 one-hot 编码矩阵 (5*1) 进行损失的计算,然后更新网络参数完成一次模型迭代
2.2.2 skipgram模式
给定一段用于训练的文本语料,再选定某段长度(窗口)作为研究对象,使用目标词汇预测上下文词汇
图中窗口大小为9,使用目标词汇对前后四个词汇进行预测
过程详解
假设给定的训练语料只有一句话:Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set。因为是skipgram模式,所以将使用can作为输入 ,Hope和set作为输出,在模型训练时 Hope,can,set 等词汇都使用其 one-hot 编码。将 can 的 one-hot 编码与变换矩阵(即参数矩阵3*5,3是指最后得到的词向量维度)相乘,得到目标词汇表示矩阵(3*1)
将目标词汇表示矩阵与多个变换矩阵(参数矩阵5*3)相乘,得到多个 5*1 的结果矩阵,将与 Hope 和 set 对应的 one-hot 编码矩阵(5*1)进行损失计算, 然后更新网络参数完成一次模型迭代
最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3*5),这个变换矩阵与每个词汇的one-hot编码(5*1)相乘,得到的3x1的矩阵就是该词汇的 word2vec 张量表示
2.3 词嵌入word embedding
- 通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间
- 广义的 word embedding 包括所有密集词汇向量的表示方法,如之前学习的 word2vec 可认为是 word embedding 的一种
- 狭义的 word embedding 是指在神经网络中加入的 embedding 层,对整个网络进行训练的同时产生的 embedding 矩阵(embedding层的参数),这个 embedding 矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵
词嵌入层会根据输入的词的数量构建一个词向量矩阵。如:有 100 个词,每个词希望转换成 128 维度的向量,那么构建的矩阵形状即为100 * 128,输入的每个词都对应了矩阵中的一个向量
在 PyTorch 中,可以使用 nn.Embedding 词嵌入层来实现输入词的向量化
- 先将语料进行分词,构建词与索引的映射,可以将这个映射称为词表,词表中每个词都对应了一个唯一的索引
- 然后使用 nn.Embedding 构建词嵌入矩阵,词索引对应的向量即为该词对应的数值化后的向量表示
nn.Embedding 对象构建时,最主要有两个参数:
- num_embeddings 表示词的数量
- embedding_dim 表示用多少维的向量来表示每个词
import torch
import torch.nn as nn
import jiebadef main():text = '北京冬奥的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途。'# 1. 分词words = jieba.lcut(text)print('words:', words)# 2. 构建词表index_to_word = {}word_to_index = {}# 分词去重unique_words = list(set(words))for idx, word in enumerate(unique_words):index_to_word[idx] = wordword_to_index[word] = idx# 3.构建词嵌入层# num_embeddings为词的数量,embedding_dim为词嵌入的维度embedding = nn.Embedding(num_embeddings=len(index_to_word), embedding_dim=4)# 4.文本转为词向量表示for word in words:idx = word_to_index[word]word_vector = embedding(torch.tensor(idx))print('%3s\t' % word, word_vector)if __name__ == "__main__":main()
words: ['北京', '冬奥', '的', '进度条', '已经', '过半', ',', '不少', '外国', '运动员', '在', '完成', '自己', '的', '比赛', '后', '踏上', '归途', '。']北京 tensor([1.1339, 1.1262, 0.6638, 1.5691], grad_fn=<EmbeddingBackward0>)冬奥 tensor([0.2753, 0.3262, 1.7691, 1.2225], grad_fn=<EmbeddingBackward0>)的 tensor([-0.7507, 1.8850, 1.4340, -0.8074], grad_fn=<EmbeddingBackward0>)
进度条 tensor([ 0.5693, -0.0951, -0.4607, 0.0555], grad_fn=<EmbeddingBackward0>)已经 tensor([-0.5726, 0.0812, 1.1051, -0.0020], grad_fn=<EmbeddingBackward0>)过半 tensor([0.0691, 0.1430, 1.9346, 1.4653], grad_fn=<EmbeddingBackward0>), tensor([-1.1009, 0.7833, -0.9021, 1.8811], grad_fn=<EmbeddingBackward0>)不少 tensor([ 0.7913, 0.0890, -0.7459, 1.1473], grad_fn=<EmbeddingBackward0>)外国 tensor([-0.6079, 0.2563, 0.8344, -0.5977], grad_fn=<EmbeddingBackward0>)
运动员 tensor([-1.9587, 0.0995, -1.0728, -0.2779], grad_fn=<EmbeddingBackward0>)在 tensor([-1.6571, -1.2508, -0.8138, -2.6821], grad_fn=<EmbeddingBackward0>)完成 tensor([-1.4124, -0.5624, 1.2548, 0.1708], grad_fn=<EmbeddingBackward0>)自己 tensor([-2.5485, -0.1839, -0.0079, 0.1326], grad_fn=<EmbeddingBackward0>)的 tensor([-0.7507, 1.8850, 1.4340, -0.8074], grad_fn=<EmbeddingBackward0>)比赛 tensor([ 0.3826, -1.8883, 0.5677, 0.1951], grad_fn=<EmbeddingBackward0>)后 tensor([ 0.2585, -1.2181, 1.0165, -0.4775], grad_fn=<EmbeddingBackward0>)踏上 tensor([-1.6933, 1.2826, 0.9993, -0.2306], grad_fn=<EmbeddingBackward0>)归途 tensor([ 3.8709, 0.5133, 0.1683, -0.9699], grad_fn=<EmbeddingBackward0>)。 tensor([-0.9352, -1.2663, -1.5860, -0.2301], grad_fn=<EmbeddingBackward0>)
词嵌入层默认使用的是均值为 0,标准差为 1 的正态分布进行初始化,也可以理解为是随机初始化。这个用来表示词的文本真的能够表达出词的含义吗?
nn.Embedding 中对每个词的向量表示都是随机生成的,当一个词输入进来后,会使用随机产生的向量来表示该词。该词向量参与到下游任务的计算,下游任务计算后,会和目标结果进行对比产生损失。接下来,通过反向传播更新所有的网络参数,就包括了 nn.Embedding 中的词向量表示。这样通过反复的前向计算、反向传播、参数更新,最终每个词的向量表示就会变得更合理
三、文本数据分析
文本数据分析能够有效帮助理解数据语料,快速检查出语料可能存在的问题,并指导之后模型训练过程中一些超参数的选择
后续文章中采用中文酒店评论语料来讲解
sentence label
早餐不好,服务不到位,晚餐无西餐,早餐晚餐相同,房间条件不好,餐厅不分吸烟区.房间不分有无烟房. 0
去的时候 ,酒店大厅和餐厅在装修,感觉大厅有点挤.由于餐厅装修本来该享受的早饭,也没有享受(他们是8点开始每个房间送,但是我时间来不及了)不过前台服务员态度好! 1
有很长时间没有在西藏大厦住了,以前去北京在这里住的较多。这次住进来发现换了液晶电视,但网络不是很好,他们自己说是收费的原因造成的。其它还好。 1
非常好的地理位置,住的是豪华海景房,打开窗户就可以看见栈桥和海景。记得很早以前也住过,现在重新装修了。总的来说比较满意,以后还会住 1
交通很方便,房间小了一点,但是干净整洁,很有香港的特色,性价比较高,推荐一下哦 1
酒店的装修比较陈旧,房间的隔音,主要是卫生间的隔音非常差,只能算是一般的 0
酒店有点旧,房间比较小,但酒店的位子不错,就在海边,可以直接去游泳。8楼的海景打开窗户就是海。如果想住在热闹的地带,这里不是一个很好的选择,不过威海城市真的比较小,打车还是相当便宜的。晚上酒店门口出租车比较少。 1
位置很好,走路到文庙、清凉寺5分钟都用不了,周边公交车很多很方便,就是出租车不太爱去(老城区路窄爱堵车),因为是老宾馆所以设施要陈旧些, 1
酒店设备一般,套房里卧室的不能上网,要到客厅去。 0
...
第一列数据代表具有感情色彩的评论文本,第二列数据 0 或 1,代表每条文本数据是积极或者消极的评论,0代表消极,1代表积极
3.1 标签数量分布
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt# 设置显示风格
plt.style.use('fivethirtyeight')# 分别读取训练tsv和验证tsv
train_data = pd.read_csv("data/cn_data/train.tsv", sep="\t")
valid_data = pd.read_csv("data/cn_data/dev.tsv", sep="\t")# 获得训练数据标签数量分布
sns.countplot(x="label", data=train_data)
plt.title("train_data")
plt.show()# 获取验证数据标签数量分布
sns.countplot(x="label", data=valid_data)
plt.title("valid_data")
plt.show()
在深度学习模型评估中,一般使用ACC作为评估指标,若想将ACC的基线定义在50%左右(随机猜测的准确率是 50%),则需要正负样本比例维持在1:1左右,否则就要进行必要的数据增强或数据删减。上图中训练和验证集正负样本都稍有不均衡,可以进行一些数据增强
3.2 句子长度分布
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt# 设置显示风格
plt.style.use('fivethirtyeight')# 分别读取训练tsv和验证tsv
train_data = pd.read_csv("data/cn_data/train.tsv", sep="\t")
valid_data = pd.read_csv("data/cn_data/dev.tsv", sep="\t")# 在训练数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度
train_data["sentence_length"] = list(map(lambda x: len(x), train_data["sentence"]))
# 在验证数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度
valid_data["sentence_length"] = list(map(lambda x: len(x), valid_data["sentence"]))# 绘制句子长度列的数量分布图
sns.countplot(x="sentence_length", data=train_data)
# 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看
plt.xticks([])
plt.show()
# 绘制dist长度分布图
sns.distplot(train_data["sentence_length"])
# 主要关注dist长度分布横坐标, 不需要绘制纵坐标
plt.yticks([])
plt.show()# 绘制句子长度列的数量分布图
sns.countplot(x="sentence_length", data=valid_data)
# 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看
plt.xticks([])
plt.show()
# 绘制dist长度分布图
sns.distplot(valid_data["sentence_length"])
# 主要关注dist长度分布横坐标, 不需要绘制纵坐标
plt.yticks([])
plt.show()
训练集句子长度分布:
验证集句子长度分布:
通过绘制句子长度分布图,可以得知语料中大部分句子长度的分布范围,因为模型的输入要求为固定尺寸的张量,合理的长度范围对之后进行句子截断补齐(规范长度)起到关键的指导作用。上图中大部分句子长度的范围大致为20-250之间
获取正负样本长度散点分布
# 绘制训练集长度分布的散点图
sns.stripplot(y='sentence_length', x='label', data=train_data)
plt.show()# 绘制验证集长度分布的散点图
sns.stripplot(y='sentence_length', x='label', data=valid_data)
plt.show()
训练集上正负样本的长度散点分布:
验证集上正负样本的长度散点分布:
通过查看正负样本长度散点图,可以有效定位异常点的出现位置,能更准确进行人工语料审查。上图中在训练集正样本中出现了异常点,句子长度近3500左右,需要人工审查
3.3 词频统计与关键词词云
不同词汇总数统计
import jieba
import pandas as pd
# 导入chain方法用于扁平化列表
from itertools import chain# 分别读取训练tsv和验证tsv
train_data = pd.read_csv("data/cn_data/train.tsv", sep="\t")
valid_data = pd.read_csv("data/cn_data/dev.tsv", sep="\t")# 进行训练集的句子进行分词, 并统计出不同词汇的总数
train_vocab = set(chain(*map(lambda x: jieba.lcut(x), train_data["sentence"])))
print("训练集共包含不同词汇总数为:", len(train_vocab))# 进行验证集的句子进行分词, 并统计出不同词汇的总数
valid_vocab = set(chain(*map(lambda x: jieba.lcut(x), valid_data["sentence"])))
print("训练集共包含不同词汇总数为:", len(valid_vocab))# 训练集共包含不同词汇总数为: 12162
# 训练集共包含不同词汇总数为: 6857
获取高频形容词词云
import jieba.posseg as pseg
import pandas as pd
import matplotlib.pyplot as plt
# 导入绘制词云的工具包
from wordcloud import WordCloud
# 导入chain方法用于扁平化列表
from itertools import chaindef get_a_list(text):"""用于获取形容词列表"""# 使用jieba的词性标注方法切分文本,获得具有词性属性flag和词汇属性word的对象# 从而判断flag是否为形容词,来返回对应的词汇r = []for g in pseg.lcut(text):if g.flag == "a":r.append(g.word)return rdef get_word_cloud(keywords_list):# 实例化绘制词云的类, 其中参数font_path是字体路径, 为了能够显示中文,# max_words指词云图像最多显示多少个词, background_color为背景颜色wordcloud = WordCloud(font_path="./data/SimHei.ttf", max_words=100, background_color="white")# 将传入的列表转化成词云生成器需要的字符串形式keywords_string = " ".join(keywords_list)# 生成词云wordcloud.generate(keywords_string)# 绘制图像并显示plt.figure()plt.imshow(wordcloud, interpolation="bilinear")plt.axis("off")plt.show()# 分别读取训练tsv和验证tsv
train_data = pd.read_csv("data/cn_data/train.tsv", sep="\t")
valid_data = pd.read_csv("data/cn_data/dev.tsv", sep="\t")# 获得训练集上正样本
p_train_data = train_data[train_data["label"] == 1]["sentence"]
# 获得训练集上负样本
n_train_data = train_data[train_data["label"] == 0]["sentence"]
# 获得验证集上正样本
p_valid_data = valid_data[valid_data["label"] == 1]["sentence"]
# 获得验证集上负样本
n_valid_data = valid_data[valid_data["label"] == 0]["sentence"]# 对训练集正样本的每个句子的形容词
train_p_a_vocab = chain(*map(lambda x: get_a_list(x), p_train_data))
#print(train_p_n_vocab)
# 获取训练集负样本的每个句子的形容词
train_n_a_vocab = chain(*map(lambda x: get_a_list(x), n_train_data))
# 对验证集正样本的每个句子的形容词
valid_p_a_vocab = chain(*map(lambda x: get_a_list(x), p_valid_data))
#print(train_p_n_vocab)
# 获取验证集负样本的每个句子的形容词
valid_n_a_vocab = chain(*map(lambda x: get_a_list(x), n_valid_data))# 调用绘制词云函数
get_word_cloud(train_p_a_vocab)
get_word_cloud(train_n_a_vocab)
get_word_cloud(valid_p_a_vocab)
get_word_cloud(valid_n_a_vocab)
训练集正样本形容词词云:
训练集负样本形容词词云:
验证集正样本形容词词云:
验证集负样本形容词词云:
根据高频形容词词云显示,可以对当前语料质量进行简单评估,同时对违反语料标签含义的词汇进行人工审查和修正,来保证绝大多数语料符合训练标准。上图中的正样本大多数是褒义词,而负样本大多数是贬义词,但是负样本词云中也存在"好"这样的褒义词,因此可以人工进行审查
四、文本特征处理
文本特征处理包括为语料添加具有普适性的文本特征,如:n-gram特征,以及对加入特征之后的文本语料进行必要的处理,如:长度规范。这些特征处理工作能够有效的将重要的文本特征加入模型训练中,增强模型评估指标
4.1 n-gram特征
给定一段文本序列,其中n个词或字的相邻共现特征即 n-gram 特征,常用的 n-gram 特征是 bi-gram 和 tri-gram 特征,分别对应n为2和3
假设给定分词列表: ["是谁", "敲动", "我心"]对应的数值映射列表为: [1, 34, 21]可以认为数值映射列表中的每个数字是词汇特征除此之外, 还可以把"是谁"和"敲动"两个词共同出现且相邻也作为一种特征加入到序列列表中假设1000就代表"是谁"和"敲动"共同出现且相邻此时数值映射列表就变成了包含 2-gram 特征的特征列表: [1, 34, 21, 1000]这里的"是谁"和"敲动"共同出现且相邻就是bi-gram特征中的一个"敲动"和"我心"也是共现且相邻的两个词汇, 因此也是 bi-gram 特征假设1001代表"敲动"和"我心"共同出现且相邻那么, 最后原始的数值映射列表 [1, 34, 21] 添加了bi-gram特征之后就变成了 [1, 34, 21, 1000, 1001]
提取n-gram特征
n_gram_range = 2def create_n_gram_set(input_list):"""description: 从数值列表中提取所有的 n-gram 特征:param input_list: 输入的数值列表, 可以看作是词汇映射后的列表,里面每个数字的取值范围为[1, 25000]:return: n-gram特征组成的集合>>> create_ngram_set([1, 4, 9, 4, 1, 4]){(4, 9), (4, 1), (1, 4), (9, 4)}"""return set(zip(*[input_list[i:] for i in range(n_gram_range)]))input_list = [1, 3, 2, 1, 5, 3]
result = create_n_gram_set(input_list)
print(result)
# {(3, 2), (1, 3), (2, 1), (1, 5), (5, 3)}
4.2 文本长度规范
一般模型的输入需要等尺寸大小的矩阵,因此在进入模型前需要对每条文本数值映射后的长度进行规范,此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度,对超长文本进行截断, 对不足文本进行补齐(一般使用数字0),这个过程就是文本长度规范
from keras.utils import pad_sequences
text_length = 10def padding_cut(data):"""description: 对输入文本张量进行长度规范:param x_train: 文本的张量表示, 形如: [[1, 32, 32, 61], [2, 54, 21, 7, 19]]:return: 进行截断补齐后的文本张量表示"""# 使用pad_sequences即可完成return pad_sequences(data, text_length)data = [[1, 23, 5, 32, 55, 63, 2, 21, 78, 32, 23, 1], [2, 32, 1, 23, 1]]
res = padding_cut(data)
print(res)
# [[ 5 32 55 63 2 21 78 32 23 1]
# [ 0 0 0 0 0 2 32 1 23 1]]
五、文本数据增强
回译数据增强目前是文本数据增强方面效果较好的增强方法,一般基于google翻译接口,将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言,即可认为得到与与原语料同标签的新语料,新语料加入到原数据集中即可认为是对原数据集数据增强
- 操作简便, 获得新语料质量高.
- 在短文本回译过程中,新语料与原语料可能存在很高重复率,并不能有效增大样本的特征空间
高重复率解决办法:
进行连续的多语言翻译,如:中文→韩文→日语→英文→中文。根据经验,最多只采用3次连续翻译,更多的翻译次数将产生效率低下,语义失真等问题