NLP

文本预处理-中

文本数据分析、文本特征处理、文本数据增强

Posted by 新宇 on September 8, 2020

一、文本数据分析

  • 文本数据分析的作用:
    • 文本数据分析能够有效帮助我们理解数据语料, 快速检查出语料可能存在的问题, 并指导之后模型训练过程中一些超参数的选择.
  • 常用的几种文本数据分析方法:
    • 标签数量分布
    • 句子长度分布
    • 词频统计与关键词词云

1. 中文酒店评论语料分析

1.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("./cn_data/train.tsv", sep="\t")
valid_data = pd.read_csv("./cn_data/dev.tsv", sep="\t")


# 获得训练数据标签数量分布

sns.countplot("label", data=train_data)
plt.title("train_data")
plt.show()


# 获取验证数据标签数量分布

sns.countplot("label", data=valid_data)
plt.title("valid_data")
plt.show()

1.2 获取训练集和验证集的句子长度分布

# 在训练数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度

train_data["sentence_length"] = list(map(lambda x: len(x), train_data["sentence"]))

# 绘制句子长度列的数量分布图

sns.countplot("sentence_length", data=train_data)
# 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看

plt.xticks([])
plt.show()

# 绘制dist长度分布图

sns.distplot(train_data["sentence_length"])

# 主要关注dist长度分布横坐标, 不需要绘制纵坐标

plt.yticks([])
plt.show()


# 在验证数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度

valid_data["sentence_length"] = list(map(lambda x: len(x), valid_data["sentence"]))

# 绘制句子长度列的数量分布图

sns.countplot("sentence_length", data=valid_data)

# 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看

plt.xticks([])
plt.show()

# 绘制dist长度分布图

sns.distplot(valid_data["sentence_length"])

# 主要关注dist长度分布横坐标, 不需要绘制纵坐标

plt.yticks([])
plt.show()

1.3 获取训练集和验证集的正负样本长度散点分布

# 绘制训练集长度分布的散点图

sns.stripplot(y='sentence_length',x='label',data=train_data)
plt.show()

# 绘制验证集长度分布的散点图

sns.stripplot(y='sentence_length',x='label',data=valid_data)
plt.show()

1.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))

1.5 获得训练集上正负的样本的高频形容词词云

# 使用jieba中的词性标注功能

import jieba.posseg as pseg

def get_a_list(text):
    """用于获取形容词列表"""

    # 使用jieba的词性标注方法切分文本,获得具有词性属性flag和词汇属性word的对象, 
    # 从而判断flag是否为形容词,来返回对应的词汇

    r = []
    for g in pseg.lcut(text):
        if g.flag == "a":
            r.append(g.word)
    return r

# 导入绘制词云的工具包

from wordcloud import WordCloud

def get_word_cloud(keywords_list):
    # 实例化绘制词云的类, 其中参数font_path是字体路径, 为了能够显示中文, 
    # max_words指词云图像最多显示多少个词, background_color为背景颜色 

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

# 获得训练集上正样本

p_train_data = train_data[train_data["label"]==1]["sentence"]

# 对正样本的每个句子的形容词

train_p_a_vocab = chain(*map(lambda x: get_a_list(x), p_train_data))
#print(train_p_n_vocab)

# 获得训练集上负样本

n_train_data = train_data[train_data["label"]==0]["sentence"]

# 获取负样本的每个句子的形容词

train_n_a_vocab = chain(*map(lambda x: get_a_list(x), n_train_data))

# 调用绘制词云函数

get_word_cloud(train_p_a_vocab)
get_word_cloud(train_n_a_vocab)

1.6 获得验证集上正负的样本的形容词词云

# 获得验证集上正样本

p_valid_data = valid_data[valid_data["label"]==1]["sentence"]

# 对正样本的每个句子的形容词

valid_p_a_vocab = chain(*map(lambda x: get_a_list(x), p_valid_data))
#print(train_p_n_vocab)

# 获得验证集上负样本

n_valid_data = valid_data[valid_data["label"]==0]["sentence"]

# 获取负样本的每个句子的形容词

valid_n_a_vocab = chain(*map(lambda x: get_a_list(x), n_valid_data))

