隠れマルコフモデル 入門

2018/06/07
このエントリーをはてなブックマークに追加

株式会社カブクで、機械学習エンジニアとしてインターンシップをしている堀内貴文(大学4年)です。

このKabukuDevBlogのテーマは、「隠れマルコフモデル」です。
時系列パターンの認識に用いられることが多く、具体的な使用例としては次のようなものがあります。
– 音声認識
– 自然言語処理(品詞推定)
– バイオインフォマティクス(塩基配列の解析)
– 株価の変動予測

 近年ではRNN(Recurrent Neural Network)などの人工ニューラルネットワークの発展を受け、隠れマルコフモデルはこれらに置き代わりつつあります。けれども、出力結果の導出過程が明瞭化されているなど、古典的な手法としての利点もまだまだあります。

 この記事では、「隠れマルコフモデル」について初めて勉強をする人が、「隠れマルコフモデル」が何であるかのイメージをつかむことを目標に、基本的な事項を記述します。
 前半は基礎的な概念についての説明をし、後半はPythonコードを交えながら「品詞推定」を題材に隠れマルコフモデルを紹介します。



目次

  • 概要
  • 定義
  • 問題への適用
  • 自然言語の品詞推定
  • Pythonでの実装


概要

 隠れマルコフモデル(Hidden Markov Model, HMM)とは何か、を端的に表現すると「確率的な状態遷移と確率的な記号出力を備えたオートマトン」と言えます。主な使用目的は「観測された記号系列の背後に存在する状態の遷移系列を推測する」ことです。

 本項目では、この(難解な)意味を(わかりやすく)理解するために、例を交えながら解説していきます。


隠れ+マルコフモデル

「隠れマルコフモデル」という言葉を構成する「隠れ」と「マルコフモデル」について詳しく見ていきます。


マルコフモデル

マルコフモデルとは、マルコフ過程に従う確率モデルです。マルコフ過程(*1)とは、任意の時刻の状態の確率分布が、直前の状態のみに依存するような確率過程(時間とともに変化する確率変数)です。

(*1) 正確には「N階マルコフ過程」と言い、直前のN個の状態のみに依存するような確率過程を表します。N=1の場合を特に「単純マルコフ過程」と言いますが、これを単に「マルコフ過程」と言うことが多いです。

整理するために、単純マルコフ過程に従うマルコフモデルの例を図1.1に示します。



    図1.1 マルコフモデルの例(単純マルコフ過程)


 図1.1は、3つの状態「sunny」、「rainy」、「cloudy」の間を矢印に添えられた確率に応じて遷移するマルコフモデルを表しています。同モデルは単純マルコフ過程(N=1)に従うため、時刻t=0に状態がsunnyであれば、t=1での状態は、sunnyからの遷移確率のみにより決定します。このようなモデルが「マルコフモデル」です。


「隠れ」

隠れマルコフモデルにおける「隠れ」の表すところを理解するために、簡単な例を紹介します。

例え話:
どこか遠いところに住んでいる友達から、あなたは毎日、彼(or 彼女)の、その日の活動の報告を受けます。
さて、友達の住む地域の天気を推定してください。

ただし、この例え話には、次の注意書きがあります。

あなたは、
・友達が「散歩・買い物・掃除」にしか関心がないことを知っている。
・天気の種類は「晴れ・曇り・雨」しかないと知っている。
・友達の住む場所の天気の移り変わりの傾向を把握している。
・天気に応じた友達の活動の傾向を把握している。

この状況を隠れマルコフモデルで表現すると、次の図1.2のようになるとします(前提)。


                  図1.2 友達の例の隠れマルコフモデル


 丸で表された {sunny, rainy, cloudy} は、友達の住む場所の天気、矢印に添えられた数値は、天気の移り変わりの確率、楕円で表された {walk, shop, clean} は、友達の行う活動、楕円に添えられた数値は、各天気の時に友達が各活動を行う確率、を表します。
 この例題では、「図1.2のモデル」と「友達からの日々の活動の報告 (walk, walk, clean, shop, shop, shop, …)」が与えられたときに、「天気の移り変わりを推定する」ことが目的となります。この場合、「友達の活動」に関しては直接知ることができますが、「天気」に関しては直接知ることはできません。そのため、ここでは「天気」が「隠れている」ということになります。
 このように、隠れマルコフモデルには、{walk, shop, clean} のような直接観測できるものの他に、それらに影響を与えている {sunny, rainy, cloudy} のような「隠れた」ものが存在します。また、例え話では「あなた」からの視点で話をしていたため、観測された友達の活動は「与えられたもの」としていましたが、「友達」の視点で見ると、これらの活動は天気に影響を受けて「出力された(行動となった)」ということができます。つまり図1.2のモデルを解釈すると、「天気が日々 確率的に変遷する中で、友達はその日の天気に応じて確率的に行動を決定する」という意味になります。


