zoukankan      html  css  js  c++  java
  • NLP系列(4)_朴素贝叶斯实战与进阶

    作者: 寒小阳 && 龙心尘
    时间:2016年2月。
    出处:http://blog.csdn.net/han_xiaoyang/article/details/50629608
    http://blog.csdn.net/longxinchen_ml/article/details/50629613
    声明:版权全部,转载请联系作者并注明出处

    1.引言

    前两篇博文介绍了朴素贝叶斯这个名字读着”萌蠢”但实际上简单直接高效的方法,我们也介绍了一下贝叶斯方法的一些细节。

    依照老规矩。『锄头』给你了。得负责教教怎么用和注意事项,也顺便带大家去除除草对吧。恩。此节作为更贴近实际应用的部分,将介绍贝叶斯方法的优缺点、常见适用场景和可优化点,然后找点实际场景撸点样例练练手。看看工具怎么用。

    PS:本文全部的python代码和ipython notebook已整理至github对应页面,欢迎下载和尝试。

    2.贝叶斯方法优缺点

    既然讲的是朴素贝叶斯,那博主保持和它一致的风格。简单直接高效地丢干货了:

    • 长处
    1. 对待预測样本进行预測。过程简单速度快(想想邮件分类的问题。预測就是分词后进行概率乘积,在log域直接做加法更快)。

    2. 对于多分类问题也相同非常有效,复杂度也不会有大程度上升。

    3. 在分布独立这个如果成立的情况下。贝叶斯分类器效果奇好,会略胜于逻辑回归,同一时候我们须要的样本量也更少一点
    4. 对于类别类的输入特征变量,效果非常好。

      对于数值型变量特征,我们是默认它符合正态分布的。

    • 缺点
    1. 对于測试集中的一个类别变量特征。如果在训练集里没见过,直接算的话概率就是0了,预測功能就失效了。当然。我们前面的文章提过我们有一种技术叫做『平滑』操作,能够缓解这个问题。最常见的平滑技术是拉普拉斯估測。
    2. 那个…咳咳。朴素贝叶斯算出的概率结果。比較大小还凑合,实际物理含义…恩,别太当真。

    3. 朴素贝叶斯有分布独立的如果前提。而现实生活中这些predictor非常难是全然独立的

    3.最常见应用场景

    • 文本分类/垃圾文本过滤/情感判别:这大概会朴素贝叶斯应用做多的地方了。即使在如今这样的分类器层出不穷的年代,在文本分类场景中,朴素贝叶斯依然坚挺地占领着一席之地。

      原因嘛,大家知道的。由于多分类非常easy。同一时候在文本数据中。分布独立这个如果基本是成立的。

      而垃圾文本过滤(比方垃圾邮件识别)和情感分析(微博上的褒贬情绪)用朴素贝叶斯也通常能取得非常好的效果。

    • 多分类实时预測:这个是不是不能叫做场景?对于文本相关的多分类实时预測,它由于上面提到的长处,被广泛应用,简单又高效。
    • 推荐系统:是的,你没听错。是用在推荐系统里。。朴素贝叶斯和协同过滤(Collaborative Filtering)是一对好搭档。协同过滤是强相关性。可是泛化能力略弱,朴素贝叶斯和协同过滤一起。能增强推荐的覆盖度和效果。

    4.朴素贝叶斯注意点

    这个部分的内容。本来应该在最后说的,只是为了把干货集中放在代码演示样例之前。先搁这儿了,大家也能够看完朴素贝叶斯的各种样例之后。回来再看看这些tips。

    • 大家也知道。非常多特征是连续数值型的,可是它们不一定服从正态分布,一定要想办法把它们变换调整成满足正态分布!!
    • 对測试数据中的0频次项,一定要记得平滑,简单一点能够用『拉普拉斯平滑』。

    • 先处理处理特征,把相关特征去掉。由于高相关度的2个特征在模型中相当于发挥了2次作用。

    • 朴素贝叶斯分类器一般可调參数比較少,比方scikit-learn中的朴素贝叶斯仅仅有拉普拉斯平滑因子alpha,类别先验概率class_prior和预算数据类别先验fit_prior。

      模型端可做的事情不如其它模型多。因此我们还是集中精力进行数据的预处理,以及特征的选择吧。

    • 那个,一般其它的模型(像logistic regression,SVM等)做完之后,我们都能够尝试一下bagging和boosting等融合增强方法。

      咳咳。非常可惜,对朴素贝叶斯里这些方法都没啥用。原因?原因是这些融合方法本质上是降低过拟合,降低variance的。

      朴素贝叶斯是没有variance能够减小。

    5. 朴素贝叶斯训练/建模

    理论干货和注意点都说完了。来提提怎么高速用朴素贝叶斯训练模型吧。博主一直提倡要站在巨人的肩膀上编程(事实上就是懒…同一时候一直非常担忧写出来的代码的健壮性…),咳咳,我们又非常自然地把scikit-learn拿过来了。

    scikit-learn里面有3种不同类型的朴素贝叶斯:

    • 高斯分布型:用于classification问题。假定属性/特征是服从正态分布的。
    • 多项式型:用于离散值模型里。比方文本分类问题里面我们提到过,我们不光看词语是否在文本中出现,也得看出现的次数。如果总词数为n。出现词数为m的话,说起来有点像掷骰子n次出现m次这个词的场景。
    • 伯努利型:这样的情况下,就如之前博文里提到的bag of words处理方式一样。最后得到的特征仅仅有0(没出现)和1(出现过)。

    依据你的数据集。能够选择scikit-learn中以上随意一种朴素贝叶斯,我们直接举个简单的样例。用高斯分布型朴素贝叶斯建模:

    # 我们直接取iris数据集,这个数据集有名到都不想介绍了...
    # 事实上就是依据花的各种数据特征,判定是什么花
    from sklearn import datasets
    iris = datasets.load_iris()
    iris.data[:5]
    #array([[ 5.1,  3.5,  1.4,  0.2],
    #       [ 4.9,  3. ,  1.4,  0.2],
    #       [ 4.7,  3.2,  1.3,  0.2],
    #       [ 4.6,  3.1,  1.5,  0.2],
    #       [ 5. ,  3.6,  1.4,  0.2]])
    
    #我们假定sepal length, sepal width, petal length, petal width 4个量独立且服从高斯分布,用贝叶斯分类器建模
    from sklearn.naive_bayes import GaussianNB
    gnb = GaussianNB()
    y_pred = gnb.fit(iris.data, iris.target).predict(iris.data)
    right_num = (iris.target == y_pred).sum()
    print("Total testing num :%d , naive bayes accuracy :%f" %(iris.data.shape[0], float(right_num)/iris.data.shape[0]))
    # Total testing num :150 , naive bayes accuracy :0.960000

    你看,朴素贝叶斯分类器,简单直接高效。在150个測试样本上,准确率为96%。

    6.朴素贝叶斯之文本主题分类器

    这是朴素贝叶斯最擅长的应用场景之中的一个,对于不同主题的文本。我们能够用朴素贝叶斯训练一个分类器,然后将其应用在新数据上。预測主题类型。

    6.1 新闻数据分类

    我们使用搜狐新闻数据来实验朴素贝叶斯分类器。这部分新闻数据包含it、汽车、財经、健康等9个类别,简洁版数据解压缩后总共16289条新闻,一篇新闻一个txt。我们把数据合并到一个大文件里,一行一篇文章。同一时候将新闻id(指明新闻的类别)放在文章之前,然后用ICTCLAS(python的话你也能够用结巴分词)进行分词,得到以下的文本内容:


    分词后内容

    我们随机选取3/5的数据作为训练集。2/5的数据作为測试集,採用互信息对文本特征进行提取,提取出1000个左右的特征词。

    然后用朴素贝叶斯分类器进行训练,实际训练过程就是对于特征词。统计在训练集和各个类别出现的次数。測试阶段做预測也是扫描一遍測试集,计算对应的概率。因此整个过程非常高效,完整的执行代码例如以下:

    # 这部分代码基本纯手撸的...没有调用开源库...大家看看就好...
    #!encoding=utf-8
    import sys, math, random, collections
    
    def shuffle(inFile):
        '''
            简单的乱序操作,用于生成训练集和測试集
        '''
        textLines = [line.strip() for line in open(inFile)]
        print "正在准备训练和測试数据,请稍后..."
        random.shuffle(textLines)
        num = len(textLines)
        trainText = textLines[:3*num/5]
        testText = textLines[3*num/5:]
        print "准备训练和測试数据准备完成,下一步..."
        return trainText, testText
    
    #总共同拥有9种新闻类别,我们给每一个类别一个编号
    lables = ['A','B','C','D','E','F','G','H','I']
    def lable2id(lable):
        for i in xrange(len(lables)):
            if lable == lables[i]:
                return i
        raise Exception('Error lable %s' % (lable))
    
    def doc_dict():
        '''
            构造和类别数等长的0向量
        '''
        return [0]*len(lables)
    
    def mutual_info(N,Nij,Ni_,N_j):
        '''
            计算互信息,这里log的底取为2
        '''
        return Nij * 1.0 / N * math.log(N * (Nij+1)*1.0/(Ni_*N_j))/ math.log(2)
    
    def count_for_cates(trainText, featureFile):
        '''
            遍历文件,统计每一个词在每一个类别出现的次数,和每类的文档数
            并写入结果特征文件
        '''
        docCount = [0] * len(lables)
        wordCount = collections.defaultdict(doc_dict())
        #扫描文件和计数  
        for line in trainText:
            lable,text = line.strip().split(' ',1)
            index = lable2id(lable[0])        
            words = text.split(' ')
            for word in words:
                wordCount[word][index] += 1
                docCount[index] += 1
        #计算互信息值
        print "计算互信息。提取关键/特征词中,请稍后..."
        miDict = collections.defaultdict(doc_dict())
        N = sum(docCount)
        for k,vs in wordCount.items():
            for i in xrange(len(vs)):
                N11 = vs[i]
                N10 = sum(vs) - N11
                N01 = docCount[i] - N11
                N00 = N - N11 - N10 - N01
                mi = mutual_info(N,N11,N10+N11,N01+N11) + mutual_info(N,N10,N10+N11,N00+N10)+ mutual_info(N,N01,N01+N11,N01+N00)+ mutual_info(N,N00,N00+N10,N00+N01)
                miDict[k][i] = mi
        fWords = set()
        for i in xrange(len(docCount)):
            keyf = lambda x:x[1][i]
            sortedDict = sorted(miDict.items(),key=keyf,reverse=True)
            for j in xrange(100):
                fWords.add(sortedDict[j][0])
        out = open(featureFile, 'w')
        #输出各个类的文档数目
        out.write(str(docCount)+"
    ")
        #输出互信息最高的词作为特征词
        for fword in fWords:
            out.write(fword+"
    ")
        print "特征词写入完成..."
        out.close()
    
    def load_feature_words(featureFile):
        '''
            从特征文件导入特征词
        '''
        f = open(featureFile)
        #各个类的文档数目
        docCounts = eval(f.readline())
        features = set()
        #读取特征词
        for line in f:
            features.add(line.strip())
        f.close()
        return docCounts,features
    
    def train_bayes(featureFile, textFile, modelFile):
        '''
            训练贝叶斯模型,实际上计算每一个类中特征词的出现次数
        '''
        print "使用朴素贝叶斯训练中..."
        docCounts,features = load_feature_words(featureFile)
        wordCount = collections.defaultdict(doc_dict())
        #每类文档特征词出现的次数
        tCount = [0]*len(docCounts)
        for line in open(textFile):
            lable,text = line.strip().split(' ',1)
            index = lable2id(lable[0])        
            words = text.split(' ')
            for word in words:
                if word in features:
                    tCount[index] += 1
                    wordCount[word][index] += 1
        outModel = open(modelFile, 'w')
        #拉普拉斯平滑
        print "训练完成。写入模型..."
        for k,v in wordCount.items():
            scores = [(v[i]+1) * 1.0 / (tCount[i]+len(wordCount)) for i in xrange(len(v))]
            outModel.write(k+"	"+scores+"
    ")
        outModel.close()
    
    def load_model(modelFile):
        '''
            从模型文件里导入计算好的贝叶斯模型
        '''
        print "加载模型中..."
        f = open(modelFile)
        scores = {}
        for line in f:
            word,counts = line.strip().rsplit('	',1)    
            scores[word] = eval(counts)
        f.close()
        return scores
    
    def predict(featureFile, modelFile, testText):
        '''
            预測文档的类标,标准输入每一行为一个文档
        '''
        docCounts,features = load_feature_words()    
        docScores = [math.log(count * 1.0 /sum(docCounts)) for count in docCounts]
        scores = load_model(modelFile)
        rCount = 0
        docCount = 0
        print "正在使用測试数据验证模型效果..."
        for line in testText:
            lable,text = line.strip().split(' ',1)
            index = lable2id(lable[0])        
            words = text.split(' ')
            preValues = list(docScores)
            for word in words:
                if word in features:                
                    for i in xrange(len(preValues)):
                        preValues[i]+=math.log(scores[word][i])
            m = max(preValues)
            pIndex = preValues.index(m)
            if pIndex == index:
                rCount += 1
            #print lable,lables[pIndex],text
            docCount += 1
        print("总共測试文本量: %d , 预測正确的类别量: %d, 朴素贝叶斯分类器精确度:%f" %(rCount,docCount,rCount * 1.0 / docCount))       
    
    
    if __name__=="__main__":
        if len(sys.argv) != 4:
        print "Usage: python naive_bayes_text_classifier.py sougou_news.txt feature_file.out model_file.out"
        sys.exit()
    
        inFile = sys.argv[1]
        featureFile = sys.argv[2]
        modelFile = sys.argv[3]
    
        trainText, testText = shuffle(inFile)
        count_for_cates(trainText, featureFile)
        train_bayes(featureFile, trainText, modelFile)
        predict(featureFile, modelFile, testText)

    6.2 分类结果

    执行结果例如以下,在6515条数据上,9个类别的新闻上,有84.1%的精确度:


    朴素贝叶斯分类器结果

    7. Kaggle比赛之『旧金山犯罪分类预測』

    7.1 旧金山犯罪分类预測问题

    没过瘾对吧,确实每次学完一个机器学习算法。不在实际数据上倒腾倒腾,总感觉不那么踏实(想起来高中各种理科科目都要找点题来做的感觉)。好。我们继续去Kaggle扒点场景和数据来练练手。正巧之前Kaggle上有一个分类问题。场景和数据也都比較简单,我们拿来用朴素贝叶斯试试水。

    问题请戳这里

    7.2 背景介绍

    我们大致介绍一下,说的是『水深火热』的大米国,在旧金山这个地方,一度犯罪率还挺高的。然后非常多人都经历过大到暴力案件,小到东西被偷。车被划的事情。当地警方也是努力地去总结和想办法降低犯罪率,一个挑战是在给出犯罪的地点和时间的之后,要第一时间确定这可能是一个什么样的犯罪类型。以确定警力等等。后来干脆一不做二不休,直接把12年内旧金山城内的犯罪报告都丢带Kaggle上。说『大家折腾折腾吧,看看谁能帮忙第一时间预測一下犯罪类型』。犯罪报告里面包含日期描写叙述星期几所属警区处理结果地址GPS定位等信息。当然,分类问题有非常多分类器能够选择,我们既然刚讲过朴素贝叶斯,刚好就拿来练练手好了。

    7.3 数据一瞥

    数据能够在Kaggle比赛数据页面下载到,大家也能够在博主提供的百度网盘地址中下载到。我们依然用pandas加载数据,先看看数据内容。

    import pandas as pd
    import numpy as np
    
    #用pandas加载csv训练数据。并解析第一列为日期格式
    train=pd.read_csv('/Users/Hanxiaoyang/sf_crime_data/train.csv', parse_dates = ['Dates'])
    test=pd.read_csv('/Users/Hanxiaoyang/sf_crime_data/test.csv', parse_dates = ['Dates'])
    train

    得到例如以下的结果:


    旧金山犯罪类型图

    我们依次解释一下每一列的含义:

    • Date: 日期
    • Category: 犯罪类型。比方 Larceny/盗窃罪 等.
    • Descript: 对于犯罪更具体的描写叙述
    • DayOfWeek: 星期几
    • PdDistrict: 所属警区
    • Resolution: 处理结果,比方说『逮捕』『逃了』
    • Address: 发生街区位置
    • X and Y: GPS坐标

    train.csv中的数据时间跨度为12年。包含了90w+的记录。

    另外,这部分数据,大家从上图上也能够看出来。大部分都是『类别』型,比方犯罪类型。比方星期几。

    7.4 特征预处理

    上述数据中类别和文本型非常多。我们要进行特征预处理,对于特征预处理的部分,我们在前面的博文机器学习系列(3)逻辑回归应用之Kaggle泰坦尼克之灾机器学习系列(6)从白富美相亲看特征预处理与选择(下)都有较细的介绍。

    对于类别特征,我们用最常见的因子化操作将其转成数值型。比方我们把犯罪类型用因子化进行encode。也就是说生成例如以下的向量:

    星期一/Monday = 1,0,0,0,...
    星期二/Tuesday = 0,1,0,0,...
    星期三/Wednesday = 0,0,1,0,...
    ...

    我们之前也提到过,用pandas的get_dummies()能够直接拿到这样的一个二值化的01向量。Pandas里面另一个非常实用的方法LabelEncoder能够用于对类别编号。对于已有的数据特征,我们打算做以下的粗略变换:

    • 用LabelEncoder**对犯罪类型做编号**。
    • 处理时间,在我看来,或许犯罪发生的时间点(小时)是非常重要的。因此我们会用Pandas把这部分数据抽出来;
    • 街区星期几时间点用get_dummies()因子化。
    • 做一些组合特征。比方把上述三个feature拼在一起,再因子化一下;

    具体的数据和特征处理例如以下:

    import pandas as pd
    import numpy as np
    from sklearn.cross_validation import train_test_split
    from sklearn import preprocessing
    
    #用LabelEncoder对不同的犯罪类型编号
    leCrime = preprocessing.LabelEncoder()
    crime = leCrime.fit_transform(train.Category)
    
    #因子化星期几,街区,小时等特征
    days = pd.get_dummies(train.DayOfWeek)
    district = pd.get_dummies(train.PdDistrict)
    hour = train.Dates.dt.hour
    hour = pd.get_dummies(hour) 
    
    #组合特征
    trainData = pd.concat([hour, days, district], axis=1)
    trainData['crime']=crime
    
    #对于測试数据做相同的处理
    days = pd.get_dummies(test.DayOfWeek)
    district = pd.get_dummies(test.PdDistrict)
    
    hour = test.Dates.dt.hour
    hour = pd.get_dummies(hour) 
    
    testData = pd.concat([hour, days, district], axis=1)
    trainData

    然后能够看到特征处理后的数据例如以下所看到的:
    特征处理之后

    7.5 朴素贝叶斯 VS 逻辑回归

    拿到初步的特征了,下一步就能够開始建模了。

    由于之前的博客机器学习系列(1)逻辑回归初步机器学习系列(2)从初等数学视角解读逻辑回归机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾中提到过逻辑回归这样的分类算法。我们这里打算一并拿来建模,做个比較。


    还须要提到的一点是。大家參加Kaggle的比赛,一定要注意最后排名和评定好坏用的标准。比方说在如今这个多分类问题中,Kaggle的评定标准并非precision,而是multi-class log_loss。这个值越小。表示最后的效果越好。

    我们能够高速地筛出一部分重要的特征。搭建一个baseline系统,再考虑步步优化。

    比方我们这里简单一点。就仅仅取星期几街区作为分类器输入特征。我们用scikit-learn中的train_test_split函数拿到训练集和交叉验证集。用朴素贝叶斯和逻辑回归都建立模型,对照一下它们的表现:

    ffrom sklearn.cross_validation import train_test_split
    from sklearn import preprocessing
    from sklearn.metrics import log_loss
    from sklearn.naive_bayes import BernoulliNB
    from sklearn.linear_model import LogisticRegression
    import time
    
    # 仅仅取星期几和街区作为分类器输入特征
    features = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
     'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
    
    # 切割训练集(3/5)和測试集(2/5)
    training, validation = train_test_split(trainData, train_size=.60)
    
    # 朴素贝叶斯建模,计算log_loss
    model = BernoulliNB()
    nbStart = time.time()
    model.fit(training[features], training['crime'])
    nbCostTime = time.time() - nbStart
    predicted = np.array(model.predict_proba(validation[features]))
    print "朴素贝叶斯建模耗时 %f 秒" %(nbCostTime)
    print "朴素贝叶斯log损失为 %f" %(log_loss(validation['crime'], predicted))
    
    #逻辑回归建模,计算log_loss
    model = LogisticRegression(C=.01)
    lrStart= time.time()
    model.fit(training[features], training['crime'])
    lrCostTime = time.time() - lrStart
    predicted = np.array(model.predict_proba(validation[features]))
    log_loss(validation['crime'], predicted)
    print "逻辑回归建模耗时 %f 秒" %(lrCostTime)
    print "逻辑回归log损失为 %f" %(log_loss(validation['crime'], predicted)) 

    实验的结果例如以下:


    nb vs lr

    我们能够看到眼下的特征和參数设定下,朴素贝叶斯的log损失还低一些,另外我们能够明显看到。朴素贝叶斯建模消耗的时间0.640398秒远小于逻辑回归建模42.856376秒。

    考虑到犯罪类型可能和犯罪事件发生的小时时间点相关。我们加入小时时间点特征再次建模,代码和结果例如以下:

    from sklearn.cross_validation import train_test_split
    from sklearn import preprocessing
    from sklearn.metrics import log_loss
    from sklearn.naive_bayes import BernoulliNB
    from sklearn.linear_model import LogisticRegression
    import time
    
    # 加入犯罪的小时时间点作为特征
    features = ['Friday', 'Monday', 'Saturday', 'Sunday', 'Thursday', 'Tuesday',
    'Wednesday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
    'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
    
    hourFea = [x for x in range(0,24)]
    features = features + hourFea
    
    # 切割训练集(3/5)和測试集(2/5)
    training, validation = train_test_split(trainData, train_size=.60)
    
    # 朴素贝叶斯建模,计算log_loss
    model = BernoulliNB()
    nbStart = time.time()
    model.fit(training[features], training['crime'])
    nbCostTime = time.time() - nbStart
    predicted = np.array(model.predict_proba(validation[features]))
    print "朴素贝叶斯建模耗时 %f 秒" %(nbCostTime)
    print "朴素贝叶斯log损失为 %f" %(log_loss(validation['crime'], predicted))
    
    #逻辑回归建模,计算log_loss
    model = LogisticRegression(C=.01)
    lrStart= time.time()
    model.fit(training[features], training['crime'])
    lrCostTime = time.time() - lrStart
    predicted = np.array(model.predict_proba(validation[features]))
    log_loss(validation['crime'], predicted)
    print "逻辑回归建模耗时 %f 秒" %(lrCostTime)
    print "逻辑回归log损失为 %f" %(log_loss(validation['crime'], predicted))


    加入小时时间点后的结果

    能够看到在这三个类别特征下,朴素贝叶斯相对于逻辑回归,依然有一定的优势(log损失更小)。同一时候训练时间非常短,这意味着模型尽管简单。可是效果依然强大。

    顺便提一下。朴素贝叶斯1.13s训练出来的模型,预測的效果在Kaggle排行榜上已经能进入Top 35%了,如果进行一些优化。比方特征处理、特征组合等,结果会进一步提高。

    8. Kaggle比赛之影评与观影者情感判定

    博主想了想。既然朴素贝叶斯最常见的应用场景就那么几个,干脆我们都一并覆盖得了。咳咳,对,另一个非常重要的应用场景是情感分析(尤其是褒贬判定)。于是我又上Kaggle溜达了一圈。扒下来一个相似场景的比赛。比赛的名字叫做当词袋/Bag of words 遇上 爆米花/Bags of Popcorn。地址为https://www.kaggle.com/c/word2vec-nlp-tutorial/,有兴趣的同学能够上去瞄一眼。

    8.1 背景介绍

    这个比赛的背景大概是:国外有一个相似豆瓣电影一样的IMDB,也是你看完电影。能够上去打个分。吐个槽的地方。然后大家就在想,有这么多数据。总得折腾点什么吧,于是乎,第一个想到的就是。赞的喷的内容都有了,咱们就来分分类。看看能不能依据内容分布褒贬。

    PS:非常多同学表示,分个褒贬有毛线难的,咳咳,计算机比較笨,另外,语言这样的东西。真心是博大精深的,我们随手从豆瓣上抓了几条《功夫熊猫3》影评下来,表示有些尽管我是能看懂,可是不处理直接给计算机看,它应该是一副『什么鬼』的表情。。。


    功夫熊猫3影评

    多说一句,Kaggle原文引导里是用word2vec的方式将词转为词向量。后再用deep learning的方式做的。深度学习好归好,可是毕竟耗时耗力耗资源,我们用最最naive的朴素贝叶斯撸一把。说不定效果也能不错,不试试谁知道呢。另外。朴素贝叶斯建模真心速度快,非常多场景下。高速建模高速迭代优化正是我们须要的嘛。

    8.2 数据一瞥

    言归正传,回到Kaggle中这个问题上来。先瞄一眼数据。Kaggle数据页面地址为https://www.kaggle.com/c/word2vec-nlp-tutorial/data,大家也能够到博主的百度网盘中下载。数据包例如以下图所看到的:


    Kaggle影评数据页面

    当中包含有情绪标签的训练数据labeledTrainData,没有情绪标签的训练数据unlabeledTrainData,以及測试数据testData。labeledTrainData包含idsentiment和**review**3个部分。分别指代用户id,情感标签,评论内容。

    解压缩labeledTrainData后用vim打开,内容例如以下:


    训练数据内容

    以下我们读取数据并做一些主要的预处理(比方说把评论部分的html标签去掉等等):

    import re  #正則表達式
    from bs4 import BeautifulSoup  #html标签处理
    import pandas as pd
    
    def review_to_wordlist(review):
        '''
        把IMDB的评论转成词序列
        '''
        # 去掉HTML标签。拿到内容
        review_text = BeautifulSoup(review).get_text()
        # 用正則表達式取出符合规范的部分
        review_text = re.sub("[^a-zA-Z]"," ", review_text)
        # 小写化全部的词。并转成词list
        words = review_text.lower().split()
        # 返回words
        return words
    
    # 使用pandas读入训练和測试csv文件
    train = pd.read_csv('/Users/Hanxiaoyang/IMDB_sentiment_analysis_data/labeledTrainData.tsv', header=0, delimiter="	", quoting=3)
    test = pd.read_csv('/Users/Hanxiaoyang/IMDB_sentiment_analysis_data/testData.tsv', header=0, delimiter="	", quoting=3 )
    # 取出情感标签。positive/褒 或者 negative/贬
    y_train = train['sentiment']
    # 将训练和測试数据都转成词list
    train_data = []
    for i in xrange(0,len(train['review'])):
        train_data.append(" ".join(review_to_wordlist(train['review'][i])))
    test_data = []
    for i in xrange(0,len(test['review'])):
        test_data.append(" ".join(review_to_wordlist(test['review'][i])))

    我们在ipython notebook里面看一眼,发现数据已经格式化了,例如以下:


    格式化后的数据

    8.3 特征处理

    紧接着又到了头疼的部分了,数据有了。我们得想办法从数据里面拿到有区分度的特征。

    比方说Kaggle该问题的引导页提供的word2vec就是一种文本到数值域的特征抽取方式。比方说我们在第6小节提到的用互信息提取关键字也是提取特征的一种。比方说在这里,我们打算用在文本检索系统中非常有效的一种特征:TF-IDF(term frequency-interdocument frequency)向量。每一个电影评论最后转化成一个TF-IDF向量。

    对了。对于TF-IDF不熟悉的同学们,我们稍加解释一下。TF-IDF是一种统计方法,用以评估一字词(或者n-gram)对于一个文件集或一个语料库中的当中一份文件的重要程度。

    字词的重要性随着它在文件里出现的次数成正比添加。但同一时候会随着它在语料库中出现的频率成反比下降。这是一个能非常有效地判定对评论褒贬影响大的词或短语的方法。

    那个…博主打算继续偷懒,把scikit-learn中TFIDF向量化方法直接拿来用。想具体了解的同学能够戳sklearn TFIDF向量类。对了。再多说几句我的处理细节,停用词被我掐掉了,同一时候我在单词的级别上又拓展到2元语言模型(对这个不了解的同学别着急,兴许的博客介绍立即就来),恩,你能够再加3元4元语言模型…博主主要是单机内存不够了,先就2元上。凑活用吧…

    from sklearn.feature_extraction.text import TfidfVectorizer as TFIV
    # 初始化TFIV对象,去停用词,加2元语言模型
    tfv = TFIV(min_df=3,  max_features=None, strip_accents='unicode', analyzer='word',token_pattern=r'w{1,}', ngram_range=(1, 2), use_idf=1,smooth_idf=1,sublinear_tf=1, stop_words = 'english')
    # 合并训练和測试集以便进行TFIDF向量化操作
    X_all = train_data + test_data
    len_train = len(train_data)
    
    # 这一步有点慢,去喝杯茶刷会儿微博知乎歇会儿...
    tfv.fit(X_all)
    X_all = tfv.transform(X_all)
    # 恢复成训练集和測试集部分
    X = X_all[:len_train] 
    X_test = X_all[len_train:]

    8.4 朴素贝叶斯 vs 逻辑回归

    特征如今我们拿到手了,该建模了,好吧,博主折腾劲又上来了。那个…咳咳…我们还是朴素贝叶斯和逻辑回归都建个分类器吧,然后也能够比較比較,恩。
    『talk is cheap, I’ll show you the code』。直接放码过来了哈。

    # 多项式朴素贝叶斯
    from sklearn.naive_bayes import MultinomialNB as MNB
    
    model_NB = MNB()
    model_NB.fit(X, y_train) #特征数据直接灌进来
    MNB(alpha=1.0, class_prior=None, fit_prior=True)
    
    from sklearn.cross_validation import cross_val_score
    import numpy as np
    
    print "多项式贝叶斯分类器20折交叉验证得分: ", np.mean(cross_val_score(model_NB, X, y_train, cv=20, scoring='roc_auc'))
    # 多项式贝叶斯分类器20折交叉验证得分: 0.950837239
    # 折腾一下逻辑回归,恩
    from sklearn.linear_model import LogisticRegression as LR
    from sklearn.grid_search import GridSearchCV
    
    # 设定grid search的參数
    grid_values = {'C':[30]}  
    # 设定打分为roc_auc
    model_LR = GridSearchCV(LR(penalty = 'L2', dual = True, random_state = 0), grid_values, scoring = 'roc_auc', cv = 20) 
    # 数据灌进来
    model_LR.fit(X,y_train)
    # 20折交叉验证。開始漫长的等待...
    GridSearchCV(cv=20, estimator=LogisticRegression(C=1.0, class_weight=None, dual=True, 
                 fit_intercept=True, intercept_scaling=1, penalty='L2', random_state=0, tol=0.0001),
            fit_params={}, iid=True, loss_func=None, n_jobs=1,
            param_grid={'C': [30]}, pre_dispatch='2*n_jobs', refit=True,
            score_func=None, scoring='roc_auc', verbose=0)
    #输出结果
    print model_LR.grid_scores_

    最后逻辑回归的结果是[mean: 0.96459, std: 0.00489, params: {'C': 30}]

    咳咳…看似逻辑回归在这个问题中。TF-IDF特征下表现要稍强一些…只是同学们自己跑一下就知道。这2个模型的训练时长真心不在一个数量级,逻辑回归在数据量大的情况下,要等到睡着…另外,要提到的一点是,由于我这里仅仅用了2元语言模型(2-gram)。加到3-gram和4-gram。最后两者的结果还会提高,并且朴素贝叶斯说不定会提升更快一点,内存够的同学们自己动手试试吧^_^

    9. 总结

    本文为朴素贝叶斯的实践和进阶篇,先丢了点干货,总结了贝叶斯方法的优缺点,应用场景,注意点和一般建模方法。紧接着对它最常见的应用场景,抓了几个样例。又来了一遍手把手系列,无论是对于文本主题分类、多分类问题(犯罪类型分类) 还是 情感分析/分类,朴素贝叶斯都是一个简单直接高效的方法。尤其是在和逻辑回归的对照中能够看出,在这些问题中。朴素贝叶斯能取得和逻辑回归相近的成绩。可是训练速度远快于逻辑回归。真正的直接和高效。

  • 相关阅读:
    成为一个会思考的学习者
    我的第4篇博客
    我的第3篇博客
    第2次作业
    第一次作业:大学,人生的另一个新的开始
    第四次作业
    第三次作业
    第二次作业
    作为大一新生的感悟
    第四次作业
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7099850.html
Copyright © 2011-2022 走看看