最码农 最码农
  • 首页
  • 动态广场
  • 探索•AI
    • AI•杂谈
    • AI•画廊
  • 精选栏目
  • 闲言碎语
  • 左邻右里
  • 注册
  • 登录
首页 › 机器学习 › 搭建情感分析系统

搭建情感分析系统

0x7C00
2月前机器学习阅读 101

本项目的目标是基于用户提供的评论,通过算法自动去判断其评论是正面的还是负面的情感。比如给定一个用户的评论:

  • 评论1: “我特别喜欢这个电器,我已经用了3个月,一点问题都没有!”
  • 评论2: “我从这家淘宝店卖的东西不到一周就开始坏掉了,强烈建议不要买,真实浪费钱”

对于这两个评论,第一个明显是正面的,第二个是负面的。 我们希望搭建一个AI算法能够自动帮我们识别出评论是正面还是负面。

情感分析的应用场景非常丰富,也是NLP技术在不同场景中落地的典范。比如对于一个证券领域,作为股民,其实比较关注舆论的变化,这个时候如果能有一个AI算法自动给网络上的舆论做正负面判断,然后把所有相关的结论再整合,这样我们可以根据这些大众的舆论,辅助做买卖的决策。 另外,在电商领域评论无处不在,而且评论已经成为影响用户购买决策的非常重要的因素,所以如果AI系统能够自动分析其情感,则后续可以做很多有意思的应用。

情感分析是文本处理领域经典的问题。整个系统一般会包括几个模块:

  • 数据的抓取: 通过爬虫的技术去网络抓取相关文本数据
  • 数据的清洗/预处理:在本文中一般需要去掉无用的信息,比如各种标签(HTML标签),标点符号,停用词等等
  • 把文本信息转换成向量: 这也成为特征工程,文本本身是不能作为模型的输入,只有数字(比如向量)才能成为模型的输入。所以进入模型之前,任何的信号都需要转换成模型可识别的数字信号(数字,向量,矩阵,张量……)
  • 选择合适的模型以及合适的评估方法。 对于情感分析来说,这是二分类问题(或者三分类:正面,负面,中性),所以需要采用分类算法比如逻辑回归,朴素贝叶斯,神经网络,SVM等等。另外,我们需要选择合适的评估方法,比如对于一个应用,我们是关注准确率呢,还是关注召回率呢?

在本次项目中,我们已经给定了训练数据和测试数据,它们分别是 train_positive.txt,train_negative.txt, test_combined.txt。请注意训练数据和测试数据的格式不一样,详情请见文件内容。 整个项目你需要完成以下步骤:

数据的读取以及清洗: 从给定的.txt中读取内容,并做一些数据清洗,这里需要做几个工作:

  • (1) 文本的读取,需要把字符串内容读进来。
  • (2)去掉无用的字符比如标点符号,多余的空格,换行符等
  • (3) 把文本转换成 TF-IDF 向量: 这部分直接可以利用 sklearn 提供的 TfidfVectorizer 类来做。
  • (4) 利用逻辑回归等模型来做分类,并通过交叉验证选择最合适的超参数

项目中需要用到的数据:

  • train.positive_txt, train_negative.txt, test_combined.txt: 训练和测试数据
  • stopwords.txt: 停用词库

1. File Reading:文本读取

# 导入相关库
from bs4 import BeautifulSoup

def process_file():
    """
    读取训练数据和测试数据,并对它们做一些预处理
    """    
    train_pos_file = "train_positive.txt"
    train_neg_file = "train_negative.txt"
    test_comb_file = "test_combined.txt"
    
    # 读取文件部分,把具体的内容写入到变量里面
    train_comments = []
    train_labels = []
    test_comments = []
    test_labels = []
    
    # 处理训练数据正样本
    with open(train_pos_file,mode="r",encoding="utf-8") as fp:
        soup = BeautifulSoup(fp,"html.parser")
        # 寻找review标签
        review_list = soup.find_all("review")
        for review in review_list:
            # 提取标签内容并去除空格和换行
            train_comments.append(review.text.replace(" ","").replace("\n",""))
            train_labels.append(1)

    # 处理训练数据负样本            
    with open(train_neg_file,mode="r",encoding="utf-8") as fp:
        soup = BeautifulSoup(fp,"html.parser")
        # 寻找review标签
        review_list = soup.find_all("review")
        for review in review_list:
            # 提取标签内容并去除空格和换行
            train_comments.append(review.text.replace(" ","").replace("\n",""))
            train_labels.append(0)

    # 处理测试数据
    with open(test_comb_file,mode="r",encoding="utf-8") as fp:
        soup = BeautifulSoup(fp,"html.parser")
        review_list = soup.find_all("review")
        for review in review_list:
            # 提取标签内容并去除空格和换行
            test_comments.append(review.text.replace(" ","").replace("\n",""))
             # 提取标签属性值
            test_labels.append(int(review['label']))
    return train_comments,train_labels,test_comments,test_labels

train_comments, train_labels, test_comments, test_labels = process_file()

