自然语言处理入门
- 内容大纲:
- 文本预处理
- 经典序列模型
- RNN及其变体
- Transformer
- 迁移学习
文本预处理
认识文本预处理
-
作用:文本语料在输送给模型前一般需要一系列的预处理工作,才能符合模型输入要求,如将文本转换成模型需要的张量、规范张量的尺寸等。而且科学的文本预处理环节还将有效知道模型超参数的选择,提高模型的评估指标。
-
文本预处理中包含的主要环节:
- 文本处理的基本方法
- 分词
- 词性标注
- 命名实体识别
- 文本张量表示方法
- one-hot编码
- Word2vec
- Word Embedding
- 文本语料的数据分析
- 标签数量分布
- 句子长度分布
- 词频统计与关键词词云
- 文本特征处理
- 添加n-gram特征
- 文本长度规范
- 数据增强方法
- 回译数据增强法
- 文本处理的基本方法
文本处理的基本方法
-
分词,指将连续的字序列按照一定的规范重新组合成词序列的过程。在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符, 分词过程就是找到这样分界符的过程。
流行中文分词工具jieba
import jieba
content = '工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作'
# 精确切割模式
jieba.cut(content, cut_all = False)
print(jieba.lcut(content, cut_all = False))
['工信处', '女干事', '每月', '经过', '下属', '科室', '都', '要', '亲口', '交代', '24', '口', '交换机', '等', '技术性', '器件', '的', '安装', '工作']
- 精确切割模式就是将语句切割成我们最习惯的词。全模式分词将句子中所有的可以成词的词语都扫描出来,速度非常快但不能消除歧义。搜索引擎模式分词是在精确模式的基础上对长词再次切分,提高召回率,适合用于搜索引擎分词。
# 全模式分词
jieba.cut(content, cut_all = True)
print(jieba.lcut(content, cut_all = True))
#结果很“危险”,不予展示
# 搜索引擎模式
jieba.cut_for_search(content)
print(jieba.lcut_for_search(content))
['工信处', '干事', '女干事', '每月', '经过', '下属', '科室', '都', '要', '亲口', '交代', '24', '口', '交换', '换机', '交换机', '等', '技术', '技术性', '器件', '的', '安装', '工作']
- 使用用户自定义词典:
- jieba会准确识别词典中的词汇,提高整体识别准确度。
- 词典格式:每一行三部分,词语、词频(可省)、词性(可省),用空格隔开。
- 将下面词典存为userdict.txt,并加载使用:
print(jieba.lcut('八一双鹿更名为八一南昌篮球队!'))
jieba.load_userdict('./userdict.txt')
print(jieba.lcut('八一双鹿更名为八一南昌篮球队!'))
['八', '一双', '鹿', '更名', '为', '八一', '南昌', '篮球队', '!']
['八一双鹿', '更名', '为', '八一', '南昌', '篮球队', '!']
流行中英文分词工具hanlp
- 中英文NLP处理工具包,基于tensorflow2.0
词性标注
- 使用jieba进行中文实体标注
import jieba.posseg as pseg
print(pseg.lcut('我爱北京天安门'))
[pair('我', 'r'), pair('爱', 'v'), pair('北京', 'ns'), pair('天安门', 'ns')]
文本张量表示方法
- 文本张量表示方法:
- one-hot编码
- Word2vec
- Word Embedding
one-hot编码
- 又称独热编码,将每个词表示成具有n个元素的向量。这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素0的位置不同,其中n的大小是整个语料中不同词汇的总数。
# 导入用于对象保存和加载的joblib
import joblib
# 导入keras中词汇映射器Tokenizer
from tensorflow.keras.preprocessing.text import Tokenizer
# 假定vocab为语料集中所有不同词汇集合
vocab = {
'李克勤', '李荣浩', '廖昌永', '汪峰'}
# 实例化一个词汇映射器对象
t = Tokenizer(num_words=None, char_level=False)
# 使用映射器拟合现有文本数据
t.fit_on_texts(vocab)
for token in vocab:
zero_list = [0] * len(vocab)
# 使用映射器转化现有文本数据,每个词汇对应从1开始的自然数
# 返回样式如:[[2]],取出其中的数字需要使用[0][0]
token_index = t.texts_to_sequences([token])[0][0] - 1
zero_list[token_index] = 1
print(token, "的one-hot编码为:", zero_list)
# 使用joblib工具保存映射器,以便之后使用
tokenizer_path = './Tokenizer'
joblib.dump(t, tokenizer_path)
廖昌永 的one-hot编码为: [1, 0, 0, 0]
李克勤 的one-hot编码为: [0, 1, 0, 0]
汪峰 的one-hot编码为: [0, 0, 1, 0]
李荣浩 的one-hot编码为: [0, 0, 0, 1]
- 使用one-hot编码
t = joblib.load(tokenizer_path)
token = '李克勤'
# 从词汇映射器中得到其index
token_index = t.texts_to_sequences([token])[0][0] - 1
# 初始化一个全零向量
zero_list = [0] * 4
zero_list[token_index] = 1
print(token, '的one-hot编码为:', zero_list)
李克勤 的one-hot编码为: [0, 1, 0, 0]
- 使用one-hot编码的优劣势:
- 优势:操作简单、容易理解
- 劣势:完全割裂了词与词之间的联系,且在大语料集情况下,每个向量长度过大,占据大量内存。
word2vec编码
-
是一种流行的将词汇表示成向量的无监督训练方法,该过程将构建神经网络模型,将网络参数作为词汇的向量表示,它包含CBOW和skipgram两种训练模式
-
CBOW(Continuous bag of words)模式:给定一段用于训练的文本预料,再选定某段长度(窗口)为研究对象,使用上下文词汇预测目标词汇。通俗来讲就是周围的词预测中间的词。
-
skipgram模式:给定一段用于训练的文本语料,再选定某段长度(窗口)作为研究对象,使用目标词汇预测上下文词汇。skipgram模式和CBOW模式刚好反过来。
-
使用fasttext包中的无监督训练方法
train_unsupervised('data\enwik9data')
:
import fasttext
model = fasttext.train_unsupervised('data\enwik9data')
Read 124M words
Number of words: 218316
Number of labels: 0
Progress: 1.8% words/sec/thread: 11116 lr: 0.049106 loss: 1.586364 ETA: 2h10m
-
这些参数都是波动的,thread指线程数,lr指学习率,使用自适应的Adam优化器,loss指平均损失
-
模型超参数的设定:
- 无监督训练模式:‘skipgram’或者’cbow’,默认为’skipgram’,在实践中,skipgram模式在利用子词方面比cbow更好
- 词嵌入维度dim:默认为100,但随着语料库的增大,词嵌入的维度往往也需要增大
- 数据循环次数epoch:默认为5,但当数据集足够大可能不需要那么多次。
- 学习率lr:默认为0.05,根据经验建议选择[0.01, 1]范围内。
- 使用的线程数thread:默认为12个线程,一般建议和CPU核数相同。
-
模型训练之后可以通过查找邻近词的方法来验证模型
print(model.get_nearest_neighbors('music'))
print(model.get_nearest_neighbors('sports'))
print(model.get_nearest_neighbors('dog'))
- 模型的保存和加载
# 使用svae_model保存模型
model.save_model('fil9.bin')
# 使用fasttext.load_model加载模型
model = fasttext.load_model('fil9.bin')
model.get_word_vector('the')
word embedding
-
通过一定方式将词汇映射到指定维度(一般是更高维度)的空间
-
广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec,即可以认为是word embedding的一种
-
狭义的word embedding是指在神经网络中加入的embedding层,对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数),这个embedding矩阵就是训练过程中所有输入词汇向量表示的矩阵。
-
通过使用tensorboard可视化嵌入的词向量
# 导入torch和tensorboard的摘要写入方法
import torch
import fileinput
from torch.utils.tensorboard import SummaryWriter
# 实例化一个摘要写入对象
writer = SummaryWriter()
# 随机初始化一个100×50的矩阵,认为他是我们已经得到的词嵌入矩阵
# 代表一百个词汇,每个词汇被表示成五十维的向量
embedded = torch.randn(100, 50)
# 导入事先准备好的100个中文词汇文件,形成meta列表原始词汇
meta = list(map(lambda x: x.strip(), fileinput.input('./vocab100.csv')))
writer.add_embedding(embedded, metadata = meta)
writer.close()
文本数据分析
- 文本数据分析能够有效帮助我们理解数据语料,快速检查出预料可能存在的问题,并指导之后模型训练过程中一些超参数的选择。
- 常用文本数据分析方法
- 标签数量分布
- 句子长度分布
- 词频统计与关键词词云
- 利用真实的二分类中文情感分析语料:某酒店的客户评价。1代表积极评价,0代表消极评价。
- 获取标签数量分布:
# 使用二分类中文情感分析语料
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()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vr4Oxwb1-1634973828830)(C:\Users\Lancibe\Desktop\train_data.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmQ3nTa4-1634973828835)(C:\Users\Lancibe\Desktop\valid_data.png)]
-
在深度学习模型评估中,一般使用ACC作为评估指标,若想将ACC的基线定义在50%左右,则需要我们的正负样本比例维持在1:1左右,否则就要进行必要的数据增强或数据删减。上图中训练集和验证集正负样本都稍有不均衡,可以进行数据增强。
-
下面是句子长度分布代码
train_data['sentence_length'] = list(map(lambda x:len(x), train_data['sentence']))
sns.countplot('sentence_length', data=train_data)
plt.xticks([])
plt.show()
sns.distplot(train_data['sentence_length'])
plt.yticks([])
plt.show()
valid_data['sentence_length'] = list(map(lambda x:len(x), valid_data['sentence']))
sns.countplot('sentence_length', data=valid_data)
plt.xticks([])
plt.show()
sns.distplot(valid_data['sentence_length'])
plt.yticks([])
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHrrHPS1-1634973828839)(C:\Users\Lancibe\Desktop\train.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UkCGKQ2S-1634973828842)(C:\Users\Lancibe\Desktop\train_density.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NbsO6eoz-1634973828844)(C:\Users\Lancibe\Desktop\valid.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbl7NNvF-1634973828846)(C:\Users\Lancibe\Desktop\valid_density.png)]
-
通过分析句子长度分布,可以得知语料中大部分句子长度的分布范围,因为模型的输入要求为固定尺寸的张量,合理的尺寸范围对之后进行句子截断补齐起到关键的指导性作用。
-
获取训练集和验证集的正负样本长度散点分布:
sns.stripplot(y = 'sentence_length', x='label', data=train_data)
plt.show()
sns.stripplot(y = 'sentence_length', x='label', data=valid_data)
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FklF7g1l-1634973828847)(C:\Users\Lancibe\Desktop\train_strip.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03uYTKJT-1634973828848)(C:\Users\Lancibe\Desktop\valid_strip.png)]
- 获得训练集与验证集不同词汇总数的统计:
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))
训练集共包含不同词汇总数为: 12162
验证集共包含不同词汇总数为: 6857
- 获得训练集上正负样本的高频形容词词云:
# 使用jieba词性标注功能
import jieba.posseg as pseg
from itertools import chain
def get_a_list(text):
# 获取形容词列表
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):
# 实例化绘制词云的类
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))
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)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8oNgj0I9-1634973828849)(C:\Users\Lancibe\Desktop\wordcloud1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8algVkay-1634973828851)(C:\Users\Lancibe\Desktop\wordcloud0.png)]
文本特征处理
- 文本特征处理包括为语料添加具有普适性的文本特征,如n-gram特征,以及对加入特征之后的文本语料进行必要的处理,如:长度规范。这些特征处理工作能够有效的将重要的文本特征加入模型训练中,增强模型评估指标。
- 常见的文本特征处理方法:
- 添加n-gram特征
- 文本长度规范
添加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):
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=input_list)
print(res)
{
(3, 2), (1, 3), (2, 1), (1, 5), (5, 3)}
文本长度规范及其作用
- 一般模型的输入需要等尺寸大小的矩阵,因此进入模型前需要对每条文本数值映射后的长度进行规范,此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度,对超长文本进行截断,对不足文本进行补齐(一般使用数字0)。
from tensorflow.keras.preprocessing import sequence
# cutlen根据数据分析中句子长度分布,覆盖90%左右语料的最短长度
# 在这里假定为10
cutlen = 10
def padding(x_train):
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]]
print(padding(x_train))
[[ 5 32 55 63 2 21 78 32 23 1]
[ 0 0 0 0 0 2 32 1 23 1]]
文本数据增强
- 常用文本数据增强方法:回译数据增强法。是目前文本数据增强方面效果较好的增强方法,一般基于google翻译接口,将文本数据翻译成另一种语言(一般选择小语种)再翻译回源语言,即可认为得到与原语料同标签的新语料,新语料的加入到原数据集中即可认为是对原数据集数据增强。
- 优势:操作简便,获得新语料质量高
- 劣势:短文本回译过程中新语料和原语料可能存在很高重复率,并不能有效增大样本特征空间。
- 高重复率解决办法:进行连续的多语言翻译,例如中-韩-日-英-中,最多只采用3次连续翻译,否则将导致效率低下,语义失真等问题。
# 假设取两条已存在的正样本、负样本
p1 = '这家酒店设施非常不错'
p2 = '这家价格很便宜'
n1 = '拖鞋都发霉了,太差了'
n2 = '电视不好用,没有看到足球'
from google_trans_new import google_translator
translator = google_translator()
# 进行一次批量翻译,目标是泰语
translations = translator.translate([p1, p2, n1, n2], lang_tgt='th')
# 打进结果
print('中间翻译结果: ')
print(translations)
# 再翻译成中文
translations = translator.translate(translations, lang_tgt='zh-cn')
print('回译得到的增强数据: ')
print(translations)
中间翻译结果:
['โรงแรมนี้มี บริษัท ที่ดีมาก', 'ราคานี้ราคาถูกมาก', 'รองเท้าแตะเป็นราที่เลวร้ายเกินไป', 'ทีวีไม่ใช่เรื่องง่ายไม่เห็นฟุตบอล ']
回译得到的增强数据:
[这家酒店有一个非常好的公司','这个价格很便宜','拖鞋太糟糕了', '电视不容易,看不到足球']
tor()
进行一次批量翻译,目标是泰语
translations = translator.translate([p1, p2, n1, n2], lang_tgt=‘th’)
打进结果
print('中间翻译结果: ')
print(translations)
再翻译成中文
translations = translator.translate(translations, lang_tgt=‘zh-cn’)
print('回译得到的增强数据: ')
print(translations)
中间翻译结果:
[‘โรงแรมนี้มี บริษัท ที่ดีมาก’, ‘ราคานี้ราคาถูกมาก’, ‘รองเท้าแตะเป็นราที่เลวร้ายเกินไป’, 'ทีวีไม่ใช่เรื่องง่ายไม่เห็นฟุตบอล ‘]
回译得到的增强数据:
[这家酒店有一个非常好的公司’,‘这个价格很便宜’,‘拖鞋太糟糕了’, ‘电视不容易,看不到足球’]