文本的预处理(pytorch)
文本的预处理
1、概述
文本语料在输送给模型前一般需要一系列的预处理工作, 才能符合模型输入的要求, 如: 将文本转化成模型需要的张量, 规范张量的尺寸等, 而且科学的文本预处理环节还将有效指导模型超参数的选择, 提升模型的评估指标.
2、文本处理的基本方法
(一)、分词
(1)、概述
分词就是将连续的字序列,按照一定的规范,重新组合成词序列的过程
一般实现模型训练的时候,模型接受的文本基本最小单位是词语,因此我们需要对文本进行分词
词语是语意理解的基本单元
英文具有天然的空格分隔符,而中文分词的目的:寻找一个合适的分词边界,进行准确分词
(2)、jieba分词
'''
前面放句子参数
cut_all:默认是Flase
'''
jieba.cut(句子, cut_all) # 返回生成器
jieba.lcut(句子, cut_all) # 返回列表
<一>、精准模式(默认首选)
按照人类擅长的表达词汇的习惯来分词,试图将句子最精确地切开,适合文本分析
import jieba
content = "我喜欢学习"
jieba.cut(content, cut_all=False) # 返回生成器,cut_all=False默认
jieba.lcut(content, cut_all=False) # 返回列表
<二>、全模式分词
将尽可能成词的词汇分割出来,速度非常快,但是不能消除歧义
import jieba
content = "我喜欢学习"
jieba.cut(content, cut_all=True) # 返回生成器
jieba.lcut(content, cut_all=True) # 返回列表
<三>、搜索引擎模式
在精确模式分词的基础上,将长粒度的词再次切分,提高召回率,适合用于搜索引擎分词
import jieba
content = "我喜欢学习"
jieba.cut_for_search(content) # 返回生成器
jieba.lcut_for_search(content) # 返回列表结果
<四>、自定义词典
自定义词典格式:
可以根据自定义词典,修改jieba分词方式,优先考虑词典里面的词来切分
格式:词语 词频(可省略) 词性(可省略)
例如:
'''
userdict:
整点薯条
去码头
'''
jieba.load_userdict('userdict.txt')
res5 = jieba.lcut(data, cut_all=False)
print(f'自定义词典分词结果:{res5}')
<五>、支持中文繁体分词
import jieba
content = "煩惱即是菩提,我暫且不提"
jieba.lcut(content)
# ['煩惱', '即', '是', '菩提', ',', '我', '暫且', '不', '提']
<六>、支持文言文
import jieba
content = '庭有枇杷树,吾妻死之年所手植也,今已亭亭如盖矣'
res3 = jieba.lcut(content)
print(content)
# ['庭有', '枇杷树', ',', '吾妻', '死', '之', '年', '所', '手植', '也', ',', '今', '已', '亭亭', '如盖', '矣']
(二)、通用信息抽取技术
(1)、命名实体识别(NER)
命名实体识别(NER):从文本中识别并分类命名实体,如人名、地名、组织名、时间、日期、货币、百分比。
import sys
sys.path.append(r'D:\python\code\NLP\NLP_proctice\uie_pytorch')
from uie_predictor import UIEPredictor
from pprint import pprint
data_3 = ['时间', '人物', '地点']
ie = UIEPredictor(model='uie-base', schema=data_3)
pprint(ie('我们将要去往何方?去码头整点薯条!'))
(2)、关系抽取(RE)
关系抽取(RE):识别文本中实体之间的关系,如“张三工作于阿里云”中的“工作于”关系。
(3)、事件抽取(EE)
事件抽取(EE):从文本中识别特定类型的事件,如灾害/意外、公司上市等。
(4)、属性抽取
属性抽取:提取实体的特定属性,如产品的价格、颜色等。
(三)、词性标注(POS)
(1)、概述
对每个词语进行词性的标注:动词、名词、形容词等。词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节。
(2)、实现方式
import jieba.posseg as pseg
# 词性标注
print(pseg.lcut("我们将要去往何方?去码头整点薯条!"))
(四)、总结
- 分词:
- 分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符, 分词过程就是找到这样分界符的过程.
- 分词的作用:
- 词作为语言语义理解的最小单元, 是人类理解文本语言的基础. 因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节.
- 中文分词工具jieba:
- 支持多种分词模式: 精确模式, 全模式, 搜索引擎模式
- 支持中文繁体分词
- 支持用户自定义词典
- 命名实体识别:
- 命名实体: 通常我们将人名, 地名, 机构名等专有名词统称命名实体. 如: 周杰伦, 黑山县, 孔子学院, 24辊方钢矫直机.
- 顾名思义, 命名实体识别(Named Entity Recognition,简称NER)就是识别出一段文本中可能存在的命名实体.
- 命名实体识别的作用:
- 同词汇一样, 命名实体也是人类理解文本的基础单元, 因此也是AI解决NLP领域高阶任务的重要基础环节.
- 词性标注:
- 词性: 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果, 常见的词性有14种, 如: 名词, 动词, 形容词等.
- 顾名思义, 词性标注(Part-Of-Speech tagging, 简称POS)就是标注出一段文本中每个词汇的词性.
- 词性标注的作用:
- 词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节.
3、文本张量表示方法
(一)、文本张量表示
(1)、概述
将一段文本使用张量进行表示,其中一般将词汇表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示。
(2)、作用
将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作。
(二)、one-hot词向量表示
(1)、概述
独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数。
(2)、实现进行编码
def onehot_get(sent):
# 准备材料
vocabs = list(set(jieba.lcut(sent)))
# 实例化Tokenizer
mytokenizer = Tokenizer()
mytokenizer.fit_on_texts(vocabs)
# 查询词的index,赋值zero_list,生成one-hot,记得要索引-1,因为Tokenizer()是从1开始的
for vocab in vocabs:
zero_list = [0] * len(vocabs)
idx = mytokenizer.word_index[vocab] - 1
zero_list[idx] = 1
print(vocab, '的onehot编码为', zero_list)
# 使用joblib保存模型
mypath = 'data/onehot_model.pkl'
joblib.dump(mytokenizer, mypath)
print('保存成功')
# 打印词->序号和序号->词的映射关系
print('词->序号的映射关系:', mytokenizer.word_index)
print('序号->词的映射关系:', mytokenizer.index_word)
if __name__ == '__main__':
sent = '庭有枇杷树,吾妻死之年所手植也,今已亭亭如盖矣'
onehot_get(sent)
结果:
手植 的onehot编码为 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
也 的onehot编码为 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
今 的onehot编码为 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
矣 的onehot编码为 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
亭亭 的onehot编码为 [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
枇杷树 的onehot编码为 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
吾妻 的onehot编码为 [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
死 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
年 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
所 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
, 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
庭有 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
已 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
如盖 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
之 的onehot编码为 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
词->序号的映射关系: {'手植': 1, '也': 2, '今': 3, '矣': 4, '亭亭': 5, '枇杷树': 6, '吾妻': 7, '死': 8, '年': 9, '所': 10, ',': 11, '庭有': 12, '已': 13, '如盖': 14, '之': 15}
序号->词的映射关系: {1: '手植', 2: '也', 3: '今', 4: '矣', 5: '亭亭', 6: '枇杷树', 7: '吾妻', 8: '死', 9: '年', 10: '所', 11: ',', 12: '庭有', 13: '已', 14: '如盖', 15: '之'}
(3)、实现编码器的使用
def onehot_use(word):
# 加载词汇映射器
path = 'data/onehot_model.pkl'
mytokenizer = joblib.load(path)
# 查询单词的idx,如果查不到就说明不在
try:
idx = mytokenizer.word_index[word] - 1
except:
print(f'“{word}”不在词汇表中')
return
# 将索引赋值给zero_list,获得独热编码
zero_list = [0] * len(mytokenizer.word_index)
zero_list[idx] = 1
print(word, '的onehot编码为', zero_list)
if __name__ == '__main__':
onehot_use('枇杷树')
onehot_use('派大星')
结果:
枇杷树 的onehot编码为 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
“派大星”不在词汇表中
(4)、小结
- 优势:操作简单,容易理解.
- 劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.
- 正因为one-hot编码明显的劣势,这种编码方式被应用的地方越来越少,取而代之的是接下来我们要学习的稠密向量的表示方法word2vec和word embedding
(三)、word2vec模型
(1)、概述
word2vec是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式
- CBOW(Continuous bag of words)模式:俗称连续词带模式。给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇。也可以理解为使用目标词汇对上下文词向量进行更新。
- skipgram模式:俗称跳词模式。给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇。也可以理解为使用上下文对目标词向量进行更新。
(2)、CBOW模式
先对数据源进行分词,例如“去码头整点薯条。”,可以被分成“去”、“码头”、“整点”、“薯条”、“。”,对每一个token进行编号{去:0,码头:1,整点:2,薯条:3,。:4},获得相对应的独热编码:去->[1,0,0,0,0],一直到。->[0,0,0,0,1],本次我们选取窗口为3,规定embedding_dim=4,也就是一个长度为4的词向量。第一个样本为【去,码头,整点】,第二个样本为【码头,整点,薯条】,第三个样本为【整点,薯条,。】。
然后就对数据做上图处理:
- 先获得上下文token的独热编码,乘上一个权重矩阵(也就相当于第一层线性层,而且此权重矩阵最终的列就是对应的词向量),获得到对应上下文token的词向量矩阵
- 这两个词向量相加取均值,获得一个向量,该向量再次乘以一个权重矩阵(相当于第二层线性层),将其转化为一个和目标独热编码向量同维度的向量
- 对该向量做softmax概率值处理,获得到最终的概率向量,与目标独热编码做交叉熵求出损失值
- 利用该损失值,反向传播修正上面两层权重矩阵的值,使得最终损失值较小
(3)、skip-gram模式
同理,该模式也需要先对数据源进行分词,例如“去码头整点薯条。”,可以被分成“去”、“码头”、“整点”、“薯条”、“。”,对每一个token进行编号{去:0,码头:1,整点:2,薯条:3,。:4},获得相对应的独热编码:去->[1,0,0,0,0],一直到。->[0,0,0,0,1],本次我们选取窗口为3,规定embedding_dim=4,也就是一个长度为4的词向量。第一个样本为【去,码头,整点】,第二个样本为【码头,整点,薯条】,第三个样本为【整点,薯条,。】。
然后对数据做上图处理(只表示了对前面一个token的修正,后一个token同理):
-
先获得目标token的独热编码,乘上一个权重矩阵(也就相当于第一层线性层,而且此权重矩阵最终的列就是对应的词向量),获得到目标token的词向量矩阵
-
将该词向量再次乘上一个权重矩阵(相当于第二层线性层),将其转化为一个和上下文独热编码向量同维度的向量
-
对该向量做softmax概率值处理,获得到最终的概率向量,与目标独热编码做交叉熵求出损失值
-
利用该损失值,反向传播修正上面两层权重矩阵的值,使得最终损失值较小
(4)、实现
<一>、获取训练数据
数据来源:http://mattmahoney.net/dc/enwik9.zip
原数据处理:
# 使用wikifil.pl文件处理脚本来清除XML/HTML格式的内容
# perl wikifil.pl data/enwik9 > data/fil9 #该命令已经执行
<二>、训练词向量
'''
# 训练词向量工具库的安装
# 方法1 简洁版
pip install fasttext
# 方法2:源码安装(推荐)
# 以linux安装为例: 目录切换到虚拟开发环境目录下,再执行git clone 操作
git clone https://github.com/facebookresearch/fastText.git
cd fastText
# 使用pip安装python中的fasttext工具包
sudo pip install .
'''
import fasttext
def fasttext_train():
my_model = fasttext.train_unsupervised('data/fil9')
print('训练词向量ok')
<三>、模型超参数设定
# 在训练词向量过程中, 我们可以设定很多常用超参数来调节我们的模型效果, 如:
# 无监督训练模式: 'skipgram' 或者 'cbow', 默认为'skipgram', 在实践中,skipgram模式在利用子词方面比cbow更好.
# 词嵌入维度dim: 默认为100, 但随着语料库的增大, 词嵌入的维度往往也要更大.
# 数据循环次数epoch: 默认为5, 但当你的数据集足够大, 可能不需要那么多次.
# 学习率lr: 默认为0.05, 根据经验, 建议选择[0.01,1]范围内.
# 使用的线程数thread: 默认为12个线程, 一般建议和你的cpu核数相同.
def fasttext_train_args():
model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)
<四>、模型效果检验
# 通过get_word_vector方法来获得指定词汇的词向量, 默认词向量训练出来是1个单词100特征
def fasttext_get_word_vector():
my_model = fasttext.load_model('data/fil9.bin')
result = my_model.get_word_vector('cat')
print(f'cat的词向量为:{result}')
print(f'cat的词向量类型为:{type(result)}')
print(f'cat的词向量形状为:{result.shape}')
# 检查单词向量质量的一种简单方法就是查看其邻近单词, 通过我们主观来判断这些邻近单词是否与目标单词相关来粗略评定模型效果好坏
def fasttest_get_nearest_neighbors():
my_model = fasttext.load_model('data/fil9.bin')
result = my_model.get_nearest_neighbors('cat')
print(f'cat的近义词为{result}')
result01 = my_model.get_nearest_neighbors('dog')
print(f'dog的近义词为{result01}')
<五>、模型的保存与重加载
def fasttext_save_load():
my_model.save_model('data/fil9.bin')
print('保存模型ok')
my_model = fasttext.load_model('data/fil9.bin')
print('加载模型ok')
(5)、小结
<一>、CBOW模式
- 优点:
- 训练速度快:因为 CBOW 是基于上下文预测目标词,所以它的训练速度通常比 Skip-Gram 快。
- 对低频词的处理较好:由于 CBOW 使用多个上下文词来预测目标词,因此在处理低频词时表现更好。
- 缺点:
- 难以捕捉词序信息:CBOW 将上下文词视为一个“袋子”,不考虑词的顺序,因此难以捕捉到词序信息。
- 对高频词敏感:CBOW 对高频词的依赖较大,可能会导致模型对高频词过拟合。
<二>、skip-gram模式
-
优点:
- 能更好地捕捉词序信息:Skip-Gram 是基于目标词预测上下文词,因此能够更好地捕捉词序信息。
- 对低频词的表示更准确:Skip-Gram 在处理低频词时通常能生成更好的词向量表示。
-
缺点:
- 训练速度慢:由于 Skip-Gram 需要为每个目标词生成多个上下文词的预测,因此训练速度通常比 CBOW 慢。
- 计算复杂度高:Skip-Gram 的计算复杂度较高,尤其是在大规模语料库上训练时。
<三>、区别
- 目标和输入不同:
CBOW:输入是上下文词,目标是预测目标词。
Skip-Gram:输入是目标词,目标是预测上下文词。 - 处理词序的方式不同:
CBOW:将上下文词视为一个“袋子”,不考虑词的顺序。
Skip-Gram:考虑词的顺序,能够更好地捕捉词序信息。 - 适用场景不同:
CBOW:适用于需要快速训练且对低频词有较好处理的场景。
Skip-Gram:适用于需要更准确的词向量表示且对词序信息有较高要求的场景。 - 模型复杂度不同:
CBOW:模型相对简单,训练速度快。
Skip-Gram:模型复杂度高,训练速度慢。
(四)、word_encodding
(1)、概述
- 通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间.
- 广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种.
- 狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵
(2)、可视化实现
import jieba
import torch.nn as nn
from tensorflow.keras.preprocessing.text import Tokenizer
from torch.utils.tensorboard import SummaryWriter
import torch
def embadding_show():
# 获取语料
sent1 = '我们将要飞往何方'
sent2 = '我准备过会去码头整点薯条'
sents = [sent1, sent2]
# 分词获得word_list,未去重
word_list = []
for s in sents:
word_list.append(jieba.lcut(s))
print(f'word_list:{word_list}')
# 实例化 Tokenizer
my_tokenizer = Tokenizer()
my_tokenizer.fit_on_texts(word_list)
print(f'my_tokenizer.word_index:{my_tokenizer.word_index}')
print(f'my_tokenizer.index_word:{my_tokenizer.index_word}')
# 获得词表
my_token_list = my_tokenizer.index_word.values()
print(f'my_token_list:{my_token_list}')
# 文本转序列
seq2id = my_tokenizer.texts_to_sequences(word_list)
print(f'seq2id:{seq2id}')
# 获得embedding
my_embed = nn.Embedding(num_embeddings=len(my_token_list), embedding_dim=10)
print(f'my_embed.weight.shape:{my_embed.weight.shape}')
print(f'my_embed.weight.data:{my_embed.weight.data}')
# 查询词汇的向量
for idx in range(len(my_tokenizer.index_word)):
vec = my_embed(torch.tensor(idx))
word = my_tokenizer.index_word[idx + 1]
print(f'{word}:{vec}')
# 可视化存储
logdir = r'C:\Users\11451\worktest'
my_summary = SummaryWriter(logdir)
my_summary.add_embedding(my_embed.weight.data, my_token_list)
my_summary.close()
print("存储完毕!")
if __name__ == '__main__':
embadding_show()
等执行完毕后,去终端下,加入相应的conda沙箱,执行命令:
# 进入相应的沙箱
conda activate NLP_test
# 执行激活可视化,其中logdir是目标文件夹,host直接广播
tensorboard --logdir=worktest --host 0.0.0.0
然后进入指定网址:
点进去后前面修改为127.0.0.1,后面端口不变,进入网站后刷新一次,即可获得可视化。
(五)、总结
- 文本张量表示:
- 将一段文本使用张量进行表示,其中一般将词汇为表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示.
- 张量表示的作用:
- 将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作.
- 学习了文本张量表示的方法:
- one-hot编码
- Word2vec
- Word Embedding
- one-hot词向量表示:
- 又称独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数.
- 学习了one-hot编码的优劣势:
- 优势:操作简单,容易理解.
- 劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.
- word2vec:
- 是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式.
- CBOW(Continuous bag of words)模式:
- 给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇.
- CBOW模式下的word2vec过程说明:
- 假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope you set,因为是CBOW模式,所以将使用Hope和set作为输入,you作为输出,在模型训练时, Hope,set,you等词汇都使用它们的one-hot编码. 如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1).
- 接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘, 得到5x1的结果矩阵, 它将与我们真正的目标矩阵即you的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代.
- 最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.
- skipgram模式:
- 给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇.
- skipgram模式下的word2vec过程说明:
- 假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope you set,因为是skipgram模式,所以将使用you作为输入 ,hope和set作为输出,在模型训练时, Hope,set,you等词汇都使用它们的one-hot编码. 如图所示: 将you的one-hot编码与变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘, 得到目标词汇表示矩阵(3x1).
- 接着, 将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵, 它将与我们hope和set对应的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模 型迭代.
- 最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.
- fasttext工具实现word2vec的训练和使用:
- 第一步: 获取训练数据
- 第二步: 训练词向量
- 第三步: 模型超参数设定
- 第四步: 模型效果检验
- 第五步: 模型的保存与重加载
- word embedding(词嵌入):
- 通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间.
- 广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种.
- 狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵.
- word embedding的可视化分析:
- 通过使用tensorboard可视化嵌入的词向量.
- 在终端启动tensorboard服务.
- 浏览器展示并可以使用右侧近邻词汇功能检验效果.
4、文本数据分析
(一)、概述
文本数据分析能够有效帮助我们理解数据语料, 快速检查出语料可能存在的问题, 并指导之后模型训练过程中一些超参数的选择
常见的几种文本数据分析方法:
- 标签数量分布
- 句子长度分布
- 词频统计与关键词词云
(二)、获取数据集各项数据
(1)、数据来源与说明
基于真实的中文酒店评论语料
-
属于二分类的中文情感分析语料, 该语料存放在"./cn_data"目录下.
-
其中train.tsv代表训练集, dev.tsv代表验证集, 二者数据样式相同.
train.tsv中的数据内容共分为2列, 第一列数据代表具有感情色彩的评论文本; 第二列数据, 0或1, 代表每条文本数据是积极或者消极的评论, 0代表消极, 1代表积极
(2)、数据标签分布
<一>、API
sns.countplot(x='需要分类的信息', data='数据集')
'''
data=None, # 数据集
x=None, # x轴分类
y=None, # y轴分类
hue=None, #
order=None, #
hue_order=None, #
orient=None, #
color=None # 颜色
'''
<二>、实现
# 导入必备工具包
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左右, 否则就要进行必要的数据增强或数据删减. 上图中训练和验证集正负样本都稍有不均衡, 可以进行一些数据增强。使用该方法可以快速查看样本数据是否均衡。
(3)、数据句子长度
<一>、方法
list( map(lambda x: len(x), train_data['sentence'])) # 获得数据集每一个句子的长度
<二>、实现
# 思路分析 : 获取句子长度分布 -绘制句子长度分布-柱状图 句子长度分布-密度曲线图
# 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之间
(4)、获取正负样本长度散点分布
<一>、API
sns.stripplot()
'''
data=None # 数据
x=None # x轴需要分类的信息
y=None # y轴需要分类的信息
hue=None
order=None
hue_order=None
jitter=True
dodge=False
orient=None
color=None # 颜色
'''
<二>、实现方式
# 获取正负样本长度散点分布,也就是按照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)、不同词汇总数统计
<一>、方法
set(chain(*map(lambda x: jieba.lcut(x), valid_data["sentence"]))) # 先对目标句子进行分词,再将多个分词的生成器利用chain(*)解包合成一个,再对获得的数据进行去重
<二>、实现
# 导入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))
(6)、训练集高频形容词词云
<一>、API
WordCloud(font_path='data/SimHei.ttf', max_words=100, background_color='white')
'''
font_path : string
Font path to the font that will be used (OTF or TTF).
Defaults to DroidSansMono path on a Linux machine. If you are on
another OS or don't have this font, you need to adjust this path.
width : int (default=400)
Width of the canvas.
height : int (default=200)
Height of the canvas.
prefer_horizontal : float (default=0.90)
The ratio of times to try horizontal fitting as opposed to vertical.
If prefer_horizontal < 1, the algorithm will try rotating the word
if it doesn't fit. (There is currently no built-in way to get only
vertical words.)
mask : nd-array or None (default=None)
If not None, gives a binary mask on where to draw words. If mask is not
None, width and height will be ignored and the shape of mask will be
used instead. All white (#FF or #FFFFFF) entries will be considerd
"masked out" while other entries will be free to draw on. [This
changed in the most recent version!]
contour_width: float (default=0)
If mask is not None and contour_width > 0, draw the mask contour.
contour_color: color value (default="black")
Mask contour color.
scale : float (default=1)
Scaling between computation and drawing. For large word-cloud images,
using scale instead of larger canvas size is significantly faster, but
might lead to a coarser fit for the words.
min_font_size : int (default=4)
Smallest font size to use. Will stop when there is no more room in this
size.
font_step : int (default=1)
Step size for the font. font_step > 1 might speed up computation but
give a worse fit.
max_words : number (default=200)
The maximum number of words.
stopwords : set of strings or None
The words that will be eliminated. If None, the build-in STOPWORDS
list will be used. Ignored if using generate_from_frequencies.
background_color : color value (default="black")
Background color for the word cloud image.
'''
<二>、实现
# 使用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)
<三>、使用场景
根据高频形容词词云显示, 我们可以对当前语料质量进行简单评估, 同时对违反语料标签含义的词汇进行人工审查和修正, 来保证绝大多数语料符合训练标准。
(7)、验证集形容词词云
<一>、方法
chain(*map(lambda x: get_a_list(x), n_valid_data))
<二>、实现
# 每句话产生形容词列表
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()
def get_a_word_cloud():
# 获得验证集上正样本
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)
<三>、使用场景
(三)、总结
- 文本数据分析的作用:
- 文本数据分析能够有效帮助我们理解数据语料, 快速检查出语料可能存在的问题, 并指导之后模型训练过程中一些超参数的选择.
- 常用的几种文本数据分析方法:
- 标签数量分布
- 句子长度分布
- 词频统计与关键词词云
- 基于真实的中文酒店评论语料进行几种文本数据分析方法.
- 获得训练集和验证集的标签数量分布
- 获取训练集和验证集的句子长度分布
- 获取训练集和验证集的正负样本长度散点分布
- 获得训练集与验证集不同词汇总数统计
- 获得训练集上正负的样本的高频形容词词云
5、文本特征处理
(一)、概述
文本特征处理包括为语料添加具有普适性的文本特征, 如:n-gram特征, 以及对加入特征之后的文本语料进行必要的处理, 如: 长度规范. 这些特征处理工作能够有效的将重要的文本特征加入模型训练中, 增强模型评估指标.
(二)、n-gram特征
(1)、定义
给定一段文本序列,其中n个词或字的相邻共现特征即为n-gram特征。常见的n-gram特征是bi-gram和tri-gram特征,分别对应为2-3。
(2)、实现
def add_gram(text):
# 设置为2,属于是bi-gram
n_gram = 2
# 组合后再进行去重
return set(zip(*[text[i:] for i in range(n_gram)]))
if __name__ == '__main__':
print(add_gram([1, 3, 2, 1, 5, 3]))
# 结果:{(2, 1), (1, 5), (5, 3), (3, 2), (1, 3)}
# 前后两个元素两两组合,直到遍历整个数组
(三)、文本长度规范及其作用
一般模型的输入需要等尺寸大小的矩阵, 因此在进入模型前需要对每条文本数值映射后的长度进行规范, 此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度, 对超长文本进行截断, 对不足文本进行补齐(一般使用数字0), 这个过程就是文本长度规范。根据数据分析中句子长度分布,覆盖90%左右语料的最短长度。
(1)、API实现:
from tensorflow.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)
if __name__ == '__main__':
# 假定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)
# [[ 5 32 55 63 2 21 78 32 23 1],[ 0 0 0 0 0 2 32 1 23 1]]
(2)、直接实现:
def get_normal_len(data, max_len):
res = []
for i in data:
i = i + ((max_len - len(i)) * [0])
i = i[:max_len]
res.append(i)
return res
if __name__ == '__main__':
# 假定x_train里面有两条文本, 一条长度大于10, 一天小于10
x_train = [[1, 23, 5, 32, 55, 63, 2, 21, 78, 32, 23, 1],[2, 32, 1, 23, 1]]
print(get_normal_len(x_train, 8))
# [[1, 23, 5, 32, 55, 63, 2, 21], [2, 32, 1, 23, 1, 0, 0, 0]]
(四)、小结
- 文本特征处理的作用:
- 文本特征处理包括为语料添加具有普适性的文本特征, 如:n-gram特征, 以及对加入特征之后的文本语料进行必要的处理, 如: 长度规范. 这些特征处理工作能够有效的将重要的文本特征加入模型训练中, 增强模型评估指标.
- 常见的文本特征处理方法:
- 添加n-gram特征
- 文本长度规范
- n-gram特征:
- 给定一段文本序列, 其中n个词或字的相邻共现特征即n-gram特征, 常用的n-gram特征是bi-gram和tri-gram特征, 分别对应n为2和3.
- 提取n-gram特征的函数: create_ngram_set
- 文本长度规范及其作用:
- 一般模型的输入需要等尺寸大小的矩阵, 因此在进入模型前需要对每条文本数值映射后的长度进行规范, 此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度, 对超长文本进行截断, 对不足文本进行补齐(一般使用数字0), 这个过程就是文本长度规范.
- 文本长度规范的实现函数: padding
6、文本特征增强
(一)、概述
回译数据增强目前是文本数据增强方面效果较好的增强方法, 一般基于google、有道等翻译接口, 将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言, 即可认为得到与与原语料同标签的新语料, 新语料加入到原数据集中即可认为是对原数据集数据增强。
- 回译数据增强优势:
- 操作简便, 获得新语料质量高.
- 回译数据增强存在的问题:
- 在短文本回译过程中, 新语料与原语料可能存在很高的重复率, 并不能有效增大样本的特征空间.
- 高重复率解决办法:
- 进行连续的多语言翻译, 如: 中文→韩文→日语→英文→中文, 根据经验, 最多只采用3次连续翻译, 更多的翻译次数将产生效率低下, 语义失真等问题
(二)、代码实现
# 导入必备的工具包
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': '价格非常便宜'}]]}
'''
语言及其对应编码:
‘AUTO’: ‘自动检测语言’
‘zh-CHS’: ‘中文’,
‘en’: ‘英文’
‘ja’: ‘日语’
‘ko’: ‘韩语’
‘fr’: ‘法语’
‘de’: ‘德语’
(三)、小结
- 常见的文本数据增强方法:
- 回译数据增强法
- 回译数据增强法:
- 回译数据增强目前是文本数据增强方面效果较好的增强方法, 一般基于google、有道等翻译接口, 将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言, 即可认为得到与与原语料同标签的新语料, 新语料加入到原数据集中即可认为是对原数据集数据增强.
- 回译数据增强优势:
- 操作简便, 获得新语料质量高.
- 回译数据增强存在的问题:
- 在短文本回译过程中, 新语料与原语料可能存在很高的重复率, 并不能有效增大样本的特征空间.
- 高重复率解决办法:
- 进行连续的多语言翻译, 如: 中文→韩文→日语→英文→中文, 根据经验, 最多只采用3次连续翻译, 更多的翻译次数将产生效率低下, 语义失真等问题.
- 回译数据增强实现