ML-情感分析2-使用向量空间模型与SVM进行情感分析


前言

ML-情感分析1-使用W2V与SVM进行情感分析这篇文章中,我们使用了word2vec模型进行了向量化,同时还为大家介绍了一下情感分析的基本流程。今天我们在这一篇文章中将为大家介绍向量空间模型向量化文字、使用卡方检验降维和使用tf-idf值规范化输入矩阵的知识。如果您对上述知识有什么不了解的地方,没关系,我都会在本篇文章中进行简略介绍。

1、语料预处理

作者在这里使用的是一个携程酒店正面负面评论的语料库,有3000条负面评论和7000条正面评论。可以从CSDN上面下载。如果您实在找不到下载资源可以从文末的给出的链接中下载。 同时我们还需要获取一个停用词表,这里我选择使用了中科院提供的一个停用词表。相关的资源都在我的ML-情感分析1-使用W2V与SVM进行情感分析这篇文章的文末给出了。 我们首先还是需要将语料转换成UTF-8的格式,相关代码在上篇文章中,接下来依次清洗数据、数据分词、去除停用词。建议首先从上一篇文章阅读

2、向量空间模型

向量空间模型是一种简便高效的文本表示模型,其基本思想是将文本看成由一组有区分能力的字、词或者短语特征项构成,每个特征项的权重是根据该特征对文档分类的重要程度来确定的。这里计算权重的方法是tf-idf方法,在使用这个方法前需要获得频数矩阵和构建词典。让我们回忆下上篇文章,如果仅仅是这样做我们的计算量还是很大的,所以需要降维(使用卡方检验降维),由于tf-idf算法的特性,所以降维需要在tf-idf之前,具体原因下面会介绍。整理下思路,读取语料->构建词典->获取频数矩阵->卡方检验降维->tf-idf。

2.1获取训练语料

代码中的Cleaner等类请参见上一篇文章

import jieba  
import numpy as np  
from scipy.stats import stats  
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer, TfidfVectorizer  
from sklearn.svm import SVC

from Dic2Vec.CleanDatas import CleanDatas


def buildDocList(dic_template,file_postfix,dic_num=0):  
    """
    根据输入的目录模板和数量,读取相应目录模板中相应的数量的文件
    :param dic_template: eg."./datas/neg/neg"
    :param dic_num:
    :param file_postfix: 文件的后缀,如txt
    :return:
    """

    results = []
    for i in range(0,dic_num):
        with open("{0}{1}.{2}".format(dic_template,i,file_postfix), 'r', encoding='utf-8', errors='ignore') as f:
            content = f.read().replace("\n", '').strip()
            cleaner = CleanDatas()
            content = cleaner.clean(content)
            seg = jieba.cut(content, cut_all=False)
            s = '-'.join(seg)
            seg_list = s.split('-')
            if len(cleaner.clean_detected_words(seg_list)) > 0:
                seg_list = cleaner.clean_detected_words(seg_list)
            s = " ".join(seg_list)
            results.append(s)
    return results

corpus_neg = buildDocList("../datas/neg/neg","txt",3000)  
corpus_pos = buildDocList("../datas/pos/pos","txt",7000)  
#拼接正负文本集为一个总文本集,先负后正
corpus = corpus_neg + corpus_pos  

2.2、构建词典

我们首先需要根据输入的语料构建词典。我们直接通过一个简单的例子来进行说明。假设我们的输入有3句话:

docs= ['Julie loves me more than Linda loves me',  
       'Jane likes me more than Julie loves me',
       'He likes basketball more than baseball']

那么我们需要提取其中的每一个单词出来构建成为我们的字典:

[me, basketball, Julie, baseball, likes, loves, Jane, Linda, He, than, more]

2.3、构建词频矩阵

词频矩阵由词频向量构成。词频向量是这么构成的:长度等于字典大小,遍历字典,每一维度的数值等于这个词在当前预料中出现的频数。来看例子:

第一句话'Julie loves me more than Linda loves me'的词频向量为:
[2, 0, 1, 0, 0, 2, 0, 1, 0, 1, 1]

