ノート/ノート
訪問者数 1921 最終更新 2010-08-13 (金) 10:48:07
まずは、ノート/テキストマイニング/oreganoを参考にして。
漢字コードのごたごたが結構あるので(後で必要になったので)nkfをインストール。
yumでバイナリをインストール(nkf-2.0.8b)
yum list | grep nkf nkf.x86_64 1:2.0.8b-7.fc13
ソースは2009年時点で既に2.0.9らしいがまあいいだろう。
Chasenは元のホームページは古いので、http://chasen-legacy.sourceforge.jp/を参照すること。
まず、iconvとdartsをインストール
iconfはyumにないので、http://www.gnu.org/software/libiconv/からソースでインストール。2009年時点ではlibiconv-1.12だったが今回は1.13になっているので、/usr/local/srcにソースを持ってきてインストール。
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.13.tar.gz tar -zxvf libiconv-1.13.tar.gz cd libiconv-1.13 ./configure make make check make install
次に、dartsをインストール
dartsはhttp://chasen.org/~taku/software/darts/参照
wget http://chasen.org/~taku/software/darts/src/darts-0.32.tar.gz tar -zxvf darts-0.32.tar.gz cd darts-0.32 ./configure make make check make install
最後にchasenをインストール
chasenはhttp://sourceforge.jp/projects/chasen-legacy/参照
古い方http://chasen.naist.jp/hiki/ChaSen/はだめ。
wget http://jaist.dl.sourceforge.jp/chasen-legacy/32224/chasen-2.4.4.tar.gz tar -zxvf chasen-2.4.4.tar.gz cd chasen-2.4.4 ./configure make make check make install
辞書として、ipadic-2.7.0を入れてみよう。http://sourceforge.jp/projects/ipadic/参照。
候補としてはUniDicというのもあるらしい。
wget http://jaist.dl.sourceforge.jp/ipadic/24435/ipadic-2.7.0.tar.gz tar -zxvf ipadic-2.7.0.tar.gz cd ipadic-2.7.0 ./configure make make check make install
これだと、EUCベースなので、UTF-8に変更する必要がある。
参照 http://www.crimson-snow.net/hmsvr/centos/memo/chasen_utf8.html、http://blog.nomadscafe.jp/archives/000482.html
要するに、ipadicをmakeしたあと、インストール(make install)する前に、EUCコードをUTF-8に変換する。スクリプトファイルconv_utf-8.shをipadicを展開したディレクトリに
!/bin/sh for file in $* do if [ -f $file ]; then nkf --utf8 $file > tmpfile mv tmpfile $file fi done exit
のように作っておいて、次のように実行する。
chmod 755 conv_utf-8.sh <= スクリプトファイルに実行権限付加 ./conv_utf-8.sh *.dic *.cha
このようにしてすべてのdic, chaファイルをUTF8へ変換後、辞書を(再?)生成する。
`chasen-config --mkchadic`/makemat -i w `chasen-config --mkchadic`/makeda -i w chadic *.dic
こうしておいてから、インストールする。
make install
それから、起動時設定ファイルの /usr/local/etc/chasenrc もUTF8対応に変換する。
cd /usr/local/etc nkf --utf8 chasenrc > chasenrc.tmp mv chasenrc.tmp chasenrc
こうしておいて、テストすればよい。
echo "私は昨日学校へ行きました" | cat > tempfile chasen -i w tempfile
結果が
私 ワタシ 私 名詞-代名詞-一般 は ハ は 助詞-係助詞 昨日 キノウ 昨日 名詞-副詞可能 学校 ガッコウ 学校 名詞-一般 へ ヘ へ 助詞-格助詞-一般 行き イキ 行く 動詞-自立 五段・カ行促音便 連用形 まし マシ ます 助動詞 特殊・マス 連用形 た タ た 助動詞 特殊・タ 基本形 EOS
のようになればよい。
最後に、chasenをいつも-i wを付けるのが面倒なので、($HOME)/.bash_profileに
alias chasen='chasen -i w'
を定義してしまうと、
chasen tempfile
だけでよくなる。
chasenのラッパーを作ってみよう ver.0 を参照のこと。そこにある下記のpychaを作成。
#include "Python.h" #include <string.h> #include <chasen.h> char *mygettoken(char *inbuf, char *token, char delimiter) { // inbuf is the buffer of the input. // token is the pointer to the return value which is the pointer to the extracted token. // delimiter is the delimiter character (single character). // mygettoken returns the new pointer to "inbuf", or nil if no token. int i; char *p; if ((inbuf == NULL)||(strlen(inbuf)==0)) return(NULL); if ((p = strchr(inbuf, delimiter)) != NULL) { *p = '\0'; strncpy(token, inbuf, p-inbuf); token[p-inbuf] = '\0'; } else { // Failed to find the delimiter. Returns NULL to the token. if (strlen(inbuf)==0) { // Input "inbuf" was an empty string strcpy(token, NULL); return(NULL); } else { // Doesn't end with "delimiter" but input possibly ends with \n strcpy(token, inbuf); return(inbuf+strlen(token)); } } return(p+1); } PyObject* pycha(PyObject* self, PyObject* args) { char *option[] = {"pycha", "-j", "-i", "w", NULL}; char *inbuf, *outp; char line[256]; char *pline = line; char item[256]; char *pitem = item; int numlines, numitems; PyObject *pytuple, *pystring, *pyline, *pyresult; if (!PyArg_ParseTuple(args, "s", &inbuf)) return NULL; if (chasen_getopt_argv(option, stderr)==1) { // chasen initialization return NULL; } outp = chasen_sparse_tostr(inbuf); // execute chasen. Read from inbuf. Output buf pointer is outp. numlines = 0; if ((pyresult = PyTuple_New(65535)) == NULL) { fprintf(stderr, "PyTuple_New failed\n"); return NULL; } while ((outp = mygettoken(outp, pline, '\n')) != NULL) { numitems = 0; if ((pyline = PyTuple_New(32)) == NULL) { fprintf(stderr, "PyTuple_New failed\n"); return NULL; } while ((pline = mygettoken(pline, pitem, '\t')) != NULL) { pystring = Py_BuildValue("s", pitem); if (PyTuple_SetItem(pyline, numitems, pystring) != 0) { fprintf(stderr, "PyTuple_SetItem failed\n"); return NULL; } numitems++; } if (_PyTuple_Resize(&pyline, numitems) == -1) { fprintf(stderr, "PyTuple_Resize failed\n"); return NULL; } if (PyTuple_SetItem(pyresult, numlines, pyline) != 0) { fprintf(stderr, "PyTuple_SetItem failed\n"); return NULL; } numlines++; pline = line; } if (_PyTuple_Resize(&pyresult, numlines) == -1) { fprintf(stderr, "PyTuple_Resize failed\n"); return NULL; } return pyresult; } static PyMethodDef pychamethods[] = { {"pycha", pycha, METH_VARARGS}, {NULL}, }; void initpycha() { Py_InitModule("pycha", pychamethods); }
これのコンパイルの準備として次のことをする。
/usr/local/libを1行入れておく。ファイル名は何でも良いようだ。
あとは、下記のテキストをMakefileとして準備して、makeする。
C = gcc CFLAGS = all : pychamodule.so pycha.o : pycha.c $(CC) -fpic -L/usr/local/lib -I/usr/include/python -lchasen $(CFLAGS) -o pycha.o -c pycha.c pychamodule.so : pycha.o $(CC) -shared $(CFLAGS) pycha.o -lchasen -o pychamodule.so clean : rm *.o pychamodule.so
これでできたsoファイルは、面倒なので/usr/lib/python2.5/site-packages/の下に突っ込んでしまった。
あと、pythonの側で、デフォルトの漢字コードをutf-8に設定しておく。これは、ファイル /usr/lib64/python2.6/site.py (注: 今回64ビット環境にするために再インストールしているので、ディレクトリパスがlib64になっている)の中で
encoding = "ascii" # Default value set by _PyUnicode_Init()
を
encoding = "utf-8"
にしておくと便利。(ページ 日本語環境でのPythonhttp://www.python.jp/Zope/articles/japanese/Python4Japanese-2 のデフォルトエンコーディングのところを参照)
さてpythonで駆動テストのため、pythonプログラム例
#!/usr/bin/env python # encoding: utf-8 # -*- coding: utf-8 -*- ## ## To run with Kanji properly, the Python default encoding should be set to utf-8. ## This is done by including the file /usr/lib/python2.5/site-packages/sitecustomize.py ## with such lines as ## #!/usr/bin/env python ## import sys ## sys.setdefaultencoding('utf-8') ## To check this is properly set, start python and issue ## import sys ## sys.getdefaultencoding() ## which should reply with utf-8. ## See http://python.matrix.jp/tips/string/encoding.html ## import sys import codecs import pycha ### A magic for printing UTF-8 characters sys.stdout = codecs.getwriter('utf_8')(sys.stdout) s = u'私は昨日学校へ行きました' ##s = u'さいたさいたさくらがさいた' t = pycha.pycha(s) for x in t: for y in x: print y print ## 改行だけ印字=空行作成
を準備する。結果は chasenのラッパーを作ってみよう ver.0 と同様に、
私 ワタシ 私 名詞-代名詞-一般 は ハ は 助詞-係助詞 昨日 キノウ 昨日 名詞-副詞可能 学校 ガッコウ 学校 名詞-一般 へ ヘ へ 助詞-格助詞-一般 行き イキ 行く 動詞-自立 五段・カ行促音便 連用形 まし マシ ます 助動詞 特殊・マス 連用形 た タ た 助動詞 特殊・タ 基本形 EOS
が得られた。
NLTKのインストールは ノート/テキストマイニング/NLTK 参照。
基本的に、http://www.nltk.org/downloadのソースインストレーション(NLTK 2.0b9)による。yumでのパッケージは1:0.9.9-2と書いてあるので、古そうなのでやめる。Linux/Unixと書いてあるセクションに従う。
Python自体は(もし未だなら)yumでインストール可能。yumのパッケージではpython-2.6.4-27で、NLTKのページでは2.6.5だと言っているが、まあ許せる範囲だろう。
PyYAML: http://pyyaml.org/download/pyyaml/PyYAML-3.09.tar.gzからダウンロードして、その中のsetup.pyを実行。
wget http://pyyaml.org/download/pyyaml/PyYAML-3.09.tar.gz tar -xf PyYAML-3.09.tar.gz cd PyYAML-3.09 python setup.py install
NLTK: http://nltk.googlecode.com/files/nltk-2.0b9.tar.gzからダウンロードして
wget http://nltk.googlecode.com/files/nltk-2.0b9.tar.gz tar -xf nltk-2.0b9.tar.gz cd nltk-2.0b9 python setup.py install
NLTKで使うデータdataをダウンロードしておく必要がある。
python >>> import nltk >>> nltk.download() c <--- Config を選ぶ d <--- データを格納するディレクトリを選ぶ New Directory> /usr/local/share/nltk_data m <--- メインメニューへ戻る d all <--- 全てダウンロード q
テストをするために、
>>> x = nltk.data.load('tokenizers/punkt/english.pickle') >>> x.tokenize("This is a pen. He is a boy. That is a dog.") ['This is a pen.', 'He is a boy.', 'That is a dog.']
となって動けばダウンロードはOKである。
Stanford Parserについては、当該サイト http://www-nlp.stanford.edu/software/lex-parser.shtm を参照
インストールは、http://www-nlp.stanford.edu/software/stanford-parser-2010-07-09.tgz (2010年7月版=最新)をダウンロード・展開
wget http://www-nlp.stanford.edu/software/stanford-parser-2010-07-09.tgz tar -zxf stanford-parser-2010-07-09.tgz ln -s stanford-parser-2010-07-09 stanford-parser cd stanford-parser ./lexparser.csh testsent.txt
で動作確認できる。
次に、ParserDemo.javaファイルを改造したいので、javacコンパイル環境を整備する。基本的にはjdkが入っていればよい。
yum install java-openjdk-devel
クラスパスCLASSPATHで、今使いたいStanford Parserのjarライブラリファイルを含めておくと、javacコマンドで一々指定しないで済む。どうせ全員使う可能性があるので、広めに入れてしまうことにする。(反論あるだろうが)具体的には、 /etc/profile.d/java.shを新規作成する。
vi /etc/profile.d/java.sh で以下2行を書込み source /etc/java/java.conf export CLASSPATH=$CLASSPATH:/usr/local/stanford-parser/stanford-parser.jar:.
ポイントは、stanford-parser.jarとjarまで書くこと。
javacが正常にParserDemo.javaをコンパイルすることを確認する
cd /usr/local/stanford-parser/ javac ParserDemo.java
ParserDemo.javaを若干改造し、。ここからはノート/テキストマイニング/NLTK+StanfordParserの2009/12/25の「サンプル 第ー1版」の項にに従う
import java.io.*; import java.util.*; import edu.stanford.nlp.trees.*; import edu.stanford.nlp.parser.lexparser.LexicalizedParser; class StanfordFromNltk{ public static void main(String[] args) { LexicalizedParser lp = new LexicalizedParser("/usr/local/stanford-parser/englishPCFG.ser.gz"); lp.setOptionFlags(new String[]{"-maxLength", "80", "-retainTmpSubcategories"}); String sent = ""; try{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // String sent = "This is an easy sentense."; sent = br.readLine(); br.close(); } catch(IOException e){ System.out.println("Input Error"); } Tree parse = (Tree) lp.apply(sent); // parse.pennPrint(); // System.out.println(); // TreebankLanguagePack tlp = new PennTreebankLanguagePack(); // GrammaticalStructureFactory gsf = tlp.grammaticalStructureFactory(); // GrammaticalStructure gs = gsf.newGrammaticalStructure(parse); // Collection tdl = gs.typedDependenciesCollapsed(); // System.out.println(tdl); // System.out.println(); // TreePrint tp = new TreePrint("penn,typedDependenciesCollapsed"); tp.printTree(parse); } }
これを/usr/local/stanford-parserディレクトリ内に置き、
cd /usr/local/stanford-parser javac StanfordFromNltk.java
にてコンパイルしておく。(結果のclassファイルは/usr/local/stanford-parser内に置いておく)
NLTKからStanford Parserを使うための設定は、ノート/テキストマイニング/NLTK+StanfordParser-2に従う。
呼び出しのためのpythonライブラリを用意する。同ページのサンプル第1版(2010/01/11)にあるStanfordDependencies.pyを、/usr/local/python/modules/StanfordDependencies.pyとして置く。
mkdir /usr/local/python mkdir /usr/local/python/modules
StanfordDependencies.pyファイルの内容は
# coding: utf-8 import sys import nltk import sys from nltk import * import re from subprocess import * def stanforddependencies(content): # 入力contentは複数の文からなる文章 # 戻り値はStanford Dependencyの表[[関係名, 要素1, 要素2], [関係名, 要素1, 要素2], ... ] のリスト # contentを文に分割するためのNLTK内のPUNKT tokenizerを準備する mytokenizer = nltk.data.load('tokenizers/punkt/english.pickle') # Need to prepare this file. # PUNKT tokenizerによって文に分割し、リストsentencesに入れる sentences = mytokenizer.tokenize(content) # contentに含まれる文の数を数えるカウンターを作っておく num_sentences = 0 # Treeの最終結果を溜めるための空のリストを用意する。リストの要素はトリーである。 trees = [] # Dependenciesの最終結果を溜めるための空のリストを用意する。リストの要素はDependenciesリストである(リストのリスト) dependencies = [] wordlists = [] relations = [] # あとでWordNetベースのStemmer(基本形抽出)であるWordNetLemmatizerを使うのでループ外でオブジェクト生成 wnl = stem.WordNetLemmatizer() # あとでre (正規表現処理)を使うので、ループより外でmref, mref2を設定 mref = re.compile(r"((\w|[-])+)\(((\w|[-])+), ((\w|[-])+)\).*", re.S) # S = DOALL mref2 = re.compile(r"((\S)+)-(\d{1,5})", re.S) # S = DOALL # Java Program "StanfordFromNltk.class" をNLTK内から起動するための初期化 nltk.internals.config_java() # sentencesリストの要素ごとに以下を繰返す for sentence in sentences: p = nltk.internals.java(['StanfordFromNltk', '-mx512m', '-Xms512m', '-cp'], '/usr/local/stanford-parser:/usr/local/stanford-parser/stanford-parser.jar', stdin=PIPE, stdout=PIPE, blocking=False) q = p.communicate(input=sentence) s = q[0] # Stanford Parserの出力sを、トリー部分とdependency部分に分割する ## 空白行が2つの部分の切れ目になっているので、それを目当てに2つにsplitする。 t = s.split('\n\n', 2) # 前半部分t[0]がトリーの印刷形になっているので、それを使う tree = nltk.bracket_parse(t[0]) trees.append(tree) wordlist = [] # トリー部分についてすべての単語のリストを作っておき、後で使う for u in tree.subtrees(): # subtreesメソッドはgeneratorなので要注意 if (isinstance(u, list) and not isinstance(u[0], list)): # 自身がlistで[0]要素がlistでないノードを探す wordlist.append((u.node, u[0])) # ('NN', 'aspirin')のようなノードのリストを作る wordlists.append(wordlist) # 文ごとのリストwordlistを、全体のリストwordlistsにアペンドする # 後半部分t[1]がdependencyの表になっているので、それを使う relation = [] for u in t[1].split('\n',2000): # 改行文字でデータを行ごとに分割した上で m = mref.search(u) # 正規表現で、abc_d(efg-h, ijk-l) を3つに分解 if m: # m.group(3)と(5)は"xxxxx-nn"の形なので、語幹だけを抽出し語尾変化等を基本形に戻す w = mref2.search(m.group(3)) # 正規表現で、head語をxxxxx-nnを2つに分解 if w: word = w.group(1) # group(1)が欲しいxxxxの部分 else: word = '' # このwordをWordNetLemmatizerを使って基本形に戻す for p in wordlist: if p[1] == word: if (p[0] == 'VBD') or (p[0] == 'VBN'): stem3 = wnl.lemmatize(word, 'v') else: stem3 = wnl.lemmatize(word) w = mref2.search(m.group(5)) # 正規表現で、tail語をxxxxx-nnを2つに分解 if w: word = w.group(1) # group(1)が欲しいxxxxの部分 else: word = '' # このwordをWordNetLemmatizerを使って基本形に戻す for p in wordlist: if p[1] == word: if (p[0] == 'VBD') or (p[0] == 'VBN') or (p[0] == 'JJ'): stem5 = wnl.lemmatize(word, 'v') else: stem5 = wnl.lemmatize(word) relation.append([m.group(1), stem3, m.group(3), stem5, m.group(5)]) # リストrelationは、[[関係名, 要素1, 要素2], [関係名, 要素1, 要素2], ... ] # のリストである relations.append(relation) # 戻り値は、(trees, relations, wordlists) である。それぞれは文ごとの出力をリストにしたもの return(trees, relations, wordlists)
この中で使うPUNKT tokenizerで、english.pickleファイルが必要なので、ダウンロードする。
詳細はNLTKドキュメントのtokenizerのページの、「6 Punkt Tokenizer]の項を参照。
<実際に使ってみる>
自分のホームディレクトリに、入力サンプルファイルcolorectal_canser1.txtと、pythonテストソースファイルmytest.pyを準備する。(ノート/テキストマイニング/NLTK+StanfordParser-2 にあるものと同じ)
#!/usr/bin/env python # encoding: utf-8 # -*- coding: utf-8 -*- # coding: utf-8 # This is mytest.py --- Stanford Parserを呼び出すpythonプログラムの例 import sys import nltk from nltk import * import re from subprocess import * mod_path = "/usr/local/python/modules" # 以下3行が呼び出しの設定のおまじない sys.path.append(mod_path) # このpath情報を理解させる from StanfordDependencies import stanforddependencies # これで関数stanforddependenciesをimport f = open('colorectal_canser1.txt') # このサンプルデータファイル中は複数の文からなる1つのabstract file_content = f.read() (trees, relations, wordlists) = stanforddependencies(file_content) # 3つの出力trees, relations, wordlistsがある ### ここからは処理結果を確認するサンプルコード ### まずは、それぞれの出力をプリントしてみる for u in trees: # treesは各文ごとのtreeのリスト print u print "===" for u in relations: # relationsは各文ごとのdependency relationのリスト print u print "===" for u in wordlists: # wordlistsは各文ごとの単語リスト[('NN','aspirin'), ('VBN','work'), ...]のリスト print u print "===" ### dependencyリストを、適宜プリントしてみる。 # 文ごとに分かれているrelations(dependencyペアのリストのリスト)を、全文通しのリスト(ペアのリスト)にする allrelation = [] for u in relations: for v in u: allrelation.append(v) # allrelationをソートしてみる キーはv[0], v[1], v[2]...の順 allrelation.sort() for v in allrelation: # 今後、allrelationリストの内容を使ってよい if v[0] != 'det': print ' ' + v[0] + ' / ' + v[1] + ' / ' + v[3] + ' *** ' + v[2] + ' ' + v[4] # v[0]は関係の名前、v[1]とv[3]は単語を標準形に直したもの、v[2]とv[4]はもとの語のまま ### print ' ' + v[0] + ' / ' + v[1] + ' / ' + v[3] # こうすればv[2]やv[4]を書かなくなる else: print '>> ' + v[0] + ' / ' + v[1] + ' / ' + v[3] + ' *** ' + v[2] + ' ' + v[4] # detだけ除外したければここを無くしてしまえばよい
サンプルデータとして、colorectal_canser1.txt (ファイル名はmytest.py中で決め打ち)を用意した。
起動は
python mytest.py
とすればよい。