マルコフモデル と 隠れマルコフモデル の関係

 ここまでで、隠れマルコフモデルの「隠れ」と「マルコフモデル」について見てきました。そこで再び、冒頭の隠れマルコフモデルについての説明:「確率的な状態遷移と確率的な記号出力を備えたオートマトン」について考えます。下の図1.3では、左に単純マルコフ過程(N=1)に従ったマルコフモデル、右に隠れマルコフモデルを示しています。二つを比べると、隠れマルコフモデルとは、マルコフモデルの各状態に「出力」が備わったものであることがわかります。また、この出力は確率的に生成されます。マルコフモデルの状態遷移が確率的に行われることを踏まえると、隠れマルコフモデルが「確率的な状態遷移確率的な記号出力を備えたオートマトン*」であることが理解できます。

(*オートマトンとは、状態遷移を表したモデルを総称して言います。)


           図1.3 「マルコフモデル」と「隠れマルコフモデル」の関係



定義

隠れマルコフモデルは、次の5項組で定義されます。定義中の説明では、図2.1を用いて先ほどの例との対応関係を示しました。(「初期状態」については、先ほどの例では説明を簡単にするために省略していました。例えるならば、友達の活動報告の初日がどの天気であるか、についての確率です。)

隠れマルコフモデル M = ( Q, Σ, A, B, π )

Q = { q_1,…, q_N } : 状態の有限集合
  e.g. {sunny, rainy, cloudy}

Σ = { o_1,…, o_M} : 出力記号の有限集合
  e.g. {walk, shop, clean}

A = { a_ij } : 状態遷移確率分布(図2.1の緑色の数値)
  e.g: a_ijは状態q_iから状態q_jへの遷移確率

B = { b_i(o_t) } : 記号出力確率分布(図2.1の赤い数値)
  e.g: b_i(o_t)は状態q_iで記号o_tを出力する確率

π = { π_i } : 初期状態確率分布(図2.1の青い数値)
  e.g: π_iは状態q_iが初期状態である確率


                  図2.1 隠れマルコフモデルの定義の説明



問題に適用するために

隠れマルコフモデルを実際に用いるとき、対象とする問題は次の1〜3に分類されます。
ここでは、Oは出力記号列、Mはモデル、P(O|M)はMからOが生成される確率を表しています。

  1. 評価問題(evaluation problem)
    OとMが与えられたときに、P(O|M)を求める問題

  2. 復号化問題(decoding problem)
    OとMが与えられたときに、Oを生成したMの最適な状態遷移系列を求める問題

  3. 推定問題(estimation problem)
    Oから、P(O|M)を最大にするようなMを求める問題


評価問題・復号化問題・推定問題を、それぞれ友達の天気の例を用いて図で表すと、図3.1のようになります。

 「評価問題」では、友達がとった一連の行動がどれほどの確率で起こり得たものなのかを導出します。「復号化問題」では、これまで「隠れ」の意味を詳しく見るために紹介してきたように、友達の活動報告から天気の遷移を推測します。「推定問題」では、今までは「既知の情報」として扱ってきたモデルMを、友達の活動報告のみから算出します。


                   図3.1 各問題の友達の天気の例との対応



 このように、隠れマルコフモデルを問題に適用する方法は主に3つありますが、本記事ではこれより「復号化問題」に注目し、自然言語の「品詞推定」を取り上げて説明します。以降、ところどころPythonコードとの対応を取りながら記述を進めます。ソースコードの全文は、本文末尾のリンク先からダウンロードできます。



品詞推定

 ここでは、英語の文章が与えられたときに適切な品詞を推定する「品詞推定」を取り上げます。
次の例は、文章 “Time flies like an arrow.” について、各単語の品詞を適切に推定した様子を示しています。

input: "Time flies like an arrow."

output: [('Time', 'NN'), ('flies', 'VBZ'), ('like', 'IN'), ('an', 'DT'), ('arrow', 'NN'), ('.', '.')]

// NN: Noun (名詞), VBZ: Verb (動詞), IN: Preposition (前置詞), DT: Determinator (限定詞), '.': Period (終止符)

 英語では、一つの単語が複数の品詞(つまり意味)を持つケースが多々あります。”Time flies like an arrow.” は、正しくは「光陰矢の如し」という意味ですが、品詞を取り誤ると「時蝿は矢を好む」とも解釈できてしまいます。文章の品詞を正しく推定するために、隠れマルコフモデル(HMM)を用います。


「復号化問題」と「品詞推定」の対応

先ほど「復号化問題」の例として「品詞推定」を取り上げると記述しましたが、その二つの対応を確認します。

復号化問題:
O と M が与えられたときに、 Oを生成したMの最適な状態遷移系列 を求める問題

品詞推定の場合、

“出力記号系列O”は、入力する英語の文章にあたります。
文章は入力しますが、モデルによりこれが生成された、つまり出力されたという捉え方をします。

“モデルM”は、データセットから作成できます。これについては次項で説明します。

“Oを生成したMの最適な状態遷移系列”は、正しい品詞の系列に当たります。
適切な品詞付けの方法については、次々項で説明します。


