【Natural Language Processing】跨語言情感分析(NLP&CC 2013)

一、任務介紹

        本任務是NLP&CC 2013的跨語言情感分析,主要是在英文資源的前提下,對測試集內的每條中文評論進行傾向性分類。

        本任務的所有數據均由主辦方提供,主要包含三部分:

        ①  英文標註數據和英文情感詞典;

        ②  中文未標註語料;

        ③  中文測試集。

        數據均採用XML格式, UTF-8 編碼存儲。除組織方提供的數據外,不得使用任何中英文標註數據、中英文未標註數據和中英文情感詞典等資源。不得人工干預評測結果,不得對測試集進行人工標註。

二、實驗環境

        Anaconda2-4.3.1(Python2.7),genism。

三、數據預處理

3.1  解析XML

        主要是對XML原文件進行解析,去除英文標註語料的標點符號,同時對於過長的英文句子只取前100個詞彙。

對應代碼:ENProcess.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import codecs
import xml.etree.ElementTree as ET
from nltk.tokenize import RegexpTokenizer

def nltkProcess(sentence):
    if not sentence:
        print("Empty sentence in nltkProcess()!")

    tokenizer = RegexpTokenizer(r'\w+')
    tokens = tokenizer.tokenize(sentence)
    #tokens = [w.lower() for w in tokens ]
    return tokens

def process(inputFile,outputfile):
    result= codecs.open(outputfile, "w", "utf-8")
    tree = ET.parse(inputFile)
    root = tree.getroot()
    for i,item in enumerate(root.findall('item')):
        summary = item.find('summary').text
        text = item.find('text').text
        label=item.find('polarity').text
        texts = summary + "," + text
        result.write(str(label) + '\t')
        results = nltkProcess(texts)
        for j,word in enumerate(results):
            if j>100:
                continue
            result.write(word + " ")
        result.write("\n")
        print i
    result.close()


if __name__ == '__main__':
    #process('../NLPCC2013/RawData/Train_EN/book/train.data', '../NLPCC2013/Process/TrainBookEN.data')
    #process('../NLPCC2013/RawData/Train_EN/music/train.data', '../NLPCC2013/Process/TrainMusicEN.data')
    process('../NLPCC2013/RawData/Train_EN/dvd/train.data', '../NLPCC2013/Process/TrainDvdEN.data')

3.2  谷歌翻譯中文語料

        主要是對XML原文件進行解析後調用谷歌翻譯進行翻譯成英文,再對所得到的未標註語料去除標點符號。

        對應代碼:GoogleTranslate.py、CNtoENProcess.py

        可見另外一篇博客:python爬蟲Google翻譯的實現

四、實驗方法

4.1  提取特徵

        ①詞彙特徵,通過觀察發現評論的句子裏含有大量的good和bad單詞,並且通常分別對應的標註是positive和negative,所以如果句子裏含有good的話特徵取1,否則爲0,而若含有bad話則取1,否則取0,同時根據一般常識知道not和good同時出現的話,那麼該句子就是消極的,所以若句子中有not,那麼取特徵爲1,否則爲0。

對應代碼:get_voca.py

#-*- coding:utf-8 -*

import numpy as np

def get_voca(data):
    vec = []
    for line in data:
        temp = []
        if 'good' in line:
            temp.append(1)
        else:
            temp.append(0)
        if 'bad' in line:
            temp.append(1)
        else:
            temp.append(0)
        if 'not' in line:
            temp.append(1)
        else:
            temp.append(0)
        vec.append(temp)
    return np.array(vec)

        ②主題模型,分別以訓練數據和測試數據爲語料建立模型,同時取num_topics爲2,最後得到每條評論的二維向量作爲特徵。

對應代碼:get_TocpicModel.py

#-*- coding:utf-8 -*

import numpy as np
from gensim import corpora
from gensim import models

