ホリケン's diary

趣味はでぃーぷらーにんぐ

janome+LDAでトピックモデル

動機

  • ニュース記事等のトピック推定については腐る程で出回ってるからちょっと違った切り口からトピックモデルを実践したかった

  • "janome"というめちゃくちゃ日本語前処理の便利なツールを見つけたから使ってみたかった

コーパス

名大会話コーパスを用いた

前処理

  • 入力文を分かち書きにする
    janome.tokenizer.Tokenizer()を用いる。引数でmmap=TrueとすることでNEologd辞書となる。

  • ユニコードの正規化
    "janome.charfilter.UnicodeNormalizeCharFilter"

  • 文字種の統一(数字→0など)、記号の排除
    NumericReplaceFilter() ← 漢数字を0にする
    RegexReplaceCharFilter('\d+', '0') ← 数字を0にする
    RegexReplaceCharFilter(u'>', '') ← 「>」を排除
    ...

  • 英文字を全て小文字化
    LowerCaseFilter()

  • 名詞のみを抽出する
    POSKeepFilter(['名詞'])

  • 漢数字を0にする
    janomeの組み込み関数にはないので自分で定義する必要がある

class NumericReplaceFilter(TokenFilter):

    def apply(self, tokens):
        for token in tokens:
            parts = token.part_of_speech.split(',')
            if (parts[0] == '名詞' and parts[1] == '数'):
                token.surface = '0'
                token.base_form = '0'
                token.reading = 'ゼロ'
                token.phonetic = 'ゼロ'
            yield token
  • 1文字のものを排除する
    OneCharacterReplaceFilter()で読み出すが、これもjanomeの組み込みにはなかったので定義する必要があった。
class OneCharacterReplaceFilter(TokenFilter):
    def apply(self, tokens):
        for token in tokens:
            if len(token)==1:
                continue
            yield token

上記の処理関数を統合してjanome.analyzerに渡して前処理機は完成だ。あと個人的にストップワードをいくつか省きたかったからそれを省くコードも下には含まれている。これだけのコードで基本的な前処理が行えてしかも追加のフィルターも簡単に定義できてしまうのがjanomeの魅力だと感じた。

def janome_meishi(sentence):

    # janomeモデルの定義
    token_filters = [
                     NumericReplaceFilter(), # 漢数字を0にする
                     POSKeepFilter(['名詞']),  # 名詞のみを抽出する
                     LowerCaseFilter(),  # 英字は小文字にする
                     ExtractAttributeFilter('base_form'),# 原型のみを取得する
                     OneCharacterReplaceFilter() # 1文字のものを除外する
                     ]
    char_filters = [UnicodeNormalizeCharFilter(), 
                    RegexReplaceCharFilter('\d+', '0'),
                    ...
                    RegexReplaceCharFilter(u'Inf', u''),
                    RegexReplaceCharFilter(u'>', u'')
                    ]
    tokenizer = Tokenizer(mmap=True) 

    analyzer = Analyzer(char_filters, tokenizer, token_filters)
    text = [w for w in analyzer.analyze(sentence)]

LDA

BoWの作成
出現回数が1回以下のものと0.4割以上の文書で現れる単語は省いたBoWを作成した

dictionary = gensim.corpora.Dictionary(text_data)
    dictionary.filter_extremes(no_below=1, no_above=0.4)

LDAの学習

corpus = [dictionary.doc2bow(words) for words in text_data]

lda = gensim.models.ldamodel.LdaModel(corpus=corpus,
                                          num_topics=2,
                                          id2word=dictionary,
                                          random_state=19)