CLOVER🍀

That was when it all began.

Clojure/lucene-kuromojiでテキストマイニング入門 〜形態素解析からワードカウントまで〜

ClojureとKuromojiを使った、面白そうなエントリがあったので

Clojure/kuromojiでテキストマイニング入門 〜形態素解析からワードカウントまで〜
http://antibayesian.hateblo.jp/entry/2013/09/10/231334

Luceneに入っているKuromojiを使って書き直してみました。

プロジェクトの作成。

$ lein new app lucene-kuromoji

project.clj

(defproject lucene-kuromoji "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [incanter "1.5.1"]
                 [org.apache.lucene/lucene-analyzers-kuromoji "4.4.0"]]
  :main lucene-kuromoji.core
  :profiles {:uberjar {:aot :all}})

元のエントリとは異なり、LuceneMaven Centralにあるのでrepositoriesの指定は不要です。

ソース。
src/lucene_kuromoji/core.clj

(ns lucene-kuromoji.core
  (:gen-class)
  (:import (org.apache.lucene.analysis Analyzer TokenStream)
           (org.apache.lucene.analysis.ja JapaneseAnalyzer)
           (org.apache.lucene.analysis.ja.tokenattributes BaseFormAttribute InflectionAttribute PartOfSpeechAttribute ReadingAttribute)
           (org.apache.lucene.analysis.tokenattributes CharTermAttribute)
           (org.apache.lucene.util Version))
  (:use (incanter core stats charts io))
  (:require [clojure.string :as str]))

(def version (Version/LUCENE_44))

(defn morphological-analysis-sentence [^String sentence & predicates]
  (let [^Analyzer analyzer (JapaneseAnalyzer. version)]
    (with-open [^TokenStream token-stream (.tokenStream analyzer "" sentence)]
      (.reset token-stream)
      (let [^CharTermAttribute char-term (.addAttribute token-stream CharTermAttribute)
            ^BaseFormAttribute base-form (.addAttribute token-stream BaseFormAttribute)
            ^InflectionAttribute inflection (.addAttribute token-stream InflectionAttribute)
            ^PartOfSpeechAttribute part-of-speech (.addAttribute token-stream PartOfSpeechAttribute)
            ^ReadingAttribute reading (.addAttribute token-stream ReadingAttribute)]
        (loop [results []]
          (if (.incrementToken token-stream)
            (let [attrs [(.toString char-term)
                         (.getReading reading)
                         (.getPartOfSpeech part-of-speech)
                         (.getBaseForm base-form)
                         (.getInflectionType inflection)
                         (.getInflectionForm inflection)]]
              (if (or (empty? predicates)
                      (reduce (fn [b p] (and b (p attrs)))
                              true
                              predicates))
                (recur (conj results attrs))
                (recur results)))
            (do (.end token-stream)
                results)))))))

(defn word-count [words]
 (reduce (fn [words word] (assoc words word (inc (get words word 0))))
  {}
  words))

(defn wc-result [words]
  (reverse (sort-by second (word-count words))))
(defn top10 [words]
  (take 10 (wc-result words)))
(defn top100 [words]
  (take 100 (wc-result words)))

(defn -main
  [& args]
  (println "===== Simple Pattern =====")
  (doseq [t (morphological-analysis-sentence "黒い大きな瞳の男の娘")]
    (println t))

  (println "===== Filter Pattern =====")
  (doseq [t (morphological-analysis-sentence
             "僕はウナギだし象は鼻が長い"
             #(not (nil? (re-find #"名詞" (nth % 2)))))]
    (println t))

  (println "===== 坊ちゃん =====")
  (let [tokens (morphological-analysis-sentence (slurp "bocchan.txt")
                                                #(not (nil? (re-find #"名詞" (nth % 2)))))
        words (flatten (map #(first %) tokens))]
    (view (bar-chart (keys (top10 words)) (vals (top10 words))))
    (save (bar-chart (keys (top10 words)) (vals (top10 words))) "natume.png" :width 600)
    (save (bar-chart (keys (top100 words)) (vals (top100 words))) "natume_zip.png" :width 600)))

Lucene Analyzers KuromojiのJapaneseAnalyzerを使うと、フィルタがかかっているので助詞とかがストップワードとして落とされます。

あと、元のソースをもうちょっとコレクションと関数呼び出しベースになるように変えました…が、Clojure力不足を思い知った感じですね…。

最終的に入力となる、坊ちゃんのデータはここから引っ張ってきました。

坊っちゃん
http://www.aozora.gr.jp/cards/000148/files/752_14964.html

一部と言わず、全部(笑)。

実行結果は、こちら。

まずはコンソールに出力する方。

===== Simple Pattern =====
[黒い クロイ 形容詞-自立 nil 形容詞・アウオ段 基本形]
[大きな オオキナ 連体詞 nil nil nil]
[瞳 ヒトミ 名詞-一般 nil nil nil]
[男 オトコ 名詞-一般 nil nil nil]
[娘 ムスメ 名詞-一般 nil nil nil]
===== Filter Pattern =====
[僕 ボク 名詞-代名詞-一般 nil nil nil]
[ウナギ ウナギ 名詞-一般 nil nil nil]
[象 ゾウ 名詞-一般 nil nil nil]
[鼻 ハナ 名詞-一般 nil nil nil]

後者は、名詞だけひっかけてるパターンですね。

出力された画像。

元記事と、びみょーに結果が違う…使った文書の量とか範囲が違うのかな…?