初步理解什么是NLP

阅读: 评论:0

初步理解什么是NLP

初步理解什么是NLP

参考博文:

超干货!一文概览 NLP 算法_AI科技大本营的博客-CSDN博客

说明:本文参考了许多博文,但是学习是基于上述博文的。其它参考的博文或资料会在文章相应的段落中指出。

概述

NLP,Nature Language Processing,自然语言处理,就是用计算机来分析和生成自然语言(文本、语音),目的是让人类可以用自然语言形式跟计算机系统进行人机交互,从而更便捷、有效地进行信息管理。

NLP在实际场景中有很多方面的应用,比如翻译,包括语音翻译,文本翻译;再比如文本归类等等。不同的应用场景,NLP的处理过程可能会略有不同,但主体思路是一致的。本文的参考博文中,是通过一个识别垃圾短信(属于文本归类)的应用来描述NLP的过程的。本文试图通过这个例子,理解NLP的主体思路。

和许多机器学习的思路一致,例子中的NLP通过对很多有标记(spam,ham)的短信进行迭代学习,识别出什么样的短信才是垃圾(spam)短信,从而得到一个判定垃圾短信的模型,然后可以用这个模型去识别一些新的、需要鉴定的短信。这里的关键是,机器怎样理解短信(文本)的内容,参考博文中提到的“NLP任务可以大致分为词法分析、句法分析、语义分析三个层面。”,我理解都属于这个关键。这也是NLP区别于其它机器学习类问题的显著特征之一。

示例中的NLP过程

归纳一下,示例中的NLP过程可以分为以下几步

  1. 读取原始数据
  2. 分句成词,或者说词法分析
  3. 学习词义
  4. 基于词义,学习句义
  5. 基于句义,建立文本分类(识别垃圾短信)的模型
  6. 利用训练的模型鉴定新的短信,或者说模型评价

下面的内容,会基于对参考博文中的示例程序进行详细的实践过程,来一步步理解NLP的过程。

读取原始数据

我本地没有安装Jupyter Notebook,而是使用了Google Colab提供的Jupyter Notebook,用法比较简单,不用本地安装什么程序。用法详见我另一篇博文Google Colab Jupyter Notebook

import pandas as pd
import numpy as np
import matplotlib.pyplot as plturl = "/%E4%B8%80%E6%96%87%E6%A6%82%E8%A7%88NLP%E7%AE%97%E6%B3%95%EF%BC%88Python%EF%BC%89/data/spam.csv"
spam_df = pd.read_csv(url, header=0, encoding="ISO-8859-1")# show data, a statistic view of "ham" v.s. "spam" lables
# using pandas.Series.plot to plot data diagram
# in raw data, col 'label' will be tagged as "ham" or "spam"
_, ax = plt.subplots(1,2,figsize=(8,4))
spam_df['label'].value_counts().plot(ax=ax[0], kind="bar", rot=90, title='label');
spam_df['label'].value_counts().plot(ax=ax[1], kind="pie", rot=90, title='label', ylabel='');
print("Dataset size: ", spam_df.shape)spam_df.head(5) # return first 5 rows

对上述程序的说明:

  • 由于我没有原始数据,故使用了博主在github上存放的数据文件,因此我在使用pandas的read_csv()时,使用的时url导入的方式。
  • pandas.Series.plot()函数可以绘制matplotlib产生的坐标轴,详见pandas的说明文档:pandas.Series.plot — pandas 2.1.3 documentation。
  • spam_df.head(5)会显示前5行输入的数据内容

上述代码运行出来的效果如下

分句成词(词法分析)

参考博文的示例中,在这个阶段执行了以下步骤:

  • 替换数字为x
  • 统一小写
  • 去除非英文字符
  • 分词
  • 删除停用词
  • 词干还原

上述动作,我理解对分析垃圾短信这个目标都是不影响的。至于其它的文本处理问题,要视具体的情况而定。

