【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%以上,但是验证集的结果普遍比测试集的结果好,主要原因可能包括验证集是从训练集划分出来的,所以具有很大的相似性,并且句子都较长,而测试集是汉语翻译为英语后再进行特征提取,可能具有一定的差异性,并且观察数据可以发现,测试集的句子普遍比较短。

        从整体实验结果上看,首先,本次实验只是简单地从三个方面进行特征的提取,还可以考虑从词袋模型或者是更详细的词汇情感上进行体征的提取,其次在算法上也没有进行过多的调参,或者是使用当前热门的深度学习技术,从这些方面上看,实验结果还有较大的提升空间。

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