ノート/ノート
訪問者数 1275      最終更新 2010-08-13 (金) 10:48:07

2010/08/11 OreganoのFedoraバージョン変更に伴う再設定(アプリインストール)

まずは、ノート/テキストマイニング/oreganoを参考にして。

nkf

漢字コードのごたごたが結構あるので(後で必要になったので)nkfをインストール。
yumでバイナリをインストール(nkf-2.0.8b)

yum list | grep nkf
nkf.x86_64                               1:2.0.8b-7.fc13

ソースは2009年時点で既に2.0.9らしいがまあいいだろう。

chasen

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.htmlhttp://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のラッパーを作る

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);
}

これのコンパイルの準備として次のことをする。

あとは、下記のテキストを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

が得られた。

2010/08/12 =続き= OreganoのFedoraバージョン変更に伴う再設定

Python NLTK (Natural Language ToolKit) のインストール

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とJava環境設定

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だけ除外したければここを無くしてしまえばよい

サンプルデータとして、filecolorectal_canser1.txt (ファイル名はmytest.py中で決め打ち)を用意した。

起動は

python mytest.py

とすればよい。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-08-13 (金) 10:48:07 (2683d)