一、基本概念
自然语言处理也就是Natural Language Processing,简称NLP。NLP就是人工只能和语言学领域的一个分支,涉及到计算机与人类语言之间的相互作用。
主要目标是让计算机能够理解、解释和生成人类语言的数据。
1 自然语言处理的基本介绍
NLP包括但不限于语言理解、语言生成、机器翻译、情感分析、语音识别和语音合成。同时在中文环境,但需要考虑中文的特殊性,如中文分词、中文语法和语义分析。
1.1 使用NLP的原因
自然语言处理就是在机器语言和人类语言之间沟通的桥梁,以实现人机交流的目的。机器也有自己的语言就是数字信息,NLP就是让机器学会处理人类的语言。
2 NLP的应用方向
2.1 自然语言理解
典型的自然语言理解:
情感分析:对给定的文本输入,在给定的选项范围内分析文本的情绪是正面还是负面。
文本分类:对于给定文本输入,在给定的选项范围内对文本进行二分类或多分类。
信息检索:搜索引擎依托于多种技术,如网络爬虫技术、检索排序技术、网页处理技术等等,为信息检索用户提供快速、高相关性的信息服务。例如百度、Google。
抽取式阅读理解:对于给定的文本输入,用文本中的内容回答问题。
语义匹配:对给定的两个文本输入,判断是否相似。
自然语言推理:对给定的两个文本输入,判断是蕴涵、矛盾还是无关。
命名实体识别:对给定的文本输入,返回含有命名实体及其对应标签的映射,例如{'糖尿病':'疾病'}。
文本摘要:对给定的文本输入,用文本中的内容对文本进行摘要。
2.2 自然语言转换
自然语言转换(NLT)任务包括但不限于:
机器翻译:将一种自然语言转换为另一种自然语言,包括从源语言到目标语言的文本或语音转换。机器学习模型目前常用seq2seq模型来实现,由Encoder和Decoder组成。
非抽取式阅读理解:接受给定文本的输入,能够理解自然语言问题,并回答问题。
文本风格转换:将文本从一种风格转换为另一种风格,如将正式文本转换为非正式文本。
语音识别:将人类的语音转换为文本,用于语音指令、口述文本、会议记录等。比如微信的语音转文字。
意图改写:对给定的文本输入,将原始文本中的意图或核心信息重新表述,以不同的词汇和句式表达相同的意思,同时保持原意的准确性和完整性。
2.3 自然语言生成
自然语言生成(NLG)任务:
文本生成:根据给定的上下文或提示,自动生成文本,如自动写作、诗歌创作、故事生成等。
语音合成:将文本转换为听起来自然的语音,用于有声书、导航系统、虚拟助手等。
聊天机器人:能够与人类实现多轮对话的聊天助手。
文本到知识:从文本中提取知识,构建知识图谱或语义网络。
语义解析:将自然语言表达转换为形式化的逻辑表示,用于命令解析、查询理解等。
2.4 同步序列到序列
特点:输入序列和输出序列是严格对齐的,即在处理一个输入序列的元素时,模型会即时产生相应的输出序列的元素。
典型应用:通常应用在那些输入和输出之间存在一对一对齐关系的任务中。例如,逐词对齐、词性标注(POS tagging)、命名实体识别(NER)。
优点:处理速度快,因为输入和输出同步生成,不需要等待整个输入处理完毕。
缺点:要求输入和输出严格对齐,不适合处理输入输出之间存在较复杂映射关系的任务。
2. 5异步序列到序列
特点:输入序列和输出序列之间不需要严格对齐,模型可以在处理完整个输入序列后,再开始生成输出序列。大多数的Seq2Seq模型都是异步的。
典型应用:用于需要对整个输入序列进行全局上下文理解后,才能生成输出的任务。例如,机器翻译(句子级别)、文本摘要生成、问答系统等。经典的Seq2Seq模型,如基于RNN或Transformer的模型,通常都属于异步类型。
优点:模型可以利用输入序列的全局上下文信息进行更准确的输出生成。
缺点:生成速度较慢,因为需要等待整个输入序列处理完毕后才开始生成输出。
3 NLP基础概率
(1)词表/词库(Vocabulary):文本数据集中出现的所有单词的集合。
(2)语料库(Corpus):用于NLP任务的文本数据集合,可以是大规模的书籍、文章、网页等。
(3)词嵌入(Word Embedding):将单词映射到低维连续向量空间的技术,用于捕捉单词的语义和语法信息。
(4)停用词(Stop Words):在文本处理中被忽略的常见单词,如"a"、"the"、"is"等,它们通常对文本的意义贡献较小。
(5)分词(Tokenization):将文本分割成一个个单词或标记的过程,为后续处理提供基本的单位。
(6) 词频(Term Frequency):在给定文档中,某个单词出现的次数。
(7)逆文档频率(Inverse Document Frequency):用于衡量一个单词在整个语料库中的重要性,是将词频取倒数并取 对数的值。
(8) TF-IDF(Term Frequency-Inverse Document Frequency):一种常用的文本特征表示方法,综合考虑了词频和逆文档频率。
(9) 词袋模型(Bag of Words):将文本表示为一个单词的集合,忽略了单词的顺序和语法结构。
(10)N-gram:连续的N个单词构成的序列,用于捕捉文本中的局部特征和上下文信息。
(11)序列:指的是一个按顺序排列的元素集合。这些元素可以是字符、单词、句子,甚至更抽象的结构。序列的每个元素都有特定的顺序和位置,这意味着它们不能随意重排,否则会影响其意义或功能。
序列的常见类型
字符序列:
一个字符串就是一个字符序列,每个字符按顺序排列。"hello"
是一个由h
、e
、l
、l
、o
组成的字符序列。单词序列:
一句话可以看作是一个单词序列,每个单词按照一定顺序排列。"I love NLP"
是一个由I
、love
、NLP
组成的单词序列。时序数据:
在时间序列中,元素是按时间顺序排列的,常用于预测问题。股票价格数据可以看作是随时间变化的数值序列。语音序列:
在语音处理任务中,语音信号可以被分解为按时间顺序排列的帧序列(特征向量序列)。其他序列:
序列还可以表示一些更抽象的结构,比如DNA序列(由碱基组成的序列)、事件序列等。
4 NLP的基本流程
中文特殊性在文本预处理环节。
1、中文文本没有像英文单词的空格,则不像英文一样直接用简单的空格和标点符号完成分词,一般需要用分词算法完成分词。
2、中文的编码不是utf-8,是Unicode,则在预处理的时候,有编码处理的问题。
3、中文NLP的基本流程由语料获取、语料预处理、文本向量化、模型构建、模型训练和模型评价。
(1)语料获取
文本语料的获取一般有以下几种方法:
(1)利用已经建好的数据集或第三方语料库。
(2)获取网络数据。
(3)与第三方合作获取数据。
(2)语料预处理
获取语料后还需要对语料进行预处理。
(1)去除数据中非文本内容
获取的文本数据可能中存在很多无用的内容,如爬取的一些HTML代码、CSS标签和不需要的标点符号等,这些都需要分步骤去除。
少量非文本内容可以直接用 Python的正则表达式删除;
复杂的非文本内容可以通过 Python的 Beautiful Soup库去除。
(2)中文分词
常用的中文分词软件:如jieba、FoolNLTK、HanLP、THULAC、NLPIR、LTP等。
其中jieba是使用 Python语言编写的。
(3)词性标注
词性标注指给词语打上词类标签,如名词、动词、形容词等。
(4)去停用词
停用词就是句子中没必要存在的词,去掉停用词后对理解整个句子的语义没有影响。
中文文本中存在大量的虚词、代词或者没有特定含义的动词、名词,在文本分析的时候需要去掉。
(3)文本向量化(特征工程)
文本数据经过预处理去除数据中非文本内容、中文分词、词性标注和去停用词后,但此时还是无法直接将文本用于任务计算,需要通过某些处理手段,预先将文本转化为特征向量。
一般可以调用一些模型来对文本进行处理,常用的模型有词袋模型(Bag of Words Model)、独热表示、TF-IDF 表示、n元语法(n-gram)模型和 Word2Vec模型等。
(4)模型构建
文本向量化后,根据文本分析的需求选择合适的模型进行模型构建,同类模型也需要多准备几个备选用于效果对比。
过于复杂的模型往往不是最优的选择,模型的复杂度与模型训练时间呈正相关,模型复杂度越高,模型训练时间往往也越长,但结果的精度可能与简单的模型相差无几。
NLP中使用的模型包括机器学习模型和深度学习模型两种。
常用的机器学习模型有SVM、Naive Bayes、决策树、K-means 等;
常用的深度学习模型有TextCNN、RNN、LSTM、GRM、Seq2Seq、transformer等。
(5)模型训练
训练时可先使用小批量数据进行试验,这样可以避免直接使用大批量数据训练导致训练时间过长等问题。
在模型训练的过程中要注意两个问题:
在训练集上表现很好,但在测试集上表现很差的过拟合问题;
模型不能很好地拟合数据的欠拟合问题,同时还要避免出现梯度消失和梯度爆炸问题。
仅训练一次的模型往往无法达到理想的精度与效果,还需要进行模型调优迭代,提升模型的性能。
模型调优往往是一个复杂、冗长且枯燥的过程,需要多次对模型的参数做出修正;调优的同时需要权衡模型的精度与泛用性,在提高模型精度的同时还需要避免过拟合。
当一个模型随着时间的推移,在新的数据集中的评价不断下降时,就意味着这个模型无法适应新的数据的变化,此时模型需要进行重新训练。
(6)模型评价
模型的评价指标指主要有准确率(Accuracy)、精确率(Precision)、召回率、F1值、ROC曲线、AUC线等。
如分类模型常用的评价方法有准确率、精确率AUC曲线等。同一种评价方法也往往适用于多种类型的模型。
二、NLP中的特征工程
特征是数据中抽取出来的对结果预测有用的信息。
因为文本是一种非结构化数据,机器学习模型无法直接处理,则必须通过特征工程啦提取有用信息。
在自然语言处理中,特征工程是指将文本数据转换成适合机器学习模型使用的数值表示过程。通过特征工程能让机器学习到文本数据中的一些特征,比如词性、语法、相似度。
1 词向量的引入
词向量也称词嵌入,这些向量能够体现词语之间的语义关系,是对词语义或含义的数值向量表示,包括字面意义和隐含意义。可以捕捉到词的内涵,将这些含义结合在一起构成一个稠密的浮点数向量,这个稠密向量支持查询和逻辑推理。
词嵌入实际上是单个词在预定义的向量空间中被表示为实数向量,每个单词都映射到一个向量。
在一个文本中包含“猫”“狗”“爱情”等若干单词,而这若干单词映射到向量空间中:
“猫”对应的向量为(0.1 0.2 0.3)
“狗”对应的向量为(0.2 0.2 0.4)
“爱情”对应的映射为(-0.4 -0.5 -0.2)
这个映射的过程就叫做词嵌入。
对于机器而言,三个词都是用0,1表示成二进制的字符串而已,无法对其进行计算。而通过词嵌入这种方式将单词转变为词向量,机器便可对单词进行计算,通过计算不同词向量之间夹角余弦值cosine而得出单词之间的相似性。
2. 传统NLP中的特征工程
2.1 独热编码 one - hot
独热编码(One-Hot Encodinga)用于将离散的类别型数据转换为数值型表示,以便输入到机器学习模型中。特点是将每个类别表示为一个向量,在该向量中,只有一个元素为1,其余元素全部为0。
One-Hot Encoding 的工作原理
假设你有一个包含以下类别的分类任务:
红色(red)、绿色(green)、蓝色(blue)
要将这些类别转换为 One-Hot 编码,为每个类别创建一个独特的二进制向量:
红色:
[1, 0, 0]
绿色:
[0, 1, 0]
蓝色:
[0, 0, 1]
则输入数据是“红色”,在使用 One-Hot 编码后,它将被表示为 [1, 0, 0]
。
在NLP当中
Time flies like an arrow.
Fruit flies like a banana.
构成词库{time, fruit, flies, like, a, an, arrow, banana}
banana的one-hot表示就是:[0,0,0,0,0,0,0,1]
"like a banana” 的one-hot表示就是:[0,0,0,1,1,0,0,1]
2.2 词频-逆文档频率(TF-IDF)
(1)词频
在计算词频(TF)时,分母是文档中的总词数,而不考虑每个词是否重复。这意味着无论词是否重复,分母始终是文档中所有词的数量总和。
例如,the在总次数为1000的文章中出现的总次数为20,则TF("the",d)=20/1000=0.02
短语,句子或者文档的词频表示就是其组成单词‘one-hot’表示向量的总和。
“Fruit flies like time flies a fruit” ,DF表示为:[1,2,2,1,1,0,0,0],总词数:(7)TF表示为:[0.14,0.29,0.29,0.14,0.14,0,0,0](1/7,2/7,2/7,1/7,1/7,0,0,0)
(2)逆文档频率(Inverse Document Frequency, IDF)
逆文档频率用来衡量一个词在整个文档集(语料库)中的重要性,是降低那些在很多文档中频繁出现的词的权重。
D
表示文档集合,t
是要计算的词。+1
是为了避免分母为 0 的情况。
例如,the在总篇数为1000篇的文章库中,在10篇文章中出现,则
3)TF-IDF 计算
最终,TF-IDF 是将 TF 和 IDF 相乘得出的结果,公式如下:
通过这个方法,一个词在特定文档中出现的频率越高(TF高),并且在整个语料库中出现得越少(IDF高),它的 TF-IDF 值就越高。
可以使模型更加关注那些在某篇文档中特别重要但不常见的词。
则"the":TF-IDF("the",d,D)=*0.02
2.3 n-grams
特征工程中的一种技术。
通过将文本中的连续 n 个词(或字符)组合起来,形成一个短语来捕捉文本中的局部上下文信息。
unigram 只关心词的独立出现频率,而 bigram 和 trigram 能捕捉到词之间的顺序关系。bigram 中几个词同时出现,这种信息在建模中会比词独立的出现频率更有价值。
假设句子为 "I love NLP and machine learning":
1-gram(Unigram): ["I", "love", "NLP", "and", "machine", "learning"]
2-grams(Bigram): ["I love", "love NLP", "NLP and", "and machine", "machine learning"]
3-grams(Trigram): ["I love NLP", "love NLP and", "NLP and machine", "and machine learning"]
将 n-grams 与 TF-IDF 相结合是文本特征工程中非常常见的做法,它不仅能够捕捉词与词之间的局部关系,还能通过 TF-IDF 来衡量这些短语在整个语料库中的重要性。
结合 n-grams 与 TF-IDF 的步骤:
生成 n-grams:首先从文本中生成 n-grams,通常使用
CountVectorizer
或类似的工具生成。计算词频 (TF):统计每个 n-gram 在文本中出现的频率。
计算逆文档频率 (IDF):计算 n-gram 在所有文档中出现的频率,稀有的 n-grams 会得到较高的权重,而常见的 n-grams 权重较低。
计算 TF-IDF:将每个 n-gram 的 TF 和 IDF 相乘,得到 TF-IDF 权重,表示该 n-gram 对特定文本的重要性。
tip:当使用 2-grams 时,I love
和 love NLP
被看作是两个单独的特征,总共有两个特征(总特征数 = 2)。
但是传统的NLP特征工程缺点:
词典多长,向量就多长,一般词典都非常大,所以计算量巨大;
如果一句话十个词,就至少向量里面有9990个0,既冗余又没有意义;
同时还存在语义鸿沟。
我们可以使用稠密编码,也就是特征嵌入。
3. 深度学习中NLP的特征输入
使用分布式单词表示技术,也称词嵌入表示。
通过查看所使用的单词的周围单词(即上下文)来学习单词表示。这种表示方式将词表示为一个粘稠的序列,在保留词上下文信息同时,避免维度过大导致的计算困难。
3.1 稠密编码(特征嵌入)
稠密编码在机器学习和深度学习中,通常指的是将离散或高维稀疏数据转化为低维的连续、密集向量表示。
稠密向量表示不再以one-hot中的一维来表示各个特征,是把每个核心特征(词,词性,位置等)都嵌入到d维空间中,并用空间中的一个向量表示。
空间维度d远小于每个特征的样本数(40000的词表,100/200维向量)。
嵌入的向量(每个核心特征的向量表示)作为网络参数与神经网络中的其他参数一起被训练。
特征嵌入,也是词嵌入,是稠密编码的一种表现形式。
目的是将离散的类别、对象或其他类型的特征映射到一个连续的向量空间。通过这种方式,嵌入后的向量可以捕捉不同特征之间的语义关系,并且便于在后续的机器学习模型中使用。
特点:
低维度:相比稀疏表示(如独热编码),稠密编码的维度更低,能够减少计算和存储成本。
语义相似性:嵌入向量之间的距离(如欧氏距离或余弦相似度)可以表示这些对象之间的语义相似性。
可微学习:嵌入表示通常通过神经网络进行学习,并且通过反向传播算法进行优化。
3.2 词嵌入算法
3.2.1 Embedding Layer
Embedding Layer是与特定自然语言处理上的神经网络模型联合学习的单词嵌入。该嵌入方法将清理好的文本中的单词进行one hot编码(独热编码),向量空间的大小或维度被指定为模型的一部分,例如50、100或200维。
向量以小的随机数进行初始化。Embedding Layer用于神经网络的前端,并采用反向传播算法进行监督。
目标是希望神经网络发现如下这样的规律:已知一句话的前几个字,预测下一个字是什么,于是有了NNLM 语言模型搭建的网络结构图:
词嵌入层的使用
词嵌入层首先会根据输入的词的数量构建一个词向量矩阵。
例如: 100 个词,每个词希望转换成 128 维度的向量,那么构建的矩阵形状即为: 100*128,输入的每个词都对应了一个该矩阵中的一个向量。
在 PyTorch可以使用 nn.Embedding 词嵌入层来实现输入词的向量化。
先将语料进行分词,构建词与索引的映射,我们可以把这个映射叫做词表,词表中每个词都对应了一个唯一的索引。
然后使用 nn.Embedding 构建词嵌入矩阵,词索引对应的向量即为该词对应的数值化后的向量表示。
例如,我们的文本数据为: "北京冬奥的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途。",接下来,我们看下如何使用词嵌入层将其进行转换为向量表示,步骤如下:
首先,将文本进行分词;
然后,根据词构建词表;
最后,使用嵌入层将文本转换为向量表示。
nn.Embedding 对象构建时,最主要有两个参数:
num_embeddings 表示词的数量
embedding_dim 表示用多少维的向量来表示每个词
nn.Embedding(num_embeddings=10, embedding_dim=4)
接下来,我们就实现下刚才的需求:
补充jieba库:
jieba.cut
:
需要分词的字符串;
cut_all(True、False)用来控制是否采用全模式;
HMM 参数用来控制是否使用 HMM 模型。
jieba.cut_for_search
:
需要分词的字符串;
是否使用 HMM 模型。
jieba.cut
以及 jieba.cut_for_search:
返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode)。
jieba.lcut
、jieba.lcut_for_search:
分词后将词存储为list,
直接返回。
jieba.Tokenizer(dictionary=DEFAULT_DICT)
:
新建自定义分词器,可用于同时使用不同词典。jieba.dt
为默认分词器,所有全局分词相关函数都是该分词器的映射。
代码实例:
import torch
import torch.nn as nn
import jieba
text='人面不知何处去,桃花依旧笑春风。'"""对文本进行精确模式分词后以列表形式返回"""
words=jieba.lcut(text)
print(words)'''初始化两个空字典,通过索引找词语、通过词语找索引'''
index_to_word={}
word_to_index={}'''set()去重,list()转换,用汉语获取不重复的词语列表'''
unique_words=list(set(words))'''遍历去重后的词语列表(unique_words),同时获取每个词语的索引和词语本身'''
for idx,word in enumerate(unique_words):'''建立索引到词语的映射,例如0:‘人’ '''index_to_word[idx]=word'''建立词语到索引的映射,例如‘人’:0 '''word_to_index[word]=idx'''nn.Embedding(),创建一个词嵌入层,以词的数量,每个词以4维向量表示,若有10个词,则每行是一个词的初始随机向量'''embed = nn.Embedding(num_embeddings=len(index_to_word), embedding_dim=4)'''遍历分词结果,对原始文本的每个词进行处理'''
for word in words:'''获取词索引,从word_to_index字典中查询该词对应的数字索引'''idx=word_to_index[word]'''转换为张量,torch.tensor(idx)将索引转换为pytorch整数张量,必需格式''''''embed()执行矩阵查找操作,等价于embed。weight[idx],返回该索引对应的4维向量'''word_vec=embed(torch.tensor(idx))'''%3s:将词语格式化为3字符宽度(对齐输出)'''print('%3s\t'%word,word_vec)
结果:
['人面', '不知', '何处', '去', ',', '桃花', '依旧', '笑', '春风', '。']
人面 tensor([-0.7112, -0.9587, -0.1539, -1.4211], grad_fn=<EmbeddingBackward0>)
不知 tensor([-0.9177, -2.1300, -1.5096, -0.5236], grad_fn=<EmbeddingBackward0>)
何处 tensor([ 0.9228, -1.7725, 0.5607, -0.4327], grad_fn=<EmbeddingBackward0>)
去 tensor([-1.0147, -1.8442, 1.8837, -0.4600], grad_fn=<EmbeddingBackward0>)
, tensor([-0.2569, -0.6907, 0.9694, 0.7406], grad_fn=<EmbeddingBackward0>)
桃花 tensor([ 1.2791, -0.7683, -0.9927, -0.2771], grad_fn=<EmbeddingBackward0>)
依旧 tensor([-0.9680, 0.3950, 0.4738, 0.2836], grad_fn=<EmbeddingBackward0>)
笑 tensor([ 0.0740, 1.4216, -1.6399, 0.7748], grad_fn=<EmbeddingBackward0>)
春风 tensor([ 1.2677, -0.1979, -0.4704, -1.1983], grad_fn=<EmbeddingBackward0>)
。 tensor([-1.3815, 1.1088, -0.4722, 0.4201], grad_fn=<EmbeddingBackward0>)
Embedding类是一个大小为(num_embedding,embedding_dim)的矩阵,每一行是某个词汇的嵌入向量。通过索引可以从这个矩阵中提取对应词汇的向量表示,因为 nn.Embedding
在内部通过索引直接查找矩阵中的行。