用LSTM实现文本的Emotion Detection(一)

最近写完模式识别的综述,终于能安安静静坐下来写一段时间的代码,现在的主要工作还是想把师姐的Emotion Detection的Ranking Loss的方法加到LSTM中,虽然怎么加ranking和loss还没有想好。这篇介绍的是文本的数据处理部分。

读取数据

数据这里用的是新浪新闻的数据,一个txt中保存的是一篇新闻对应的信息,包括时间、情感标签(6种)、标题和文本,具体如下。我们需要使用的就是情感标签和文本两个部分。

[url]
http://news.sina.com.cn/s/2014-10-26/110031046607.shtml
[publish_date]
2014-10-26
[emotion_votes]
moved:6   shocked:2   funny:41   sad:2   strange:1   angry:11
[headline]
余秋雨谈君子小人之别:小人想成为土豪
[body]
  【余秋雨谈君子与“土豪”】著名作家余秋雨25日在武汉表示,中华民族文化的人格理想是君子。君子之道的精神核心是德,君子怀德,小人怀土,利人利他利天下为德,怀土就是时刻想到占有、占有,成为“土豪”。君子之道的阀门是有耻,要做到知耻和不耻。对伪君子,要识破文化之伪、道义之伪、风度之伪。

在这段文本中,我们需要的是情感标签和文本信息。而且它们之间都有明显的分隔符[emotion_votes]、[headline] 、[body],所以可以通过正则的方法来读取。不过其中有一个坑就是,这段文本其实有很多的换行符,但是在txt文件中打开并没有显示出有换行符,所以正则一直读的都有问题,还是后来把换行符替换了才成功的。
我们这里因为是针对长文本进行操作,所以我是将一篇文本信息读进来,当成一条进行处理。这是篇章级的操作,如果是句子级也可以一行行读进来进行处理。

def load_sina(filename):
    files = os.listdir(filename)
    headlinetxt = open('./data/sinatext.txt','w')
    labels=[]
    headline=[]
    texts=[]
    rule1 = r"\[emotion_votes\](.+?)\[headline\]"
    rule2 = r"\[headline\](.+?)\[body\]"
    rule3 = r"\[body].*"
    for file in files:  # 遍历文件夹
        paths = filename + file
        # 读取文件
        infile = open(paths)
        text = infile.read()
        text = re.sub(r"\n", " ", text)

        #得到标签
        content = re.search(rule1, text)
        content = re.sub(r"( )+", " ", content.group())
        content = content.split(' ')[1:-1]
        label = [int(emt.split(':')[1]) for emt in content]
        labels.append(label)

        #得到标题
        content = re.search(rule2, text)
        content = re.sub(r"( )+", " ", content.group())[len('[headline]')+1:-1-len('[body]')]
        headline.append(content)
        print(content,file=headlinetxt)

        #得到内容
        content = re.search(rule3, text)
        content = re.sub(r"( )+", " ", content.group())[len('[body]') + 3: ]
        texts.append(content)
        print(content, file=headlinetxt)

        infile.close()
    return labels, headline, texts

文本预处理

对文本进行分词,将文本中停用词、标点之类的都去掉。其中停用词表可以在网上下载得到。

主函数

这是主函数部分,其中有些是在发现word2vec之前写的,结果后来发现word2vec自带了这部分的功能,比如删除出现次数过少的单词。

if __name__ == "__main__":
    train_file = './data/sinanews/'
    stopwordsfile='./data/stopwords.txt'

    labels, headline, articles=load_sina(train_file)
    stopwords=stopwordslist(stopwordsfile)

    texts=[]
    for text in articles:
        # 去掉数字和标点
        text=clean_text(text)
        # 分词
        text=segment_word(text)
        # 去掉停用词
        outstr = ''
        for word in text:
            if word not in stopwords:
                if word != '\t' and len(word)>1:
                    outstr += word
                    outstr += " "
        text=outstr
        texts.append(text.split(' '))

    #将词保存到txt
    #save_texts(texts, './data/sinatextwords.txt')

    #建立词典
    #wordlist=build_vocab(texts) # 去掉只在一个文本中出现的单词后,词典减少了很多

    #根据字典去掉句子中的不符合条件的词
    #textwords=remove_words(texts, wordlist)

    #word embedding
    #sentences = word2vec.LineSentence('./data/sinatextwords.txt')

加载停用词表

def stopwordslist(filename):
    f=open(filename)
    stopwords = [line.strip() for line in f.readlines()]
    f.close()
    return stopwords

清除非汉字部分

标点部分使用了zhon.hanzi包。

from zhon.hanzi import punctuation

def clean_text(text):
    # 清除空格
    text=text.replace(' ','')

    #让文本只保留汉字
    text = re.sub(r"[0-9]+", "", text)
    text = re.sub(r"[%s]+" % punctuation, "", text)  # 去除标点
    return text

分词

使用jieba对文本进行分词。

def segment_word(text):
    #使用jieba对文本进行分词(精准模式)
    text=jieba.cut(text)
    return text

得到词向量

使用gensim得到单词的词向量。这中间有个问题是我头疼了很久,就是计算得到的词向量模型中的词是一个个字,不是一个个词,这个问题我在网上找了很久,也没有找到解决办法。无论是直接从文本中读取,还是用处理得到的text进行计算,都是这样子。后来debug的时候才发现text是一个单层列表,就是虽然分好了词,但是给我处理完又将所有词合在一起构成了一个string。而word2vec需要的是一个两层列表。后来改完了就可以了。

from gensim.models import word2vec

def word_embedding(texts,filename):
    word2vecmodel = word2vec.Word2Vec(texts, size=100, min_count=2)
    word2vecmodel.save(filename)

词表的建立和单词的过滤

这部分其实word2vec带了这部分的功能,但是写下来保存一下,万一以后想用呢。

def build_vocab(texts):
    wordset=[list(set(text.split(' '))) for text in texts]  #统计每个文章的词集合
    wordcounts = Counter(itertools.chain(*wordset))
    wordlist=[word[0] for word in wordcounts.most_common() if word[1]>0]    #去掉只在一个文本中出现的单词
    return wordlist

def remove_words(texts, wordlist):
    textwords=[]
    for text in texts:
        words=[]
        text=text.split(' ')
        for word in text:
            if word in wordlist:
                words.append(word)
        textwords.append(words)
    return textwords

具体的代码可以在这里:https://git.coding.net/Mikitol/Emotion_Detection_with_LSTM.git