本文,我将利用一个例子教大家使用python中的机器学习库构建一个可以进行情感分析的模型。
首先,我们构建模型需要数据集,我们这里使用一个互联网电影数据库中的大量电影评论数据。
这里给出下载链接https://ai.stanford.edu/~amaas/data/sentiment/ai.stanford.edu
我们观察我们下载的数据集
test和train目录下都有25000个数据集,分别在neg 与 pos文件夹下,代表消极(6分以下)和积极(6分以上)的评论。
现在,我们的工作是着手于将我们的数据集进行处理,得到方便我们进行机器学习的CSV文件。
我们先想一想我们这个CSV文档应该如何构建。
每一个样例应该有一个特征,评论 还有一个标签,我们用1代表积极,用0代表消极
运行以下代码实现我们CSV文档的构建:
import pyprind
import pandas as pd
import os
pbar = pyprind.ProgBar(50000)
labels = {'pos':1,'neg':0}
df = pd.DataFrame()
for s in ('test','train'):
for l in ('pos','neg'):
path = 'C:\\Users\\lenovo1\\Desktop\\aclImdb\\%s\\%s' % (s,l)
for file in os.listdir(path):
with open(os.path.join(path,file),'r',encoding='UTF-8') as infile:
txt = infile.read()
df = df.append([[txt,labels[l]]],ignore_index=True)
pbar.update()
df.columns = ['review','sentiment']
import numpy as np
np.random.seed(0)
df = df.reindex(np.random.permutation(df.index))
df.to_csv('C:\\Users\\lenovo1\\Desktop\\aclImdb\\movie_data.csv',index=False)
df = pd.read_csv('C:\\Users\\lenovo1\\Desktop\\aclImdb\\movie_data.csv')
print(df.head(3))
pbar是我们构建的一个迭代进度条,实现下面的可视化效果:
然后我们将文档中的文本加入,并且对应是积极的评论还是消极的评论。
最终打乱顺序,以防影响学习效果。
然后输出到我们想要的位置
打印开头的三个评论,检查是否正常运行
我们有了原始的数据集了,我们就可以着手构建我们的模型了。
但是在构建模型之前有几个问题需要解决:
1:但是我们处理的特征是一个文本,我们机器学习的过程中只能由数字数据构成
2:我们的文本中含有大量的HTML符号,影响我们的学习过程
首先我们解决第一个问题:
如何将文本数据转化为矩阵数据
首先向大家介绍一种在文本处理中常见的模型-词袋模型
模型简单来说就是将一个大整体数据(在本例中是50000条评论)中出现过的所有词汇进行逐一编号,比如一个数据集中一共出现了10个词,第一条中出现了其中的5个,它的矩阵可能就是【0 0 0 0 1 1 0 1 1 1】 ,第i个位置表示第i号单词出现的次数
这样我们就可以将一句话转化为一个向量进行学习
但是这样会衍生出一个问题,我们日常用语中会出现大量的“无意义”词汇,比如英文中的 is he she are am等等,这样的词汇在我们的向量中会多次出现,对我们的学习过程会产生不确定的影响。
所以我们要解决这个问题有两个方案:
1 词频-逆文档频率:本文中不对该方式的数学推导及数学意义进行讲解,只是告诉读者这种方式会降低矩阵中在不同词条里面多数出现的词的权重。
2 停用词:这种方式就是提前准备好一个集合,里面是所有我们预先规定的停用词,也就是在句子中一般无意义的词汇,将他们在我们的数据集中进行剔除
解决这个问题之后还有一个小的问题,我们既然使用词袋模型,就一定会对文本数据进行词汇切割,就是将单个词汇分开的过程,最简单的方式就是利用文本中的空格进行分割,但是我们还有一种方式就是将文本按照空格分割后进行回归原型的处理,这种方式会将一个单词的各种形式诸如 过去式 将来时 复数形式全部转化为原型。但是这两种方式哪种比较好还是要视数据集而定。
现在第一个问题我们有了解决的办法,那么我们来看看第二个问题:
如何消去文本中的HTML符号:
原来做过爬虫或者文本处理的小伙伴应该知道正则表达式,这里我们就用正则表达式来进行对特定符号的剔除
没有了解过正则表达式的小伙伴也不用担心,简单来说正则表达式就是你利用特殊符号构建的一种规则,将文本中符合这种规则的字符串提取或者消除的一种方式,在这里我们就是构建了符合HTML符号的一种规则,将他们在我们的文本数据中进行剔除。
明确这些,我们就可以构建我们的模型了,代码如下:
import pandas as pd
df = pd.read_csv('C:\\Users\\lenovo1\\Desktop\\aclImdb\\movie_data.csv')#读出我们放入CSV中的数据
import re
def delete_html(text):
text = re.sub(']*>', " ", text)
emoticion = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)',text)
text = re.sub('[\W]+',' ',text.lower())+''.join(emoticion).replace('-','')
return text
df['review']= df['review'].apply(delete_html)
def tokenizer(text):
return text.split()
from nltk.stem.porter import PorterStemmer
def stem_tokenizer(text):
porter = PorterStemmer()
return [porter.stem(word) for word in text.split()]
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
stop = stopwords.words('english')
X_train = df.loc[:1000,'review'].values
y_train = df.loc[:1000,'sentiment'].values
X_test = df.loc[1000:2000,'review'].values
y_test = df.loc[1000:2000,'sentiment'].values
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(strip_accents=None,lowercase=False,preprocessor=None)
param_grid = [{
'vect__ngram_range':[(1,1)],
'vect__stop_words':[stop,None],
'vect__tokenizer':[tokenizer,stem_tokenizer],
'vect__use_idf':[False,True],
'vect__norm':[None,'l1','l2'],
'clf__penalty':['l1','l2'],
'clf__C':[1.0,10.0,100.0]
}]
lr_tfidf = Pipeline([
('vect',tfidf),
('clf',LogisticRegression(random_state=0))
])
gs_lr_tfidf = GridSearchCV(lr_tfidf,param_grid,scoring='accuracy',cv=5,verbose=1,n_jobs=-1)
gs_lr_tfidf.fit(X_train,y_train)
print('best choice')
print(gs_lr_tfidf.best_params_)
print('Cv score')
print(gs_lr_tfidf.best_score_)
其中def delete_html(text)就是我们利用正则表达式去除文档中的HTML符号
def stem_tokenizer(text)是我们将文本变成原型分割的方式
tokenizer是单纯利用空格将文本分割的方式
param_grid是我们在用网格搜索方式时使用的参数列表,其中前面的字符串表示我们想调试的参数的名字,后面的【】中是我们可供选择的参数数据
网格搜索是一种暴力穷举的方式,他会将我们给出的参数组合的所有可能暴力搜索一遍,给出最优的组合方式
(关于网格搜索和正则表达式我会单独写文章向大家介绍,这里大家只需要明白大概功能即可)
然后利用网格搜索,选择不同的惩罚项,是否使用停用词,是否使用词频-逆文档频率,怎样分割文本,模型的惩罚项,等等。
最终得到我们的最优组合。
best choice
{'clf__C': 100.0, 'clf__penalty': 'l2', 'vect__ngram_range': (1, 1), 'vect__norm': 'l2', 'vect__stop_words': None, 'vect__tokenizer': , 'vect__use_idf': True}
Cv score
0.8441558441558441
这就是上述代码的运行结果,大概需要5分钟左右。如果是全部的数据可能需要1个多小时,但是得到的结果也会更好
这还只是我们的数据集并不算非常大的时候,如果数据集过大怎么办?
我们有一种方式叫做在线学习,可以理解为一种牺牲精确度换取模型快速收敛的算法
具体实现如下:
import numpy as np
from nltk.corpus import stopwords
stop = stopwords.words('english')
import re
def delete_html(text):
text = re.sub(']*>', " ", text)
emoticion = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)',text)
text = re.sub('[\W]+',' ',text.lower())+''.join(emoticion).replace('-','')
token = [w for w in text.split() if w not in stop]
return token
def stream_doc(path):
with open(path,'r', encoding='UTF-8') as csv:
next(csv)
for line in csv:
text,lable = line[:-3],int(line[-2])
yield text,lable
def get_doc(doc_stream,size):
doc,y = [],[]
try:
for _ in range(size):
text,lable = next(doc_stream)
doc.append(text)
y.append(lable)
except StopIteration:
return None,None
return doc,y
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.linear_model import SGDClassifier
vect = HashingVectorizer(decode_error='ignore',n_features=2**21,preprocessor=None,tokenizer=delete_html)
clf = SGDClassifier(loss='log',random_state=1,n_iter_no_change=1)
doc_stream = stream_doc(path='C:\\Users\\lenovo1\\Desktop\\aclImdb\\movie_data.csv')
import pyprind
pb = pyprind.ProgBar(45)
classes = np.array([0, 1])
for _ in range(45):
x_train,y_train = get_doc(doc_stream,size=1000)
if not x_train:
break
x_train = vect.transform(x_train)
clf.partial_fit(x_train,y_train,classes=np.array([0, 1]))
pb.update()
x_test,y_test = get_doc(doc_stream,size=5000)
print(x_test)
x_test = vect.transform(x_test)
print(clf.score(x_test,y_test))
这里的delete_html(text):不仅仅完成了对HTML符号的去除,还完成了文本的分割,使用了停用词
stream_doc(path):每次读取CSV文档中的一行(相当于一个评论文本+标签)
get_doc(doc_stream,size):读取Size行数据,返回评论文本+标签的集合
明确了几个函数的作用,我们发现了在线学习的一个问题就是,每一次读取的都是总数据集的一个子集,我们无法构建词袋模型,所以我们这里使用的Hash的方式,为每一个词汇分配一个不同的HASH值,但是无法做到回归原型的做法了。
在我们的FOR循环中,每次使用1000个数据进行迭代,对模型进行训练,我们只需要不到一分钟的时间就可以完成,但是准确率应该是0.87,比上一种方式训练全部训练集的0.89的准确度有所下降。
这就是我们利用机器学习进行情感分析的过程了,当然, 这只是简单的情感分析,只是一个简单的二类分别,实际中还有各种各样的复杂情况需要我们解决。
如果你看完觉得有帮助就给读者点个赞,谢谢大嘎,笔芯。