簡単で効率的♪ Pythonをつかって、Nグラム表をささっと作成する

前回に引き続き、Nグラムの話です。タイトルをクックパッドぽくしてみました。nグラム表を作って、そこからフレーズを取り出してみます。以下の文献を参考にしました。
長尾眞, 森信介, 1993, 「大規模日本語テキストのnグラム統計の作り方と語句の自動抽出」, 情報処理学会研究報告. 自然言語処理研究会報告 93(61), 1-8
1993年の文献だけあって、「処理能力が向上」、「64MBのメモリ」などなど懐かしさこみあげる文言が踊っています。それだけあって、いかに効率的にやるかという点に焦点があてられています。やはり、人の営為を研ぎ澄ませるのはいつでも制約条件ですね。
まずは下ごしらえです。L文字の文章資源を、i=1,2..文字目からL文字目までのL本の文字列にして、それを辞書順にソート、前後の文字列が何文字目まで同一かを調べます。

from collections import defaultdict

raw_string = お手元の文章資源(ここでは参院選期間中の候補者のツイートを使用)
# 改行コードと半角スペースをなきものにしちゃったけどいいのかな…。
raw_string = raw_string.replace(u"\n",u"").replace(u" ",u"")

# 下ごしらえ
# i文字目からlen(raw_string)文字目までのlen(raw_string)本の文字列をつくる
strings = []
for i in range(len(raw_string)):
	strings.append([raw_string[i:], i])

# 辞書順で並び替え(時間掛かる)
strings = sorted(strings, key=lambda x:x[0])

# i番目の文字列とi+1番目の文字列は、なん文字目まで一致するか?(最大MAX_N文字に設定)
MAX_N = 12
for i in range(len(strings)-1):
	count, _MAX_N = 0, MAX_N
	if len(strings[i][0]) < MAX_N or len(strings[i+1][0]) < MAX_N: # 文字列の長さがMAX_N未満の場合
		if len(strings[i][0]) < len(strings[i+1][0]):
			_MAX_N = len(strings[i][0])-2
		else:
			_MAX_N = len(strings[i+1][0])-2
	for j in range(_MAX_N):
		if strings[i][0][j] == strings[i+1][0][j]:
			count += 1
		else:
			break
	strings[i].append(count)
	#if len(strings[i]) < 3: strings[i].append(count)
	#else: strings[i][2] = count

nグラム表をつくる関数を定義して、試しに10グラム表を出力してみます。論文を読んでいて、ここが一番美しかった!

# nグラム表と、n文字列の左右の1文字辞書(あとで使う)の作成
l_str, r_str = defaultdict(list), defaultdict(list)
def make_ngram_table(raw, strs, n_gram, l_str=l_str, r_str=r_str):
	ngram_table = defaultdict(int)
	for i in range(len(strs)-1):
		if len(strs[i][0]) < n_gram: continue # 文字列の長さがn数未満の場合はスルー
		if strs[i][2] >= n_gram:
			ngram_table[strs[i][0][:n_gram]] += 1 # nグラム表(dict)を作成
			if strs[i][1] != 0: l_str[strs[i][0][:n_gram]].append(raw[strs[i][1]-1]) # 左の1文字辞書へ追記
			r_str[strs[i][0][:n_gram]].append(strs[i][0][n_gram]) # 右の1文字辞書へ追記
	return ngram_table

# 試しに10グラム表を作ってみる
ngram_table = make_ngram_table(raw_string, strings, 10)
# 頻度30回以上に絞り込んで、頻度が高い順にソート
ngram_table_30 = sorted([n for n in  ngram_table.items() if n[1] >= 30], key=lambda x:x[1], reverse=True) 
# 10文字列|頻度 となるように出力
for nstring in ngram_table_30:
	print u"%s|%s" % (nstring[0], nstring[1])
# tp://t.co/|252
# ttp://t.co|252
# http://t.c|252
# (BOT)#石田まさ|124
# OT)#石田まさひろ|124
# BOT)#石田まさひ|124
# す(BOT)#石田ま|85
# 。http://t.|79
# ます(BOT)#石田|66
# ブログを更新しました|45
# ログを更新しました。|45
# ...http://|42
# .http://t.|42
# ..http://t|42
# 』http://t.|40
# す。http://t|37
# グを更新しました。『|36
# ます。http://|31

続いて、n文字列の左右の1文字のレパートリが多い場合に、そのn文字列がひとつの塊である可能性が高いという仮説のもと、先ほど作ったl_str, r_strを利用して、塊の可能性が高いものを出力してみます。

# n文字列の左右の1文字のレパートリが多いとき、単語の可能性が高いと仮定し、それを出力する
def extract_words(ngram_table, freq_thres, lr_thres, l_str=l_str, r_str=r_str):
	for w in ngram_table.items():
		if w[1] >= freq_thres:
			lr_count = len(list(set(l_str[w[0]]))) + len(list(set(r_str[w[0]])))
			if lr_count >= lr_thres:
				print u"%s|%s|%s" % (w[0], w[1], lr_count)



# In [329]: extract_words(make_ngram_table(raw_string, strings, 10), 10, 10)
# Out [329]:
# 単語(候補)|頻度|左右の文字の種類数
# ありがとうございます|24|20
# (BOT)#石田まさ|124|10
# ブログを更新しました|45|30
# tp://t.co/|252|61
# ...http://|42|16
# 』http://t.|40|23
# OT)#石田まさひろ|124|50
# 次の@YouTube|12|12
# りがとうございます。|14|12
# 。http://t.|79|15
# 新しい写真をFace|17|13
# グを更新しました。『|36|15
# http://t.c|252|35
# せていただきました。|12|12

このあと論文は、塊をうまく取り出す方法を幾つか提案しているのですが、それはまた後日ということで今日はここまで。

Nグラムを使った未知語の抽出(仮)

n-gramsってどう使うのかよく分かんないなー、どうしてGoogle IMEは「灼眼のシャナ」とか「やはり俺の青春ラブコメはまちがっている。」とかをひとつのフレーズとして認識しているのだろう・・・とググっていたら、こんな論文をみつけた。
森信介, 長尾眞, 1998, 「nグラム統計によるコーパスからの未知語抽出」, 『情報処理学会論文誌』, 39:7, 2093-2100.

「品詞ごとに、前後にくる文字にはパターンがある」という仮定に基いて未知語を探すらしい。
名詞の場合、コーパスを分析すると「ご<名詞>の」とか「、<名詞>し」とかいうパターンが多かった、みたいな。

この論文だと、このパターンの辞書を各品詞について作成したあとに、各単語についても同様のパターンを作成して、なんだか最適化問題を解いているのだけど、まず、各単語についてそれぞれ辞書をつくるほどコストを掛けていたら朝になってしまいそうなので、この論文を数リットルの水で薄めたようなコードを書いてみた。

