読者です 読者をやめる 読者になる 読者になる

北野坂備忘録

主にインストールやプログラミングのメモを載せています。

言語処理100本ノック 2015年版 (70~72)

第8章: 機械学習

本章では,Bo Pang氏とLillian Lee氏が公開しているMovie Review Dataのsentence polarity dataset v1.0を用い,文を肯定的(ポジティブ)もしくは否定的(ネガティブ)に分類するタスク(極性分析)に取り組む.

このあたりからついてこれる人間とついてこれない人間を振り分けている感じがします。

70. データの入手・整形

文に関する極性分析の正解データを用い,以下の要領で正解データ(sentiment.txt)を作成せよ.

rt-polarity.posの各行の先頭に"+1 "という文字列を追加する(極性ラベル"+1"とスペースに続けて肯定的な文の内容が続く)
rt-polarity.negの各行の先頭に"-1 "という文字列を追加する(極性ラベル"-1"とスペースに続けて否定的な文の内容が続く)
上述1と2の内容を結合(concatenate)し,行をランダムに並び替える

sentiment.txtを作成したら,正例(肯定的な文)の数と負例(否定的な文)の数を確認せよ.

いきなり文字コードでつまづきました。元データが latin_1 でした。

#!/usr/bin/env python

import codecs
import random

fpos = codecs.open('rt-polarity.pos', 'r', 'latin_1')
fneg = codecs.open('rt-polarity.neg', 'r', 'latin_1')
fsen = codecs.open('sentiment.txt', 'w', 'latin_1')

if __name__ == "__main__":
  pos = ["+1 "+line for line in fpos]
  neg = ["-1 "+line for line in fneg]
  sentiment = pos + neg

  random.shuffle(sentiment)

  [fsen.writelines(line) for line in sentiment]

わざわざ混ぜ合わせた sentiment.txtから正例と負例の数を取るのはすごく無駄が多いような気がしますが……。unixコマンドでやるとこんな感じ。

>cut -c1-3 sentiment.txt | sort | uniq -c
   5331 +1 
   5331 -1 

71. ストップワード

英語のストップワードのリスト(ストップリスト)を適当に作成せよ.さらに,引数に与えられた単語(文字列)がストップリストに含まれている場合は真,それ以外は偽を返す関数を実装せよ.さらに,その関数に対するテストを記述せよ.

適当に作成せよ

適当に作成せよ

適当に作成せよ

じゃあ単語にバラして頻度をとりましょうか。
collectionモジュールを用います。

#!/usr/bin/env python

import codecs
import re
from collections import Counter

fsen = codecs.open('sentiment.txt', 'r', 'latin_1')
words = []

if __name__ == "__main__":

  for line in fsen:
    words = words + re.compile(r'[,.:;\s]').split(line)

  counter = Counter(words)

  [print(word,count) for word,count in counter.most_common(100)]

結果

 70150
the 10098
a 7282
and 6196
of 6062
-1 5349
+1 5346
to 4233
is 3368
in 2629
that 2470
it 2283
as 1801

上から500語ほど取ったグラフは以下のとおり。
f:id:kenichia:20160222111601p:plain
グラフを見て上から50語も削除すればいいかなと思いましたが、削除すればするほどその他の処理がしやすいので100語をストップワードとします。
director や movies まで入ってしまいました。
プラス1とマイナス1は削除されたくないので除きます。
まあこのあたりの増減は簡単にできるのでよしとしましょう。
関数とテストはこちら

#!/usr/bin/env python

import codecs
import sys

def checkstopwords(word):
  fstop = codecs.open('stoplist.txt', 'r', 'latin_1')
  stopwords = [ line[:-1] for line in fstop]
  return True if word in stopwords else False

if __name__ == "__main__":
  param = sys.argv
  word = param[1] if len(param)>1 else ""

  print(checkstopwords(word))

72. 素性抽出

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

 素性(そせい)とは何かというと、文書または文の特徴を表すベクトルです。今回であれば文書ではなく文をベクトル化したい。ベクトル化すると何がうれしいのかというと、文の違いや同質性を計算しやすいのです。
 もともと素性という概念があったのではなく、文書や文を数学的に取扱いたいので特徴をベクトル化したい。そのベクトルを「素性」と呼ぼう、ということです。もっとダイレクトな「素性ベクトル」という言葉もあります。

 素性をどうするかはいろいろあって、代表的なものを3つあげると、
・ステミング
・見出し語化(レンマ)
・品詞タグ付け
 となります。
 他にも
・文書の中で各単語が出てくる回数
・割合
・単語数
・文の長さ
 など、いくらでも考え出すことができます。

 ステミングは以前の設問にあったので今回は見出し語化を試します。
 いきなりレビューという単語が出てきますがこれは sentiment.txt のこととします。
 見出し語化は信頼と実績の nltk を用います。
 各文を読み込んで、ストップワードを削除し、見出し語化した単語のリストを今回の極性分析用素性とします。
 見出し語化のためには小文字化しないといけないんですが、それはもともと済んでいるようです。

#!/usr/bin/env python

import codecs
import re
from nltk import stem

fsen = codecs.open('sentiment.txt', 'r', 'latin_1')
fout = codecs.open('72.txt', 'w', 'latin_1')

stopwords = []
features = []

def checkstopwords(word,stopwords):
  return True if word in stopwords else False

if __name__ == "__main__":

  lemmatizer = stem.WordNetLemmatizer()

  fstop = codecs.open('stoplist.txt', 'r', 'latin_1')
  stopwords = [ line[:-1] for line in fstop]

  for line in fsen:
    string = re.compile(r'[,.:;\s]').split(line)
    for word in string:
      if not checkstopwords(word,stopwords):
        feature = lemmatizer.lemmatize(word)
        features.append(feature)
    line = " ".join(features) + "\n"
    fout.writelines(line)
    features = []

次が重いので今回はここまでにします。