2. Explorary Analysis:做一些简单的可视化分析

# 训练数据和测试数据大小
print (len(train_comments), len(test_comments))
  • 8065 2500

这里有一个假设想验证。我觉得,如果一个评论是负面的,则用户留言时可能会长一些,因为对于负面的评论,用户很可能会把一些细节写得很清楚。但对于正面的评论,用户可能就只写“非常好”,这样的短句。我们想验证这个假设。 为了验证这个假设,打算画两个直方图,分别对正面的评论和负面的评论。 具体的做法是:

  1. 把正面和负面评论分别收集,之后分别对正面和负面评论画一个直方图。
  2. 直方图的X轴是评论的长度,所以从是小到大的顺序。然后Y轴是对于每一个长度,出现了多少个正面或者负面的评论。 通过两个直方图的比较,即可以看出 评论 是否是一个靠谱的特征。
# 对于训练数据中的正负样本,分别画出一个histogram
# histogram的x抽是每一个样本中字符串的长度,y轴是拥有这个长度的样本的数量
# 分析样本长度是否对情感有相关性

# 导入相关库
import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline
plt.rcParams ['font.sans-serif'] = ['SimHei']

data0 = []
data1 = []

for i in range(len(train_comments)):
    # 统计长度小于等于100的字符串
    if len(train_comments[i]) <= 100:
        if train_labels[i] == 0:
            data0.append(len(train_comments[i]))
        else:
            data1.append(len(train_comments[i]))
plt.title('负样本分布')
plt.hist(data0, bins=100)
搭建情感分析系统-最码农
plt.title('正样本分布')
plt.hist(data1, bins=100)
搭建情感分析系统-最码农

由此可见,情感跟评论长度有一定的相关性。

3. 文本预处理

在此部分需要做文本预处理方面的工作。 分为几大块:

  • 去掉特殊符号 比如#$….
  • 把数字转换成特殊单词 把数字转换成 “NUM”。
  • 分词并过滤掉停用词 停用词库已经提供,需要读取停用词库,并按照此停用词库做过滤。 停用词库使用给定的文件:stopwords.txt
import re
import jieba

def clean_symbols(text):
    """
    对特殊符号做一些处理
    """
    text = re.sub('[!!]+', "!", text)
    text = re.sub('[??]+', "?", text)
    text = re.sub("[a-zA-Z#$%&\'()*+,-./:;:<=>@,。★、…【】《》“”‘’[\\]^_`{|}~]+", "OOV", text)
    text = re.sub("\s+", " ", text)
    return text

def clean_num(text):
    """
    把数字转换成特殊字符
    """
    text = re.sub('\d+\.\d+','NUM',text)
    text = re.sub('\d+','NUM',text)
    return text


def cut_word(text):
    """
    分词
    """
    text = jieba.cut(text,cut_all=False)   
    return list(text)

def clean_stopwords(text):
    """
    处理停用词
    """
    f = open('stopwords.txt', mode='r',encoding='utf-8')
    stopwords = f.readlines()
    f.close()
    for i in range(len(stopwords)):
        stopwords[i] = stopwords[i].replace('\n','')
    clean_words = [word for word in text if word not in stopwords]
    return clean_words

train_comments_cleaned = [] 
test_comments_cleaned = []
train_labels_clearned = []
test_labels_clearned = []

# 批量处理
for i in range(len(train_comments)):
    train_comments[i] = clean_symbols(train_comments[i])
    train_comments[i] = clean_num(train_comments[i])
    train_comments[i] = cut_word(train_comments[i])
    train_comments[i] = clean_stopwords(train_comments[i])
    # 过滤长度为0的句子
    if len(train_comments[i]) !=0:
        train_comments_cleaned.append(' '.join(train_comments[i]))
        train_labels_clearned.append(train_labels[i])

for i in range(len(test_comments)):
    test_comments[i] = clean_symbols(test_comments[i])
    test_comments[i] = clean_num(test_comments[i])
    test_comments[i] = cut_word(test_comments[i])
    test_comments[i] = clean_stopwords(test_comments[i])
    if len(test_comments[i]) !=0:
        test_comments_cleaned.append(' '.join(test_comments[i])) 
        test_labels_clearned.append(test_labels[i])
# 打印一下看看
print (train_comments_cleaned[0])
print (test_comments_cleaned[0])

4. 把文本转换成向量

预处理好文本之后,我们就需要把它转换成向量形式,这里我们使用tf-idf的方法。 sklearn自带此功能,直接调用即可。输入就是若干个文本,输出就是每个文本的tf-idf向量。详细的使用说明可以在这里找到: 参考:https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html 这里需要特别注意的一点是:对于训练数据调用fit_transform, 也就是训练的过程。 但对于测试数据,不能再做训练,而是直接使用已经训练好的object做transform操作。

from sklearn.feature_extraction.text import TfidfVectorizer