コーパスには、手元にあった、先の参院選のときの候補者のツイートを使った。
※mecab_libraryについてはこちらを参照してください。

import mecab_library
from collections import defaultdict

corpus = 先の参院選のときの候補者のツイート
tokens = mecab_library.Tokens(corpus.encode("utf-8")).tokens

#In [103]: len(tokens)
#Out[103]: 104666

# 名詞の環境構築
# 名詞の前後にくる文字をひらがなと括弧だけに限定した(reでやるのめんどくさかった)
hiragana = [h for h in u"あぁいぃうぅえぇおぉかがきぎくぐけげこごさざしじすずせぜそぞただちぢつづってでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもやゃゆゅよょらりるれろわゎをん、。「」【】『』()”"]
pos = "名詞"
lr_str = defaultdict(int)
pos_tokens = [token.surface for token in tokens if token.pos == pos]
# 各名詞をコーパスから探して、その前後のひらがなをキーにした辞書を作成(値は頻度)
for pos_token in pos_tokens:
	index = 0
	len_token = len(pos_token.decode("utf-8"))
	_corpus = corpus
	index = _corpus.find(pos_token.decode("utf-8"))
	if index > 0 and index+len_token < len(_corpus) and _corpus[index-1] in hiragana and _corpus[index+len_token] in hiragana:
		lr_str[(_corpus[index-1], _corpus[index+len_token])] += 1
	_corpus = _corpus[index+len_token:]

# なんとなく頻度で並び替えて、前後の文字の組み合わせが生じる確率とか出してみる
lr_sorted = {}
for k, v in sorted(lr_str.items(), key=lambda x:x[1], reverse=True):
	lr_sorted[k] = 1.0*v/len(pos_tokens)

# 左右の文字の組み合わせを使って、それに挟まれた文字列を取り出す
lrs = [(k[0], k[1]) for k, v in lr_sorted.items()]
surfaces = [token.surface.decode("utf-8") for token in tokens]
newwords = defaultdict(int)
for lr in lrs:
	lindex = 0
	_corpus = corpus.replace(u"\n",u" ").replace(u" ",u"").replace(u" ",u"")
	while lindex != -1:
		lindex = _corpus.find(lr[0])
		_corpus = _corpus[lindex+1:]
		r = u""
		i = 0
		# 組み合わせの右の文字に当たるまで一文字ずつ文字列を増やしていく
		while i <= 7 and i < len(_corpus): # 7文字のフレーズまで探すことにした
			r = _corpus[i]
			if i == 0 and r in hiragana: # もうこの辺、ぐっちゃぐちゃ・・・
				break
			if r != lr[1] and r in hiragana: # フレーズにひらがながはいらないようにしちゃった・・・
				break
			if r == lr[1]: # 7文字以内に右側の文字がみつかったよ!
				newword = _corpus[0:i]
				if len(newword) != 1 and newword.find(u"/") == -1 and newword not in surfaces:
					newwords[newword] += 1
				break
			i += 1
# 頻度順に並べ替えて出力(これを確率とかにして閾値とか設定したらいいんじゃないかな・・・)
newwords2 = sorted([(k, v) for k, v in newwords.items()], key=lambda x:x[1], reverse=True)
for newword2 in newwords2[:10]:
	print u"%s\t%s" % (newword2[0], newword2[1])

結果は、↓みたいな感じになった。まぁ、そこまでひどくないけど、ひらがなや括弧を含まないようにしたせいで、微妙なのも混じってしまって、決してよいとは言えない感じだ。それにこれでは、「灼眼のシャナ」とか「やはり俺の青春ラブコメはまちがっている。」はやはり取り出せない・・・。

看護職	22
街頭演説	12
加盟組合	11
目指	10
投票日	10
公式サイト	9
選挙戦	9
一日	9
看護師	8
個人演説会	7
見直	7
夜勤体制	6
定期大会	6
人材確保	6
医療現場	6
看護・介護職	6
利用者	6
義務教育	5

まだまだだなぁ・・・。

Django1.5でGoogle App Engineのチュートリアルをやったときのメモ

Google App Engine上のDjangoでのチュートリアル「Using Django with Appengine」邦訳 - WebOS Goodiesをやろうとしたところ、チュートリアルで使われているDjangoとGAEのバージョンが古くそのままでは動きませんでした。

色々なサイトを参考にしながら動くところまでこぎつけたので、そのメモです。
※どのディレクトリのファイルか分かりづらかったのでパスも書きました。
参考サイト
GAE - GoogleAppEngine/PythonでDjangoを使う方法 - Qiita
まず、GAEに付属しているDjango1.5のあるディレクトリにパスを通します。

$ vim ~/.bash_profile

PYTHONPATH = "/usr/local/google_appengine/lib/django-1.5:$PYTHONPATH"
export PYTHONPATH

Creating a project
ホームディレクトリにGAEというフォルダを作成し、プロジェクトを開始します。

$ cd ~
$ mkdir GAE
$ cd GAE
$ /usr/local/google_appengine/lib/django-1.5/django/bin/django-admin.py startproject appproject

これで、~/GAE以下にappprojectという名前のディレクトリが作成されます。チュートリアルにある、main.pyを作成する必要はありませんが、代わりに~/GAE/appproject/にappengine_config.pyを作成します。

$ cd appproject
$ vim appengine_config.py

# -*- coding: utf-8 -*-
import os
import sys

if os.environ.get('SERVER_SOFTWARE','').startswith('Dev'):
    sys.path.append('/Users/あなたのユーザ名/GAE/appproject/appproject')

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "appproject.settings")

つづいて、~/GAE/appproject/にapp.yamlを作成します。

application: appproject
version: 1
runtime: python27
api_version: 1
threadsafe: yes

libraries:
- name: django
  version: "1.5"

builtins:
- django_wsgi: on

続いて、チュートリアルにしたがって、以下のコマンドを打ちます。

$ python manage.py startapp poll

Editing the settings.py file
ここでは、~/GAE/appproject/appproject/settings.pyを編集していきます。

110 import os
111 ROOT_PATH = os.path.dirname(__file__)
112 
113 TEMPLATE_DIRS = (
114     # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
115     # Always use forward slashes, even on Windows.
116     # Don't forget to use absolute paths, not relative paths.
117     ROOT_PATH + '/templates',
118 )
119
120 INSTALLED_APPS = (
121     'django.contrib.auth',
122     'django.contrib.contenttypes',
123     'django.contrib.sessions',
124     'django.contrib.sites',
125     'django.contrib.messages',
126     'django.contrib.staticfiles',
127     # Uncomment the next line to enable the admin:
128     # 'django.contrib.admin',
129     # Uncomment the next line to enable admin documentation:
130     # 'django.contrib.admindocs',
131     'poll',
132 )