モデルMの導出

 HMMを用いて適切な品詞を推定するためには、当然ながら隠れマルコフモデルMを準備する必要があります。モデルを構築する方法のひとつに「POSタグセット」と呼ばれるデータセットを用いる方法があります。POSタグセットには、多数の文章データが含まれていて、各単語に適切な品詞が紐づけられています。

 次は、POSタグセット「Penn Treebank」のデータの一部分を示したものです。実際のデータセットにはこのような文章が数千件 含まれています。(’NNP’などの各記号が表す品詞については、こちらを参照してください。)

[[('Pierre', 'NNP'), ('Vinken', 'NNP'), (',', ','), ('61', 'CD'), ('years', 'NNS'), ('old', 'JJ'), (',', ','), ('will', 'MD'), ('join', 'VB'), ('the', 'DT'), ('board', 'NN'), ('as', 'IN'), ('a', 'DT'), ('nonexecutive', 'JJ'), ('director', 'NN'), ('Nov.', 'NNP'), ('29', 'CD'), ('.', '.')], ...]

このようなデータセットを元に、モデルの各要素は次のように求めることができます。


隠れマルコフモデル M = ( Q, Σ, A, B, π )

Q = { q_1,…, q_N } : 状態の有限集合 => 出現する全ての品詞

Σ = { o_1,…, o_M} : 出力記号の有限集合 => 出現する全ての単語

A = { a_ij } : 状態遷移確率分布 => 文章内の単語の並びで、二つの品詞が隣接して出現する割合

B = { b_i(o_t) } : 記号出力確率分布 => 着目する品詞に対し、ある単語が現れる割合

π = { π_i } : 初期状態確率分布 => 各品詞が文頭に現れる割合


モデルMの導出について、ここまでの内容をPythonコードで表すと次のようになります。

# POSタグセットのロード (状態の有限集合・出力記号の有限集合)
tagged_sents = nltk.corpus.treebank.tagged_sents()

# 各文章の中の、隣接する単語・品詞の組のリストを生成する; また、文頭に「<s>記号」を付加する
tagged_word_bigrams = list(make_tagged_word_bigrams(tagged_sents))

# 各品詞について、データセットに含まれる全ての単語について、その出現確率を計算する (記号出力の確率分布)
t_w = nltk.ConditionalFreqDist([(d[0][1], d[0][0]) for d in tagged_word_bigrams])

# データセットのタグ付けされた単語について、ある品詞とある品詞が隣接している確率を計算する (状態遷移確率分布)
# 文頭を表す「<s>記号」と隣接した品詞は、初期状態とみなすことができる (初期状態確率分布)
t_t = nltk.ConditionalFreqDist([(d[0][1], d[1][1]) for d in tagged_word_bigrams])

 品詞推定のためのHMMの概要を掴むために、単語を’time’, ‘flies’, ‘like’, ‘an’, ‘arrow’に限定した「小さいモデル*」を図4.1に示しました。円は品詞つまり状態を表し、矢印の上の数値は状態遷移確率、”[]”内の数値は記号出力確率、状態内の数値は初期状態確率を表します。
(*このモデルはPOSタグセットから生成したものではなく、イメージを掴むためのHMMの例です。)

            図4.1 “time flies like an arrow”の隠れマルコフモデル


適切な品詞付け

 ここまでで、「出力記号列O」と「モデルM」が揃ったので、次はこれらを用いて「適切な状態遷移系列(適切な品詞の並び)」を求めます。

 これを考えるに当たり、図4.2に示したような状態遷移系列グラフを用います。図4.2の例では、図4.1のモデルが記号列 “time flies like an arrow” を生成する際に取りうる状態の遷移を左から右に向かって表したものです。

               図4.2 “time flies like an arrow”の状態遷移系列グラフ


 図4.2のグラフから分かるように、”time flies like an arrow” を出力する状態遷移系列は複数存在します(文頭から文末に至る経路;この例では6通り)。それぞれの状態遷移系列のパターンが生成される確率は、「文頭」から始め、遷移の系列を辿りながら、状態遷移確率と出力確率の積を計算していくことで計算できます。

 例えば、誤った品詞付のパターンのひとつである「時蝿は矢を好む」が生成される確率は、次のように計算できます。

P(文頭 → time/名詞 → flies/名詞 → like/動詞 → an/冠詞 → arrow/名詞) 
= 0.6 * 0.6 * 0.3 * 0.1 * 0.4 * 0.7 * 0.2 * 1.0 * 0.7 * 0.3 
= 0.00012701

 また、正しい品詞付のパターンである「光陰矢の如し」の生成確率は、次のような計算になります。

