聖刻の里

趣味や日常について自由に語る日記です。

【自然言語処理】N-gramの概要とpythonでの実装方法について

f:id:scarlet09Libra:20210208170742j:plain

 

どうもLibraです。

 

 今回は自然言語処理(Natural Language Processing: NLP)でよく使われるN-gramという技術について解説します。シンプルでわかりやすい手法であることから、文字列検索や辞書生成など幅広い技術に応用されており、ここでは単語N-gramと文字N-gram、それらの使用例とPythonによる実装方法についてみていきます。

 

【目次】

 

実行環境

OS: MacOS Catalina 10.15.7

Python: 3.7.2

ライブラリ: scikit-learn 0.24.1

辞書: IPAdic

 

N-gramとは

 一言で説明すると「ある文字列をN単語or文字で分割する手法」です。Nはいくつで分けても良いのですが、N = 1で分けた場合はユニグラム(uni-gram)、N = 2で分けた場合はバイグラム(bi-gram)、N = 3で分けた場合はトライグラム(tri-gram)とそれぞれ名前がついています。

 また、文字列を分割する際、単語ごとに分割する場合は単語N-gram、文字ごとに分割する場合は文字N-gramと呼びます。

 

N-gramの使用例

 今日は雨が降っています。を単語N-gramと文字N-gramで分割してみましょう。

 

単語N-gram 

ユニグラム(uni-gram)

 1単語ごとに分けるだけです。形態素解析した結果と同じになりますね。

今日 / は / 雨 / が / 降っ / て / い / ます / 。

 

バイグラム(bi-gram)

 2単語ごとに分けます。

今日は / は雨 / 雨が / が降っ / 降って / てい / います / ます。

 

トライグラム(tri-gram)

 3単語ごとに分けます。やってることは全部同じですね。

今日は雨 / は雨が / 雨が降っ / が降って / 降ってい / ています / います。

 

文字N-gram

ユニグラム(uni-gram)

 1文字ごとに分けます。

 今 / 日 / は / 雨 / が / 降 / っ / て / い / ま / す / 。

 

バイグラム(bi-gram)

 2文字ごとに分けます。

今日 / 日は / は雨 / 雨が / が降 / 降っ / って / てい / いま / ます / す。

 

トライグラム(tri-gram)

 3文字ごとに分けます。

今日は / 日は雨 / は雨が / 雨が降 / が降っ / 降って /ってい / ていま / います / ます。

 

N-gramの長所と短所について

長所
  • 辞書を必要としない

 文字N-gramの場合、辞書なしで単語の切り出しができます。

 

  • 語順情報を抽出できる

 例えば十分は「じゅっぷん」「じゅうぶん」の二通りの読み方がありますが、十分前に着くわ!君はもう十分がんばったなどの文字列をN-gramを用いて分割した場合、「十分前」「もう十分がんばった」などを語彙として抽出することで、「十分」が「じゅっぷん」「じゅうぶん」のどちらを意味しているのかを正確に把握できます。

 形態素解析やBag of Wordsでは語順情報が失われてしまいますが、N-gramを用いることで、文脈を考慮した処理が可能になります

 

短所
  • Nを増やすほど語彙が増える

 単語N-gramも文字N-gramもそうですが、基本的にNを増やすほど語彙が多くなり、メモリを圧迫したり計算負荷が高くなります。

    

  • 検索結果に誤りが生じやすい

 例えば京都という文字列を検索した場合、「東京都」がヒットしてしまう場合があります。 これは「東京都」を文字bi-gramで分割すると「東京」「京都」となってしまうためです。このようにN-gramでは意図しない検索結果(検索ノイズ)が発生することがあります。

 

実装方法

import MeCab

from sklearn.feature_extraction.text import CountVectorizer


# 分かち書きを行う関数
def wakachi(text):

mecab = MeCab.Tagger("-Owakati")

return mecab.parse(text).strip().split(" ")


# サンプルテキスト
sentence = ["今日は雨が降っています。"]


# 単語bi-gramで分割
vectrizer = CountVectorizer(tokenizer = wakachi, ngram_range = (2, 2))
vectrizer.fit(sentence)


print(vectrizer.vocabulary_)

# {'今日 は': 5, 'は 雨': 3, '雨 が': 7, 'が 降っ': 1, '降っ て': 6, 'て い': 2, 'い ます': 0, 'ます 。': 4}

 

まとめ  

 仕組みが非常にシンプルで有用性が高いですが、欠点も多いです。自然言語処理の分野ではこの考え方をベースに様々な検索サービスやテキスト解析ソフトなどが開発されています。気になった技術があればまた記事にするのでその時はどうぞよしなに。

 

f:id:scarlet09Libra:20210209201527p:plain