# 调用绘制词云函数

get_word_cloud(valid_p_a_vocab)
get_word_cloud(valid_n_a_vocab)

二、文本特征处理

  • 文本特征处理的作用:
    • 文本特征处理包括为语料添加具有普适性的文本特征, 如:n-gram特征, 以及对加入特征之后的文本语料进行必要的处理, 如: 长度规范. 这些特征处理工作能够有效的将重要的文本特征加入模型训练中, 增强模型评估指标.
  • 常见的文本特征处理方法:
    • 添加n-gram特征
    • 文本长度规范

1. n-gram特征

给定一段文本序列, 其中n个词或字的相邻共现特征即n-gram特征, 常用的n-gram特征是bi-gram和tri-gram特征, 分别对应n为2和3.

# 一般n-gram中的n取2或者3, 这里取2为例

ngram_range = 2

def create_ngram_set(input_list):
    """
    description: 从数值列表中提取所有的n-gram特征
    :param input_list: 输入的数值列表, 可以看作是词汇映射后的列表, 
                       里面每个数字的取值范围为[1, 25000]
    :return: n-gram特征组成的集合

    eg:
    >>> 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(ngram_range)]))

input_list = [1, 3, 2, 1, 5, 3]
res = create_ngram_set(input_list)
print(res)

2. 文本长度规范

一般模型的输入需要等尺寸大小的矩阵, 因此在进入模型前需要对每条文本数值映射后的长度进行规范, 此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度, 对超长文本进行截断, 对不足文本进行补齐(一般使用数字0), 这个过程就是文本长度规范.

from keras.preprocessing import sequence

# cutlen根据数据分析中句子长度分布,覆盖90%左右语料的最短长度.
# 这里假定cutlen为10

cutlen = 10

def padding(x_train):
    """
    description: 对输入文本张量进行长度规范
    :param x_train: 文本的张量表示, 形如: [[1, 32, 32, 61], [2, 54, 21, 7, 19]]
    :return: 进行截断补齐后的文本张量表示 
    """
    # 使用sequence.pad_sequences即可完成

    return sequence.pad_sequences(x_train, cutlen)

# 假定x_train里面有两条文本, 一条长度大于10, 一天小于10

x_train = [[1, 23, 5, 32, 55, 63, 2, 21, 78, 32, 23, 1],
           [2, 32, 1, 23, 1]]

res = padding(x_train)
print(res)

三、文本数据增强

1. 回译数据增强法

  • 回译数据增强目前是文本数据增强方面效果较好的增强方法, 一般基于google翻译接口, 将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言, 即可认为得到与与原语料同标签的新语料, 新语料加入到原数据集中即可认为是对原数据集数据增强.
  • 回译数据增强优势:
    • 操作简便, 获得新语料质量高.
  • 回译数据增强存在的问题:
    • 在短文本回译过程中, 新语料与原语料可能存在很高的重复率, 并不能有效增大样本的特征空间.
  • 高重复率解决办法:
    • 进行连续的多语言翻译, 如: 中文–>韩文–>日语–>英文–>中文, 根据经验, 最多只采用3次连续翻译, 更多的翻译次数将产生效率低下, 语义失真等问题.
# 假设取两条已经存在的正样本和两条负样本
# 将基于这四条样本产生新的同标签的四条样本

p_sample1 = "酒店设施非常不错"
p_sample2 = "这家价格很便宜"
n_sample1 = "拖鞋都发霉了, 太差了"
n_sample2 = "电视不好用, 没有看到足球"

# 导入google翻译接口工具

from googletrans import Translator
# 实例化翻译对象

translator = Translator()
# 进行第一次批量翻译, 翻译目标是韩语

translations = translator.translate([p_sample1, p_sample2, n_sample1, n_sample2], dest='ko')
# 获得翻译后的结果

ko_res = list(map(lambda x: x.text, translations))
# 打印结果

print("中间翻译结果:")
print(ko_res)


# 最后在翻译回中文, 完成回译全部流程

translations = translator.translate(ko_res, dest='zh-cn')
cn_res = list(map(lambda x: x.text, translations))
print("回译得到的增强数据:")
print(cn_res)