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

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

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

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

サンプル・プログラム

KAKASIの準備

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

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

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

サンプル・プログラムの解説:形態素に分解する

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

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

0129: /**
0130:  * kakasiを使って単語に分解する
0131:  * @param string $kakasi kakasiの実行パス
0132:  * @param string $str    分解するコンテンツ
0133:  * @param string $words  分解結果を格納する配列
0134:  * @return string 分解の際に使った文字エンコード、またはFALSE(分解失敗)
0135: */
0136: function parsing_simple($kakasi$str, &$words) {
0137:     $encode = (constant('PHP_OS') == 'WINNT') ? 'sjis' : 'sjis';
0138:     $str = mb_convert_encoding($str$encode, INTERNAL_ENCODING);
0139: 
0140:     //形態素解析をしたい文章を渡しつつ、kakasiへのハンドルオープン
0141:     $cmd = "echo '{$str}' | {$kakasi} -w -i {$encode} -o {$encode}";
0142:     $handle = popen($cmd, 'r');
0143:     if ($handle == FALSE)   return FALSE;
0144: 
0145:     //結果を1行ずつ取得
0146:     while ($get_kakasi = fgets($handle)) {
0147:         //kakasiの結果を分解
0148:         $get_kakasi = mb_convert_encoding($get_kakasi, INTERNAL_ENCODING, $encode);
0149:         $result = preg_split("/[\s' ]+/ums", $get_kakasi);
0150:         //結果を配列に格納する
0151:         foreach ($result as $key=>$val$words[] = $val;
0152:     }
0153:     if (pclose($handle) == (-1)) return FALSE;
0154: 
0155:     return $encode;
0156: }

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

解説:形態素の比較

0158: /**
0159:  * 配列各要素の文字列長の二乗の合計を計算する
0160:  * @param array $items 単語の入っている配列
0161:  * @return int 重み付けカウント
0162: */
0163: function count_weight($items) {
0164:     $ret = 9;
0165:     foreach ($items as $word)   $ret += mb_strlen($word) * mb_strlen($word);
0166:     return $ret;
0167: }
0168: 
0169: /**
0170:  * 2つのテキストの類似度を計算する
0171:  * @param string $sour 元のテキスト
0172:  * @param string $dest 比較するテキスト
0173:  * @return string $enc  分解の際に使った文字エンコードを格納
0174:  * @return double 類似度(0~1)/FALSE:計算に失敗
0175: */
0176: function similar_kakasi($sour$dest, &$enc) {
0177:     $items_sour = array();
0178:     $items_dest = array();
0179: 
0180:     //1行ずつ取り出して単語に分解する
0181:     $str = strtok($sour, "\n");
0182:     while ($str != FALSE) {
0183:         $enc = parsing_simple(KAKASI$str$items_sour);
0184:         if ($enc == FALSE)  return FALSE;
0185:         $str = strtok("\n");
0186:     }
0187:     $str = strtok($dest, "\n");
0188:     while ($str != FALSE) {
0189:         $enc = parsing_simple(KAKASI$str$items_dest);
0190:         if ($enc == FALSE)  return FALSE;
0191:         $str = strtok("\n");
0192:     }
0193: 
0194:     $result = count_weight(array_intersect($items_sour$items_dest));
0195: 
0196:     return (double)$result / count_weight($items_dest);
0197: }

分解した形態素を比較するには、関数  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 に比べて、動的なページを実現することができる。


結果は 45.1% となる。

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

ちなみに、PHP とはまったく関係ない以下のテキストを「比較するテキスト」に入れてみると――

さきたま古墳公園(埼玉県行田市大字埼玉4834)は、8 基の前方後円墳と 1 基の円墳が集中する東日本最大の古墳遺跡である。
1938 年(昭和 13 年)8 月に国の史跡の指定を受け、1968 年には金錯銘鉄剣(きんさくめいてっけん)が出土したことで一躍脚光を浴びた。


類似度は 9.0% になる。一方、similar_text を使った結果では 19.2% である。
形態素解析では、日本語の内容の違いをきちんと見て比較していることが分かる。

参考サイト

(この項おわり)
header