Write the URL configuration file
~/GAE/appproject/appproject/urls.pyを編集します。

 7 urlpatterns = patterns('',
  8     # Examples:
  9     # url(r'^$', 'appproject.views.home', name='home'),
 10     # url(r'^appproject/', include('appproject.foo.urls')),
 11 
 12     # Uncomment the admin/doc line below to enable admin documentation:
 13     # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
 14 
 15     # Uncomment the next line to enable the admin:
 16     # url(r'^admin/', include(admin.site.urls)),
 17     (r'^$', 'poll.views.index'),
 18     (r'^create/$', 'poll.views.create'),
 19     (r'^poll/(?P<poll_key>[^\.^/]+)/$', 'poll.views.poll_detail'),
 20     (r'^poll/(?P<poll_key>[^\.^/]+)/results/$', 'poll.views.poll_results'),

Write the models.py file.
~/GAE/appproject/poll/models.pyはそのままで動きました。
Writing the forms
~/GAE/appproject/poll/bforms.pyを作成します。

from django import newforms as forms

とありますが、newformsはformsに統一されたようなので、

from django import forms

で問題ありません。
また、crean_dataをcreaned_dataに変更します。

    def save(self):
        choice = models.Choice(poll = self.poll, choice = self.cleaned_data['choice'])
        choice.put()

Write the view
~/GAE/appproject/poll/views.pyを編集します。
モジュールのインポートが少し違います。CSRFはフォーム送信関係のセキュリティ対策のようです。

  1 from django.http import HttpResponse, HttpResponseRedirect
  2 import models
  3 import bforms
  4 from django.shortcuts import render_to_response
  5 from django.core.context_processors import csrf
 
 35     payload = dict(pollform=pollform, choiceforms=choiceforms)
 36     payload.update(csrf(request))
 37     return render('create.html', payload)

Writing the templates
~/GAE/appproject/poll/に、templatesディレクトリを作成し、そこにテンプレートファイル(.html)を作成していきます。チュートリアルではファイル名が分かりづらいですが、一個目のコードがindex.html、二個目のコードがbase.htmlです。
create.htmlに先ほどのCSRF関係のコードを書き足します。

{% extends 'base.html' %}

{% block contents %}
<form action="." method="post">
{% csrf_token %}
{{pollform.as_p}}

{% for form in choiceforms %}
{{form.as_p}}
{% endfor %}

<input type="submit" name="createpoll" value="createpoll" />

</form>

{% endblock %}

これで、サーバーを立ち上げ、http://localhost:8080にアクセスすれば動いている(はず)です。

$ cd ~/GAE/appproject
$ dev_appserver.py .

それで、Applications OverviewでCreate Applicationして、

$ cd ~/GAE/appproject
$ appcfg.py --oauth2 -A プロジェクト名 update プロジェクトのディレクトリ

とやればデプロイされ、http://プロジェクト名.appspot.comでアクセスできるらしいです(このプロジェクト名だともう使われているだろうけど…)。

ただ、このチュートリアルのコードだとどうがんばっても投票数が増えていかないと思うんですが、どうなんだろう…。

「客観的に」認められたいとい欲望は、結局は自尊の相関だ。

「客観的」なんて純粋にはあり得ない。神の視点なんてない。あったとしても、その視点から見える私はちっぽけでどうでもよいものだろう。客観性のニヒリズムだ。

そうではない。客観的な見方の第一歩は、「もし相手だったらどう思うか?」という私の主観だったはずだ。そして二歩目以降もそれは変わらない。

つまり、「客観的」は、私の主観から出発している以上、私の性向と必要性の上にある。

すなわち、「相手だったら?」「みんなから見たら?」と考えずにはいられない性向と、その視点から見ても自分はマトモだと思いたいという必要性だ。

そうして、私は、私の延長線にある遠い視点からも、私のことを大切な存在と思いたい、そんなわがままな思いを抱いて、見えないなにかと闘い続けるのです。

生きることは、主体と対象の理想的時間の闘争だ

ハイデッガーが言っているのはこういうことだ。私たちは何かによって退屈させられている時、その何かがもつ時間にうまく適合していないと言っているのである。
つまりある物とそれに接する人間がいるとして、両者の間の時間のギャップによってこの第一形式の退屈(引用者注―何かによって退屈させられること)が生じるのである。何かによって退屈させられるという現象の根源には、物と主体との間の時間のギャップが存在している。それによって〈引きとめ〉が生じ、〈空虚放置〉される。

國分功一郎『暇と退屈の倫理学』(p.216)からの引用だ。この引用部分の直前で、物にはそれ特有の時間があり、例えば駅舎に特有の時間とは駅舎の理想的時間、すなわち、列車発車の直前であると述べている。
この箇所が、この本を通読した後、妙に頭に残っている。なぜだろうか。おそらく、私の中に、なんらかの物との時間のギャップによって生じる苦悩*1があるからではないか。
たとえば、病で長く床に臥せるとき、私は私の所属している社会に置いて行かれる感覚に苦悩する。社会のもつ理想的時間に病の身はうまく適合できない。このとき、社会の理想的時間は朝9時の出社と十数時間の労働が5日間連続でなされることだ。病の身はこの時間に着いていけない
一方で、病がその治療に数年の時間を要することが判明したとしよう。このとき、私は苦悩する。しかしそれは先ほどとは異なった不適合によってである。なぜならば、このとき病者は、病者の時間とでも言いうる時間を生きようとしているからだ。すなわち、治療を最大の目的に置き、時間の流れをできるだけ遅くするような時間だ。私がこのような時間を生きるとき、病者の時間と社会の時間の間のせめぎあいが生じる。このせめぎあいが、苦悩となる。社会の時間に着いていけないのではない。重篤な病は、主体が社会の時間に束縛され、奴隷状態となることから解き放つ
ただ、このせめぎあいに病者が勝利を収めることはなかなかに難しい。結局は社会の時間に変更を迫るのではなく、一度そこから退出し、現在とは異なる社会に新たに所属するようにするしかない場合がほとんどだろう。
私が苦悩しているのは、これまで私が所属していた社会と、最適な時間が異なり、私は社会の側の時間の非柔軟性故に闘いを挑むことすら叶わないという様相なのだ。

暇と退屈の倫理学

暇と退屈の倫理学

*1:ショーペンハウアーが私たちは退屈と苦悩の間を行ったり来たりしていると言ったように、退屈と苦悩は同じ数直線上の現象であり、退屈への説明は苦悩の説明に利用できるところが多いだろう。

Unicode型とstr型。Pythonさん、勝手にasciiでデコードしないでください。

Pythonは便利ですが、日本語の取り扱いになると突然面倒になる。

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128) 

ってなエラーが出る度に日本語圏に生まれたことを呪う…とまではいかないけど、結構いらいらする。