P(文頭 → time/名詞 → flies/動詞 → like/前置詞 → an/冠詞 → arrow/名詞) 
= 0.6 * 0.6 * 0.4 * 0.2 * 0.2 * 1.0 * 0.3 * 1.0 * 0.7 * 0.3 
= 0.00036288

 このようにして、全6パターンの生成確率を計算し、そのうち最も高い生成確率を示したものが最適な品詞付であるということになります。

 今回のように、単語を ‘time’, ‘flies’, ‘like’, ‘an’, ‘arrow’ に限定した「小さいモデル」であれば、”time flies like an arrow” の遷移パターンが6通りであるため、一つずつ計算することもできます。しかし、単語や品詞の数が大きいモデルでは、このような計算では計算量が膨大になってしまいます。

 そこで、計算を効率化するためのアルゴルズムとして「ビタビ・アルゴリズム(Viterbi)」という計算法が考案されています。(Andrew Viterbi, 1967)


ビタビ・アルゴリズム

 ビタビ・アルゴリズムは、動的計画法を用いることにより、各状態遷移パターンの生成確率の計算の回数を小さく抑えています。動的計画法とは「対象となる問題を複数の部分問題に分割し、部分問題の計算結果を記録しながら解いていく手法(Wikipedia)」です。この考えに基づき、品詞推定におけるビタビ・アルゴリズムは、「各状態について、最も累積確率が高くなるような、一つ前の状態のみを記録しておけば良い」という着想で成り立っています。

 例えば、図4.3の状態Aに着目したとき、Aに遷移する状態としてBとCがあります。Bの単語出力確率と状態遷移確率の積が、Cのそれよりも大きいとき、記憶領域の状態Aに対応する箇所に「B」と記録します。このような記録を全ての状態について行うことで、最終的に最適な状態遷移系列が求められます。

                図4.3 ビタビ・アルゴリズムの着想


 ビタビ・アルゴリズムの使用例として、再び “time flies like an arrow” の状態遷移グラフを見てみます。
図4.4は、’time’ ~ ‘arrow’ の単語の各状態について、最も高い累積確率を与える一つ前の状態からの遷移の矢印を赤で示しています。計算の具体例をひとつ示します。

like(前置詞)」に注目したとき、これに向かって伸びる矢印は「flies(名詞)」と「flies(動詞)」の2通りです。これらの経路を辿った場合のそれぞれの累積確率を計算し、大きい値を示した方を記録します。ここで累積確率は、「一つ前の状態での最大の累積確率」、「注目する状態への状態遷移確率」、「注目する状態での単語出力確率」の三項の積で計算します。
「flies(名詞)」からの累積確率は「0.0108 x 0.2 x 1.0 = 0.00216」であるのに対し、「flies(動詞)」からの累積確率は「0.0288 x 0.2 x 1.0 = 0.00576」です。「flies(動詞)」からの値の方が大きいため、「like(前置詞)」には「flies(動詞)」が紐付けられます。


       図4.4 “time flies like an arrow” のビタビ・アルゴリズムによる解法


 最適な品詞の系列は、最後の単語’arrow’から、矢印を逆順に辿ることで求めることができます。
このように、ビタビ・アルゴリズムでは、各状態について「最も累積確率が高くなるような、一つ前の状態」を記録していくことで、最終的に、最も生成確率が高い状態遷移系列を導出することができます。

 Pythonでのビタビ・アルゴリズムの実装コードを下に掲載しました。ここで鍵となるのが「累積確率の最大値を記録するための確率テーブルV」です。これは、行に品詞(全S行)、列に単語(全T列)を配置した次のような表です。ビタビ・アルゴリズムでは、この確率テーブルVのマスを全て埋めた後、表を後ろから走査し、各列について最大の確率を与えた品詞を特定します。

確率テーブルの例:

<s\> Time flies like an arrow .
NN 0 -14.356 -23.687 -35.074 -42.038 -45.012 -59.735
VB 0 -12.448 -25.742 -28.661 -44.542 -53.772 -60.426
IN 0 -14.679 -23.560 -25.969 -43.468 -48.491 -60.008

ビタビ・アルゴリズム:

def viterbi(sentence, pos_tags, t_w, t_t):

    # 入力された文章sentenceを単語に分割する
    tokens = nltk.word_tokenize(sentence)
    # ['Time', 'flies', 'like', 'an', 'arrow', '.']

    tokens.insert(0, '<s>')
    # ['<s>', 'Time', 'flies', 'like', 'an', 'arrow', '.']

    # 品詞の種類の個数 (47)
    S = len(pos_tags)

    # 入力された文章の単語の個数
    T = len(tokens)

    # 累積確率の最大値を記録するための確率テーブル
    V = np.zeros((S, T), dtype=np.float32)

    # 累積確率を最大化する直前の品詞のインデックスを保持するインデックステーブル
    B = np.zeros((S, T), dtype=int)

    ## 確率テーブルV・インデックステーブルを埋める
    for i in range(1, T):
        for j in range(S):
            V[j][i], B[j][i] = calc_table(S, T, V, i, j, pos_tags, tokens, t_w, t_t)

    ## テーブルを基に、各単語について、最適な品詞を選択していく
    X = np.zeros((T), dtype=int)
    max_prob = 0.0
    for j in range(S):
        if V[j][T-1] > max_prob:
            max_prob = V[j][T-1]
            X[T-1] = j
    for i in range(T-2, -1, -1):
        X[i] = B[X[i+1]][i+1]

    # 品詞のインデックスから品詞の名称を得る
    pos_seq = []
    for pos_idx in X:
        pos_seq.append(pos_tags[pos_idx])

    return max_prob, list(zip(tokens[1:], pos_seq[1:]))

 表を埋める際の具体的な累積確率の計算は、関数calc_table()で行なっています。基本的には、表の各マスについて「直前の状態での累積確率 × 直前の状態から現在の状態への遷移確率 × 現在の状態でのその単語の出力確率 」を計算します。つまり、それぞれのマスについて、一つ前の全ての状態に対して、この計算を行います。