def clean_non_english_xdig(txt,isstem=True, gettok=True):txt = re.sub('[0-9]', 'x', txt) # 去数字替换为xtxt = txt.lower() # 统一小写txt = re.sub('[^a-zA-Z]', ' ', txt) #去除非英文字符并替换为空格word_tokens = word_tokenize(txt) # 分词(tokenize)if not isstem: #是否做词干还原filtered_word = [w for w in word_tokens if not w in stop_words]  # 删除停用词else:filtered_word = [stemmer.stem(w) for w in word_tokens if not w in stop_words]   # 删除停用词及词干还原if gettok:   #返回为字符串或分词列表return filtered_word #返回分词列表else:return " ".join(filtered_word) #返回为字符串spam_df['token'] = ssage.apply(lambda x:clean_non_english_xdig(x))

上述代码中,我们使用了很多Python模块,包括

  • NLTK: Natural Language Toolkit,处理自然语言的工具包,具体可以去查阅其官网NLTK :: Natural Language Toolkit。NLTK :: Natural Language Toolkit前面提到的分词、删除停用词、词干还原都需要用到这个工具包
  • re:正则表达式模块。这个是Python提供的内置模块,详见re --- 正则表达式操作 — Python 3.12.0 文档。前面提到的替换数字、去除非英文字符都用到这个模块

import nltk # nltk: Natural Language Toolkit
nltk.download('stopwords')from nltk import word_tokenize
pus import stopwords
from nltk.stem import SnowballStemmer
import re  # regular expression# get stemmer(词干还原器) for English
stemmer = SnowballStemmer('english')# stopwords.words('english') will return all stopwords in English
stop_words = set(stopwords.words('english'))#print(stop_words)

关于停用词,

  • Google Colab使用stopwords模块前需要下载安装,因此有语句“nltk.download('stopwords')”
  • 所谓停用词,是从自然语言处理的角度提出的。这些词对于自然语言处理没有什么作用,甚至会产生干扰。比如限定词:"the”、“a”、“an”、“that”、和“those”。具体可以去百度。去除这些停用词,可以提高自然语言处理的效率。
  • 放开“print(stop_words)”的注释,可以查看NLTK提供的英文停用词词表

关于词干,这个应该说是英语特有的(其它表音的词汇可能也有)。比如英文单词中很多含义相同的名词和形容词就差在一个后缀上,此时这两个词的共同部分就是一个词干。还原词干也是为了提高处理的效率。

学习词义

计算机怎么理解词义,答案是词向量。因此,这个步骤的关键就是产生词向量。

词向量产生的方法,参考博文中提到了两种:One-hot编码、词嵌入分布式方法。我理解One-hot编码应该不是我们接下来讲到的方法,而且在实际应用中应该几乎用不到,所以可以直接跳过。词嵌入分布式方法,就是我们假设整个词库中存在一组“基础词汇”,所有词汇都可以通过这些“基础词汇”来解释。就好像在色彩领域中,我们可以用三原色的比例搭配来表达所有的颜色一样;所有词汇也可以用这些“基础词汇”的比例搭配构成,所有这些比例的值、放在一起就构成了词向量。

以参考博文中的图为例,"living being", "feline", "human",..."plural"就是一组“基础词汇”,"cat"就是目标词汇。目标词汇和基础词汇关系越紧密,表示出来的系数越高;反之系数越低。如果是否定关系,则表示为负数,否定程度越高,负数的绝对值越大。

词向量具有一个性质:如果两个词的词向量所在的线性空间中的欧氏距离越近,表明这两个词的词义更接近。上图展示了一个七维到两维的转换,这个我不是太理解。但我认为,即使在七维的实数线性空间(欧氏空间)中,cat、kitten这两个向量之间的距离也是接近的。