PythonのUnicodeEncodeErrorを知る - HDEラボ
http://lab.hde.co.jp/2008/08/pythonunicodeencodeerror.html
を参考に、忘備録的に記録します。

上記サイト様によれば、最初のエラーは、
str型を文字コード'ascii'でデコードしてunicode型にしようとしたのですが、できませんでした、というエラーです。
また、
encodeは、「Unicode型を特定の文字コードのバイト列(のstr型)にエンコードする」ためのメソッドです。
decodeは、「特定の文字コードのバイト列(のstr型)をデコードしてUnicode型にする」ためのメソッドです。

とのこと。

In [115]: u"あ"
Out[115]: u'\u3042'

In [116]: "あ"
Out[116]: '\xe3\x81\x82'

In [117]: type("あ")
Out[117]: str

In [118]: type(u"あ")
Out[118]: unicode

In [119]: "あ".decode("utf-8")
Out[119]: u'\u3042'

In [120]: u"あ".encode("utf-8")
Out[120]: '\xe3\x81\x82'

MeCabとNLTKを使って最瀕語と共起関係を出力する

MecabとNLTKを使って最瀕語と共起関係を出力するコードを書きました。
Mecabのインストールについては、Windowsなら結構難なく行くようですが、Mac OSX Lionだととても躓きました。
その辺りの経緯は、mecab-pythonをMac OSX 10.7 Lion、Python2.7にインストールする - Men talking over coffee with smoking Ark Royal.を参考にしてください。

まず、このMeCab形態素解析を行うコードですが、以前、『入門ソーシャルデータ』勉強会で、Kenji Koshikawa (Kshi_Kshi)さんに頂いた、mecab_library.pyを元にしています(元のリンクが見つけられませんでした、申し訳ございません)。

mecab_library.py

# -*- coding: utf-8 -*-

import MeCab

mecab = MeCab.Tagger('-Ochasen')

class Tokens(object):
	"""textの形態素情報を保持"""
	def __init__(self, text):
		self.text = text
		#print mecab.parse(text)
		node = mecab.parseToNode(text)
		self.tokens = []
		while node:
			self.tokens.append(Token(node.surface, *node.feature.split(',')))
			node = node.next


class Token(object):
	"""形態素情報"""
	def __init__(self, surface, *args):
		# Mecab Format
		# 表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音
		self.surface = surface	# 表層形
		try:
			self.pos = args[0]	# 品詞
			self.pos_detail1 = args[1]	# 品詞細分類^Z1
			self.pos_detail2 = args[2]	# 品詞細分類^Z2
			self.pos_detail3 = args[3]	# 品詞細分類^Z3
			self.verb_form = args[4]	# 活用形
			self.verb_type = args[5]	# 活用型
			self.basic = args[6]	# 原型
			self.reading = args[7]	# 読み
			self.pronunciation = args[8]	# 発音
			self.type = True # 全ての要素が格納できたとき
		except IndexError:
			self.type = False # 全ての要素が格納できなかったとき

このmecab_library.pyを用いて、tokenize(text, pos_list=["名詞","動詞"])という関数を作りました。ストップワーズは適宜編集してください。
mecab_tokenizer.py

# -*- coding: utf-8 -*-

import mecab_library
import pickle

# tweets = pickle.load(open("cluster2_tweets.pickle","r"))

def splitStr(str, num):
	l = []
	for i in range(num):
		l.append(str[i::num])
	l = ["".join(i) for i in zip(*l)]
	rem = len(str) % num  # zip で捨てられた余り
	if rem:
		l.append(str[-rem:])
	return l

# pos: 名詞, 動詞, 形容詞, 副詞, 助詞, 接続詞, 助動詞, 連体詞, 感動詞, * 
def classifyPos(words, pos_list=["名詞", "動詞"]):
	
	stop_words = ["@","RT","bit","ly","goo","gl","こと","もの","好き"]
	
	texts = []
	for token in words.tokens:
		if unicode(token.surface, 'utf-8') in stop_words: continue
		elif len(unicode(token.surface, 'utf-8')) == 1: continue # 1文字はスルー
		elif token.pos in pos_list:
			if token.basic == "*":
				texts.append(token.surface)
			else:
				texts.append(token.surface)
	
	return texts

def tokenize(text, pos_list=["名詞","動詞"]):
	sent = text.encode('utf-8')
	if len(sent) > 2000000:
		sents = splitStr(sent, 2000000)
		words = mecab_library.Tokens(sents[0])
		texts = classifyPos(words, pos_list)
		for i in range(1,len(sents)):
			w = mecab_library.Tokens(sents[i])
			texts += classifyPos(w)
			print len(texts)
		return texts
		
	else:
		words = mecab_library.Tokens(sent)
		texts = classifyPos(words)
		return texts

そして、これらを元にNLTKを用いて、最瀕語と共起関係を出力する関数が以下のものです。引数には、unicode型のstrと、閾値頻度を取ります。(共起と最瀕語で閾値を変えたいときは適宜変更してください)

# -*- coding: utf-8 -*-

import nltk
import mecab_tokenizer

# 共起と最頻語を出力する
def collocations_and_freq_words(text, freq=25):
	tokens = mecab_tokenizer.tokenize(text)
	corpus = nltk.Text(tokens)
	
	print u"-----最頻語(頻度%d回以上)-----" % freq
	
	fdist1 = nltk.FreqDist(tokens)
	saihin1 = fdist1.keys()
	for voc in saihin1:
		if fdist1[voc] >= freq:
			print "%s\t%s" % (voc, fdist1[voc])
	
	print u""
	print u"-----共起関係(共起頻度%d回以上)-----" % freq

	bigrams = nltk.bigrams(corpus)
	cfd = nltk.ConditionalFreqDist(bigrams)
	kyouki = cfd.keys()
	for voc in kouki:
		for (key, value) in list(cfd[voc].viewitems()):
			if value >= freq:
				print "%s\t%s\t%s" % (voc, key, value)

NLTKすごい!ということで。

入門 自然言語処理

入門 自然言語処理

『エンディング・ボット』

自分の書いた大量の文章をトークン化し、語彙資源化して、バイグラムの頻度分布を作成することで、自分の文章のようなランダムテキストを生成できる。
これを用いて、たとえばこれまでの自分のTwitterのポストを解析し、ランダムなトークンから始まるツイートを生成しつぶやくプログラムをcronにスケジューリングすることで、たとえ自分が死んだとしてもサーバの生きる限り自分のような文章をつぶやき続けるbotを作ることができる。
最近、『エンディング・ノート』とか流行ってるから、ソーシャルメディアのエンディング・ボットとか流行らないかな。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import nltk
import json
import sys

FILENAME = sys.argv[1]

def generate_model(cfdist, word, num=15):
 for i in range(num):
  print word,
  word = cfdist[word].max()

