Pythonで自然言語処理

自然言語処理というか、文書を特徴ベクトルに変換するまでの tips などを書いていきます

用意した文書を整形

文書のエンコーディングははっきりしてる?

してる→unicode型に変換しましょう

# 例:EUC-JPの場合
text = text.decode('euc-jp')

してない→文字コードを自動判別しましょう。

日本語の文書であることがはっきりしている場合、nkf、pykf などが便利です。
nkf for python: http://city.plala.jp/moin/NkfPython
pykf: http://memo.jj-net.jp/jjnet_sandbox/pykf/pykf-0.3.4.tgz(有志によるミラー)
SourceForge? にも pykf のソースはありますが、そっちはファイルが足りないらしくコンパイルが通りませんでした。

日本語以外の文書が含まれてる場合、chardet が便利です。
chardet: http://chardet.feedparser.org/
ただし、chardet も完璧ではないので try and error で変換を一つ一つ試した方がいい場合もあります。

文字列操作をする前に

Python で文字列操作(置換、切り出しなど)をする場合、必ず unicode 型に変換してから行うようにしましょう。扱ってる変数が unicode 型か str 型かはっきりしないときは

print type(text)

で確認することができます。

改行コードを統一させましょう

string.replace() で文字の置換が可能です。

text = text.replace('\r\n','\n')
text = text.replace('\r','\n')
# さらに改行コードを除去する場合
text = text.replace('\n','')

全角/半角を統一させましょう

Mac と Macなどのような半角文字と全角文字はそれぞれ別物として扱われます. 人間から見た場合は同じ意味の単語なので,できるだけ一つの表記に統一させた方がいいです.

unicodedata モジュールの normalize 関数を使う

normalize 関数は半角カナ→全角カナ、全角英数→半角英数へと正規化します。

import unicodedata
text = u'MacMac123123アア'
text = unicodedata.normalize('NFKC', text)
print text

zenhan.py を使う

zenhan.py の使い方について: http://straitmouth.jp/blog/setomits/139
最新版(2009/11/7時点): http://straitmouth.jp/blog/setomits/877

数字だけ半角にする、など unicodedata に比べて細かい指定が可能です。

HTMLファイルを扱う場合

HTMLから本文部分のみ抽出したい

extractcontent という本文抽出モジュールを使うのがお手軽です。

HTMLタグ等を取り除きましょう

単純な方法としては、re モジュールを使って

import re
htmltag = re.compile(r'<.*?>', re.I | re.S)
text = htmltag.sub('', html)

こんな感じで取り除くことができます。

スクレイピングをしたい場合は Beautiful Soup などの HTML パーサーを使うと便利です。
http://www.crummy.com/software/BeautifulSoup/

HTML特殊文字を通常の文字に変換しましょう

HTML特殊文字とは、&lt; などの文字のことです。

私は下記のページの htmlentity2unicode() を使わせていただいてます。
http://www.programming-magic.com/20080820002254/

不要な記号は取り除きましょう

# などの記号は MeCab? では「名詞、サ変接続」などと判別されてしまうので形態素解析にかける前に 取り除いておいた方がいいと思います。

整形した文書を形態素解析

どの形態素解析器を使うか?

日本語→MeCab?
http://mecab.sourceforge.net/
Python 用のラッパーも一緒に配布されてます。

英語、フランス語、ドイツ語など→TreeTagger?
http://www.ims.uni-stuttgart.de/projekte/corplex/TreeTagger/

Python 用のラッパーは以下から。 http://www.limsi.fr/Individu/pointal/python/treetaggerwrapper-doc/
確認しようとしたところソース本体は403…。(09/11/7)

言語ごとに形態素解析器を使い分けたい場合

文書の言語判定を行いましょう

既存のライブラリ

guess-language: http://pypi.python.org/pypi/guess-language/
UTF-8のみの対応っぽい

私が使ってた手法

ngram.py と Lingua::LanguageGuesser? の言語モデルを組み合わせる

ngram.py: http://thomas.mangin.me.uk/
ページ右側の検索窓で ngram.py で検索するとダウンロードリンクを見つけることができます。
もしくは
http://thomas.mangin.me.uk/data/source/ngram.py
から直接。

Lingua::LanguageGuesser?
http://gensen.dl.itc.u-tokyo.ac.jp/LanguageGuesser/hajimete_monogatari.html

この二つのモジュールは TextCat? という Perl で書かれた言語判定スクリプトの

  • Python 移植版→ngram.py
  • Perl モジュール化&UTF-8への対応を強化→Lingua::LanguageGuesser?

という関係性があります。

ngram.py を動かすには言語モデル(言語ごとのプロファイル)を記録したファイルが必要です。配布元では TextCat? の言語モデルを使用するように、とされてますがこれに Lingua::LanguageGuesser? の言語モデルを使用することで UTF-8 へ対応できるようにします。

HTMLの場合

HTMLの場合,HTMLタグがついたままだと精度が著しく落ちます.
そのため,HTMLタグを除去する必要があるのですが str 型のままだと文字列操作できないので
文字コード判定 → Unicode に変換 → HTMLタグ除去 → UTF-8 にエンコード → 言語判定
という手順になると思います.
HEADタグ中の文字コードや言語コードなどのメタ情報を利用するのも一つの手段です
(というか,先にそっちをやるべき)

MeCab?の辞書について

デフォルト (ipadicなど) だけだと上手く単語が切り出せない場合は他の辞書を併用するなり自分で単語を追加するなりしましょう.

単語の追加や辞書作成についてはこちらで.
http://mecab.sourceforge.net/dic.html

特徴ベクトルを作成

文書における特徴ベクトルは,多くの場合文書中の単語を要素,重みとして出現頻度やTF-IDFによって表現されます.

