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

サンプル・プログラム
KAKASIの準備

KAKASI がインストールできたら、その実行パスを定数 KAKASI に記述する。OSの種類(WindowsとLinux)は、組み込み定数 PHP_OS を使って自動識別できるようにしてある。
解説:形態素に分解する
- KAKASI を使って、元のテキストと比較するテキストを形態素に分解、各々を配列 $items_sour と $items_dest に代入する。
- 2つの配列の違いを比較する。
- 結果をパーセントで返す。
解説: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: }
解説:形態素の比較
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 は、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基の円墳が集中する東日本最大の古墳遺跡である。類似度は 9.0% になる。一方、similar_text を使った結果では 19.2% である。
1938年(昭和13年)8月に国の史跡の指定を受け、1968年(昭和43年)には金錯銘鉄剣(きんさくめいてっけん)が出土したことで一躍脚光を浴びた。
形態素解析では、日本語の内容の違いをきちんと見て比較していることが分かる。
参考サイト
- KAKASI:公式サイト
- PHPで2つの文章の類似度を計算する:ぱふぅ家のホームページ
- PHPとKAKASIを使って単語に分解する(その1):ぱふぅ家のホームページ
- PHPとKAKASIを使って単語に分解する(その2):ぱふぅ家のホームページ
- PHPで形態素解析を行う:ぱふぅ家のホームページ
結果として、流用された文章かどうかを判定するというよりは、2つの文章がまったく違うテーマを扱っているのかどうかを判定するプログラムが出来上がった。
(2021年6月19日)PHP8対応,リファラ・チェック追加