def get_tm(words):
    dic = corpora.Dictionary(words)
    corpus = [dic.doc2bow(text) for text in words]
    tfidf = models.TfidfModel(corpus)
    corpus_tfidf = tfidf[corpus]
    lda = models.LdaModel(corpus_tfidf, id2word=dic, num_topics=2)
    corpus_lda = lda[corpus_tfidf]

    vec = []
    for line in corpus_lda:
        vec.append([line[0][1], line[1][1]])
    return np.array(vec)

        ③doc2vec的c-bow模型,其與word2vec的c-bow方法類似,主要的區別點有:訓練過程中新增了paragraph id,即訓練語料中每個句子都有一個唯一的id。paragraph id和普通的word一樣,也是先映射成一個向量,即paragraph vector。

        paragraph vector與word vector的維數雖一樣,但是來自於兩個不同的向量空間。在之後的計算裏,paragraph vector和word vector累加或者連接起來,作爲輸出層softmax的輸入。在一個句子或者文檔的訓練過程中,paragraph id保持不變,共享着同一個paragraph vector,相當於每次在預測單詞的概率時,都利用了整個句子的語義。

        在預測階段,給待預測的句子新分配一個paragraph id,詞向量和輸出層softmax的參數保持訓練階段得到的參數不變,重新利用梯度下降訓練待預測的句子。待收斂後,即得到待預測句子的paragraph vector,然後取其作爲特徵,本次主要利用gensim實現。

對應代碼:get_D2V.py

#-*- coding:utf-8 -*

import random
import gensim
import numpy as np
import codecs
from gensim import corpora

LS=gensim.models.doc2vec.LabeledSentence

def labelIndex1(comment,label_type):
    data=[]
    for i ,c in enumerate(comment):
        label='%s_%s'%(label_type,i)
        data.append(LS(c,[label]))
    return data

def labelIndex2(comment1,comment2,comment3):
    all_data=[];data1=[];data2=[];data3=[]
    for i ,c in enumerate(comment1):
        label='%s_%s'%('train_data',i)
        data1.append(LS(c,[label]))
    all_data.extend(data1)
    for j ,c in enumerate(comment2):
        label='%s_%s'%('test_data',j)
        data2.append(LS(c,[label]))
    all_data.extend(data2)
    for k ,c in enumerate(comment3):
        label='%s_%s'%('unlabel_data',k)
        data3.append(LS(c,[label]))
    all_data.extend(data3)
    return data1,data2,data3,all_data

def getVecs(model, corpus, size=13):
    vecs = [np.array(model.docvecs[z.tags[0]]).reshape((1, size)) for z in corpus]
    return np.concatenate(vecs)


def get_vec(train_data,test_data,unlabel_data):
    train_data, test_data, unlabel_data, all_data = labelIndex2(train_data, test_data, unlabel_data)
    model_dm = gensim.models.Doc2Vec(min_count=1, window=10, size=13, \
                                     sample=1e-3, negative=5, workers=5)
    model_dbow = gensim.models.Doc2Vec(min_count=1, window=10, size=13, \
                                       sample=1e-3, negative=5, dm=0, workers=5)

    model_dm.build_vocab(all_data)
    model_dbow.build_vocab(all_data)

    all_train = np.concatenate((train_data, unlabel_data))
    for epoch1 in range(10):
        perm = np.random.permutation(np.arange(len(all_data)))
        for i in perm:
            model_dm.train(all_data[i:i + 1])
            model_dbow.train(all_data[i:i + 1])
        print epoch1
    train_vecs_dm = getVecs(model_dm, train_data)
    train_vecs_dbow = getVecs(model_dbow, train_data)
    train_vecs = np.hstack((train_vecs_dm, train_vecs_dbow))
    print len(train_vecs)
    print 'train_data ok!'

    for epoch2 in range(10):
        perm = np.random.permutation(np.arange(len(test_data)))
        for i in perm:
            model_dm.train(test_data[i:i + 1])
            model_dbow.train(test_data[i:i + 1])
        print epoch2
    test_vecs_dm = getVecs(model_dm, test_data)
    test_vecs_dbow = getVecs(model_dbow, test_data)
    test_vecs = np.hstack((test_vecs_dm, test_vecs_dbow))
    print len(test_vecs)
    print 'test_data ok!'
    return train_vecs,test_vecs

4.2 模型分類

        基於以上提取的特徵採用機器學習的方法進行分類,並且從訓練集中隨機劃分10%的數據作爲驗證集;本次一共採用了兩種機器學習分類器,具體算法和參數如下:。

        ①SGDClassifier(loss='log', penalty='l1')

        ②GradientBoostingClassifier(n_estimators=100,max_depth=5)

對應代碼:main_model.py

#-*- coding:utf-8 -*

import numpy as np
import codecs
import sklearn.metrics as metrics
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
from sklearn.ensemble import GradientBoostingClassifier

from get_TopicModel import get_tm
from get_voca import get_voca
from get_D2V import get_vec