对于计算机来说,要想得到一个词汇的词向量,显然也是需要通过大量的实际文本来学习的,也就是文中提到的“通过神经网络学习构造一个低维、稠密,隐含词语间关系的向量表示”。因此,词向量的产生也需要一个机器学习模型,现有的常见有Word2Vec、Fasttext、Bert等模型。示例中使用了FastText作为词向量的生成模型。

关于FastText模型的原理,这也是一个很大的主题,这里就不展开了,后面有机会再深入研究。至于本专题,只需要理解,通过FastText模型产生了每个词的词向量,这个就可以看作计算机所理解的词义。

代码非常简单

dels import FastText# for dimension size of the word vector(vector_size): > 8.33*logN, N is the mount
#  of words in the total vocabulary (about 100000 for English)
fmodel = FastText(ken, vector_size=100, sg=1, window=3, min_count=1, epochs=10, min_n=3, max_n=6,word_ngrams=1,workers=12) 
print("输出hello的词向量",fmodel.wv['hello']) # 词向量

其中,vector_size=100就是我们设定的词向量的维度,这个值越大,对某个词的解释就越清楚。上例中也顺便打印了hello这个词对应的词向量

输出hello的词向量 [-0.06486855 -0.11669812  0.01481049  0.11959482  0.01505752 -0.211060330.0508242   0.534427    0.04983698 -0.4678988  -0.07739533 -0.2590439-0.27452448  0.34640867  0.05644388  0.0411542  -0.17742024 -0.14724205-0.40772647 -0.32718262 -0.38424245  0.02967255  0.4522676  -0.359254450.02203463 -0.14818795 -0.29914513  0.15254086 -0.11818233 -0.2659126-0.29137042  0.03555868  0.75406     0.16979928 -0.06291115  0.194953590.21627317  0.11791238 -0.24001747  0.1328767  -0.20400654 -0.4325799-0.15280439  0.04770649 -0.54049116 -0.4499988  -0.21762426 -0.04849562-0.09015825  0.465206    0.31826168 -0.2359138   0.09191308 -0.12536253-0.15756528 -0.23976502  0.4348795   0.13041997 -0.36585805  0.28864990.44637403 -0.27144343 -0.24118404  0.19216292  0.37490252  0.417626380.16895205  0.11977152  0.06955243  0.320308   -0.10889991  0.09682916-0.07709634 -0.39132398  0.43337774 -0.37753922  0.16335873 -0.3171749-0.29864448  0.24556115 -0.19857529 -0.06220499 -0.00083902 -0.04367374-0.23262507  0.02321449 -0.06271665  0.17982881 -0.16868348 -0.1960884-0.19682828 -0.06968057 -0.04842337  0.15578814  0.31849754  0.3703826-0.3100309   0.04458625  0.05850064  0.5650316 ]

基于词义,学习句义

计算机理解的词义是词向量,那么计算机理解的句义就是句向量。问题转化为怎么基于词向量产生句向量。

关于这个问题,可以参考以下博文:​​​​​​​(Word2Vec)怎么将得到的词向量变成句子向量,以及怎么衡量得到词向量的好坏_词向量转换到句子向量-CSDN博客这里简单说一下前两种计算方法:

  • 平均词向量:这个很好理解,对句子中每个词的词向量取算数平均,得到句向量
  • TF-IDF加权平均词向量:对每个词向量按照这个词的 TF-IDF 值进行打分,然后进行加权平均,得到最终的句向量

TF-IDF的解释:TF词频(Term Frequency),IDF逆向文件频率(Inverse Document Frequency)。TF表示词条在某个文档中出现的频率,频率越高说明这个词条具有很好的类别区分能力。IDF的主要思想是:如果包含某个词条的文档越少,IDF越大,则说明该词条具有很好的类别区分能力。因此,TF-IDF结合在一起,表达的意思是:如果某个词或短语在一篇文章中出现的频率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。详见:tf-idf_百度百科 (baidu)

参考博文的示例采用了平均词向量的方法。