tokens = json.load(open(FILENAME,"r"))
text = nltk.Text(tokens)
bigrams = nltk.bigrams(text)
cfd = nltk.ConditionalFreqDist(bigrams) 

if __name__ = '__main__':
 print cfd['living']
 # <FreqDist: 'creature': 7, 'thing': 4, 'substance': 2, ',': 1, '.': 1, 'soul': 1>

 generate_model(cfd, 'living')
 # living creature that he said , and the land of the land of the land

名詞評価極性辞書を利用したTwitterの感情分析(Positeve/Negative判定)

Twitter感情分析所 さんを利用しようとしたら、結構重たくて、大量の処理を実行するのは申し訳ない…。と思い、じゃあ自分でコードを書いてしまえ、と思い、調べていたところ、東山昌彦, 乾健太郎, 松本裕治, 述語の選択選好性に着目した名詞評価極性の獲得, 言語処理学会第14回年次大会論文集, pp.584-587, 2008.(日本語評価極性辞書)がありました。

日本語評価極性辞書(名詞編)ver.1.0(2008年12月版)pn.csv.m3.120408.trim.gz をダウンロード→解凍し、拡張子に.txtを設定し、適当なエディタで開きます。
Python標準モジュールのcsvで読み込ませるときに、タブ区切りが上手く読み込めなかったので、\tを,に置換して、保存します(Mac OSXの場合、\はoption+\でバックスラッシュを入力)。

また、以下のサイトを参考に、Yahoo!のアプリケーションIDを取得し、形態素解析APIを利用可能にします。
PythonからYahoo!形態素解析APIを使う - 人工知能に関する断創録 | http://d.hatena.ne.jp/aidiary/20090415/1239802199

def get_tweets_from_streaming_listener(filename):は、Streaming APIで大量のつぶやきをリアルタイムに保存する方法(Python編) | inquisitor http://blog.unfindable.net/archives/4257 を参考に、tweepyからTwitter Streaming APIを利用して収集したツイートがresult01.datに保存されている場合に使える関数です。

コンソールから実行すると、スクリーンネームを聞かれますので、入力すると、そのひとのツイートと、その評価が出力されます。(※tweepyを使ってOAuth認証が必要です。)

また、毎度のことですが、Python最大の難所、PythonのUnicodeEncodeErrorを知る - HDEラボ http://lab.hde.co.jp/2008/08/pythonunicodeencodeerror.html を参考にしました。

精度がどのくらいのものなのか、P/N以外の感情も判定したい、などと色々考えていますので、コメントなど頂けると嬉しいです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import csv
import json
import urllib
import urllib2
import tweepy
from BeautifulSoup import BeautifulSoup

# dev.twitter.comから取得してくる。
consumer_token = "********************************"
consumer_secret = "********************************"
access_token = "********************************"
access_token_secret = "********************************"

