schedule2018-07-17

自然言語処理100本ノックのn-gramをPythonで実装

はじめに

言語処理100本ノック 2015

Pythonを勉強するため、東京工業大学の岡崎教授が出題されている言語処理100本ノック 2015を解いていきます。

より深く理解するため、別解や利用したライブラリの解説もまとめていきます。

環境

Python3.6

OS : mac

問題

05. n-gram
与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.

n-gramとは

検索対象を単語単位ではなく文字単位で分解し、後続の N-1 文字を含めた状態で出現頻度を求める方法。 Nの値が1なら「ユニグラム(英: uni-gram)」、2なら「バイグラム(英: bi-gram)」、3なら「トライグラム(英: tri-gram)」と呼ばれる。
WiKiペディアより

wikiの記述は、問題文でいうところの、文字bi-gramに当たります。
後続の1文字を含めるため、文字列"Hello World"とあったら、['He', 'el', 'll', 'lo', 'o ', ' W', 'Wo', 'or', 'rl', 'ld']となります。

単語bi-gramは、単語単位で分解し、後続のN-1単語を含めた状態です。Nは2となります。

"I am your father"を単語bi-gramで分解すると、[['I', 'am'], ['am', 'your'], ['your', 'father']]となります。

疑問なのが、句読点などの処理や先頭と末端の処理です。 こちらは、調査しておきます。

今回はn-gramを以下のように定義して解答しました。

  • 後続のN-1文字(単語)を含める
  • 句読点は考慮しない
  • 終端はN文字(単語)確保できるところまで。

また、bi-gramnだけでなくnに対応します。

解答:単語n-gram

sentence = "I am an NLPer"

def word_n_gram(sentence, N):
    """
    単語のn-gramを返す。
    """
    words = sentence.split()
    result = []
    for it, c in enumerate(words):
        if it + N > len(words):
            return result
        result.append(words[it: it+N])

出力

# bi-gram
print(word_n_gram(sentence, N=2))
# => [['I', 'am'], ['am', 'an'], ['an', 'NLPer']]

# tri-gram
print(word_n_gram(sentence, N=3))
# => [['I', 'am', 'an'], ['am', 'an', 'NLPer']]

解答:文字n-gram

import re

sentence = "I am an NLPer"

def char_n_gram(sentence, N):
    """
    文字のn-gramを返す。
    """
    result = []
    for it in range(len(sentence)):
        if it + N > len(sentence):
            return result
        result.append(sentence[it: it+N])

出力

# bi-gram
print(char_n_gram(sentence, N=2))
# => ['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']

# tri-gram
print(char_n_gram(sentence, N=3))
# => ['I a', ' am', 'am ', 'm a', ' an', 'an ', 'n N', ' NL', 'NLP', 'LPe', 'Per']

Ngram

Pythonライブラリにpython-ngramがあるそうです。

使い方はPythonでN-Gram

終わりに

いつも解いた後、@segavvyさんの素人の言語処理100本ノック:まとめを見て答えあわせや別解の確認をしています。

そこで気づいたのですが、char_n_gram()に渡す形式を変えるだけで、単語n-gramと文字n-gram両方に対応していました。

sentence = "I am an NLPer"

# 文字n-gram
print(char_n_gram(sentence, N=3))
# => ['I a', ' am', 'am ', 'm a', ' an', 'an ', 'n N', ' NL', 'NLP', 'LPe', 'Per']

# 単語n-gram
print(char_n_gram(sentence.split(), N=3))
# => [['I', 'am', 'an'], ['am', 'an', 'NLPer']]

単語のリストを渡せば単語n-gramもいけるんですね。

@segavvyさんありがとうございます。

続いての記事

Python3で言語処理100本ノックまとめ

前の問題:04. 元素記号

次の問題:06. 集合