# 对每个句子的所有词向量取均值,来生成一个句子的vector
def build_sentence_vector(sentence,w2v_model,size=100):sen_vec&#s((size,))count=0for word in sentence:try:sen_vec+=w2v_model[word]#.reshape((1,size))count+=1except KeyError:continueif count!=0:sen_vec/=countreturn sen_vec# 句向量
sents_vec = []
for sent in spam_df['token']:sents_vec.append(build_sentence_vector(sent,fmodel.wv,size=100))print(len(sents_vec))
#for x in range(5):
#  print(sents_vec[x])

说明:

  • np是前面import的numpy模块,用于数据处理,也是一个基于Python的开源模块。
  • w2v_model这个形参,传进来的实参是fmodel.wv就是前面基于FastText得到的生成词向量的模型
  • 主要的算法是:对句子(sentence)的每一个词(word)的词向量(w2v_model[word])取算术平均,得到最终的句向量sen_vec

基于句义,建立文本分类模型

在训练数据之前,对于标签,还需要进行处理。原始数据中的标签是字符串{"spam","ham"},这个不利于计算机处理。这实际上是一个二分类问题,可以将标签转化为{0, 1},从而用二分类的模型去处理。比如定义:"spam" = 1, "ham" = 0。

# convert col 'lable' to '0'(not spam) or '1'(spam)
spam_df['label'] = (spam_df.label=='spam').astype(int)
# print(spam_df['label'])
spam_df.head(3)

spam_df.head(3)显示了头三行的内容,可以看到,label列已经被转化为0,1这样的值了

于是,我们用前面产生的每一条短消息对应的句向量作为输入,转化后的label(值为0, 1)作为标签,然后可以开始训练模型了。

Logistic 回归(Logistic Regression,LR)是一种常用的处理二分类问题的线性模型。示例中使用了LightGBM(Light Gradient Boosting Machine)模型,但也包含了使用Logistic 回归模型的代码(被注释了,可以解开注释使用)。

del_selection import train_test_split
from lightgbm import LGBMClassifier
from sklearn.linear_model import LogisticRegressiontrain_x, test_x, train_y, test_y = train_test_split(sents_vec, spam_df.label,test_size=0.2,shuffle=True,random_state=42)
result = []
clf = LGBMClassifier(class_weight='balanced',n_estimators=300, num_leaves=64, reg_alpha= 1,reg_lambda= 1,random_state=42)
#clf = LogisticRegression(class_weight='balanced',random_state=42)clf.fit(train_x,train_y)

说明:

  • train_test_split将数据按照8:2的比例划分为训练数据(train_x, train_y)和验证数据(test_x, test_y),其中test_size=0.2决定了这一比例。
  • fit()是很多模型都使用的训练函数名

模型评价

所谓模型评价,通俗点说,就是训练出来的模型,如果拿去用到新的数据上测试,看看模型预测出来的结果是否和实际情况一致。在前面的模型训练中,我们预留了20%的数据用作做这个模型测试。

对于二分类问题,ROC曲线是一种非常流程的模型评价方法,示例中也使用了这种方法。

关于ROC曲线,下面这篇文章讲得比较透彻

详解机器学习模型评估ROC曲线! (360doc)

这里简单说一下。

对于一个二分类模型,假定负样本为0,正样本为1,于是模型评价结果y_hat就有(0,1)两种取值,而实际标签y也有(0, 1)两种取值,两两组合,会有以下4种情况:

  • TP: True Positive。模型认为是正(Positive),结果模型判断对了(True)
  • FP: False Positive。模型认为是正(Positive),结果模型判断错了(False)
  • FN: False Negtive。模型认为是负(Negtive),结果模型判断错了(False)
  • TN: True Negtive。模型认为是负(Negtive),结果模型判断对了(True)

再定义:

  • FPR = FP/(FP+TN) = FP / N
  • TPR = TP/(TP+FN) = TP / P