# ある品詞における、ある単語の「出力確率」を計算する; 「+alpha」により累積確率が0になることを防ぎ、未知の単語に対応している
def p_t_w(t_w, tag, word, alpha=0.01):
    return (t_w[tag][word] + alpha ) / (t_w[tag].N() + alpha * t_w[tag].B())

# 引数に与えられた二つの品詞の間の「状態遷移確率」を計算する; 「+alpha」は関数p_t_w()と同様
def p_t_t(t_t, tag1, tag2, alpha=0.01):
    return (t_t[tag1][tag2] + alpha ) / (t_t[tag1].N() + alpha * t_t[tag1].B())

# ビタビ・アルゴリズムの累積確率の計算処理 (累積 * 出力確率 * 状態遷移確率)
# (注:計算のアンダーフローを防ぐために、確率を対数に変換している。)
def calc_table(S, T, V, i, j, pos_tags, tokens, t_w, t_t):
    max_prob = 0.0
    max_k = 0
    for k in range(S):
        prob = V[k][i-1] + math.log(p_t_w(t_w, pos_tags[j], tokens[i])) 
                         + math.log(p_t_t(t_t, pos_tags[k], pos_tags[j]))
        if prob > max_prob:
            max_prob, max_k = prob, k
    return max_prob, max_k

 上のコードでは、関数N()と関数B()という、NLTKライブラリの機能を使用しています。今回のプログラムの中では、これらは(例えば)次のような値を返します。(関数についての詳細は、FreqDist.NFreqDist.Bを参照してください。)

t_w['NNP'].N()  # POSタグセット中で、品詞に'NNP'をもつ単語の出現回数 => 7610
t_w['NNP'].B()  # POSタグセット中で、品詞に'NNP'をもつ単語の種類の総数 => 2189

t_t['NNP'].N()  # POSタグセット中で、'NNP'に隣接して出現する品詞の出現回数 => 7610
t_t['NNP'].B()  # POSタグセット中で、'NNP'に隣接して出現する品詞の種類の総数 => 36

品詞推定のまとめ

ここで再び「復号化問題」の前提と目的を確認します。

復号化問題:
O と M が与えられたときに、 Oを生成したMの最適な状態遷移系列 を求める問題
  • 出力記号系列Oは、入力する文章
  • モデルMは、POSタグセットから作成可能
  • 最適な状態遷移系列は、ビタビ・アルゴリズムにより求められる解

…ということで、隠れマルコフモデルの実問題への適用方法のひとつである「復号化問題」について、品詞推定をテーマに、ここまで見てきました。



実装

ここからは、これまでに見てきたコードの完成版と、その実行例を紹介します。

コードの完成版は下のリンク先においてあります。ぜひご覧になってください。
https://github.com/takafumihoriuchi/natural_language_processing/blob/master/viterbi_pos_estimate.py


次はコードを実行した結果の例です。(実行時の条件として、POSタグセットにPennTreebankを用い、データを8対2の比率でトレーニング用データとテスト用データに分割しています。)

kabuku:hmm_blog thoriuchi$ python viterbi_pos_estimate.py
+-----------------------------------------------------------------------+
loading POS tagsets ...
-------------------------------------------------------------------------
input a sentence: The best way to predict the future is to create it.                
POS estimation result:
('The', 'DT')
('best', 'JJS')
('way', 'NN')
('to', 'TO')
('predict', 'VB')
('the', 'DT')
('future', 'NN')
('is', 'VBZ')
('to', 'TO')
('create', 'VB')
('it', 'PRP')
('.', '.')
-------------------------------------------------------------------------
measuring precision of model ...
[=======================================================================]
model precision
token based accuracy    : 0.860571884824592
sentence based accuracy : 0.1417624521072797
+------------------------------------------------------------------------+

補足:

token based accuracy‘ は、単語単位での正答率を表します。
例: ’I’, ‘have’, ‘a’, ‘dream’, ‘.’, ‘We’, ‘choose’, ‘to’, ‘go’, ‘to’, ‘the’, ‘moon’, ‘.’
   というふたつの文に対し、推定結果が「正・正・正・正・正」と「正・誤・正・正・正・誤・正・正」の場合は、
   (1 + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 1 + 1 + 0 + 1 + 1) / 13 = 0.85 という正答率の計算になります。

