一、文本预处理
文本预处理
- 前言
- 一、文本处理的基本方法
- 1.1 分词
- 1.1.1 举例:
- 1.1.2 作用
- 1.1.3 分词工具——jieba
- 1.1.3.1 jieba特性
- 1.1.3.2 jieba 的 API 代码演示
- 二、命名实体识别
- 2.1 命名实体
- 2.2 命名实体识别
- 2.3 举例
- 2.4 作用
- 三、词性标注
- 3.1 词性
- 3.2 词性标注
- 3.3 jieba词性对照表
- 3.4 举例说明
- 3.5 作用
- 3.6 jieba进行中文词性标注
- 四、文本张量表示方法
- 4.1 one-hot 词向量表示
- 4.1.1 概念解释
- 4.1.2 举例
- 4.1.3 代码演示
- 4.1.4 one-hot编码的优劣势
- 4.2 Word2vec
- 4.2.1 CBOW (Continuous bag of words) 模式
- 4.2.2 skipgram模式
- 4.3 Word Embedding
- 4.3.1 词嵌入层
- 4.3.2 代码演示
- 五、文本数据分析
- 5.1 标签数量分布
- 5.2 句子长度分布
- 5.3 获取正负样本长度散点分布
- 5.4 获取不同词汇总数统计
- 5.5 词频统计与关键词词云
- 六、文本特征处理
- 6.1 n-gram特征
- 6.1.1 举例
- 6.2 文本长度规范
- 七、文本数据增强
- 7.1 回译数据增强法
- 7.1.1 概念
- 7.1.2 回译数据增强优势:
- 7.1.3 回译数据增强存在的问题
- 7.1.4 高重复率解决办法:
- 7.1.5 回译数据增强实现(基于有道翻译接口)
- 总结
前言
一、文本处理的基本方法
1.1 分词
- 分词就是将一句话,分成多个词的过程
1.1.1 举例:
- 有一句话是:我爱北京天安门。可以分词为:
- [“我”,“爱”,“北京”,“天安门”,“。”]
1.1.2 作用
- 词作为语言语义理解的最小单元,是人类理解文本语言的基础.,
- 因此也是 AI 解决 NLP 领域高阶任务,如:自动问答、机器翻译、文本生成的重要基础环节。
1.1.3 分词工具——jieba
- 目前 python 最流行的中文分词组件
1.1.3.1 jieba特性
- 1.支持多种分词模式
- 精确模式
- 全模式
- 搜索引擎模式
- 2.支持中文繁体分词
- 3.支持用户自定义的词典
1.1.3.2 jieba 的 API 代码演示
- 1、精确模式分词:
- 试图将句子最精确的切开,适合文本分析
import jieba
# 设置输出日志等级的
jieba.setLogLevel('INFO')
sent = '小米公司是一家上市公司,其产品理念是:为发烧而生。我是忠实的米粉。'
# 1 精确模式分词
res = jieba.cut(sent, cut_all=False) # 只能返回一个生成器对象
print('返回的生成器对象是', res)
# 返回的生成器对象是 <generator object Tokenizer.cut at 0x000002ACFE8D1DD0>
# 通过列表接受分词结果
res = jieba.lcut(sent)
print('返回的列表内容是', res)
# 返回的列表内容是 ['小米', '公司', '是', '一家', '上市公司', ',', '其', '产品', '理念', '是', ':', '为', '发烧', '而生', '。', '我', '是', '忠实', '的', '米粉', '。']
- 2、全模式分词:
- 把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能消除歧义
import jieba
# 设置输出日志等级的
jieba.setLogLevel('INFO')
sent = '小米公司是一家上市公司,其产品理念是:为发烧而生。我是忠实的米粉。'
# 2 全模式分词
res = jieba.lcut(sent, cut_all=True)
print('全模式下的分词结果', res)
# 全模式下的分词结果 ['小米', '公司', '是', '一家', '上市', '上市公司', '公司', ',', '其', '产品', '理念', '是', ':', '为', '发烧', '而', '生', '。', '我', '是', '忠实', '的', '米粉', '。']
- 3、搜索引擎分词:
- 在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词
import jieba
# 设置输出日志等级的
jieba.setLogLevel('INFO')
sent = '小米公司是一家上市公司,其产品理念是:为发烧而生。我是忠实的米粉。'
# 3 搜索引擎模式分词
res = jieba.lcut_for_search(sent)
print('搜索引擎模式的分词', res)
# 搜索引擎模式的分词 ['小米', '公司', '是', '一家', '上市', '公司', '上市公司', ',', '其', '产品', '理念', '是', ':', '为', '发烧', '而生', '。', '我', '是', '忠实', '的', '米粉', '。']
- 4、使用用户自定义词典:
- 添加自定义词典后, jieba能够准确识别词典中出现的词汇,提升整体的识别准确率
- 词典格式:每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。
import jieba
jieba.setLogLevel('INFO')
sent = '小米公司是一家上市公司,其产品理念是:为发烧而生。我是忠实的米粉。'
res = jieba.lcut(sent, cut_all=False)
print(f'没加载用户自定义词典之前:{res}')
# 没加载用户自定义词典之前:['小米', '公司', '是', '一家', '上市公司', ',', '其', '产品', '理念', '是', ':', '为', '发烧', '而生', '。', '我', '是', '忠实', '的', '米粉', '。']
# 我的词典userdict.txt中存放了以下三行数据
# 产品理念
# 为发烧而生
# 上市公司
# 加载用户词典
jieba.load_userdict('./userdict.txt')
res1 = jieba.lcut(sent, cut_all=False)
print(f'加载用户自定义词典以后的分词:{res1}')
# 加载用户自定义词典以后的分词:['小米', '公司', '是', '一家', '上市公司', ',', '其', '产品理念', '是', ':', '为发烧而生', '。', '我', '是', '忠实', '的', '米粉', '。']
二、命名实体识别
2.1 命名实体
- 通常我们将人名,地名,机构名等专有名词统称命名实体
- 如:周杰伦、黑山县、孔子学院、24辊方钢矫直机
2.2 命名实体识别
- 命名实体识别 (Named Entity Recognition,简称 NER) 就是识别出一段文本中可能存在的命名实体
2.3 举例
- 鲁迅,浙江绍兴人,五四新文化运动的重要参与者,代表作朝花夕拾。
- 鲁迅 (人名) / 浙江绍兴 (地名) 人 / 五四新文化运动 (专有名词) / 重要参与者 / 代表作 / 朝花夕拾 (专有名词)
2.4 作用
- 同词汇一样,命名实体也是人类理解文本的基础单元
- 因此也是 AI 解决 NLP 领域高阶任务的重要基础环节
三、词性标注
3.1 词性
- 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果,常见的词性有14种
- 如:名词、动词、形容词等.
3.2 词性标注
- 词性标注 (Part-Of-Speech tagging, 简称POS) 就是标注出一段文本中每个词汇的词性
3.3 jieba词性对照表
- a 形容词
- ad 副形词
- ag 形容词性语素
- an 名形词
- b 区别词
- c 连词
- d 副词
- df
- dg 副语素
- e 叹词
- f 方位词
- g 语素
- h 前接成分
- i 成语
- j 简称略称
- k 后接成分
- l 习用语
- m 数词
- mg
- mq 数量词
- n 名词
- ng 名词性语素
- nr 人名
- nrfg
- nrt
- ns 地名
- nt 机构团体名
- nz 其他专名
- o 拟声词
- p 介词
- q 量词
- r 代词
- rg 代词性语素
- rr 人称代词
- rz 指示代词
- s 处所词
- t 时间词
- tg 时语素
- u 助词
- ud 结构助词 得
- ug 时态助词
- uj 结构助词 的
- ul 时态助词 了
- uv 结构助词 地
- uz 时态助词 着
- v 动词
- vd 副动词
- vg 动词性语素
- vi 不及物动词
- vn 名动词
- vq
- x 非语素词
- y 语气词
- z 状态词
- zg
3.4 举例说明
-
这里有一句话:我爱自然语言处理
- 我/rr, 爱/v, 自然语言/n, 处理/vn
-
rr: 人称代词
-
v: 动词
-
n: 名词
-
vn: 动名词
3.5 作用
- 词性标注以分词为基础,是对文本语言的另一个角度的理解,因此也常常成为AI解决NLP领域高阶任务的重要基础环节
3.6 jieba进行中文词性标注
代码如下(示例):
import jieba
import jieba.posseg as pseg
jieba.setLogLevel('INFO')
sent = '北京欢迎你,我爱北京天安门'
res = pseg.lcut(sent)
print(res)
# [pair('北京', 'ns'), pair('欢迎', 'v'), pair('你', 'r'), pair(',', 'x'), pair('我', 'r'), pair('爱', 'v'), pair('北京', 'ns'), pair('天安门', 'ns')]
# 结果返回一个装有pair元组的列表, 每个pair元组中分别是词汇及其对应的词性
四、文本张量表示方法
- 将一句话拆分成若干个词,将每个词用一个向量来表示,那么一句话就会变成一个矩阵,这样我们就完成了文本转化成计算机能识别的形式
# 例如:我们将“我爱北京天安门。”切分成以下的词:
# [“我”,“爱”,“北京”,“天安门”,“。”]
# 每个词对应矩阵中的一个向量,如下所示:
[[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],
[3.8, 4.56, 0.98, 8.46]]
4.1 one-hot 词向量表示
4.1.1 概念解释
- one-hot 编码又称独热编码,将每个词表示成具有 n 个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中 n 的大小是整个语料中不同词汇的总数
4.1.2 举例
# ["我", "将", "如何", "存在"] 可以被 one-hot 编码成下面矩阵
[[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]
4.1.3 代码演示
代码如下(示例):
import jieba
import numpy as np
# 设置打印日志等级
jieba.setLogLevel('INFO')
def onehoe_encoder(sent):
# 先对进来的句子进行分词操作
vocabs = list(set(jieba.lcut(sent)))
print(vocabs)
# 获取词汇表大小
vocab_size = len(vocabs)
# 初始化一个全零的二维数组
one_hot = np.zeros((vocab_size, vocab_size))
# 生成one-hot编码
for i, word in enumerate(vocabs):
one_hot[i, word_to_index[word]] = 1
return one_hot
if __name__ == '__main__':
sent = '阿辉是个小烧杯'
print(onehoe_encoder(sent))
# 打印结果如下:
# ['烧杯', '阿辉', '是', '个', '小']
"""
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
"""
4.1.4 one-hot编码的优劣势
- 优势:操作简单,容易理解.
- 劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.
- 正因为one-hot编码明显的劣势,这种编码方式被应用的地方越来越少,取而代之的是接下来我们要学习的稠密向量的表示方法 word2vec 和 word embedding.
4.2 Word2vec
- word2vec 是一种流行的将词汇表示成向量的无监督训练方法,该过程将构建神经网络模型,将网络参数作为词汇的向量表示,包含 CBOW 和 skipgram 两种训练模式
4.2.1 CBOW (Continuous bag of words) 模式
- 给定一段用于训练的文本语料,再选定某段长度 (窗口) 作为研究对象,使用上下文词汇预测目标词汇
- 假设我们有一句话为:Hope can set you free (愿你自由成长),窗口大小为 3,步骤为下图所示:
- 1 .首先确定窗口大小为3,那么我们先取 Hope can set 这三个单词,并且都通过 one-hot 进行编码
- 2 .其次因为是 CBOW 模式,所以我们使用 Hope 和 set 作为输入,can 作为输出来构建神经网络模型
- 3.对于 Hope 来说,它是一个 5x1 的向量,经过与参数矩阵 W(这里是3x5,这里的 3 指最后得到的词向量维度)进行运算得到一个 3x1 的 上下文表示矩阵
- 4.对于 set 来说,也通过 W 参数矩阵,得到一个 3x1 的上下文矩阵
- 5.将 Hope 和 set 分别得到的 3x1 的向量进行相加,得到最终的 3*1 的上下文表示矩阵
- 6.将上一步得到的 1x3 的上下文表示矩阵,再通过一个参数矩阵 W1 得到一个 5x1 的结果矩阵
- 7.将预测的结果矩阵与我们真正的目标矩阵即 can 的 one-hot 编码矩阵(5x1) 进行损失的计算,然后更新网络参数(所有的变换矩阵共享参数)完成一次模型迭代
- 8.最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵W (3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的(3x1)的矩阵就是该词汇的 word2vec 张量表示
4.2.2 skipgram模式
- 给定一段用于训练的文本语料,再选定某段长度(窗口)作为研究对象,使用目标词汇预测上下文词汇
- 假设我们有一句话为:Hope can set you free (愿你自由成长),窗口大小为 3,步骤为下图所示:
- 1 .首先确定窗口大小为 3,那么我们先取 Hope can set 这三个单词,并且都通过 one-hot 进行编码
- 2 .其次因为是 skipgram 模式,所以我们使用 can 作为输入,Hope和set作为输出来构建神经网络模型
- 3.对于 can 来说,它是一个 5x1 的向量,经过与参数矩阵 W(这里是3x5,这里的 3 指最后得到的词向量维度)进行运算得到一个 3x1 的 目标词汇表示矩阵
- 4.将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵
- 5.结果矩阵将与我们 Hope 和 se t对应的 one-hot 编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代
- 6.最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵W (3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的(3x1)的矩阵就是该词汇的 word2vec 张量表示
4.3 Word Embedding
- 通过一定的方式将词汇映射到指定维度 (一般是更高维度) 的空间
- 广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec,即可认为是word embedding的一种
- 狭义的word embedding是指在神经网络中加入的 embedding 层,对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数),这个embedding 矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵
4.3.1 词嵌入层
- 词嵌入层的作用就是将文本转换为向量
- 词嵌入层首先会根据输入的词的数量构建一个词向量矩阵,例如: 我们有 100 个词,每个词希望转换成 128 维度的向量,那么构建的矩阵形状即为: 100*128,输入的每个词都对应了一个该矩阵中的一个向量。
4.3.2 代码演示
- 在 PyTorch 中,使用 nn.Embedding 词嵌入层来实现输入词的向量化
from torch import nn
# num_embeddings 表示词的数量
# embedding_dim 表示用多少维的向量来表示每个词
nn.Embedding(num_embeddings=10, embedding_dim=4)
代码如下:
import torch
import torch.nn as nn
import jieba
if __name__ == '__main__':
# 0.文本数据
text = '优秀的公司赚取利润,伟大的公司赢得人心。'
# 1. 文本分词
words = jieba.lcut(text)
print('文本分词:', words)
# 2.分词去重并保留原来的顺序获取所有的词语
unique_words = list(set(words))
print("去重后词的个数:\n", len(unique_words))
# 3. 构建词嵌入层:num_embeddings: 表示词的总数量;embedding_dim: 表示词嵌入的维度
embed = nn.Embedding(num_embeddings=len(unique_words), embedding_dim=4)
print("词嵌入的结果:\n", embed)
# 4. 词语的词向量表示
for i, word in enumerate(unique_words):
# 获得词嵌入向量
word_vec = embed(torch.tensor(i))
print('%3s\t' % word, word_vec)
结果如下:
文本分词: ['优秀', '的', '公司', '赚取', '利润', ',', '伟大', '的', '公司', '赢得', '人心', '。']
去重后词的个数:
10
词嵌入的结果:
Embedding(10, 4)
人心 tensor([1.0198, 0.3295, 0.0804, 0.7331], grad_fn=<EmbeddingBackward0>)
伟大 tensor([-0.7830, 0.9381, -0.1188, 0.0803], grad_fn=<EmbeddingBackward0>)
优秀 tensor([-0.0610, 0.9905, -0.4726, -2.1333], grad_fn=<EmbeddingBackward0>)
的 tensor([ 0.0793, -0.2007, 0.0263, 0.3062], grad_fn=<EmbeddingBackward0>)
公司 tensor([-1.8080, -0.8241, 0.8195, -2.2967], grad_fn=<EmbeddingBackward0>)
利润 tensor([ 2.5544, 1.7704, -1.3012, 0.6469], grad_fn=<EmbeddingBackward0>)
。 tensor([-0.5065, -1.5874, -0.6295, -1.0325], grad_fn=<EmbeddingBackward0>)
赚取 tensor([-1.2589, -0.8940, 0.6219, -0.4133], grad_fn=<EmbeddingBackward0>)
赢得 tensor([ 0.6651, 0.2513, -0.0312, -1.6196], grad_fn=<EmbeddingBackward0>)
, tensor([-1.0262, -0.4078, 0.6153, 1.4638], grad_fn=<EmbeddingBackward0>)
五、文本数据分析
- 文本数据分析能够有效帮助我们理解数据语料,快速检查出语料可能存在的问题,并指导之后模型训练过程中一些超参数的选择
5.1 标签数量分布
演示代码如下:
# 导入必备工具包
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
# 思路分析 : 获取标签数量分布
# 0 什么标签数量分布:求标签0有多少个 标签1有多少个 标签2有多少个
# 1 设置显示风格plt.style.use('fivethirtyeight')
# 2 pd.read_csv(path, sep='\t') 读训练集 验证集数据
# 3 sns.countplot() 统计label标签的0、1分组数量
# 4 画图展示 plt.title() plt.show()
# 注意1:sns.countplot()相当于select * from tab1 group by
def dm_label_sns_countplot():
# 1 设置显示风格plt.style.use('fivethirtyeight')
plt.style.use('fivethirtyeight')
# 2 pd.read_csv 读训练集 验证集数据
train_data = pd.read_csv(filepath_or_buffer = './cn_data/train.tsv', sep='\t')
dev_data = pd.read_csv(filepath_or_buffer = './cn_data/dev.tsv', sep='\t')
# 3 sns.countplot() 统计label标签的0、1分组数量
sns.countplot(x='label', data = train_data)
# 4 画图展示 plt.title() plt.show()
plt.title('train_label')
plt.show()
# 验证集上标签的数量分布
# 3-2 sns.countplot() 统计label标签的0、1分组数量
sns.countplot(x='label', data = dev_data)
# 4-2 画图展示 plt.title() plt.show()
plt.title('dev_label')
plt.show()
- 训练集标签数量分布
- 验证集标签数量分布
- 分析
- 在深度学习模型评估中,我们一般使用ACC作为评估指标,若想将ACC的基线定义在50%左右,则需要我们的正负样本比例维持在1:1左右,否则就要进行必要的数据增强或数据删减。
- 上图中训练和验证集正负样本都稍有不均衡,可以进行一些数据增强
5.2 句子长度分布
演示代码如下:
# 思路分析 : 获取句子长度分布 -绘制句子长度分布-柱状图 句子长度分布-密度曲线图
# 0 什么是句子长度分布:求长度为50的有多少个 长度51的有多少个 长度为52的有多少个
# 1 设置显示风格plt.style.use('fivethirtyeight')
# 2 pd.read_csv(path, sep='\t') 读训练集 验证集数据
# 3 新增数据长度列:train_data['sentence_length'] = list(map(lambda x:len(x) , ...))
# 4-1 绘制数据长度分布图-柱状图 sns.countplot(x='sentence_length', data=train_data)
# 画图展示 plt.xticks([]) plt.show()
# 4-2 绘制数据长度分布图-曲线图 sns.displot(x='sentence_length', data=train_data)
# 画图展示 plt.yticks([]) plt.show()
def dm_len_sns_countplot_distplot():
# 1 设置显示风格plt.style.use('fivethirtyeight')
plt.style.use('fivethirtyeight')
# 2 pd.read_csv 读训练集 验证集数据
train_data = pd.read_csv(filepath_or_buffer='./cn_data/train.tsv', sep='\t')
dev_data = pd.read_csv(filepath_or_buffer='./cn_data/dev.tsv', sep='\t')
# 3 求数据长度列 然后求数据长度的分布
train_data['sentence_length'] = list( map(lambda x: len(x), train_data['sentence']))
# 4 绘制数据长度分布图-柱状图
sns.countplot(x='sentence_length', data=train_data)
# sns.countplot(x=train_data['sentence_length'])
plt.xticks([]) # x轴上不要提示信息
# plt.title('sentence_length countplot')
plt.show()
# 5 绘制数据长度分布图-曲线图
sns.displot(x='sentence_length', data=train_data)
# sns.displot(x=train_data['sentence_length'])
plt.yticks([]) # y轴上不要提示信息
plt.show()
# 验证集
# 3 求数据长度列 然后求数据长度的分布
dev_data['sentence_length'] = list(map(lambda x: len(x), dev_data['sentence']))
# 4 绘制数据长度分布图-柱状图
sns.countplot(x='sentence_length', data=dev_data)
# sns.countplot(x=dev_data['sentence_length'])
plt.xticks([]) # x轴上不要提示信息
# plt.title('sentence_length countplot')
plt.show()
# 5 绘制数据长度分布图-曲线图
sns.displot(x='sentence_length', data=dev_data)
# sns.displot(x=dev_data['sentence_length'])
plt.yticks([]) # y轴上不要提示信息
plt.show()
-
训练集句子长度分布
-
验证集句子长度分布
- 分析
- 通过绘制句子长度分布图,可以得知我们的语料中大部分句子长度的分布范围,因为模型的输入要求为固定尺寸的张量,合理的长度范围对之后进行句子截断补齐(规范长度)起到关键的指导作用。
- 上图中大部分句子长度的范围大致为 20-250 之间。
5.3 获取正负样本长度散点分布
演示代码如下:
# 获取正负样本长度散点分布,也就是按照x正负样本进行分组 再按照y长度进行散点图
# train_data['sentence_length'] = list(map(lambda x: len(x), train_data['sentence']))
# sns.stripplot(y='sentence_length', x='label', data=train_data)
def dm03_sns_stripplot():
# 1 设置显示风格plt.style.use('fivethirtyeight')
plt.style.use('fivethirtyeight')
# 2 pd.read_csv 读训练集 验证集数据
train_data = pd.read_csv(filepath_or_buffer='./cn_data/train.tsv', sep='\t')
dev_data = pd.read_csv(filepath_or_buffer='./cn_data/dev.tsv', sep='\t')
# 3 求数据长度列 然后求数据长度的分布
train_data['sentence_length'] = list(map(lambda x: len(x), train_data['sentence']))
# 4 统计正负样本长度散点图 (对train_data数据,按照label进行分组,统计正样本散点图)
sns.stripplot(y='sentence_length', x='label', data=train_data)
plt.show()
sns.stripplot(y='sentence_length', x='label', data=dev_data)
plt.show()
- 训练集上正负样本的长度散点分布
- 验证集上正负样本的长度散点分布
- 分析
- 通过查看正负样本长度散点图,可以有效定位异常点的出现位置,帮助我们更准确进行人工语料审查。
- 上图中在训练集正样本中出现了异常点,它的句子长度近3500左右,需要我们人工审查
5.4 获取不同词汇总数统计
演示代码如下:
# 导入jieba用于分词
# 导入chain方法用于扁平化列表
import jieba
from itertools import chain
# 进行训练集的句子进行分词, 并统计出不同词汇的总数
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))
训练集共包含不同词汇总数为: 12147
训练集共包含不同词汇总数为: 6857
5.5 词频统计与关键词词云
演示代码如下:
# 使用jieba中的词性标注功能
import jieba.posseg as pseg
from wordcloud import WordCloud
# 每句话产生形容词列表
def get_a_list(text):
r = []
# 使用jieba的词性标注方法切分文本 找到形容词存入到列表中返回
for g in pseg.lcut(text):
if g.flag == "a":
r.append(g.word)
return r
# 根据词云列表产生词云
def get_word_cloud(keywords_list):
# 实例化词云生成器对象
wordcloud = WordCloud(font_path="./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()
# 思路分析 训练集正样本词云 训练集负样本词云
# 1 获得训练集上正样本 p_train_data
# eg: 先使用逻辑==操作检索符合正样本 train_data[train_data['label'] == 1]
# 2 获取正样本的每个句子的形容词 p_a_train_vocab = chain(*map(a,b))
# 3 调用绘制词云函数
def dm_word_cloud():
# 1 获得训练集上正样本p_train_data
# eg: 先使用逻辑==操作检索符合正样本 train_data[train_data['label'] == 1]
train_data = pd.read_csv(filepath_or_buffer='./cn_data/train.tsv', sep='\t')
p_train_data = train_data[train_data['label'] == 1 ]['sentence']
# 2 获取正样本的每个句子的形容词 p_a_train_vocab = chain(*map(a,b))
p_a_train_vocab = chain(*map(lambda x: get_a_list(x) , p_train_data))
# print(p_a_train_vocab)
# print(list(p_a_train_vocab))
# 3 调用绘制词云函数
get_word_cloud(p_a_train_vocab)
print('*' * 60 )
# 训练集负样本词云
n_train_data = train_data[train_data['label'] == 0 ]['sentence']
# 2 获取正样本的每个句子的形容词 p_a_train_vocab = chain(*map(a,b))
n_a_train_vocab = chain(*map(lambda x: get_a_list(x) , n_train_data) )
# print(n_a_dev_vocab)
# print(list(n_a_dev_vocab))
# 3 调用绘制词云函数
get_word_cloud(n_a_train_vocab)
-
训练集正样本形容词词云
-
训练集负样本形容词词云
-
分析
- 根据高频形容词词云显示,我们可以对当前语料质量进行简单评估,同时对违反语料标签含义的词汇进行人工审查和修正,来保证绝大多数语料符合训练标准。
- 上图中的正样本大多数是褒义词,而负样本大多数是贬义词,基本符合要求,但是负样本词云中也存在"便利"这样的褒义词,因此可以人工进行审查。
六、文本特征处理
- 文本特征处理包括
- 为语料添加具有普适性的文本特征,如:n-gram特征,
- 以及对加入特征之后的文本语料进行必要的处理,如:长度规范。
- 这些特征处理工作能够有效的将重要的文本特征加入模型训练中, 增强模型评估指标
6.1 n-gram特征
- 给定一段文本序列,其中 n 个词或字的相邻共现特征即 n-gram 特征,常用的 n-gram 特征是 bi-gram 和 tri-gram 特征,分别对应 n 为 2 和 3
6.1.1 举例
假设给定分词列表: ["是谁", "敲动", "我心"]
对应的数值映射列表为: [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]
6.2 文本长度规范
- 一般模型的输入需要等尺寸大小的矩阵,因此在进入模型前需要对每条文本数值映射后的长度进行规范
- 此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度
- 对超长文本进行截断,
- 对不足文本进行补齐(一般使用数字0)
- 这个过程就是文本长度规范
七、文本数据增强
7.1 回译数据增强法
7.1.1 概念
- 回译数据增强目前是文本数据增强方面效果较好的增强方法,一般基于google、有道等翻译接口,将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言,即可认为得到与与原语料同标签的新语料,新语料加入到原数据集中即可认为是对原数据集数据增强
7.1.2 回译数据增强优势:
- 操作简便,获得新语料质量高
7.1.3 回译数据增强存在的问题
- 在短文本回译过程中,新语料与原语料可能存在很高的重复率,并不能有效增大样本的特征空间
7.1.4 高重复率解决办法:
- 进行连续的多语言翻译,如:中文→韩文→日语→英文→中文
- 根据经验,最多只采用3次连续翻译,更多的翻译次数将产生效率低下,语义失真等问题
7.1.5 回译数据增强实现(基于有道翻译接口)
演示代码如下:
# 导入必备的工具包
import requests
# 思路分析
# 1 定义需要访问的有道翻译API接口--url
# 2 定义需要翻译的文本:text
# 3 定义data数据:from代表原始语言, to代表目标语言, i代表需要翻译的文本, doctype:文本的类型
# 4 requests.post(url=url, params=data)即代表访问api接口的方法
def dm_translate():
url = 'http://fanyi.youdao.com/translate'
# 第一次翻译,目标语言英文
text1 = '这个价格非常便宜'
data1 = {'from': 'zh-CHS', 'to': 'en', 'i': text1, 'doctype': 'json'}
response1 = requests.post(url=url, params=data1)
res1 = response1.json()
# 打印第一次翻译结果
print(res1)
# 第二次翻译, 目标语言中文
text2 = 'The price is very cheap'
data2 = {'from': 'en', 'to': 'zh-CHS', 'i': text2, 'doctype': 'json'}
response2 = requests.post(url=url, params=data2)
res2 = response2.json()
# 打印第二次翻译结果
print(res2)
打印结果如下:
第一次翻译结果:{'type': 'ZH_CN2EN', 'errorCode': 0, 'elapsedTime': 1, 'translateResult': [[{'src': '这个价格非常便宜', 'tgt': 'The price is very cheap'}]]}
第二次翻译结果:{'type': 'EN2ZH_CN', 'errorCode': 0, 'elapsedTime': 1, 'translateResult': [[{'src': 'The price is very cheap', 'tgt': '价格非常便宜'}]]}
总结
- 文章对在自然语言处理阶段的开始阶段:即对文本转换张量或者文本处理进行了总结分析。为后边的模型训练奠定良好的基础。