一、概念及主要环节简介
概念
文本预处理是自然语言处理(NLP)任务中的关键步骤,旨在将原始文本数据转换为适合机器学习或深度学习模型处理的结构化、干净、标准化的形式。由于自然语言具有噪声(如拼写错误、特殊符号、非结构化格式等),预处理能显著提升模型的性能和鲁棒性。
主要环节
文本基本处理、文本张量的表示、语料的数据分析、文本的特征处理、数据增强。
二、文本处理基本方法
1.分词
概念:分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。
作用:词作为语言语义理解的最小单元, 是人类理解文本语言的基础.。因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节。
jieba分词工具
多种分词模式:
①精确模式(最常用)
就是按照人类擅长的表达词汇的习惯来分词
import jieba
words1 = jieba.lcut(content, cut_all=False)
print(words1)
②全模式
将尽可能成词的词汇分割出来, 无法消除歧义
import jieba
words2 = jieba.lcut(content, cut_all=True)
print(words2)
③搜索引擎模式
在精确模式分词的基础上,将长粒度的词再次切分
import jieba
words3 = jieba.lcut_for_search(content)
print(words3)
支持中文繁体分词
import jieba
fanti_content = "煩惱即是菩提,我暫且不提"
print(fanti_content)
words = jieba.lcut(fanti_content, cut_all=False)
print(words)
支持用户自定义词典
import jieba
# 1. 加载用户自定义词典
jieba.load_userdict('自定义的词典路径')
# 2. 准备语料
content ='输入的语料'
# 3. 使用jieba的api进行分词
words = jieba.lcut(content, cut_all=False)
print(words)
2.命名实体识别(ner)
从一段文本中识别出命名实体,通常我们将人名, 地名, 机构名等专有名词统称命名实体。
作用:同词汇一样,命名实体也是人类理解文本的基础单元,也是AI解决NLP领域高阶任务的重要环节。
3.词性标注(pos)
对每个词语进行词性的标注:动词、名词、形容词等14种。
作用:词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节。
import jieba.posseg as pseg
def pos_demo():
# 1. 准备语料
content = "我爱北京天安门"
# 2. 使用jieba的api进行分词
result = pseg.lcut(content)
for word, flag in result:
print(word, flag)
if __name__ == '__main__':
pos_demo()
三、文本张量表示方法
概念
将一段文本使用张量进行表示,其中一般将词汇表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示。
作用
使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作
表示方法
1.独热编码(one-hot)
将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数。
词汇映射器Tokenizer
from tensorflow.keras.preprocessing.text import Tokenizer
import joblib
"""
思路步骤:
# 1 准备语料 vocabs
# 2 实例化词汇映射器Tokenizer, 使用映射器拟合现有文本数据 (内部生成 index_word word_index)
# 2-1 注意idx序号-1
# 3 查询单词idx 赋值 zero_list,生成onehot
# 4 使用joblib工具保存映射器 joblib.dump()
"""
# import jieba
# 导入keras中的词汇映射器Tokenizer
from tensorflow.keras.preprocessing.text import Tokenizer
# 导入用于对象保存与加载的joblib
# pip install scikit-learn
# from sklearn.externals import joblib
import joblib
"""
需求:手动实现onehot编码
思路步骤:
# 1 准备语料 vocabs
# 2 实例化词汇映射器Tokenizer, 使用映射器拟合现有文本数据 (内部生成 index_word word_index)
# 2-1 注意idx序号-1
# 3 查询单词idx 赋值 zero_list,生成onehot
# 4 使用joblib工具保存映射器 joblib.dump()
"""
def dm_onehot_gen():
# 1 准备语料 vocabs
vocabs = {"张三", "李四", "张五", "王五", "赵六", "赵七"}
# 2 实例化词汇映射器Tokenizer, 使用映射器拟合现有文本数据 (内部生成 index_word word_index)
tokenizer = Tokenizer()
tokenizer.fit_on_texts(vocabs)
# 2-1 注意idx序号-1
for vocab in vocabs:
index = tokenizer.word_index[vocab] - 1
# 2.2 查询单词idx 赋值 zero_list,生成onehot
onehot_vector = [0] * len(tokenizer.word_index)
# 2.3. 给对应索引位置赋值
onehot_vector[index] = 1
print(f'单词:{vocab}, onehot编码:{onehot_vector}')
# 4 使用joblib工具保存映射器 joblib.dump()
joblib.dump(tokenizer, './tokenizer_onehot.pkl')
print(tokenizer.word_index)
print(tokenizer.index_word)
"""
需求:基于已经保存的词汇映射器,实现onehot编码
# 思路分析
# 1 加载已保存的词汇映射器Tokenizer joblib.load(mypath)
# 2 查询单词idx 赋值zero_list,生成onehot 以token为'王五'
# 3 token = "狗蛋" 会出现异常
"""
def onehot_encode():
vocabs = {"王五"}
# 1 加载已保存的词汇映射器Tokenizer joblib.load(mypath)
tokenizer = joblib.load('./tokenizer_onehot.pkl')
# 2 查询单词idx 赋值zero_list,生成onehot 以token为'王五'
for vocab in vocabs:
index = tokenizer.word_index[vocab] - 1
onehot_vector = [0] * len(tokenizer.word_index)
onehot_vector[index] = 1
print(f'单词:{vocab}, onehot编码:{onehot_vector}')
if __name__ == '__main__':
# dm_onehot_gen()
onehot_encode()
优势:操作简单,容易理解。
劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存。
2.Word2vec
是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式。
CBOW(通过上下文预测目标词)
给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇。
词向量的检索获取:第一个线性层在对应1的位置就是对应的词向量。
skipgram(通过目标词预测上下文)
给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇。
使用fasttext工具实现word2vec
准备语料、无监督学习、验证效果
import fasttext
def fasttext_demo():
# 1. 准备语料库 ./data/fil9
path = 'data/fil9'
# 2. 使用无监督训练api ,训练模型
model = fasttext.train_unsupervised(path)
# 也可以直接加载已经训练好的模型fil9.bin.1
model = fasttext.load_model('./data/fil9.bin.1')
print(f'单词数量:{len(model.words)}')
print(f'模型维度:{model.get_dimension()}')
# 3. 验证模型的效果, 查看模型的向量
vector = model.get_word_vector('apple')
print(vector)
# 4. 模型的近邻单词的表现
print(model.get_nearest_neighbors('sports'))
print(model.get_nearest_neighbors('apple'))
# 5. 保存模型
model.save_model('./data/fil9.bin.1')
# 6. 加载模型,查看是否可以正常加载
model1 = fasttext.load_model('./data/fil9.bin.1')
vector2 = model1.get_word_vector('apple')
print(vector == vector2)
if __name__ == '__main__':
fasttext_demo()
模型超参数的设定
如:①无监督训练模式:'skipgram' 或者 'cbow', 默认为'skipgram', 在实践中,skipgram模式在利用子词方面比cbow更好;
②词向量维度dim:默认为100,但随着语料库的增大,词向量维度也要增大;
③循环次数epoch:默认为5,当数据集足够时可以适当减少;
④学习率lr:默认为0.05,一般在[0.01,1]内调整;
⑤线程数thread:默认12个线程,一般建议和使用者的cpu核数相同。
# 使用无监督训练api ,训练模型
model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)
3.Word Embedding
通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间。
广义的word embedding包括所有密集词汇向量的表示方法(如word2vec);
狭义的word embedding是指在神经网络中加入的embedding层对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵。
代码可视化实现
1. 首先我们要导包,在此过程中可能会出现SummaryWriter报错问题,下面给出了解决办法
#导包
import torch
from tensorflow.keras.preprocessing.text import Tokenizer
from torch.utils.tensorboard import SummaryWriter
import jieba
import torch.nn as nn
# 下面是解决SummaryWriter报错问题
import tensorflow as tf
import tensorboard as tb
tf.io.gfile = tb.compat.tensorflow_stub.io.gfile
import os
import tensorboard.compat.tensorflow_stub.io.gfile as gfile
gfile.join = os.path.join
2. 定义一个函数
def word_embedding_demo():
3. 对句子做分词
sentence1 = '竹影扫阶尘不动,萤光穿水夜无痕。'
sentence2 = "空庭坐久忘言说,一树松风代客论。"
# 把两个句子放在一个列表里面,方便后续分词
sentences = [sentence1, sentence2]
word_list = []
# 循环遍历两个句子,并分词。
for sentence in sentences:
# TODO:把分词后的结果放到word_list中,word_list包含所有的单词
word_list.append(jieba.lcut(sentence))
print(f'word_list: {word_list}')
4. 获取词表大小、词转索引
# 对句子求my_token_list,对句子文本数值化sentence2id
tokenizer = Tokenizer()
# TODO: 对句子中所有出现过的单词进行拟合
tokenizer.fit_on_texts(word_list)
# TODO : 获取词表大小
# my_token_list里面包含所有的不重复单词
my_token_list = tokenizer.index_word.values()
nums_embedding = len(my_token_list)
print(f'my_token_list: {my_token_list}')
print(f'nums_embedding: {nums_embedding}')
# TODO: 把句子中所有的单词转换成索引 ['竹影'] -> [2]
sentence2id = tokenizer.texts_to_sequences(word_list)
print(f'sentence2id: {sentence2id}')
5. 创建nn.Embedding层(需要词表大小和词嵌入维度)
embd = nn.Embedding(num_embeddings=nums_embedding, embedding_dim=8)
6. 创建SummaryWriter对象, 可视化词向量
# SummaryWriter中logdir参数可以指定日志存放的目录
summary_writer = SummaryWriter()
# add_embedding 是专门针对词嵌入场景写日志的api,需要两个参数:
# 参数1:embedding层中的网络参数。 参数2: 词表(包含所有去重单词的列表)
# 为了方便演示,节省时间,这里直接把模型初始化的参数放进来了
summary_writer.add_embedding(embd.weight.data, my_token_list)
summary_writer.close()
7. 通过tensorboard观察词向量相似性
在命令行执行: tensorboard --logdir=runs 或者 tensorboard --logdir=runs --host=0.0.0.0
或者tensorboard --logdir=runs --host=127.0.0.1
注意:执行命令时要在文件所在的目录下
8. 从nn.Embedding层中根据idx拿词向量
for index in range(nums_embedding):
# 6.1 把索引转化成张量
input = torch.tensor(index)
# 6.2 TODO: 输入embd,进行前向传播
word_vector = embd(input)
# 6.3 打印结果
print(f'单词:{tokenizer.index_word[index + 1]} ,索引:{index}, 词向量:{word_vector.detach().numpy()} ')
9. 主程序
if __name__ == '__main__':
word_embedding_demo()
整体效果如下