sentence based accuracy‘ は、文章単位での正答率を表します。
例: ’I’, ‘have’, ‘a’, ‘dream’, ‘.’, ‘We’, ‘choose’, ‘to’, ‘go’, ‘to’, ‘the’, ‘moon’, ‘.’
   というふたつの文に対し、推定結果が「正・正・正・正・正」と「正・誤・正・正・正・誤・正・正」の場合は、
   (1 + 0) / 2 = 0.5 という正答率の計算になります。

 文章単位での精度が0.14と低いですが、これは文章の中のひとつの単語の品詞を誤ってしまう、といったケースが多いことが原因のようです。特に固有名詞の品詞推定は精度が低い傾向にあります*。これらの精度を上げるためには、学習データの量と質の向上、モデルの改良などが必要になります。


*参考までに、現在の実装での品詞別の精度は下の表のようになっています。(学習データ:テストデータ = 8:2)

品詞の多くは7割以上の割合で正しく推定されており、推定機として機能する(と感じられる)水準にあります。
一方で、NNP(単数形の固有名詞、0.64)やNNPS(複数形の固有名詞、0.33)の精度はあまり良いとは言えません。この原因として、入力された文章に登場する固有名詞が、学習データにとって「未知」である場合が多いことが寄与していると考えられます。未知の単語に対する記号出力確率は極小の値をとるような実装となっているため、必然的に固有名詞の判定精度は低くなります。このような未知の単語に関する欠点を克服するためには、様々な固有名詞に対応できるよう、バリエーションに富んだデータを大量に学習することが重要になります。

品詞別の推定精度(参考):
品詞 正解数 総数 精度(正解数 / 総数)
<s\> 0 0 None
NNP 1152 1800 0.64
, 909 930 0.9774193548387097
CD 865 1032 0.8381782945736435
NNS 894 1127 0.7932564330079858
JJ 805 1087 0.7405703771849126
MD 202 204 0.9901960784313726
VB 460 498 0.9236947791164659
DT 1548 1611 0.9608938547486033
NN 2344 2901 0.807997242330231
IN 1843 1952 0.9441598360655737
. 742 762 0.973753280839895
VBZ 280 324 0.8641975308641975
VBG 188 261 0.7203065134099617
CC 391 429 0.9114219114219114
VBD 607 743 0.8169582772543742
VBN 344 452 0.7610619469026548
-NONE- 1321 1340 0.985820895522388
RB 403 478 0.8430962343096234
TO 457 464 0.9849137931034483
PRP 219 222 0.9864864864864865
RBR 7 16 0.4375
WDT 104 104 1.0
VBP 126 165 0.7636363636363637
RP 30 34 0.8823529411764706
PRP$ 119 122 0.9754098360655737
JJS 30 38 0.7894736842105263
POS 185 196 0.9438775510204082
““` “` 80 81 0.9876543209876543
EX 7 7 1.0
‘’ 76 78 0.9743589743589743
WP 26 26 1.0
: 73 77 0.948051948051948
JJR 62 76 0.8157894736842105
WRB 24 27 0.8888888888888888
$ 241 242 0.9958677685950413
NNPS 21 64 0.328125
WP$ 4 4 1.0
-LRB- 23 26 0.8846153846153846
-RRB- 22 26 0.8461538461538461
PDT 4 6 0.6666666666666666
RBS 5 5 1.0
FW 0 0 None
UH 0 0 None
SYM 0 0 None
LS 0 0 None
# 2 2 1.0
上の表の棒グラフ(精度がNoneの品詞は除外):



最後に

 この記事では、「隠れマルコフモデルとは何か」についての説明から、実問題の適用方法のひとつである「復号化問題」について記述しました。最適な状態遷移系列を求める「復号化問題」の説明として自然言語の「品詞推定」を取り上げ、計算の効率化の手段としての「ビタビ・アルゴリズム」の概要に触れました。

 本記事を通して「隠れマルコフモデル… (・∀・;)?」の状態から「HMMが何かはわかった!(*゚∀゚)/」の状態へ~~遷移できた~~レベルアップできた方が少しでもいれば幸いです。


ソースコード

本記事で紹介した「品詞推定」のコードは↓にあります。
https://github.com/takafumihoriuchi/natural_language_processing/blob/master/viterbi_pos_estimate.py


参考文献

[1] 言語と計算4 確率的言語モデル, 北研二, 東京大学出版会, 1999.

その他の記事

Other Articles

2022/06/03
拡張子に Web アプリを関連付ける File Handling API の使い方

2022/03/22
<selectmenu> タグできる子; <select> に代わるカスタマイズ可能なドロップダウンリスト

2022/03/02
Java 15 のテキストブロックを横目に C# 11 の生文字列リテラルを眺めて ECMAScript String dedent プロポーザルを想う

2021/10/13
Angularによる開発をできるだけ型安全にするためのKabukuでの取り組み

2021/09/30
さようなら、Node.js

2021/09/30
Union 型を含むオブジェクト型を代入するときに遭遇しうるTypeScript型チェックの制限について