我们首先查看词典的第一个维度me,它在第一句话中出现了2次,频数为2,所以词向量第一位是2,basketball在第一句话中没有出现,所以是0,以此类推。 所有语料的词向量就构成了我们的词频矩阵:

Julie loves me more than Linda loves me的词频向量为:[2, 0, 1, 0, 0, 2, 0, 1, 0, 1, 1]  
Jane likes me more than Julie loves me的词频向量为:[2, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1]  
He likes basketball more than baseball的词频向量为:[0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1]  
合在一起,就是词频矩阵:
[[2, 0, 1, 0, 0, 2, 0, 1, 0, 1, 1], [2, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1], [0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1]]

那么到了Python3中应该如何实现呢?别急,我们可以直接使用sklearn中的工具来实现:

#使用scipy的工具CountVectorizer,根据文本集自动创建字典以及单词频数矩阵
count_vectorizer = CountVectorizer(min_df=1)  
term_freq_matrix = count_vectorizer.fit_transform(corpus).toarray()  
# 可以用下面这条语句查看字典
#print(count_vectorizer.vocabulary_)

2.4、卡方检验降维

首先简单说一下什么是卡方检验。卡方检验又称作X2检验,在文本分类任务重,是一种常见的特征选择算法。改方法假设特征项和目标类别之间的关系服从一阶自由度的卡方分布,特征项和目标类的相互关联程度反映了该特征项对分类效果起的作用程度。我们通过一个例子来简单说明一下卡方检验降维:
假设我们手头有一组新闻标题数据,我们想要区分出其中的娱乐新闻。接下来我们提出了一个假设:标题中含有“吴亦凡”的标题有更大的可能性是娱乐新闻。那么我们应该怎么验证呢?观察下面的表格,其中的数字代表相应的标题数量:

通过这组观测数据我们可以计算出四个数值对应的期望数据: 随机选取一条新闻属于娱乐新闻的概率(19 + 34) / (19 + 34 + 24 +10) = 60.9%
现在我们就可以计算出这条假设的卡方值: \[ \chi^2 = \Sigma{ \frac{(A-T)^2}{T}}\] 其中A为实际值,也就是第一个四格表里的4个数据,T为理论值,也就是理论值四格表里的4个数据。 最终计算出的\(x^2\)值为10.01,通过查表可得p值小于0.05,则说明标题是否含有吴亦凡与新闻是否属于娱乐无关的可能性小于0.05,即我们的假设是正确的。 那么这一切在Python中应该如何实现呢?

def chi_square(dictionary, matrix, neg_num=0, pos_num=0):  
    """
    计算各个特征的卡方值,a,b,c,d分别为观测值,A,B,C,D为预测值,这里因为
    采用的训练语料是非平衡的,比例为3:7,因此A=(a+b)*.7,B=(a+b)*.3,以此类推
            正    负
    包含x1   a     b
    不包含x1 c     d
    通常用计算出的卡方值筛选特征
    :param dictionary: 字典
    :param matrix: 文本的频率矩阵
    :param neg_num: 负文本的数量
    :param pos_num: 正文本的数量
    :return:一个一维数组,包含每个特征X的卡方值,p值越小说明越有区分力,应当选取
    """
    chi_squares = []
    As = []
    Ts = []
    for i in range(0,len(dictionary)):
        a = 0
        b = 0
        for j in range(0,len(matrix)):
            if matrix[j][i] > 0 and j < neg_num:
                b += 1
            if matrix[j][i] > 0 and j >=neg_num:
                a += 1
        c = pos_num - a + 0.01
        d = neg_num - b + 0.01
        A = [a, b, c, d]
        T = [(a+b)*0.7, (a+b)*0.3, (c+d)*0.7, (c+d)*0.3]
        As.append(A)
        Ts.append(T)
        chi_squares.append(stats.chisquare(A, f_exp=T)[1])
    print(As)
    print(Ts)
    return chi_squares

