liblinearと自然言語処理を用いた文の分類
少し前の記事で、k-meansを用いた分類について述べた。今回は、自然言語処理とliblinearを用いた分類について述べてみたい。
liblinearは国立台湾大学で開発されたオープンソースのSVM(support vector machine)である。SVMは値教師あり学習を用いるパターン認識モデルの一つで、分類や回帰への適用が可能なアルゴリズムとなる。liblinearはそのSVMを用いてinstanceや特徴が100万桁のデータを高速に線形分離可能である特徴を持ち、様々なタスクに活用される。
今回はそのClojureのラッパーであるclj-liblinearを用いて、自然言語処理ツールであるkuromojiと組み合わせて、文の分類を行うタスクを行ってみた。
clj-liblinearを利用するには、project.cljファイルの:dependenciesに以下を加える。
[clj-liblinear "0.1.0"]
clj-liblinearに記載のサンプルコードは以下のようになる。
(use '[clj-liblinear.core :only [train predict]]
'[clojure.string :only [split lower-case]])
(def facetweets [{:class 0 :text "grr i am so angry at my iphone"}
{:class 0 :text "this new movie is terrible"}
{:class 0 :text "disappointed that my maximum attention span is 10 seconds"}
{:class 0 :text "damn the weather sucks"}
{:class 1 :text "sitting in the park in the sun is awesome"}
{:class 1 :text "eating a burrito life is super good"}
{:class 1 :text "i love weather like this"}
{:class 1 :text "great new album from my favorite band"}])
(let [bags-of-words (map #(-> % :text (split #" ") set) facetweets)
model (train bags-of-words (map :class facetweets))]
(map #(predict model (into #{} (split % #" ")))
["damn it all to hell!"
"i love everyone"
"my iphone is super awesome"
"the weather is terrible this sucks"]))
;; => (0 1 1 0)
facetweetに、文とclassを指定したデータを設定し、そのデータを使ってbag-of-wordsとmodelを生成して学習、その結果を使って入力した文を判定するシンプルなモジュールとなっている。サンプルでは入力が英語だが、日本語の文も英語と同様にトークンに分割した後、スペースを挿入して並べることで同様に利用できる。
そこで日本語の形態素解析ツールであるkuromojiを用いてトークンに分解した。kuromojiとして利用可能なモジュールは複数あるが、今回はオープンソースの検索エンジン Apache Lucene のテキスト解析部で用いられているJava で書かれたモジュールを、Clojureのjava互換性の特質を利用して用いた。
project.cljの:dependenciesには以下のようにjavaのmavenにあるモジュールを登録する
[org.apache.lucene/lucene-analyzers-kuromoji "5.0.0"]
これをClojureのコードの中で利用するには、以下に示すように:importで指定する。
(:import (java.io File FileInputStream InputStreamReader BufferedReader StringReader
BufferedWriter OutputStreamWriter FileOutputStream)
(org.apache.lucene.analysis.ja JapaneseAnalyzer JapaneseTokenizer)
(org.apache.lucene.analysis.ja.tokenattributes PartOfSpeechAttribute)
(org.apache.lucene.analysis.tokenattributes CharTermAttribute OffsetAttribute)
(org.apache.lucene.analysis.util CharArraySet))
この時、lucine.kuromojiモジュールだけではなく、java.ioモジュールもimportして文字列の操作が可能とする。後は、以下のコードで示されるようにトークンに分解してその間にスペースを挿入した文を生成する関数を設定する。
;;形態素解析関数
(defn morphological-analysis
[src]
(let [analyzer (JapaneseAnalyzer. nil
JapaneseTokenizer/DEFAULT_MODE
CharArraySet/EMPTY_SET
#{})
rdr (StringReader. src)]
(with-open [ts (.tokenStream analyzer "field" rdr)]
(let [^OffsetAttribute offsetAtt (.addAttribute ts OffsetAttribute)
^PartOfSpeechAttribute posAtt (.addAttribute ts PartOfSpeechAttribute)
_ (.reset ts)
surface #(subs src (.startOffset offsetAtt) (.endOffset offsetAtt))
pos #(.getPartOfSpeech posAtt)
tokens (->> #(if (.incrementToken ts)
[(surface) (pos)]
nil)
repeatedly
(take-while identity)
doall)
_ (.end ts)]
tokens))))
;;形態素解析の結果を(品詞,単語)の形に変形
(defn simple-morph [n] (map #(list (first (str/split (% 1) #"-")) (% 0)) (morphological-analysis n)))
;;名詞と動詞を抽出する関数 (名詞、動詞、助詞、形容詞、記号、助動詞)
(defn tokutei-token [n]
(keep
#(cond
(= "名詞" (first %)) (second %)
(= "動詞" (first %)) (second %)
:else nil)(simple-morph n)))
(defn tokutei-token2 [n] (str/join #" " (tokutei-token n)))
;;全ての単語のみの抽出
(defn token-all [n](map #(second %)(simple-morph n)))
(defn token-all2 [n] (str/join #" " (token-all n)))
ここで、単純にトークン全てを用いるだけではなく、品詞のフィルタリングをしたバージョンの関数も規定した。これに先程のliblinearの関数を加えれば処理ができるようになる。
;;教師データ
(def facetweets [{:class 0 :text (token-all2 "今日のタスクは何ですか?")}
{:class 0 :text (token-all2 "何の仕事がありますか?")}
{:class 0 :text (token-all2 "何をするべきですか?")}
{:class 0 :text (token-all2 "今日は何が残っていますか?")}
{:class 3 :text (token-all2 "おはようございます")}
{:class 3 :text (token-all2 "こんにちは")}
{:class 3 :text (token-all2 "今日の天気は何ですか?")}
{:class 3 :text (token-all2 "ご機嫌いかが?")}
{:class 4 :text (token-all2 "はい")}
{:class 4 :text (token-all2 "yes")}
{:class 4 :text (token-all2 "お願いします")}
{:class 4 :text (token-all2 "YES")}
{:class 4 :text (token-all2 "そうですね")}
])
;;分類判定関数
(defn categorize [input]
(let [bags-of-words (map #(-> % :text (str/split #" ") set) facetweets)
model (liblinear/train bags-of-words (map :class facetweets))]
(map #(liblinear/predict model (into #{} (str/split % #" ")))
[(token-all2 input)])))
;;(categorize "こんにちは") ;(1.0)
教師データの格納と/取り出しに前回に紹介したデータベースを活用すると、リアルタイムに修正する機械学習ツールができることになる。
コメント
[…] 前回に引き続き、clojureを用いた機械学習を述べたい。今回は教師なし学習であるk-meansについて。 […]
[…] Clojureでのliblinearと自然言語をを用いた文の分類 […]
[…] liblinearと自然言語処理を用いた文の分類 […]
[…] liblinearと自然言語処理を用いた文の分類 […]