目录: 一、IMDb电影评论数据预处理 二、词袋模型应用 三、文本数据清洗 四、模型训练:逻辑回归 五、核外学习:大规模数据应用 六、潜在狄氏分配(LDA)主题建模 首先祝各位读者朋友新春快乐,牛年大吉,学习工作顺利! 刚好过年,这篇文章耽搁了快一个星期啦,今天赶紧梳理一下之前的思路,完成任务!
一、IMDb电影评论数据预处理
这一篇有一个目标任务:电影评论情感分析。情感分析也是自然语言处理(NLP)领域的一个重要内容。我将记录通过机器学习算法对IMDb电影评论的数据进行分析。构建一个可以区分正面(positive)和负面(negative)的预测模型。 因此,这篇文章主要分为以下步骤:
- 预处理IMDb数据集获取目标数据
- 使用词袋模型建立特征向量
- 清洗数据集,去除噪声
- 逻辑回归算法模型训练
- 核外学习,提高效率来处理大规模数据
- LDA主题建模分类
IMDb电影评论数据下载:http://ai.stanford.edu/~amaas/data/sentiment/
下载压缩包之后需要解压,文件目录如下:
可以发现,该数据集中的评论文本和标签是分离开的,所以我们需要进行预处理成一个csv文件,存放所有数据文本以及对应的标签。
"""
encoding: utf-8
@author: Charzous
@license: Copyright © 2021 Charzous
@software: Pycharm
@file: IMDb_PreProcess.py
@time: 2021/2/1 16:17
"""
import pyprind
import pandas as pd
import numpy as np
import os
import re
# 加载数据进度条并生成csv数据文件
file = 'aclImdb'
labels = {'pos': 1, 'neg': 0}
pbar = pyprind.ProgBar(50000)#进度条
df = pd.DataFrame()
for s in ('test', 'train'):
for l in ('pos', 'neg'):
path = os.path.join(file, s, l)
for f in os.listdir(path):
with open(os.path.join(path, f), 'r', encoding='utf-8') as infile:
txt = infile.read()
df = df.append([[txt, labels[l]]], ignore_index=True)
pbar.update()
df.columns = ['review', 'sentiment']
# 电影评论数据集保存为csv文件
np.random.seed(0)
df = df.reindex(np.random.permutation(df.index))
df.to_csv('movie_data.csv', index=False, encoding='utf-8')
这时,打开保存的csv文件,内容如图:
二、词袋模型应用
词袋,就是将文本数据转化为数字类型的特征向量表示,应用在情感分析任务中,把每个评论文本转化成对应的特征向量,首先是基于给定的文档创建一个词汇表,然后计算其中每个词语在文档中的频率。 基于词袋模型的特征向量,是建立文本表示的基础,进一步通过词频逆文档频率来评估单词之间的相关性。
count=CountVectorizer()
docs=np.array(['The weather is sunny','The sun is shining','The sun is shining and the weather is sweet'])
bag=count.fit_transform(docs)
print(count.vocabulary_)
print(bag.toarray())
上面演示了几个句子转化为向量,结果如下: {‘the’: 6, ‘weather’: 7, ‘is’: 1, ‘sunny’: 4, ‘sun’: 3, ‘shining’: 2, ‘and’: 0, ‘sweet’: 5} [[0 1 0 0 1 0 1 1] [0 1 1 1 0 0 1 0] [1 2 1 1 0 1 2 1]] 首先,建立了3个句子的词汇表,然后生成三个句子的向量表示,数组下标表示词汇表的值,数组每个值表示该词在句子中的频次。 在接下来的模型构建中,将会使用词袋模型的知识来对评论进行建模。
三、文本数据清洗
在第一步保存的数据集中,可以发现评论句子会包括HTML标记和一些符号,对此进行清洗,去除噪声并将文本的单词全部转化为小写字母。使用正则表达式,定义以下函数:
# 清洗文本数据
def processor(text):
text = re.sub('<[^>]*>', "", text)
emotionicons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)', text)
text = (re.sub('[\W]+', " ", text.lower()) + " ".join(emotionicons).replace('-', ''))
return text
另外,使用NLTK来去除停用词、获取词根,更好地获取一个句子的关键信息。
# 分词以及获取词根
porter = PorterStemmer()
def tokenizer_stop(text):
return [porter.stem(word) for word in text.split()]
def tokenizer(text):
return text.split()
nltk.download('stopwords')
stop = stopwords.words('english')
下面用一个测试例子来观察使用结果:
# 测试用例
wordlist = tokenizer('runners like running and they are the best and runs a lot')
print(wordlist)
# nltk.download('stopwords') # 下载停用词,出现error需要自己手动下载
# print(nltk.find('.')) # 找到nltk_data文件夹位置
for w in wordlist:
if w not in stop:
print(w,end=" ")
[‘runners’, ‘like’, ‘running’, ‘and’, ‘they’, ‘are’, ‘the’, ‘best’, ‘and’, ‘runs’, ‘a’, ‘lot’] runners like running best runs lot Fitting 5 folds for each of 24 candidates, totalling 120 fits
四、模型训练:逻辑回归
到了这一步,我们已经将准备工作完成,对前提知识和应用有一定的理解,下面进行模型的训练。 模型使用了逻辑回归,对于该任务有比较好的效果,结合网格搜索找到最佳参数。
df = pd.read_csv('movie_data.csv', encoding='utf-8')
X_train = df.loc[:25000, 'review'].values
y_train = df.loc[:25000, 'sentiment'].values
X_test = df.loc[25000:, 'review'].values
y_test = df.loc[25000:, 'sentiment'].values
tfidf = TfidfVectorizer(strip_accents=None, lowercase=False, preprocessor=None)
param_grid = [{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [str.split],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [str.split],#tokenizer,tokenizer_stop
'vect__use_idf':[False],
'vect__norm':[None],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]}
]
lr_tfidf = Pipeline([('vect', tfidf), ('clf', LogisticRegression(random_state=2021))])
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("Train Acc:%.3f" % gs_lr_tfidf.best_score_)
clf = gs_lr_tfidf.best_estimator_
print("Test ACC:%.3f" % clf.score(X_test, y_test))
结果正确率: Train Acc:0.897 Test ACC:0.899 在模型训练过程中可以发现,对于50000条数据,花费的时间需要几十分钟,相对比较慢。 因此,我们将使用核外学习的方法,来处理大规模数据集。
五、核外学习:大规模数据应用
在网格搜索参数时,模型训练在构造每个特征向量划分花费了大量时间。所以,这里适合用“核外学习”的技术:通过对数据集的小批量来模拟分类器完成大型数据的处理工作。 完整代码如下:
"""
encoding: utf-8
@author: Charzous
@license: Copyright © 2021 Charzous
@software: Pycharm
@file: movie_emotion.py
@time: 2021/2/10 17:17
"""
import pyprind
import numpy as np
import re
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
import pickle
import os
stop = stopwords.words('english')
path = 'movie_data.csv'
# 清洗数据并去除停用词
def tokenizer(text):
text = re.sub('<[^>]*>', "", text)
emotionicons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)', text)
text = (re.sub('[\W]+', " ", text.lower()) + " ".join(emotionicons).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, label = line[:-3], int(line[-2])
yield text, label # yield 生成器
# print(next(stream_doc(path=path)))
# 获取小batch数据
def get_minibatch(doc_stream, size):
docs, y = [], []
try:
for _ in range(size):
text, lable = next(doc_stream)
docs.append(text)
y.append(lable)
except StopIteration:
return None, None
return docs, y
vect = HashingVectorizer(decode_error='ignore', n_features=2 ** 21, preprocessor=None, tokenizer=tokenizer)
clf = SGDClassifier(loss='log', random_state=2021)
# clf=DecisionTreeClassifier(max_depth=1, criterion='entropy', random_state=2021)
doc_stream = stream_doc(path=path)
# 核外学习
classes = np.array([0, 1])
pbar = pyprind.ProgBar(40)
for _ in range(40):
X_train, y_train = get_minibatch(doc_stream, size=1200)
if not X_train:
break
X_train = vect.transform(X_train)
clf.partial_fit(X_train, y_train, classes=classes) # 从硬盘直接获取流式文件,小批次训练模型
pbar.update()
X_test, y_test = get_minibatch(doc_stream, size=5000)
X_test = vect.transform(X_test)
print("Test Acc:%.3f" % clf.score(X_test, y_test))
结果可以看出,核外学习得到的模型效果也是比较好的,达到87%的正确率。 如果得到较好的模型,可以将模型保存下来,以供相关应用。
# 保存模型
clf=clf.partial_fit(X_test,y_test)
dest=os.path.join('movieClassifier','pkl_objects')
if not os.path.exists(dest):
os.makedirs(dest)
pickle.dump(stop,open(os.path.join(dest,'stopWords.pkl'),'wb'),protocol=4)
pickle.dump(clf,open(os.path.join(dest,'movieClassifier.pkl'),'wb'),protocol=4)
六、潜在狄氏分配(LDA)主题建模
主题建模是对无标签文本文档进行分配主题的任务。潜在狄氏分配(LDA)就是一种主题建模技术。 对于这一篇电影评论情感分析的任务,我就使用LDA来预测电影评论对应的主题,在sklearn中的LatentDirichletAllocation类封装了该算法。
"""
encoding: utf-8
@author: Charzous
@license: Copyright © 2021 Charzous
@software: Pycharm
@file: lda_movie.py
@time: 2021/2/10 21:39
"""
import pandas as pd
import numpy as np
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
df = pd.read_csv('movie_data.csv', encoding='utf-8')
count = CountVectorizer(stop_words='english', max_df=.1, max_features=5000)
X = count.fit_transform(df['review'].values)
lda = LatentDirichletAllocation(n_components=10, random_state=2021,
learning_method='batch') # 学习方法batch类似计算词袋矩阵,比较慢;'online'在线学习块
X_topics = lda.fit_transform(X)
# print(lda.components_.shape)
从10个主题的前5个关键词可以看出,每个主题的类别,比如Topic 5是艺术电影,Topic 6 是恐怖电影,我们提取Topic 6的前300词的文字描述,确实是恐怖主题的:
n_top_word = 5
feature_names = count.get_feature_names()
for topic_idx, topic in enumerate(lda.components_):
print("Topic %d:" % (topic_idx + 1))
print(" ".join([feature_names[i] for i in topic.argsort()[:-n_top_word - 1:-1]]))
horror = X_topics[:, 5].argsort()[::-1]
for iter_idx, movie_idx in enumerate(horror[:3]):
print(df['review'][movie_idx][:300], '...')
print()
1:Fulci is one of my all time favorite Italian splatter directors. He is also a very good story teller mixing horror, the supernatural, and psychedelic themes altogether very well. This film was truly his last great story before he directed such disappointments as “Voices From Beyond”. The story is si … 2:I just finished reading Forsyth’s novel ‘Icon’. I thought it was one of the most in depth, detailed, and page-turning books I ever read, definitely in my top 10. I acquired a DVD version of the book starring Mr. Swayze. OK, let me first point out that to fit a decent adaptation of the novel into 2.5 … 3:”One Dark Night” is a staple in the 1980’s low budget horror genre. Filled with retro puns, clothing and scenery, “ODN” transports the viewer to a simpler time, when horror films were just that… Horror!<br /><br />Nothing so intense that you can’t understand whats going on, the film tells a dark f … 到此,电影评论情感分析任务的模型建立全部完成,构建一个可以区分正面(positive)和负面(negative)的预测模型。现在看看效果,我对自己写的评论进行了预测,模型预测效果明显不错: Comment: I love this movie, it is good nice Prediction: positive Probability:91.54% Comment: I think the movie is wonderful Prediction: positive Probability:90.65% Comment: I don”t like this film, because it too boring Prediction: negative Probability:93.16% Comment: the movie is awful and stupid Prediction: negative Probability:99.09% 这一篇完成了一个实战性比较强的任务:电影评论情感分析。情感分析也是自然语言处理(NLP)领域的一个重要内容。通过机器学习算法对IMDb电影评论的数据进行分析。构建一个可以区分正面(positive)和负面(negative)的预测模型,模型预测达到不错的效果。 如果觉得不错欢迎三连哦,点赞收藏关注,一起加油进步!原创作者:Charzous.