2021/09/16
[ECMAScript] Pipe operator 論争まとめ – F# か Hack か両方か

2021/07/05
TypeScript v4.3 の機能を使って immutable ライブラリの型付けを頑張る

2021/06/25
Denoでwasmを動かすだけの話

2021/05/18
DOMMatrix: 2D / 3D 変形(アフィン変換)の行列を扱う DOM API

2021/03/29
GoのWASMがライブラリではなくアプリケーションであること

2021/03/26
Pythonプロジェクトの共通のひな形を作る

2021/03/25
インラインスタイルと Tailwind CSS と Tailwind CSS 入力補助ライブラリと Tailwind CSS in JS

2021/03/23
Serverless NEGを使ってApp Engineにカスタムドメインをワイルドカードマッピング

2021/01/07
esbuild の機能が足りないならプラグインを自作すればいいじゃない

2020/08/26
TypeScriptで関数の部分型を理解しよう

2020/06/16
[Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れない

2020/03/19
[Web フロントエンド] Elm に心折れ Mint に癒しを求める

2020/02/28
さようなら、TypeScript enum

2020/02/14
受付のLooking Glassに加えたひと工夫

2020/01/28
カブクエンジニア開発合宿に行ってきました 2020冬

2020/01/30
Renovateで依存ライブラリをリノベーションしよう 〜 Bitbucket編 〜

2019/12/27
Cloud Tasks でも deferred ライブラリが使いたい

2019/12/25
*, ::before, ::after { flex: none; }

2019/12/21
Top-level awaitとDual Package Hazard

2019/12/20
Three.jsからWebGLまで行きて帰りし物語

2019/12/18
Three.jsに入門+手を検出してAR.jsと組み合わせてみた

2019/12/04
WebXR AR Paint その2

2019/11/06
GraphQLの入門書を翻訳しました

2019/09/20
Kabuku Connect 即時見積機能のバックエンド開発

2019/08/14
Maker Faire Tokyo 2019でARゲームを出展しました

2019/07/25
夏休みだョ!WebAssembly Proposal全員集合!!

2019/07/08
鵜呑みにしないで! —— 書籍『クリーンアーキテクチャ』所感 ≪null 篇≫

2019/07/03
W3C Workshop on Web Games参加レポート

2019/06/28
TypeScriptでObject.assign()に正しい型をつける

2019/06/25
カブクエンジニア開発合宿に行ってきました 2019夏

2019/06/21
Hola! KubeCon Europe 2019の参加レポート

2019/06/19
Clean Resume きれいな環境できれいな履歴書を作成する

2019/05/20
[Web フロントエンド] 状態更新ロジックをフレームワークから独立させる

2019/04/16
C++のenable_shared_from_thisを使う

2019/04/12
OpenAPI 3 ファーストな Web アプリケーション開発(Python で API 編)

2019/04/08
WebGLでレイマーチングを使ったCSGを実現する

2019/03/29
その1 Jetson TX2でk3s(枯山水)を動かしてみた

2019/04/02
『エンジニア採用最前線』に感化されて2週間でエンジニア主導の求人票更新フローを構築した話

2019/03/27
任意のブラウザ上でJestで書いたテストを実行する

2019/02/08
TypeScript で “radian” と “degree” を間違えないようにする

2019/02/05
Python3でGoogle Cloud ML Engineをローカルで動作する方法

2019/01/18
SIGGRAPH Asia 2018 参加レポート

2019/01/08
お正月だョ!ECMAScript Proposal全員集合!!

2019/01/08
カブクエンジニア開発合宿に行ってきました 2018秋

2018/12/25
OpenAPI 3 ファーストな Web アプリケーション開発(環境編)

2018/12/23
いまMLKitカスタムモデル(TF Lite)は使えるのか

2018/12/21
[IoT] Docker on JetsonでMQTTを使ってCloud IoT Coreと通信する

2018/12/11
TypeScriptで実現する型安全な多言語対応(Angularを例に)

2018/12/05
GASでCompute Engineの時間に応じた自動停止/起動ツールを作成する 〜GASで簡単に好きなGoogle APIを叩く方法〜

2018/12/02
single quotes な Black を vendoring して packaging

2018/11/14
3次元データに2次元データの深層学習の技術(Inception V3, ResNet)を適用

2018/11/04
Node Knockout 2018 に参戦しました

2018/10/24
SIGGRAPH 2018参加レポート-後編(VR/AR)

2018/10/11
Angular 4アプリケーションをAngular 6に移行する

2018/10/05
SIGGRAPH 2018参加レポート-特別編(VR@50)

2018/10/03
Three.jsでVRしたい

2018/10/02
SIGGRAPH 2018参加レポート-前編

2018/09/27
ズーム可能なSVGを実装する方法の解説

2018/09/25
Kerasを用いた複数入力モデル精度向上のためのTips

2018/09/21
競技プログラミングの勉強会を開催している話

2018/09/19
Ladder Netwoksによる半教師あり学習

2018/08/10
「Maker Faire Tokyo 2018」に出展しました

2018/08/02
Kerasを用いた複数時系列データを1つの深層学習モデルで学習させる方法

2018/07/26
Apollo GraphQLでWebサービスを開発してわかったこと

2018/07/19
【深層学習】時系列データに対する1次元畳み込み層の出力を可視化

2018/07/11
きたない requirements.txt から Pipenv への移行

2018/06/26
CSS Houdiniを味見する

2018/06/25
不確実性を考慮した時系列データ予測

2018/06/20
Google Colaboratory を自分のマシンで走らせる

2018/06/18
Go言語でWebAssembly

2018/06/15
カブクエンジニア開発合宿に行ってきました 2018春

2018/06/08
2018 年の tree shaking

2018/05/30
DASKによる探索的データ分析(EDA)

2018/05/10
TensorFlowをソースからビルドする方法とその効果

2018/04/23
EGLとOpenGLを使用するコードのビルド方法〜libGLからlibOpenGLへ

2018/04/23
技術書典4にサークル参加してきました

2018/04/13
Python で Cura をバッチ実行するためには

2018/04/04
ARCoreで3Dプリント風エフェクトを実現する〜呪文による積層造形映像制作の舞台裏〜

2018/04/02
深層学習を用いた時系列データにおける異常検知

2018/04/01
音声ユーザーインターフェースを用いた新方式積層造形装置の提案

2018/03/31
Container builderでコンテナイメージをBuildしてSlackで結果を受け取る開発スタイルが捗る

2018/03/23
ngUpgrade を使って AngularJS から Angular に移行

2018/03/14
Three.jsのパフォーマンスTips

2018/02/14
C++17の新機能を試す〜その1「3次元版hypot」

2018/01/17
時系列データにおける異常検知

2018/01/11
異常検知の基礎

2018/01/09
three.ar.jsを使ったスマホAR入門

2017/12/17
Python OpenAPIライブラリ bravado-core の発展的な使い方

2017/12/15
WebAssembly(wat)を手書きする

2017/12/14
AngularJS を Angular に移行: ng-annotate 相当の機能を TypeScrpt ファイルに適用

2017/12/08
Android Thingsで4足ロボットを作る ~ Android ThingsとPCA9685でサーボ制御)