其中,N是真实的负样本个数,P是真实的正样本个数。于是:FPR表示了、所有负样本中预测错误的比例;TPR表示了、所有正样本中预测正确的比例。

明确了FPR和TPR的概念之后,ROC曲线是这样绘制的:以FPR为横坐标、TPR为纵坐标,按照判决正负样本的阈值从1开始、一直变化到0,得到的(FPR, TPR)的点连接而成的曲线。其中,这个阈值的含义是:当 y_hat > 阈值,认为是正样本;反之是负样本。

特殊地:当阈值=1时,所有样本都判决为负样本,于是TP=0、FP=0,从而TPR=0、FPR=0,对应坐标原点(0, 0)。当阈值=0时,所有样本都判决为正样本,所有正样本也都会被模型判定为正样本,于是TPR=TP/P=1 (对于P中每一个样本,模型都判断对了,TP=P);而所有负样本也都会被模型判定为正样本,于是FPR=FP/N=1(对于N中每一个样本,模型都判断错了,FP=N)。于是当阈值=0时,(FPR, TPR)对应坐标轴上的点(1, 1)。

下面看代码

ics import auc,roc_curve,f1_score,precision_score,recall_score
def model_metrics(model, x, y,tp='auc'):""" 评估 """yhat = model.predict(x) # the esmatied y (0 or 1) for each xyprob = model.predict_proba(x)[:,1] # return the predicted probability [0, 1]# pos_label: None, if y_true is in {-1, 1}; 1 if y_true is in {0, 1}fpr,tpr,_ = roc_curve(y, yprob, pos_label=1)metrics = {'AUC':auc(fpr, tpr),'KS':max(tpr-fpr),'f1':f1_score(y,yhat),'P':precision_score(y,yhat),'R':recall_score(y,yhat)}roc_auc = auc(fpr, tpr)plt.plot(fpr, tpr, 'k--', label='ROC (area = {0:.2f})'.format(roc_auc), lw=2)plt.xlim([-0.05, 1.05])  # 设置x、y轴的上下限,以免和边缘重合,更好的观察图像的整体plt.ylim([-0.05, 1.05])plt.xlabel('False Positive Rate')plt.ylabel('True Positive Rate')  # 可以使用中文,但需要导入一些库即字体plt.title('ROC Curve')plt.legend(loc="lower right")return metricsprint('train ',model_metrics(clf,  train_x, train_y,tp='ks'))
print('test ',model_metrics(clf, test_x,test_y,tp='ks'))

说明:

  • 可以看到计算ROC曲线的函数roc_curve(),依赖于真实标签和预测结果,从而计算出TPR和FRP。注意,这里的预测结果并不是最终预测的标签,而是模型计算出的原始概率(即没有和阈值比较之前的值)。这个值一般是一个介于0、1之间的小数,比如0.6。此时,如果阈值为0.5,那么模型最终的预测结果就是Positive(1);如果阈值为0.7,那么模型最终的预测结果就是Negative(0)。由于ROC曲线计算中,本身需要阈值在[1, 0]区间内滑动,因此其需要的是模型计算出的原始概率,而不是模型最终计算出的判断结果。
  • AUC是Area Under Curve的缩写,即AOC曲线的面积。在AOC曲线评级体系中,AUC越大、越接近1,说明结果越好。
  • 最后的两个print语句,分别调用了一次model_metrics(),因此绘制了两条AOC曲线,其中一条是针对训练集(train_x, train_y)的,另一条是针对测试集(test_x,test_y)的。我们需要关注的重点就是测试集对应的曲线

下面是运行结果,可以看出对于训练集,AUC=1,已经是理想情况了。对于测试集,AUC也已经达到了0.99,非常好的结果。从ROC曲线的形状来看,都非常接近左上角的顶点(0, 1)。

本文发布于:2024-02-02 06:21:35,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170682609641947.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:NLP
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23