2018年09月25日

言語処理100本ノックでPython入門 #72 - 機械学習、素性抽出

  

今日は言語処理100本ノック 2015の第8章・機械学習の問題72に挑戦です。

■ 問題
72. 素性抽出
極性分析に有用そうな素性を各自で設計し,学習データから素性を抽出せよ.素性としては,レビューからストップワードを除去し,各単語をステミング処理したものが最低限のベースラインとなるであろう.

■ どうやって解くか

素性を各自で設計しと言われても、その知識が無いので困りました。

ただ、「素性としては,レビューからストップワードを除去し,各単語をステミング処理したものが最低限のベースラインとなるであろう.」 とあるので、極性分析(+評価か-評価か)に不要そうな単語を取り除けばよさそうです。 ステミングは、第6章でやったので、その時に使った nltkモジュールを使いました。

Stopwordsクラスは前問と同じものです。 新たに、SentimentFeaturesクラスを定義しました。
main関数では、このSentimentFeaturesクラスのenumerateで素性を列挙し、確認用に先頭50個だけを表示しています。

SentimentFeaturesクラスのメソッドは以下の3つです。

isValid
極性分析に有効そうな単語かどうかを判断、isValidならば、素性データとして残す。無効な単語は、「ストップワード」「文字数が2以下の単語」「記号からなるもの」の3つ。

getFromLine
与えられた1行(問題70で作成したテキストファイルの1行)を単語に分割しisValidの単語を抜き出し、それをステミングしたものを返す。この時、極性データ(+1, -1)とともにタプルで返す。

enumerate
問題70で作成したsentiment.txtから1行ずつ読み込み、getFromLineにその1行を渡して得られたデータ(極性データと、素性としての単語のタプル)を列挙。


ところで、getFromLineメソッドの中でfilterとmap関数を使っていますが、ラムダ式ではなくて、関数名だけを指定することもできるんですね。 これは、C#と同じです。ちゃんとpylintが指摘してくれました。

■ Pythonのコード

import re
from nltk import stem

class Stopwords:
    words = ['a', 'about', 'all', 'an', 'and', 'any', 'are', 'as', \
            'at', 'be', 'been', 'but', 'by', 'can', 'could', 'do', \
            'does', 'for', 'from', 'has', 'have', 'he', 'her', 'his', \
            'how', 'i', 'if', 'in', 'into', 'is', 'it', 'its', 'made', \
            'make', 'may', 'me', 'my', 'no', 'not', 'of', 'on', 'one', \
            'or', 'out', 'she', 'should', 'so', 'some', 'than', 'that', \
            'the', 'their', 'them', 'there', 'then', 'they', 'this', \
            'those', 'to', 'too', 'us', 'was', 'we', 'what', 'when',\
            'which', 'who', 'with', 'would', 'you', 'your', ''
        ]

    @staticmethod
    def exists(word):
        return word in  Stopwords.words

class SentimentFeatures:
    def __init__(self, filename):
        self.filename = filename
        self.stemmer = stem.PorterStemmer()

    @staticmethod
    def isValid(word):
        if word == '' or len(word) <= 2:
            return False
        if re.match(r'^[-=!@#$%^&*()_+|;";,.<>/?]+$', word):
            return False
        return not Stopwords.exists(word)

    def getFromLine(self, line):
        sentiment = line[:3]
        array = re.split(r'\s|,|\.|\(|\)|\'|/|\'|\[|\]|-', line[3:])
        # こういう時はlambda キーワードいらないんですね。
        words = filter(self.isValid, array)
        xs = map(self.stemmer.stem, words)
        return sentiment, xs

    def enumerate(self):
        with open(self.filename, 'r') as fin:
            for line in fin:
                yield self.getFromLine(line)

def main():
    sf = SentimentFeatures('chapter08/sentiment.txt')
    for sentiment, features in list(sf.enumerate())[:50]:
        print(sentiment, " ".join(features))

if __name__ == '__main__':
    main()


■ 結果

10個だけ載せます。

-1  film depress life itself
-1  ill fit tuxedo strictli off rack
-1  clich escap perfervid treatment gang warfar call ce wild
-1  circuit queen won learn thing busi curs film strateg place white sheet
-1  waterlog script plumb unchart depth stupid incoher sub sophomor sexual banter
+1  imax strap pair goggl shut real world take vicari voyag last frontier space
-1  analyz movi three word thumb friggin down
+1  stori haven seen big screen befor stori american human be know
-1  bray complet sea noth savag garden music video resum clue make movi
+1  teen review such recommend onli under year age onli veri mild rental