def get_data(infile):
    data = []
    label = []
    with codecs.open(infile) as infile:
        for line in infile.readlines():
            temp = line.split()
            data.append(temp[1:])
            if temp[0] == 'P':
                label.append(1)
            else:
                label.append(0)
    return data,label

def get_result(infile1,infile2,infile3):
    with codecs.open(infile3) as infile:
        unlabel_data = [line.split() for line in infile.readlines()]
    train_data,train_label=get_data(infile1)
    test_data, test_label = get_data(infile2)
    train_vecs, test_vecs=get_vec(train_data,test_data,unlabel_data)

    train_tm=get_tm(train_data)
    test_tm=get_tm(test_data)

    train_voca=get_voca(train_data)
    test_voca=get_voca(test_data)

    train_vecs=np.hstack((train_vecs,train_tm,train_voca))
    test_vecs=np.hstack((test_vecs,test_tm,test_voca))

    train_vecs, dev_vecs, train_label, dev_label = train_test_split(train_vecs, train_label, test_size=0.1)

    SGDC = SGDClassifier(loss='log', penalty='l1')
    SGDC.fit(train_vecs, train_label)
    print('SGDClassifier:Dev Accuracy: %.4f' % SGDC.score(dev_vecs, dev_label))
    print('SGDClassifier:Test Accuracy: %.4f' % SGDC.score(test_vecs, test_label))

    GBDT = GradientBoostingClassifier(n_estimators=100, max_depth=5)
    GBDT.fit(train_vecs, train_label)
    dev_pre = GBDT.predict(dev_vecs)
    test_pre = GBDT.predict(test_vecs)
    print('GradientBoostingClassifier:Dev Accuracy: %.4f' % metrics.accuracy_score(dev_pre, dev_label))
    print('GradientBoostingClassifier:Test Accuracy: %.4f' % metrics.accuracy_score(test_pre, test_label))

if __name__=='__main__':
    # infile1 = '../NLPCC2013/Process/TrainBookEN.data'
    # infile2 = '../NLPCC2013/Process/TestBookCNtoEN.data'
    # infile3 = '../NLPCC2013/Process/UnlabelBookCNtoEN.data'

    # infile1='../NLPCC2013/Process/TrainDvdEN.data'
    # infile2 = '../NLPCC2013/Process/TestDvdCNtoEN.data'
    # infile3 = '../NLPCC2013/Process/UnlabelDvdCNtoEN.data'

    infile1='../NLPCC2013/Process/TrainMusicEN.data'
    infile2 = '../NLPCC2013/Process/TestMusicCNtoEN.data'
    infile3 = '../NLPCC2013/Process/UnlabelMusicCNtoEN.data'

    get_result(infile1,infile2,infile3)

五、實驗結果

Model 

Dev Accuracy

Test Accuracy

SGDClassifier

73.25%

70.30%

GradientBoostingClassifier

76.25%

72.22%





表1  BOOK實驗結果

Model

Dev Accuracy

Test Accuracy

SGDClassifier

74.00%

72.55%

GradientBoostingClassifier

77.50%

73.12%





表2 DVD實驗結果

Model 

Dev Accuracy

Test Accuracy

SGDClassifier

72.25%

73.35%

GradientBoostingClassifier

79.20%

70.05%





表3  MUSIC 實驗結果

六、分析思考

        本次主要是嘗試了採用機器學習的方法進行跨語言情感分類,主要採用了doc2vec和主題模型的方法進行建模提取特徵,以及簡單的對good、bad和not這三個帶有比較強烈的感情色彩的詞進行特徵提取,最後進行分類。

        觀察以上實驗結果可以發現無論是驗證集還是測試集的實驗結果正確率都在70%以上,但是驗證集的結果普遍比測試集的結果好,主要原因可能包括驗證集是從訓練集劃分出來的,所以具有很大的相似性,並且句子都較長,而測試集是漢語翻譯爲英語後再進行特徵提取,可能具有一定的差異性,並且觀察數據可以發現,測試集的句子普遍比較短。

        從整體實驗結果上看,首先,本次實驗只是簡單地從三個方面進行特徵的提取,還可以考慮從詞袋模型或者是更詳細的詞彙情感上進行體徵的提取,其次在算法上也沒有進行過多的調參,或者是使用當前熱門的深度學習技術,從這些方面上看,實驗結果還有較大的提升空間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章