PHPで2つの文章の類似度を計算する(KAKASI版)

(1/1)
前回、PHPの組み込み関数  similar_text  や N-gram を使うことで、異なる2つの文章の類似度を計算する方法を紹介した。今回は、「PHPとKAKASIを使って単語に分解する」で紹介した形態素解析ツール「KAKASI」を使い、日本語の特徴に即した類似度を計算するプログラムを作ってみることにする。
結果として、流用された文章かどうかを判定するというよりは、2つの文章がまったく違うテーマを扱っているのかどうかを判定するプログラムが出来上がった。

(2021年6月19日)PHP8対応,リファラ・チェック追加

サンプル・プログラムの実行例

PHPで2つの文章の類似度を計算する(KAKASI版)

サンプル・プログラム

KAKASIの準備

  35: //kakasiの実行パス;各自の環境に合わせて変更すること
  36: if (constant('PHP_OS') == 'WINNT') {
  37:     define('KAKASI', 'C:\\kakasi\\bin\\kakasi.exe');    //Windowsの場合
  38: else {
  39:     define('KAKASI', '/usr/local/bin/kakasi');          //Linuxの場合
  40: }

このプログラムを実行するには、KAKASI がインストールされている必要がある。KAKASI の入手方法やインストール方法については公式サイトを参照されたい。

KAKASI がインストールできたら、その実行パスを定数 KAKASI に記述する。OSの種類(WindowsとLinux)は、組み込み定数 PHP_OS を使って自動識別できるようにしてある。

解説:形態素に分解する

プログラムの流れは次の通り――。
  1. KAKASI を使って、元のテキストと比較するテキストを形態素に分解、各々を配列 $items_sour$items_dest に代入する。
  2. 2つの配列の違いを比較する。
  3. 結果をパーセントで返す。

解説:PHPから KAKASI を呼び出す

 182: /**
 183:  * kakasiを使って単語に分解する
 184:  * @param   string $kakasi kakasiの実行パス
 185:  * @param   string $str    分解するコンテンツ
 186:  * @param   string $words  分解結果を格納する配列
 187:  * @return  string 分解の際に使った文字エンコード、またはFALSE(分解失敗)
 188: */
 189: function parsing_simple($kakasi, $str, &$words) {
 190:     $encode = (constant('PHP_OS') == 'WINNT'? 'sjis' : 'sjis';
 191:     $str = mb_convert_encoding($str, $encode, INTERNAL_ENCODING);
 192: 
 193:     //形態素解析をしたい文章を渡しつつ、kakasiへのハンドルオープン
 194:     $cmd = "echo '{$str}' | {$kakasi} -w -i {$encode} -o {$encode}";
 195:     $handle = popen($cmd, 'r');
 196:     if ($handle == FALSE)   return FALSE;
 197: 
 198:     //結果を1行ずつ取得
 199:     while ($get_kakasi = fgets($handle)) {
 200:         //kakasiの結果を分解
 201:         $get_kakasi = mb_convert_encoding($get_kakasi, INTERNAL_ENCODING, $encode);
 202:         $result = preg_split("/[\s' ]+/ums", $get_kakasi);
 203:         //結果を配列に格納する
 204:         foreach ($result as $key=>$val$words[] = $val;
 205:     }
 206:     if (pclose($handle) == (-1))    return FALSE;
 207: 
 208:     return $encode;
 209: }

KAKASI を呼び出しテキストを単語に分解するユーザー関数 parsing_simple は、「PHPとKAKASIを使って単語に分解する(その1)」で作成した関数 parsing とほぼ同じだが、出現関数をカウントせず、分解した単語を(重複しているものを含め)そのまま配列の要素として格納している。

解説:形態素の比較

 222: /**
 223:  * 2つのテキストの類似度を計算する
 224:  * @param   string $sour 元のテキスト
 225:  * @param   string $dest 比較するテキスト
 226:  * @return  string $enc  分解の際に使った文字エンコードを格納
 227:  * @return  double 類似度(0~1)/FALSE:計算に失敗
 228: */
 229: function similar_kakasi($sour, $dest, &$enc) {
 230:     $items_sour = array();
 231:     $items_dest = array();
 232: 
 233:     //1行ずつ取り出して単語に分解する
 234:     $str = strtok($sour, "\n");
 235:     while ($str !FALSE) {
 236:         $enc = parsing_simple(KAKASI, $str, $items_sour);
 237:         if ($enc == FALSE)  return FALSE;
 238:         $str = strtok("\n");
 239:     }
 240:     $str = strtok($dest, "\n");
 241:     while ($str !FALSE) {
 242:         $enc = parsing_simple(KAKASI, $str, $items_dest);
 243:         if ($enc == FALSE)  return FALSE;
 244:         $str = strtok("\n");
 245:     }
 246: 
 247:     $result = count_weight(array_intersect($items_sour, $items_dest));
 248: 
 249:     return (double)$result / count_weight($items_dest);
 250: }

分解した形態素を比較するには、関数  array_intersect  を利用した。
array_intersect は、2つの配列を比較し、一致する要素を抽出して返してくれる。
そこで、返ってきた要素――つまり、一致した形態素に対して、ユーザー関数 count_weight を適用して、類似度を計算させている。

ユーザー関数 count_weight は、その配列に含まれる要素の文字数の二乗を返す。つまり、長い形態素が一致しているほど、より類似度が高くなるという結果が得られる。

ユーザー関数 similar_kakasi は、一致した形態素配列に count_weight を適用した値を、比較する方の形態素配列に count_weight を適用した値で割り、類似度をパーセントとして返す。

サンプル・プログラムの実行例

前回と同じテキストを使って実行してみよう。
まず、「元のテキスト」として、以下のWikipediaの引用文を入れる。これは「PHP: Hypertext Preprocessor」からの抜粋である。
PHP:Hypertext Preprocessor(ピー・エイチ・ピー:ハイパーテキストプリプロセッサー)とは、動的にHTMLデータを生成することによって、動的なウェブページを実現することを主な目的としたプログラミング言語、およびその言語処理系である。PHPは、HTML埋め込み型のサーバサイド・スクリプト言語として分類される。この言語処理系自体は、C言語で記述されている。
「比較するテキスト」には、以下の文章を入れてみよう。
PHP(Hypertext Preprocessor;ピー・エイチ・ピー)とは、動的にHTMLデータを生成することによって、動的なウェブページを実現すること目的としたプログラミング言語である。PHPは、HTML埋め込み型のサーバサイド・スクリプト言語の一種で、処理系自体はC言語で記述されている。
結果は 75.5% である。
2つめの文章は、一見すると元の文章とは異なっているが、じつはWikipediaの引用文の順番を変えただけである。
このような違いでは、かなり高い類似度の値となる。

次に、「比較するテキスト」に以下の文章を入れて実行してみていただきたい。これは「PHPとは何か」(ぱふぅ家のホームページ)の冒頭部分である。
「PHP(Hypertext Preprocessor)」は、オープンソースのサーバ・サイド・スクリプト言語である。サーバ・サイド・スクリプトとは、データベースサーバなどのサーバ群と Web ブラウザ(クライアント)を結ぶインターフェースの役割をするもので、Webサーバ上で動作する。HTMLに比べて、動的なページを実現することができる。
結果は 44.0% となる。

前回、similar_text を使った結果は、最初の比較例では 83.7%、2番目の比較例では 27.3% だった。
単語の順番を入れ替えただけの最初の例では類似度が若干下がっている一方、オリジナルの文章同士を比較しているにもかかわらず2番目の例では類似度が大きく上がっている。
このように形態素解析を使った類似度比較では、文章の引用・転用というより、その文章が扱っている対象が同じかどうかによって類似度が決まるのである。

ちなみに、PHPとはまったく関係ない以下のテキストを「比較するテキスト」に入れてみると――
さきたま古墳公園(埼玉県行田市大字埼玉4834)は、8基の前方後円墳と1基の円墳が集中する東日本最大の古墳遺跡である。
1938年(昭和13年)8月に国の史跡の指定を受け、1968年(昭和43年)には金錯銘鉄剣(きんさくめいてっけん)が出土したことで一躍脚光を浴びた。
類似度は 9.0% になる。一方、similar_text を使った結果では 19.2% である。
形態素解析では、日本語の内容の違いをきちんと見て比較していることが分かる。

参考サイト

(この項おわり)
header