2017/12/06
Raspberry PIとDialogflow & Google Cloud Platformを利用した、3Dプリンターボット(仮)の開発 (概要編)

2017/11/20
カブクエンジニア開発合宿に行ってきました 2017秋

2017/10/19
Android Thingsを使って3Dプリント戦車を作ろう ① ハードウェア準備編

2017/10/13
第2回 魁!! GPUクラスタ on GKE ~PodからGPUを使う編~

2017/10/05
第1回 魁!! GPUクラスタ on GKE ~GPUクラスタ構築編~

2017/09/13
「Maker Faire Tokyo 2017」に出展しました。

2017/09/11
PyConJP2017に参加しました

2017/09/08
bravado-coreによるOpenAPIを利用したPythonアプリケーション開発

2017/08/23
OpenAPIのご紹介

2017/08/18
EuroPython2017で2名登壇しました。

2017/07/26
3DプリンターでLチカ

2017/07/03
Three.js r86で何が変わったのか

2017/06/21
3次元データへの深層学習の適用

2017/06/01
カブクエンジニア開発合宿に行ってきました 2017春

2017/05/08
Three.js r85で何が変わったのか

2017/04/10
GCPのGPUインスタンスでレンダリングを高速化

2017/02/07
Three.js r84で何が変わったのか

2017/01/27
Google App EngineのFlexible EnvironmentにTmpfsを導入する

2016/12/21
Three.js r83で何が変わったのか

2016/12/02
Three.jsでのクリッピング平面の利用

2016/11/08
Three.js r82で何が変わったのか

2016/12/17
SIGGRAPH 2016 レポート

2016/11/02
カブクエンジニア開発合宿に行ってきました 2016秋

2016/10/28
PyConJP2016 行きました

2016/10/17
EuroPython2016で登壇しました

2016/10/13
Angular 2.0.0ファイナルへのアップグレード

2016/10/04
Three.js r81で何が変わったのか

2016/09/14
カブクのエンジニアインターンシッププログラムについての詩

2016/09/05
カブクのエンジニアインターンとして3ヶ月でやった事 〜高橋知成の場合〜

2016/08/30
Three.js r80で何が変わったのか

2016/07/15
Three.js r79で何が変わったのか

2016/06/02
Vulkanを試してみた

2016/05/20
MakerGoの作り方

2016/05/08
TensorFlow on DockerでGPUを使えるようにする方法

2016/04/27
Blenderの3DデータをMinecraftに送りこむ

2016/04/20
Tensorflowを使ったDeep LearningにおけるGPU性能調査

→
←

関連職種

Recruit

→
←

お客様のご要望に「Kabuku」はお応えいたします。
ぜひお気軽にご相談ください。

お電話でも受け付けております
03-6380-2750
営業時間:09:30~18:00
※土日祝は除く