# 利用tf-idf从文本中提取特征,写到数组里面
tfidf_model = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b").fit(train_comments_cleaned)
# 训练数据的特征
X_train = tfidf_model.transform(train_comments_cleaned)
# 训练数据的label
y_train = train_labels_clearned
# 测试数据的特征
X_test = tfidf_model.transform(test_comments_cleaned)
# 测试数据的label
y_test = test_labels_clearned

print (np.shape(X_train), np.shape(X_test), np.shape(y_train), np.shape(y_test))
  • (8064, 24794) (2500, 24794) (8064,) (2500,)

5. 通过交叉验证来训练模型

接下来需要建模了! 这里我们分别使用逻辑回归,朴素贝叶斯和SVM来训练。针对于每一个方法我们使用交叉验证(gridsearchCV), 并选出最好的参数组合,然后最后在测试数据上做验证。

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
  
# 利用逻辑回归来训练模型
#     1. 评估方式: F1-score
#     2. 超参数(hyperparater)的选择利用grid search 
#     3. 打印出在测试数据中的最好的结果(precision, recall, f1-score, 需要分别打印出正负样本,以及综合的)
# 请注意:做交叉验证时绝对不能用测试数据。测试数据只能用来最后的“一次性”检验。
# 对于逻辑回归,经常调整的超参数为: C

params_c = np.logspace(-5,2,15)
hyperparameters = dict(C=params_c)

clf = GridSearchCV(LogisticRegression(penalty='l2',max_iter=10000), param_grid=hyperparameters, scoring ='f1', cv=5, verbose=0)
model = clf.fit(X_train, y_train)

print(model.best_params_)
  • {‘C’: 1.0}
predictions = model.predict(X_test)
print(classification_report(y_test, predictions))
搭建情感分析系统-最码农
from sklearn.naive_bayes import MultinomialNB

# 利用朴素贝叶斯来训练模型
#     1. 评估方式: F1-score
#     2. 超参数(hyperparater)的选择利用grid search 
#     3. 打印出在测试数据中的最好的结果(precision, recall, f1-score, 需要分别打印出正负样本,以及综合的)
# 请注意:做交叉验证时绝对不能用测试数据。 测试数据只能用来最后的“一次性”检验。
# 对于朴素贝叶斯,一般不太需要超参数的调节。但如果想调参,有几个参数是可以调节的。
 
params_alpha = np.linspace(0.1,1,10)  
hyperparameters = dict(alpha=params_alpha)

clf = GridSearchCV(MultinomialNB(), param_grid=hyperparameters, scoring ='f1', cv=5, verbose=0)
model = clf.fit(X_train, y_train)

print(model.best_params_)
  • {‘alpha’: 0.4}
predictions = model.predict(X_test)
print(classification_report(y_test, predictions))
搭建情感分析系统-最码农
from sklearn.svm import SVC

# 利用SVM来训练模型
#    1. 评估方式: F1-score
#    2. 超参数(hyperparater)的选择利用grid search https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html
#    3. 打印出在测试数据中的最好的结果(precision, recall, f1-score, 需要分别打印出正负样本,以及综合的)
# 请注意:做交叉验证时绝对不能用测试数据。 测试数据只能用来最后的“一次性”检验。
# 对于SVM模型,经常调整的超参数为:C, gamma, kernel。这里的参数C跟逻辑回归是一样的,gamma和kernel是针对于SVM的参数
  
params_c = np.logspace(-4, 1, 11)
params_kernel  = ['linear', 'poly', 'rbf', 'sigmoid']
hyperparameters = dict(C=params_c, kernel=params_kernel)

clf = GridSearchCV(SVC(), param_grid=hyperparameters, scoring ='f1', cv=5, verbose=0)
model = clf.fit(X_train, y_train)

print(model.best_params_)
  • {‘C’: 1.0, ‘kernel’: ‘rbf’}
predictions = model.predict(X_test)
print(classification_report(y_test, predictions))
搭建情感分析系统-最码农
人工智能
赞(0) 收藏(0)
广告点击率预测
上一篇
再想想
暂无评论
随 机 推 荐
Hadoop 常用的调优参数
Flume 企业真实面试题
Hive 自定义函数
MapReduce框架原理-OutputFormat 数据输出
Flume 自定义Source
MapReduce框架原理-Join 多种应用
Hadoop HA 高可用
Hadoop HDFS(一)
  • 0
  • 0
介绍

我们致力于打造一个原创的计算机相关技术的博客网站,旨在为访客提供一个优质的计算机技术教程交流平台。网站开辟了很多于计算机相关的栏目,并且收集了不少实用资源,同时也鼓励欢迎访客一起分享、交流、学习。

灵魂推荐
Veer图库 数码荔枝
栏目标题
首页 动态广场 探索•AI AI•杂谈 AI•画廊 精选栏目 闲言碎语 左邻右里
Copyright © 2021-2023 最码农. 苏ICP备20033168号
  • 首页
  • 动态广场
  • 探索•AI
  • 精选栏目
  • 闲言碎语
  • 左邻右里
# 教程 # # Hadoop # # Hive # # Flume # # 人工智能 #
0x7C00
即使世界毁灭,也总有回光返照的那一刻
117
文章
0
评论
404
喜欢