「文書における特徴ベクトルの要素は文書中の単語です」という文を実際に特徴ベクトルに変換してみます.

文を形態素解析する

% mecab
文書における特徴ベクトルの要素は文書中の単語です
文書    名詞,一般,*,*,*,*,文書,ブンショ,ブンショ
における        助詞,格助詞,連語,*,*,*,における,ニオケル,ニオケル
特徴    名詞,一般,*,*,*,*,特徴,トクチョウ,トクチョー
ベクトル        名詞,固有名詞,一般,*,*,*,*,はてなキーワード
の      助詞,連体化,*,*,*,*,の,ノ,ノ
要素    名詞,一般,*,*,*,*,要素,ヨウソ,ヨーソ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
文書    名詞,一般,*,*,*,*,文書,ブンショ,ブンショ
中      名詞,接尾,副詞可能,*,*,*,中,チュウ,チュー
の      助詞,連体化,*,*,*,*,の,ノ,ノ
単語    名詞,一般,*,*,*,*,単語,タンゴ,タンゴ
です    助動詞,*,*,*,特殊・デス,基本形,です,デス,デス

解析結果をまとめる

mecab の結果から,文中に出てきた単語と出現頻度を得ることができます.

単語出現頻度
文書2
における1
特徴1
ベクトル1
2
要素1
1
1
単語1
です1

この表が「文書における特徴ベクトルの要素は文書中の単語です」という文の特徴ベクトルです.

Python で実装してみるとこんな感じです.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
feature_vector.py

指定されたファイル中の文章を解析して
単語とその出現頻度を返す

使い方(コマンドラインから)
% python feature_vector.py file

使い方 2(Pythonスクリプト中で)

import feature_vector

text = '単語とその出現頻度を返す'
result = feature_vector.analyse(text)
"""
import MeCab

def analyse(text):
   mecab = MeCab.Tagger() ## MeCab のインスタンス作成
   feature_vector = {} ## 結果を納める辞書
   node = mecab.parseToNode(text) ## 解析を実行
   while node:
       print node.surface, node.feature ## それぞれ単語とその情報(品詞など)
       surface = node.surface.decode('utf-8')
       feature_vector[surface] = feature_vector.get(surface, 0) + 1 # 辞書に単語を追加
       node = node.next
   
   return feature_vector

if __name__ == '__main__':
   import sys
   filename = sys.argv[1]
   file = open(filename).read()
   feature_vector = analyse(file)

   for word,freq in feature_vector.items():
       print "%s\t%d" % (word,freq)

実行結果

yono@orca% cat test.txt                                              
文書における特徴ベクトルの要素は文書中の単語です

yono@orca% python feature_vector.py test.txt             
 BOS/EOS,*,*,*,*,*,*,*,*
文書 名詞,一般,*,*,*,*,文書,モンショ,モンショ
における 助詞,格助詞,連語,*,*,*,における,ニオケル,ニオケル特徴 名詞,一般,*,*,*,*,特徴,トクチョウ,トクチョー
ベクトル 名詞,一般,*,*,*,*,ベクトル,ベクトル,ベクトル
の 助詞,連体化,*,*,*,*,の,ノ,ノ
要素 名詞,一般,*,*,*,*,要素,ヨウソ,ヨーソ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
文書 名詞,一般,*,*,*,*,文書,モンショ,モンショ
中 名詞,接尾,副詞可能,*,*,*,中,ナカ,ナカ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
単語 名詞,一般,*,*,*,*,単語,タンゴ,タンゴ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
 BOS/EOS,*,*,*,*,*,*,*,*
        2
要素    1
ベクトル        1
中      1
は      1
の      2
単語    1
です    1
文書    2
特徴    1
における        1

同じものを github にも置いておきますので,参考程度に.
http://gist.github.com/231879
MeCab?MeCab? 用の Python ラッパーをインストールしておく必要があります.
(テキストの文字コードをUTF-8に決め打ちしたりなど適当な点があるので,実際に使う際には適宜書き換えてください)

接尾語をまとめる

MeCab? だと「労働者」という単語が「労働」「者」と分かれてしまいます.

yono@orca% mecab                                                       
労働者
労働    名詞,サ変接続,*,*,*,*,労働,ロウドウ,ロードー
者      名詞,接尾,一般,*,*,*,者,モノ,モノ
EOS

「可能性」も同様に「可能」「性」と分かれます.

可能性
可能    名詞,形容動詞語幹,*,*,*,*,可能,カノウ,カノー
性      名詞,接尾,一般,*,*,*,性,セイ,セイ
EOS

「第二次」という単語は「第」「二」「次」と分かれます.

第二次
第      接頭詞,数接続,*,*,*,*,第,ダイ,ダイ
二      名詞,数,*,*,*,*,二,ニ,ニ
次      名詞,接尾,助数詞,*,*,*,次,ジ,ジ
EOS

これらはいずれも接頭語,接尾語が分解されるからです. 場合によりけりですが,労働者や可能性などは一つの単語として用いた方が都合がいいと思われます. そこで,これらを一つの単語として扱うためのスクリプトを書いたので github に置いておきました.(ただし,正常に動作しないと思われます.時間があったら後で修正します.)追記: 修正しました.上記の例に関しては全て連結されるようになりました.
http://gist.github.com/271862

上記のスクリプトは

  • 接頭語は次に名詞が来たら接続する
  • 接尾辞は一つ前に名詞が来たら接続する
  • 数字の次に助数詞が来たら接続する
  • 接頭詞,数接続 の次に数字が来たら接続する

というルールに基づいてます. ただし,このルールだけだと適切でない接続も多く発生すると思われます.