auth = tweepy.OAuthHandler(consumer_token, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
oauth_api = tweepy.API(auth)

# 名詞評価極性辞書を読み込む
in_file = csv.reader(open('pne.txt',"rb"))
pne = []
for line in in_file:
    try:
        if line[1] == 'p': score = 1.0
        elif line[1] == 'e': score = 0.5
        elif line[1] == 'n': score = 0.0
        pne.append((line[0],score))
    except: pass

# トークンのリストのP/Nを判定する。
def judge_pn(tokens):
	score = 0
	num_score = 0
	for token in tokens:
		for _pne in pne:
			if token == _pne[0]:
				score += _pne[1]
				num_score += 1
	if num_score != 0:
		pn_rate = float(score)/float(num_score)
	else: pn_rate = 0.5
	
	return pn_rate

	
# 参考:PythonからYahoo!形態素解析APIを使う - 人工知能に関する断創録 http://d.hatena.ne.jp/aidiary/20090415/1239802199
# http://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html
appid = "********************************"  # 登録したアプリケーションID
pageurl = "http://jlp.yahooapis.jp/MAService/V1/parse"
def morph(sentence, appid=appid, results="ma", filter="9"):
    sentence = sentence.encode("utf-8")
    params = urllib.urlencode({'appid':appid, 'results':results, 'filter':filter, 'sentence':sentence})
    c = urllib2.urlopen(pageurl, params)
    soup = BeautifulSoup(c.read())
    return [str(w.surface.string) for w in soup.ma_result.word_list]

# Twitter Streaming APIを使ってfilenameにツイートが保存されているものとする
def get_tweets_from_streaming_listener(filename):
	# Streaming Listenerから読み込み
	lines = open(filename,"r")
	lines = [line for line in lines]
	tweets=[]
	for line in lines:
		try:
			tweet = json.loads(line)
			tweets.append(tweet)
		except:
			pass
	# ツイートから本文だけを取り出す
	texts = [tweet["text"] for tweet in tweets]
	
	return tweets

# tweets = get_tweets_from_streaming_listener("result01.dat")

# 個々のツイート本文をトークン化
def tokenize_list(list):
	sents = []
	for text in texts:
		tokenized_text = morph(text)
		sents.append((text, tokenized_text))
	return sents

# sents = tokenize_list(texts)

# 個々のツイート本文のP/Nを判定し、pn_ratesに格納
def pn_rates_and_sents(sents):
	pn_rates = []
	pn_rates_with_sents = []
	for sent in sents:
		pn_rate = judge_pn(sent[1])
		pn_rates.append(pn_rate)
		pn_rates_with_sents.append((sent[0], pn_rate))
	return pn_rates, pn_rates_with_sents

# pn_rates, pn_rates_with_sents = pn_rate_and_sents(sents)

# P/E/Nスコアを算出して出力する
def print_scores(pn_rates):
	p, e, n = 0.0, 0.0, 0.0
	p_num, e_num, n_num = 0.0, 0.0, 0.0
	for pn in pn_rates:
		if pn > 0.5: 
			p += pn
			p_num += 1
		elif pn == 0.5:
			e += pn
			e_num += 1
		elif pn < 0.5:
			n += pn
			n_num += 1
	sum = p_num + e_num + n_num
	
	print p, e, n, p_num, e_num, n_num
	print p_num/sum, e_num/sum, n_num/sum

if __name__ == '__main__':
	screen_name = raw_input("Enter Screen Name. > ")
	tweets = oauth_api.user_timeline(screen_name=screen_name, count=20)
	texts = [tweet.text for tweet in tweets]
	sents = tokenize_list(texts)
	pn_rates, pn_rates_with_sents = pn_rates_and_sents(sents)
	print_scores(pn_rates)
	for pn_rate_with_sent in pn_rates_with_sents:
		print "%s\t%s\n" % (pn_rate_with_sent[0], pn_rate_with_sent[1])

入門 自然言語処理

入門 自然言語処理

後輩「ツイートをGoogleカレンダーに登録して、みんなの予定管理ができるようにできないかなぁ。」

寝る前にTwitter見るかなぁ、とTLを眺めてたら某後輩が「ツイートをGoogleカレンダーに登録して、みんなの予定管理ができるようにできないかなぁ。」などといったことを呟いていた。
ついこないだ初めて会ったような気がしていて、そのときはまだ1年生の終わりだか2年生の始まりだったのに、もう4年生になっていて、サークルの部室の管理とか、そういうのやってるのかな、などと、後輩の年齢や学年は本当に分からない。
後輩たちは後輩というクラスのメンバーであって、そのクラスには下位クラスは特にないのです。
といった経緯で、0.5時間弱を掛けて「なんとなく今度国立行った時に遊んでもらおー」とか考えながら、なんとなくコードを書いた。特に面白いことはしてません。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tweepy
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import getopt, sys, string, time, atom

# Googleカレンダーにログイン
calendar_service = gdata.calendar.service.CalendarService()
calendar_service.email = 'あなたのGmailアドレス'
calendar_service.password = 'あなたのパスワード'
calendar_service.source = 'Twitter2Calendar'
calendar_service.ProgrammaticLogin()

# *******は、hogehoge@gmail.comの場合、hogehoge%40gmail.com(初期設定)
feedURI = 'http://www.google.com/calendar/feeds/********/private/full'

# dev.twitter.comからアプリケーションを登録して取得
consumer_token = ""
consumer_secret = ""
access_token = ""
access_token_secret = ""

# TwitterにOAuth認証
auth = tweepy.OAuthHandler(consumer_token, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
oauth_api = tweepy.API(auth)

# Googleカレンダーにイベントを作成する
def creatEvent(title, start, end):
    event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text = title)
    event.when.append(gdata.calendar.When(start_time = start, end_time = end))
    new_event = calendar_service.InsertEvent(event, feedURI)
 
# Twitterのメンションを20件取得し、
# 「@hoge 09-18T15:00,09-18T18:00,カフェに行く」という形式のものを
# パースして、Googleカレンダーにイベントを作成する。
# (*正規表現使って精度を上げたいけど、眠いからまた今度やる。)
def parseTweetandCreateEvent():
    mentions = oauth_api.mentions()
    for mention in mentions:
        len_screen_name = len(mention.in_reply_to_screen_name) + 2
        calData = mention.text[len_screen_name:].split(",")
 
        if len(calData) == 3:
            try:
                start = u'2012-' + calData[0] + u':00.000+09:00'
                end = u'2012-' + calData[1] + u':00.000+09:00'
                title = calData[2]
                creatEvent(title, start, end)
            except:
                 pass

if __name__ == '__main__':
    parseTweetandCreateEvent()

※これをcronとかに設定して、自動実行させるためにはFLOWERSのカレーとハチティーが必要です。

あるツイートをRTしたひとがその次になにをつぶやいたか?

公式RTが普及してから、非公式RTでコメントする代わりに、公式RTした次のツイートでコメントするのをよく見かけるようになりました。
しかし、それがどのようなものかを発信者は知ることができません。

一回、最大101回のAPIリクエストを消費する頭の悪いコードですが、もっとスマートなやり方ないんかなぁ。
Webアプリ化したいけど、サーバ代払えなくて先日レンタルサーバが消えたばかりなので、ちょっと無理…orz
だれか、Webアプリにしてくれないかなぁ…。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import tweepy

consumer_token = ""
consumer_secret = ""
access_token = ""
access_token_secret = ""

auth = tweepy.OAuthHandler(consumer_token, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
oauth_api = tweepy.API(auth)

def rt_users_ids(tweet_id):
    users_ids = []
    try:
        retweeted_by_ids = oauth_api.retweeted_by_ids(id=tweet_id, count=100)
        users_ids += retweeted_by_ids
    except tweepy.error.TweepError:
        try:
            limit = oauth_api.rate_limit_status()
            print 'remaining hits is ' + str(limit['remaining_hits']) + ' of ' + str(limit['hourly_limit'])
        except tweepy.error.TweepError:
            print 'Error. Try again one more hour later.'

    return users_ids

status_id = raw_input("ツイートのステータスIDを入力してください > ")

rt_users_ids = rt_users_ids(int(status_id))

print str(len(rt_users_ids)) + u"件のRTしたユーザーのIDを取得しました。"
num_get_tweet = raw_input(str(len(rt_users_ids)) + "件のうち、なん件のツイートを取得しますか? > ")

for rt_user_id in rt_users_ids[:int(num_get_tweet)]:
    tweets = oauth_api.user_timeline(user_id=rt_user_id, count=200, include_rts=True)
    rts = [tweet for tweet in tweets if hasattr(tweet, 'retweeted_status')]

    rt_tweet_id = -1
    for rt in rts:
        if rt.retweeted_status.id == int(status_id):
            rt_tweet_id = rt.id

    status_ids = [tweet.id for tweet in tweets]

    if rt_tweet_id != -1:
        index = status_ids.index(rt_tweet_id) - 1

        while hasattr(tweets[index], "retweeted_status"):
            index -= 1
			
        if index < 0:
            print u"%s\t該当するツイートはありません。" % tweets[0].user.screen_name

        else:
            print u"%s\t%s\n" % (tweets[index].user.screen_name, tweets[index].text)
	
    else:
        print u"%s\t該当するツイートはありません。" % tweets[0].user.screen_name

cites:ベイトソン『精神の生態学』、『精神と自然』

精神の生態学

精神の生態学

「『ハート(情)には、リーゾン(理性)が感取しえない独自のリーゾン(理)がある。』フランス人、パスカルの言葉だ。」(G.ベイトソン

「交わされるメッセージに先行して関係があるのではない。メッセージが相互に組み上がり結びあっていく、そのコンビネーション・パターンを、言語的コードによって記述したものが、たとえば『愛』であるわけである。」(G.ベイトソン

「芸術とは、心の無意識を伝え合う術であるといえる。あるいは、そんな種類のコミュニケーションをもっと豊かに実践できるよう、心を鍛えてゆくための遊技だともいえる。」(G.ベイトソン

「(適応のための)試行錯誤には必ず錯誤が伴う。錯誤は、生存を脅かし、精神の安定をも脅かす。そのマイナスを最小限に食い止めるには、適応が常にヒエラルキー構造を持っていることが必要になる。」(G.ベイトソン)習慣、学習は試行錯誤の回数を減らす。「前提を分析にかけない習慣」

「注目すべきことは、習慣の前提とすることが、抽象的な事柄だという点だ。…習慣でうまく処理できるのは、一般的に、あるいは反復的に、真である命題に限られる…人間関係を記述し決定する(このような)命題の数々が習慣として心の着床し、それによって…種々の症候群が生まれる」(G.ベイトソン

外界に正対する一かたまりの主体性――《場》の変化によって変わらない安定したパターン――を《わたし》と呼ぶとすると、時の地殻の大変動のなかで、その《わたし》が希薄化し、消散しつつあるように思える。―佐藤良明『ラバーソウルの弾みかた』>>しっかしベイトソンは面白い。

ベイトソンは「ダブルバインド、1969」(イルカの実験)のなかで複数のコンテクストの衝突による主体の混乱をスキゾな苦痛に陥るものとする一方で、それを乗り越えた主体が創造性を獲得することを述べた。

[イルカショーでのオペラント条件付けの物語]「1)他の動物との重要な関係性を律する規則を誤解するような状況に追いやられた動物は、激しい苦痛感と不適応症状を呈する…2)そうした病変への落ち込みをすり抜けた、あるいはそれに耐え抜いた動物にあっては、創造性が促進される」(Gベイトソン

「複数のコンテクストを股にかける才能によって豊かな人生を送る人たちがいる一方で、複数のコンテクストの衝突による混乱から生きる力を失ってしまう人たちがいる。その両者に共通しているのは、世界を二重に受けとるという点だ。」(G.ベイトソン

「愛を装う母」エピソードに心痛む。ベイトソン精神分裂病の理論化に向けて」

時があまりにツラく流れるから、同じ事実が昔と今では違う意味を持ってしまう。近くにいたいのに遠ざけたい記憶。未だに答えが出せないのはこの種の記憶による苦しみ。どうしたらいいのだろう。「トランスコンテクスチュアルシンドローム」(ベイトソン

今日存在するものは記憶と呼ばれる、過去についてのメッセージであり、これらのメッセージはその時々で新しくフレームされ、変奏され続けていくのです。―G.Bateson「精神分裂症の集団力学」

ベイトソンは本能や本性などの概念に、『研究促進的概念』というカテゴリーを設けた。

「本論は、有機体の行動に見られる学習の現象を、論理学のフォーマットに合わせて分類しようというものである。動物のみならず機械も含めたコミュニケーションの世界に、階型理論のようなものが当てはまることを、本論は主張する」(G.Bateson[1971])

「学習」とはなんらかの変化を指し示す、ということから、「変化」をヒエラルキー構造で捉えたニュートン以来の物理学のアナロジーから切り込もうとするベイトソン

自我の強さとは自分がいま、どのコンテクストに置かれているか理解する能力だというベイトソンの定義は結構、現実をうまく捉えさせてくれる。

「科学は証明[prove]しない。探索[probe]するだけだ。」(ベイトソン

趣味とは、生活に句読点を打つことだ

「趣味とは、生活に句読点を打つことだ」と以前読んだ(下掲書)。
句読点のまったくない文章も、句読点だらけの文章もともに読みづらい。文章にはリズムがある。句読点だけでない。一文の長さ、接続詞の有無、など。

僕はタバコは趣味だと言って憚らない。
カフェで珈琲を待つ間、保湿器で60~70%の湿度を保った葉を取り出し、フィルタを載せたペーパーで巻く。ペーパーの端は切手の裏側のように糊になっていて、そこを舐めて円柱状に留める。この動作を女性がすると色香があって好きだ。珈琲は中南米産の豆を中心にミディアムからシティにローストしたブレンドが好きだ。やはり一杯一杯ペーパーでハンドドリップされた珈琲が一番美味しい。いつものカフェのいつもの席でいつもの珈琲を待ちながらいつものバージニア葉を巻いた煙草を片手にバッグから本とノート、筆記具などを取り出す。熱い珈琲は味がよくわからないので少し温度が下がってから口に付ける。珈琲を味わうときは、舌の色々な部位を転がすようにして舌上の各味の感受部位とその組み合わせの刺激を楽しむ。
一口目がなにより重要だ。
その日のハンドドリップの癖、豆の状態、そして、自分の健康状態を知ることができる。これらが揃って初めて美味しいと感じる。思わず「美味しい」と声が漏れる。カウンター越しに店員さんの笑顔が返ってくる。そして、ジジのZIPPOを点火し、バージニア葉を巻いた手巻たばこに火を灯す。煙を吸い、吐き出す。続いて、深呼吸。灰皿に灰を落とす。珈琲を一口含み、飲み込む。やがて煙草の先端を灰皿の上で転がし、汚く潰れないように火を消す。そして、本を手に取る。昨日まで読んだページを開き、新しく読み始める箇所の右上に日付と場所を書き入れる。ブルーブラックのペンからオレンジの蛍光ペンに持ち替えて、本に向かう。

・・・以上が、僕が一日の勉強を始めるまでの手順だ。もはや儀式と言っていいかもしれない。この一連の儀式の中で、モードを切り替える。スイッチをオンにする。そして一日が始まる。

問題はスイッチをオフにするときだ。今日はうまくスイッチがオフにできない。だからこの文章を書いている。家に帰り、シャワーを浴び、ストレッチをして、セレッシャルのブレンドハーブティー「スリーピー・タイム」を淹れる。この前後で煙草を吸うときもある。一本の煙草を巻き、吸うことは、生活の句読点を打つことだからだ。

セレッシャル スリーピータイム 20p

セレッシャル スリーピータイム 20p

スイッチをオフにできないとそもそも寝ることができない。オフにする儀式が必要かもしれない。そうでないと、たとえ眠れたとしても夢のなかでも現実の様々な刻限に追われてしまう。

今日は気の置けない友人たちとひたすら喋った。昔の話、中身の無い話、取り留めもない話・・・。会話自体に意味はない。これらのコミュニケーションにはスイッチをオフにするという意味以外必要とされていない。

Sennheiser カナル型ヘッドフォン CX 400-II BLACK

Sennheiser カナル型ヘッドフォン CX 400-II BLACK

いま、ゼンハイザーのCX400で音楽を聞いている。10~15年以上聴いている音楽だ。音楽は意志の直接の表現として、僕の代わりに泣き、怒り、笑い、そして、焦慮を和らげてくれる。「焦慮は罪である」と以前読んだ本に書かれていた。その通りだと思った。

生活のリズムには文章の句読点のようなそれを美しくせしめる儀式が必要だ。それを忘れ生きるとき、焦慮の影が背を焼き始める。

「えたいの知れない不吉な塊が私の心を始終 圧 ( おさ ) えつけていた。 焦躁 ( しょうそう ) と言おうか、嫌悪と言おうか――酒を飲んだあとに 宿酔 ( ふつかよい ) があるように、酒を毎日飲んでいると宿酔に相当した時期がやって来る。」(梶井基次郎檸檬』)

檸檬 (新潮文庫)

檸檬 (新潮文庫)

「R二乗値なんて信仰にすぎない!」について(F統計量のお話)

「R二乗値なんて神話・信仰の類ですよ!」と先生が仰るものだから、われわれは啓蒙されなければならない、と思い、ちょっと覚書を残しておこうと思います。
本当は、重回帰モデルの診断の話まで書きたかったのですが、それはまた後日…。

(修正済み)R二乗値だけを見て「こんな小さいR二乗値じゃ、駄目だよ」と言う人はたくさんいるらしい。
しかし、そうではない、R二乗値がいくつであれば回帰モデルは妥当か?を、F統計量から判断する、という話です。

これは、R二乗値をF統計量に変換し、それが有意か調べるという方法をとります。
使用するソフトはRです。

F統計量は定数項以外の係数が全て0という帰無仮説のもとで分子の自由度がn-1、分母の自由度がn-KのF分布に従う。
nがKに対して大きいとき、F分布の期待値はほぼ1。

今回、この重回帰モデルを例にみてみます。

修正済みR二乗値が0.335です。なんか小さい気がする…大丈夫かな…という不安がよぎります。
まだ啓蒙されてないぼくには、この値は心を不安にさせます。

サンプル数と説明変数の数(定数項含む)を指定

> n=275
> K=4

まずは、このn, KのもとでのF分布を描画する。

> x=seq(0, 10, by=0.01)
> y=df(x,K-1,n-K)
> plot(x,y,type="l")

次に、95%点を計算します。

> qf(0.95,K-1,n-K)
[1] 2.637913

そしてそれを描画。

> abline(v=F95, col="red")

帰無仮説のもとではこの分布にF統計量が従うわけです。
ということは、この赤線の右側にF統計量があれば「やった!」となる。

それで、その「やった!」となるF統計量に対応するR二乗値はいくつか、計算します。

> F95=qf(0.95,3,271)
> F95/(F95+(n-K)/(K-1))
[1] 0.02837343
>

なんとまぁ、R二乗値が0.028より大きければ回帰モデルには意味がある!ということになりました。わぁ♪

というわけでこれで、「修正済みR二乗値が0.335?そんなモデルじゃあダメだ」などとドヤ顔で言われても、
「あなたのその顔をエンライトメントしてやんよ」と切り返すことができるようになりました◎

mecab-pythonをMac OSX 10.7 Lion、Python2.7にインストールする

入門 自然言語処理

入門 自然言語処理

苦節一月半、mecab-pythonをMac OSX 10.7 Lion, Python2.7にインストールできました。

時系列で載せます。

まず、Homebrewでmecabと辞書をインストールした(後にアンインストールした)。

以下、mecab-python-0.991との戦いの記録

mecab-python-0.991 r_onodr$ ARCHFLAGS='-arch i386 -arch x86_64' python setup.py build

lipo: can't figure out the architecture type of: /var/folders/mq/pxbw7qg97994xjhnhf444dr00000gn/T//ccAJndUU.out
error: command 'gcc-4.2' failed with exit status 1

エラー。なんか、gcc-4.2ってヤツが真面目に働く気がないようだ。

代わりを探す。

mecab-python-0.991 r_onodr$ which gcc
/usr/bin/gcc

gccさんにがんばってもらう。

mecab-python-0.991 r_onodr$ CC=/usr/bin/gcc python setup.py build
...
ld: warning: ignoring file /usr/local/Cellar/mecab/0.99/lib/libmecab.dylib, file was built for unsupported file format which is not the architecture being linked (i386)

「お!」と思ったけどまたエラー。

i386?32bit??」って思って、色々試す(要らなかったかも…)。

mecab-python-0.991 r_onodr$ CC=/usr/bin/gcc ARCHFLAGS='-arch i386' python setup.py build
mecab-python-0.991 r_onodr$ CC=/usr/bin/gcc CFLAGS='-m32' python setup.py build
...
ld: warning: ignoring file /usr/local/Cellar/mecab/0.99/lib/libmecab.dylib, file was built for unsupported file format which is not the architecture being linked (i386)

結局同じエラー。

どうやら、Homebrewでインストールしたmecab本体が32bitなのが悪いっぽい。
というわけで、アンインストール。

~ r_onodr$ brew uninstall mecab
~ r_onodr$ brew uninstall mecab-ipadic

そして、mecabと辞書をダウンロードしてきて64bitでインストール!

mecab-0.993 r_onodr$ CC="gcc -m64" CXX="g++ -m64" ./configure --with-charset=utf8
mecab-0.993 r_onodr$ make
mecab-0.993 r_onodr$ sudo make install

mecab-ipadic-2.7.0-20070801 r_onodr$ CC="gcc -m64" CXX="g++ -m64" ./configure --with-charset=utf8
mecab-ipadic-2.7.0-20070801 r_onodr$ make
mecab-ipadic-2.7.0-20070801 r_onodr$ make install

mecab-python-0.991 r_onodr$ python setup.py build
mecab-python-0.991 r_onodr$ python setup.py install

通った!!

テスト

~ r_onodr$ python
Python 2.7.2 (v2.7.2:8527427914a2, Jun 11 2011, 15:22:34) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import MeCab
>>> m = MeCab.Tagger("-Ochasen")
>>> str = "八頭身モナーはキモい"
>>> dst = m.parse(str)
>>> n = m.parseToNode(str)
>>> print dst
八頭身	ハットウシン	八頭身	名詞-一般		
モナー	モナー	モナー	名詞-一般		
は	ハ	は	助詞-係助詞		
キモ	キモ	キモ	名詞-固有名詞-一般		
い	イ	い	名詞-一般		
EOS

>>> while n:
...     print n.surface + " " + n.feature.split(",")[0]
...     n = n.next
... 
 BOS/EOS
八頭身 名詞
モナー 名詞
は 助詞
キモ 名詞
い 名詞
 BOS/EOS
>>> 

わーい♪


【参考になったページ】
MeCab のインストール | 自宅サーバFedora http://honana.com/mysql/tritonn/mecab.html
OS X Lion, make/configure issue, trying to compile 64-bit libFLAC/libFLAC++ - Ars Technica OpenForum http://arstechnica.com/civis/viewtopic.php?f=20&t=1164408
Rob Hess氏によるSIFTの実装をOSX SnowLeopardにインストール. | GO HOME http://kzm42.blog24.fc2.com/blog-entry-31.html
éternuement: Snow Leopardで32bitビルドの仕方 http://eternuement.blogspot.com/2011/02/snow-leopard32bit.html
Work-around for Mac OS X python package install error ― "lipo: can’t figure out the architecture type" | post past :: james murty http://www.jamesmurty.com/2011/01/29/work-around-osx-lipo-figure-out-architecture-type/
python - Why is GCC ignoring ARCHFLAGS in Snow Leopard? - Stack Overflow http://stackoverflow.com/questions/6988528/why-is-gcc-ignoring-archflags-in-snow-leopard

入門 ソーシャルデータ ―データマイニング、分析、可視化のテクニック

入門 ソーシャルデータ ―データマイニング、分析、可視化のテクニック