用語「レーベンシュタイン距離」について説明。2つの系列(文字列やDNA配列など)を比較して、一方から他方へ変換するのに最も少ない編集操作(挿入/削除/置換)の回数をカウントすることで、2系列間の距離を計測する方法。文章の編集作業量の計測、スペルチェック、データクリーニング、DNA配列の比較などに利用され、データ間の違いや類似度を評価できる。
情報理論/機械学習/自然言語処理におけるレーベンシュタイン距離(Levenshtein distance)とは、2つの系列(文字列やDNA配列など)間で片方からもう片方への「編集操作の最小回数」をカウントすることで、「距離」を計算する方法である。この編集操作には、
の3つが含まれる。このため、レーベンシュタイン距離は編集距離(Edit distance)とも呼ばれる。
例えば、
編集する場合のレーベンシュタイン距離は6となる。これは、下記の6回の編集操作によって「おはようございます。」を「おはやいですね。」に変換できるからだ(図1)。
図1は、編集前の文字列を行に取り、編集後の文字列を列に取った表である。レーベンシュタイン距離の計算には、一般的に動的計画法によるアルゴリズムが用いられる。動的計画法(DP:Dynamic Programming)とは、大きな問題を小さな部分問題に分解し、それらの解を組み合わせて最終的な解を求める手法である。レーベンシュタイン距離の計算では、先頭に(空)文字列のセルを置くため、長さn文字と長さm文字の場合は、n+1行m+1列の二次元行列の表を作成し、その各セルで部分問題を解いていく。図1では「なし」「削除」「挿入」「置換」がセルに書かれているが、実際には各セルにおけるレーベンシュタイン距離が記入される(図2)。
そして、この表の一番右下にあるセルの数値が、2つの文字列間での「最小回数の編集操作」を計算したレーベンシュタイン距離となる。ここだけ計算すればよさそうなものだが、これを計算するには、それまでに計算したセルの値を使う必要があるため、結局は一番左上のセルから計算を始めて、表内の全てのセルにおけるレーベンシュタイン距離を計算してくことになる。計算が大量になるので、通常は手計算ではなくプログラミングによって処理する方が効率が良い。
図2は、実際に表内の全セルでレーベンシュタイン距離を計算した結果だ(ちなみに、これらの数値はPythonプログラムによって計算した。Pythonによるレーベンシュタイン距離の計算方法は後述する)。
図2の表の作り方を説明する。
まず数学やプログラミングで表現しやすいように、2つの文字列をxとyと表現することにしよう。
図2のように、n+1行m+1列の表を作成したら、表の0行目と0列目を初期化する(※行番号と列番号は0からスタートした方が計算結果と一致して分かりやすいので、最初の行と列を「0行目」と「0列目」と表現している)。
【挿入】とあるのは、左のセルよりも1文字挿入したということ。1セルで1操作なので1つずつカウントアップしている。
【削除】とあるのは、上のセルよりも1文字削除したということ。1セルで1操作なので1つずつカウントアップしている。
以上で初期化が済んだので、次に各文字列で構成された行列の各セルで計算していく。計算方法は、以下のルールに従う。
実は、上記の0行目の【挿入】や0列目【削除】も同じルールで計算されている。
例えば、以下のように計算できる。対象のセルまでの計算結果を踏まえた上で、対象セルの計算を加算(+0か+1か)していることに注意してほしい。
各セルの計算を確かめやすいように図2を再掲する。
念のため見方を丁寧に説明すると、例えばセル(4,3)は、以下の3つの編集コストが選択肢となる。
この場合、【削除】と【置換】が2で、【挿入】が3なので、最小コストは2である。【削除】か【置換】のいずれかで処理すればよい。距離は同じなので、どちらでも構わない。筆者が作成したプログラムでは、【削除】【挿入】【置換】の順で処理したので、【削除】が選択されている。
以上で説明した「各セルにおける計算ルール」を数式で表現すると次のようになる。iは現在の行番号で、jは現在の列番号だ。
これで以下の意味になる。
この数式における1(xi ≠ yj)は、条件(xi ≠ yj)が真の場合は1、偽の場合は0を返す関数(指示関数)である。つまり、比較している2つの文字が異なる(例えばセル(4,3)なら「う」と「や」で異なる)場合にのみ1を足すことを意味する。
レーベンシュタイン距離は、以下のような場面で活用できる。
このようにレーベンシュタイン距離は、幅広い分野で利用されている。ただし、計算量が多いため、大規模データに対しては注意が必要である。
レーベンシュタイン距離を計算するには、先ほどのルールをプログラムに落とし込むだけである。とはいえ、手軽にコピー&ペーストで試したいだろう。そこで、筆者が(ChatGPTを使って)書いたサンプルコードを最後に示しておく。コード内容は説明はしないので、必要があれば、本稿の説明と比べながら自分で読み解いてほしい。Google Colabのscratchpadにコードを入力すれば手軽に試せる。
import numpy as np
import pandas as pd
def levenshtein_distance_matrix(x, y):
# 表を作成
n, m = len(x), len(y)
dp = np.zeros((n + 1, m + 1), dtype=int)
# 初期化
for j in range(m + 1):
dp[0][j] = j
for i in range(n + 1):
dp[i][0] = i
# 各セルで計算していく
for i in range(1, n + 1):
for j in range(1, m + 1):
if x[i - 1] == y[j - 1]:
dp[i][j] = dp[i - 1][j - 1] # 【変更なし】
else:
dp[i][j] = min(dp[i - 1][j] + 1, # 【削除】
dp[i][j - 1] + 1, # 【挿入】
dp[i - 1][j - 1] + 1) # 【置換】
# 逆方向にたどって操作を記録(この部分は検証用で、実際には不要)
steps = []
i, j = n, m
while i > 0 or j > 0:
if i > 0 and j > 0 and x[i - 1] == y[j - 1]:
steps.append(f"変更なし: {x[i - 1]}")
i -= 1
j -= 1
elif i > 0 and (j == 0 or dp[i][j] == dp[i - 1][j] + 1):
steps.append(f"削除: {x[i - 1]}")
i -= 1
elif j > 0 and (i == 0 or dp[i][j] == dp[i][j - 1] + 1):
steps.append(f"挿入: {y[j - 1]}")
j -= 1
else:
steps.append(f"置換: {x[i - 1]} → {y[j - 1]}")
i -= 1
j -= 1
return dp[n][m], steps[::-1], dp
# 「おはようございます。」と「おはやいですね。」の編集距離と手順を計算
x = "おはようございます。"
y = "おはやいですね。"
distance, steps, matrix = levenshtein_distance_matrix(x, y)
print("レーベンシュタイン距離:")
print(distance)
print("\n操作手順:")
for step in steps:
print(step)
print("\n距離の計算行列:")
matrix_df = pd.DataFrame(matrix, index=[""] + list(x), columns=[""] + list(y))
print(matrix_df)
# レーベンシュタイン距離:
# 6
# 操作手順:
# 変更なし: お
# 変更なし: は
# 置換: よ → や
# 削除: う
# 削除: ご
# 削除: ざ
# 変更なし: い
# 置換: ま → で
# 変更なし: す
# 挿入: ね
# 変更なし: 。
# 距離の計算行列:
# お は や い で す ね 。
# 0 1 2 3 4 5 6 7 8
# お 1 0 1 2 3 4 5 6 7
# は 2 1 0 1 2 3 4 5 6
# よ 3 2 1 1 2 3 4 5 6
# う 4 3 2 2 2 3 4 5 6
# ご 5 4 3 3 3 3 4 5 6
# ざ 6 5 4 4 4 4 4 5 6
# い 7 6 5 5 4 5 5 5 6
# ま 8 7 6 6 5 5 6 6 6
# す 9 8 7 7 6 6 5 6 7
# 。 10 9 8 8 7 7 6 6 6
レーベンシュタイン距離と同様に、2つの系列(文字列)間の距離を測定する方法にハミング距離がある。レーベンシュタイン距離は、「ハミング距離の一般化」と見なせる。
具体的には、適用範囲が広がっているという点で一般化されている。ハミング距離が同じ長さの系列(文字列)にのみ適用可能なのに対し、レーベンシュタイン距離は長さの異なる系列(文字列)間にも適用できる。さらに、ハミング距離は「置換」のみを対象として系列を変換するが、レーベンシュタイン距離では「挿入」「削除」「置換」の3つの操作を考慮する。そのため、レーベンシュタイン距離はより柔軟で幅広い状況に対応できる。
2024年9月9日:「【応用】ハミング距離との関係」を追記しました。
Copyright© Digital Advantage Corp. All Rights Reserved.