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}})
元のエントリとは異なり、LuceneはMaven 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]
後者は、名詞だけひっかけてるパターンですね。
元記事と、びみょーに結果が違う…使った文書の量とか範囲が違うのかな…?