#计算字典中每一个特征向量的卡方
chi_square_list = chi_square(count_vectorizer.vocabulary_,term_freq_matrix,3000,7000)  
#卡方检验降维之后的频数矩阵
term_freq_matrix_after_demetion_reduciton = []  
#保留的特征在矩阵中的下角标的集合,如第三、九个特征保留下来,则term_index = [2,8]
term_index = []  
#卡方检验中,p值小于这个值的会被保存下来,通常用0.05
INDEX = 0.05  
#遍历卡方数组,将需要保存下来的特征下角标保存下来
for i in range(0,len(chi_square_list)):  
    if chi_square_list[i] <= INDEX:
        term_index.append(i)
#遍历频数矩阵,将相应的需要保留下来的特征保留下来,保存到term_freq_matrix_after_demetion_reduciton(这是一个二维数组)
for i in term_freq_matrix:  
    each_term = []
    for j in term_index:
        each_term.append(i[j])
    term_freq_matrix_after_demetion_reduciton.append(each_term)

表格中的abcd分别对应第一个表格的左上、右上、左下、右下。ABCD对应第二个表格的相应部分。

2.5、tf-idf

TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。

在一份给定的文件里,词频 (term frequency, TF) 指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(分子一般小于分母 区别于IDF),以防止它偏向长的文件。(同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否。)
逆向文件频率 (inverse document frequency, IDF) 是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。
TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TFIDF实际上是:TF * IDF,TF词频(Term Frequency),IDF反文档频率(Inverse Document Frequency)。TF表示词条在文档d中出现的频率。IDF的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力。如果某一类文档C中包含词条t的文档数为m,而其它类包含t的文档总数为k,显然所有包含t的文档数n=m+k,当m大的时候,n也大,按照IDF公式得到的IDF的值会小,就说明该词条t类别区分能力不强。
说了这么多,我们通过一个例子来进行说明:
词频 (TF) 是一词语出现的次数除以该文件的总词语数。假如一篇文件的总词语数是100个,而词语“母牛”出现了3次,那么“母牛”一词在该文件中的词频就是3/100=0.03。一个计算文件频率 (DF) 的方法是测定有多少份文件出现过“母牛”一词,然后除以文件集里包含的文件总数。所以,如果“母牛”一词在1,000份文件出现过,而文件总数是10,000,000份的话,其逆向文件频率就是 log(10,000,000 / 1,000)=4。最后的TF-IDF的分数为0.03 * 4=0.12。
在Python中我们依旧可以通过sklearn中的模块来实现:

#使用scipy中的TfidfTransformer自动计算频数矩阵的td-idf值,然后L2标准化,然后用根据td-idf权值制成矩阵
tfidf = TfidfTransformer(norm="l2")  
tfidf.fit(term_freq_matrix_after_demetion_reduciton)  
tf_idf_matrix = tfidf.transform(term_freq_matrix_after_demetion_reduciton)  

3、使用SVM模型进行分类

这部分与上一篇文章是相同的,最终准确率在88.1%

#分割、构建训练集和测试集
x_matrix = np.array(tf_idf_matrix.todense())  
X_reduced_train = np.concatenate((x_matrix[0:2700],x_matrix[3000:9300]))  
X_reduced_test = np.concatenate((x_matrix[2700:3000],x_matrix[9300:10000]))  
y1 = np.ones(7000)  
y0 = np.zeros(3000)  
y =  np.concatenate((y0, y1))  
y_reduced_train = np.concatenate((y[0:2700], y[3000:9300]))  
y_reduced_test = np.concatenate((y[2700:3000], y[9300:10000]))  
#使用SVM进行分类,使用线性和函数
clf = SVC(C = 2, probability = True, kernel='linear')  
clf.fit(X_reduced_train, y_reduced_train)  
print('Test Accuracy: %.5f'% clf.score(X_reduced_test, y_reduced_test))  
#Accuracy:0.881

4、参考文章

卡方检验原理及应用
用Python进行卡方分析
用Python给文本创立向量空间模型的教程
中文短文本的情感分析-袁丁、周延泉-北京邮电大学


分享博文


评论博文


Last one :   彻底学会PCA1-理解PCA原理

Next article :   ML-情感分析1-使用W2V与SVM进行情感分析