NLP入门
一、自然语言处理
1 什么是自然语言处理
【什么是人工智能,分别对应哪几个领域】
AI是模仿甚至超越人的某项机能,NLP、CV、ASR
NLP是机器理解并生成人类语言
2 自然语言处理的发展简史
1950 -- 图灵提出“机器能思考吗”,划时代性的话题
1956 -- 达特茅斯会议,提出人工智能(Artificial Intelligence),AI的元年,之前都是人类智能(Human Intelligence)
1957-1970 -- NLP开始出现两大阵营:规则 + 统计
1994-1999 -- 统计学开始占据主导地位
2000-2012 -- 机器学习开始盛行
2012-2022 -- 传统的深度学习占据主导地位
2023 -- 大模型时代
- 基于规则举例:设计语言学规则,解析句子结构,例如一个句子由主谓宾构成。
- 基于统计举例:统计机器翻译模型,收集大量平行语料库,例如中英互译,统计不同短语的分配概率,计算给定源语言翻译为目标语言的可能性。
3 自然语言处理的应用场景
语音助手:小爱同学
机器翻译:谷歌翻译
搜素引擎:百度翻译
智能对话:文心一言
推荐系统:短视频app推荐
......
二、文本预处理
1 认识文本预处理
-
【文本预处理及作用】
所处阶段:数据输入到模型之前 作用:数据清洗、指导超参数的确定,,,
-
文本预处理的主要环节
1.文本处理的基本方法:分词、NER、POS 2.文本张量的表示方法:one-hot、word2vec、wordEmbedding 3.文本语料的数据分析:标签数量分析(类别不均衡问题)、句子长度分析、词频统计和关键词词云 4.文本特征处理:添加n-gram特征、文本长度规范 5.数据增强方法:回译数据增强
2 文本处理的基本方法
【文本处理的基本方法有几种】
分词、pos、ner(2步,第一步命名实体的边界识别,序列标注任务,就是token级别的分类;第二步,对span进行分类,可以看做是句子级别的分类。)
2.1 分词
- 分词的意义
分词就是将连续的字序列,按照一定的规范,重新组合成词序列的过程
一般实现模型训练的时候,模型接受的文本基本最小单位是词语,因此我们需要对文本进行分词
词语是语意理解的基本单元
英文具有天然的空格分隔符,而中文分词的目的:寻找一个合适的分词边界,进行准确分词
-
常用分词工具
-
jieba分词工具
- 精确模式:就是按照人类擅长的表达词汇的习惯来分词
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) # 返回列表结果
- 支持中文繁体分词
import jieba content = "煩惱即是菩提,我暫且不提" jieba.lcut(content) ['煩惱', '即', '是', '菩提', ',', '我', '暫且', '不', '提']
-
支持用户自定义词典
- 词典的意义
可以根据自定义词典,修改jieba分词方式,优先考虑词典里面的词来切分 格式:词语 词频(可省略) 词性(可省略)
- 代码实现
import jieba content = "我喜欢学习" jieba.load_userdict("字典文件路径") jieba.lcut(content)
-
2.2 命名实体识别(实体抽取、ner)
- 定义
命名实体:通常指人名、地名、机构名等专有名词
NER:从一段文本中识别出上述描述的命名实体
常见的7类命名实体:人名、地名、机构名、时间、日期、货币、百分比
- 作用
同词汇一样,命名实体也是人类理解文本的基础单元,也是AI解决NLP领域高阶任务的重要环节
2.3 词性标注(pos)
- 定义
对每个词语进行词性的标注:动词、名词、形容词等
- 实现方式
import jieba.posseg as pseg # one.flag 词性 one.word token
content = "我喜欢学习"
pseg.lcut(content)
3 文本张量的表示方法
【文本张量的表示方法有几种】
onehot、word2vec、word embedding
【word2vec有几种方法,分别解释一下】
cbow、skipgram
3.1 文本张量表示
意义:将文本转换为向量(数字)的形式,使得模型能够识别进而实现训练,一般是进行词向量的表示
实现的方式:
one-hot
word2Vec
wordEmbedding
3.2 One-Hot 词向量表示
- 定义
针对每一个词汇,都会用一个向量表示,向量的长度是n(n就是词表大小),n代表去重之后的词汇总量,而且向量中只有0和1两种数字
俗称:独热编码、01编码
-
代码实现
import jieba from tensorflow.keras.preprocessing.text import Tokenizer import joblibdef onehot_gen(sent: str):# 1 准备语料vocabsvocabs = list(set(jieba.lcut(sent)))# 2 实例化词汇映射器Tokenizer,使用映射器拟合现有文本数据,内部生成 index_word、word_indexmy_tokenizer = Tokenizer()my_tokenizer.fit_on_texts(vocabs)# 3 查询单词idx 赋值zero_list 生成onehotfor vocab in vocabs:zero_list = [0] * len(vocabs)# word_index 要对应 -1,my_tokenizer 从 1 开始idx = my_tokenizer.word_index[vocab] - 1zero_list[idx] = 1print(vocab, '\t', zero_list)# 4 使用joblib工具保存映射器 joblib.dump()my_path = 'onehot_vec.pkl'joblib.dump(my_tokenizer, my_path)print('my_tokenizer saved')print(my_tokenizer.word_index) # char: idxprint(my_tokenizer.index_word) # idx: chardef onehot_use(char):my_path = 'onehot_vec.pkl'my_tokenizer = joblib.load(my_path)zero_list = [0] * len(my_tokenizer.word_index)idx = my_tokenizer.word_index[char] - 1zero_list[idx] = 1print(f'{char}的onehot编码为{zero_list}')if __name__ == '__main__':sent = '两只黄鹂鸣翠柳,一行白鹭上青天。'onehot_gen(sent)onehot_use('黄鹂')
-
【one-hot的优缺点】
优点:简单,容易理解
缺点:1、相似度为0;2、大语料情况下,占用大量资源
3.3 Word2Vec模型
word2vec是一种无监督(自监督)的训练方法,本质是训练一个模型,
将模型的参数矩阵当作所有词汇的词向量表示
两种训练方式:CBOW Skipgram
CBOW
核心思想:给一段文本,选择一定的窗口,然后利用上下文预测中间目标词
- 实现过程
假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),
窗口大小为3,因此模型的第一个训练样本来自Hope can set,
因为是CBOW模式,所以将使用Hope和set作为输入,can作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码.
如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1)
接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘,
得到5x1的结果矩阵, 它将与我们真正的目标矩阵即can的one-hot编码矩阵(5x1)进行损失的计算,
然后更新网络参数完成一次模型迭代.
最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.
skip-gram
核心思想: 给出一段文本,选择一定窗口获取数据集,利用中间词来预测上下文,
每个窗口要更新n-1次,训练更充分
Fasttext训练word2vec
- 基本过程
1.获取数据
2.训练模型(训练词向量) pip install fasttext-wheel
3.向量的检验
4.超参数设定
5.模型的保存和加载
- 代码实现
# coding: utf-8
import fasttext# 1 获取训练数据,已完成
# 2 词向量的训练保存加载
def dm_fasttext_train_save_load():# 01 训练模型my_model = fasttext.train_unsupervised('./data/file')# 02 保存模型my_model.save_model('./data/file.bin')# 03 加载模型my_model = fasttext.load_model('./data/file.bin')# 3 查看单词对应的词向量
# 通过get_word_vector方法来获得指定词汇的词向量, 默认词向量训练出来是1个单词100特征
def dm_fasttext_get_word_vector():# 01 加载模型my_model = fasttext.load_model("./data/file.bin")# 02 查询某个词汇的词向量result = my_model.get_word_vector("the")print(f'the的词向量是{type(result)}')print(f'the的词向量是{result.shape}')print(f'the的词向量是{result}')# 4 检验模型效果
# 查找"运动"的邻近单词, 可以发现"体育网", "运动汽车", "运动服"等
def dm_fasttext_get_nearest_neighbors():# 01 加载模型my_model = fasttext.load_model("data/file.bin")# 02 查询sports的临近单词results1 = my_model.get_nearest_neighbors("sports")print(f'sports的邻居--》{results1}')# 查询music的临近单词results2 = my_model.get_nearest_neighbors("music")print(f'music的邻居--》{results2}')# 查询music的临近单词results3 = my_model.get_nearest_neighbors("dog")print(f'dog的邻居--》{results3}')# 5 模型超参数设定
def dm_fasttext_train_args():# 训练模型 设置超参数my_model = fasttext.train_unsupervised('./data/file',"cbow",epoch=1,lr=0.1,dim=300,thread=8)# # 保存模型# my_model.save_model('./data/file.bin')if __name__ == '__main__':# dm_fasttext_train_save_load()# dm_fasttext_get_word_vector()dm_fasttext_get_nearest_neighbors()# dm_fasttext_train_args()
3.4 词嵌入Word Embedding
【word2vec缺点】
静态的固定的vec表示方法,多义词效果不佳
- 实现过程
借助nn.Embedding(vocab_size, embed_dim): vocab_size代表词汇(去重之后)的总量,embed_dim是我们设定的词向量维度;本质创建一个权重矩阵(充当向量矩阵),矩阵参数会随着模型的训练迭代更新
- 代码实现
# coding:utf-8
import torch
import torch.nn as nn
from torch.utils.tensorboard import SummaryWriter
import tensorflow as tf
import tensorboard as tb
from tensorflow.keras.preprocessing.text import Tokenizer
import jieba
from rich import print# 实验:nn.Embedding层词向量可视化分析
# 1 对句子分词 word_list
# 2 对句子word2id求my_token_list,对句子文本数值化sentence2id
# 3 创建nn.Embedding层,查看每个token的词向量数据def dm_embedding_show():# 1 获取语料sentence1 = '我爱我的祖国'sentence2 = "我爱自然语言处理"sentences = [sentence1, sentence2]# 2 对句子分词 word_listword_list = []for s in sentences:word_list.append(jieba.lcut(s))print(f'word_list--》{word_list}')# 3 实例化Tokenizermy_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}')print('*' * 80)# 4 拿到所有的单词# my_token_list = list(my_tokenizer.index_word.values())my_token_list = my_tokenizer.index_word.values()print(my_token_list)print('*' * 80)# 5 打印句子2idsequence2id = my_tokenizer.texts_to_sequences(word_list)print(f'sequence2id--》{sequence2id}')# 6 获得embeddingmy_embed = nn.Embedding(num_embeddings=len(my_token_list), embedding_dim=8)print(f'my_embed--》{my_embed.weight.shape}')print(my_embed.weight.data)# 7 查询词汇的向量for idx in range(len(my_tokenizer.index_word)):temp_v = my_embed(torch.tensor(idx))word = my_tokenizer.index_word[idx + 1]print(f'{word}--->:词汇对用的向量是:{temp_v}')# 8 可视化my_summary = SummaryWriter()my_summary.add_embedding(my_embed.weight.data, my_token_list)my_summary.close()# 终端操作可视化:tensorboard --logdir=runs --host 0.0.0.0if __name__ == '__main__':dm_embedding_show()
-
可视化工具:
- 工具:tensorboard
- 命令:tensorboard --logdir=runs --host 0.0.0.0
4 文本数据分析
【文本数据分析有哪些方法】
1、标签数量分布;2、句子长度分布;3、词频统计和词云
4.1 文本数据分析的作用
1: 帮助我们理解语料
2: 可以分析语料中可能存在的问题,指导我们设定模型的超参数等功能
4.2 标签数量分布
目的: 查询样本类别是否均衡,如果样本过大,需要减少数据;如果样本过少,需要增加数据
- 代码实现
import jieba
import jieba.posseg as pseg
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
from itertools import chain
from wordcloud import WordClouddef dm_label_sns_countplot():'''获取标签数量分布0 什么标签数量分布:求标签0有多少个 标签1有多少个 标签2有多少个1 设置显示风格plt.style.use('fivethirtyeight')2 pd.read_csv(path, sep='\t') 读取训练集 验证集csv: 使用逗号, 作为列与列之间的分隔符; tsv: 使用制表符(Tab,\t)作为分隔符。3 sns.countplot() 统计label标签的0、1分组数量4 画图展示 plt.title() plt.show()'''# 1 设置显示风格plt.style.use('fivethirtyeight')# plt.style.use('Solarize_Light2')# 2 读取数据train_data = pd.read_csv('./cn_data/train.tsv', sep="\t")print(f'train_data--》{train_data.head()}')print(f'train_data--》{type(train_data)}')dev_data = pd.read_csv('./cn_data/dev.tsv', sep="\t")# 3 统计训练集label标签的0、1分组数量# hue="label"表示根据"label"显示不同的颜色sns.countplot(x="label", data=train_data, hue="label")# sns.countplot(y="label", data=train_data)# sns.countplot(x=train_data["label"])plt.title("train_data")plt.show()# 4 统计验证集label标签的0、1分组数量sns.countplot(x="label", data=dev_data, hue="label")plt.title("dev_data")plt.show()
4.3 句子长度分布
目的: 模型一般规定需要输入固定的尺寸,也就是长度统一,通过分析句子长度,可以明确大部分样本属于什么长度范围,然后进行句子的长短补齐或截断
- 代码实现:(柱状图、曲线图)
def dm_len_sns_countplot_distplot():'''获取句子长度分布'''# 1 设置显示风格plt.style.use('fivethirtyeight')# 2 读取数据train_data = pd.read_csv('./cn_data/train.tsv', sep="\t")dev_data = pd.read_csv('./cn_data/dev.tsv', sep="\t")# 3 添加一列 句子长度train_data["sentence_length"] = list(map(lambda x: len(x), train_data["sentence"]))print(f'修改后的train_data--》{train_data.head()}')dev_data["sentence_length"] = list(map(lambda x: len(x), dev_data["sentence"]))print(f'修改后的dev_data--》{dev_data.head()}')# 4 画图训练集# countplot 柱状图、displot 直方图、kde=True 生成核密度曲线# plt.xticks([]) 不显示任何 x 轴的刻度标签,设置与否都是为了观看sns.countplot(x="sentence_length", data=train_data)plt.xticks([])# plt.yticks([i for i in range(0, 100, 10)])plt.show()sns.displot(x="sentence_length", data=train_data, kde=True)plt.xticks([i for i in range(0, 3500, 200)])# plt.yticks([i for i in range(0, 100, 10)])plt.show()# 5 画图验证集sns.countplot(x="sentence_length", data=dev_data)plt.xticks([])plt.show()sns.displot(x="sentence_length", data=dev_data, kde=True)plt.yticks([])plt.show()
- 代码实现(散点图)
def dm_sns_stripplot():'''获取正负样本长度散点分布'''# 1 设置显示风格plt.style.use('fivethirtyeight')# 2 读取数据train_data = pd.read_csv('./cn_data/train.tsv', sep="\t")dev_data = pd.read_csv('./cn_data/dev.tsv', sep="\t")# 3 添加一列 句子长度train_data["sentence_length"] = list(map(lambda x: len(x), train_data["sentence"]))dev_data["sentence_length"] = list(map(lambda x: len(x), dev_data["sentence"]))# 4 画图训练集sns.stripplot(y="sentence_length", x="label", data=train_data, hue="label")plt.show()# 5 画图验证集sns.stripplot(y="sentence_length", x="label", data=dev_data)plt.show()
4.4 词频和高频词云
词频: 这里指的是统计样本中词汇的总数量(需要去重)
高频词云目的: 可以方便我们查看数据集中是否存在脏数据,进而实现人工的审核和清洗
- 词频:词汇总数统计
def dm_tj_words_counts():'''获取不同词汇总数统计'''# 1 读取数据train_data = pd.read_csv('./cn_data/train.tsv', sep="\t")# print(f'train_data--》{train_data.head()}')dev_data = pd.read_csv('./cn_data/dev.tsv', sep="\t")# 2 获取训练数据语料的词语数量# chain(*) 扁平化处理train_vocab = set(chain(*map(lambda x: jieba.lcut(x), train_data["sentence"])))print(f'训练集词语数量---》{len(train_vocab)}')# 3 获取验证数据语料的词语数量dev_vocab = set(chain(*map(lambda x: jieba.lcut(x), dev_data["sentence"])))print(f'验证集词语数量---》{len(dev_vocab)}')
- 高频词云
# 7 获取训练集高频形容词词云
# 7.1 获取每个句子的形容词列表
def get_a_list(text):r = []for g in pseg.lcut(text):# g.flag 词性;g.word 具体strif g.flag == "a":r.append(g.word)return r# 7.2 根据词云列表产生词云
def get_word_cloud(keywords_list):# 实例化词云对象my_wordcloud = WordCloud(font_path='./cn_data/SimHei.ttf', max_words=100, background_color="white")# 准备数据,要有分隔符,一个stra_str = " ".join(keywords_list)# 产生词云my_wordcloud.generate(a_str)# 画图展示plt.figure()# interpolation="bilinear" 指定图像在显示时的插值方法,用于根据已知数据点估计未知数据点的值# "bilinear" 插值是一种常用插值方法,它使用四个最近邻像素的加权平均来估计新像素的值plt.imshow(my_wordcloud, interpolation="bilinear")# off 关闭或隐藏坐标轴plt.axis("off")plt.show()# 7.3 生成词云
def dm_word_cloud():# 1 获取数据train_data = pd.read_csv('./cn_data/train.tsv', sep="\t")# 2 获取积极的评论样本p_train_data = train_data[train_data["label"] == 1]["sentence"]# print(f'p_train_data--》{p_train_data.head()}')# 3 获取每个句子中形容的列表p_a_words = list(chain(*map(lambda x: get_a_list(x), p_train_data)))# print(f'p_a_words--》{p_a_words[:3]}')# print(f'p_a_words--》{len(p_a_words)}')get_word_cloud(p_a_words)# 4 获取消极的评论样本n_train_data = train_data[train_data["label"] == 0]["sentence"]# print(f'n_train_data--》{n_train_data.head()}')# 5 获取每个句子中形容的列表n_a_words = list(chain(*map(lambda x: get_a_list(x), n_train_data)))get_word_cloud(n_a_words)
5 文本特征处理
【介绍一下n-gram】
将n个连续相邻的token组合到一起,n-gram特征
【为什么要规范文本长度】
模型的输入需要【每个batch】或者【整个数据集】,统一句子长度
对语料添加普适性的特征:n-gram
对语料进行规范长度:适配模型的输入
5.1 添加N-Gram特征
定义: 将连续的相邻的词或者字组合到一块,就称为n-gram特征
- 代码实现
# coding:utf-8
def add_n_gram(a: list):n_gram = 2print(f'列表推导式结果--》{[a[i:] for i in range(n_gram)]}')# * 把list中每个元素作为独立的部分 传给zipreturn set(zip(*[a[i:] for i in range(n_gram)]))result = add_n_gram(a=[1, 3, 2, 1, 5, 3])
print(result)
5.2 文本长度规范
意义: 模型一般需要固定尺寸的输入,因此需要对文本句子进行补齐(一般用0补齐)或者截断
- 代码实现
# coding:utf-8
from keras.preprocessing import sequence# 1 使用 api 填充
def padding(x_train):max_len = 10return sequence.pad_sequences(x_train, max_len, padding="post", truncating="pre")# 假定x_train里面有两条文本, 一条长度大于10, 一天小于10
x_train = [[1, 23, 5, 32, 55, 63, 2, 21, 78, 32, 23, 1],[2, 32, 1, 23, 1]]# 2 自定义填充
def my_padding(x: list):max_len = 5# paddiingx = x + (max_len - len(x)) * [0]# truncatingx = x[:max_len]return xa = my_padding(x=[1, 2, 3, 45, 5, 6, 7, 8])
print(a)
6 文本数据增强
目的: 增加数据集(扩展数据集)
6.1 回译数据增强
定义: 通过将一种语言翻译成不同的语言,再转换回来的一种方式
eg: 中文---韩文----英语---中文