Apache Solrには同義語の定義ができます。
要するに検索する際のキーワードやインデックスのAとBは同じ言葉ということを定義できるわけですが、こういったことを人力で行うのは不可能に近いことです。
で、もっとも身近なデータから機械的に同義語データを定義できたらいいなということでWikipediaのデータを使って同義語ファイルを作成してみました。
結果からいうとこの同義語ファイルはまぁまぁ・・・な内容です。
Wikipediaでは厳密に同義語だという扱いをしているわけではなく、情報を正式名称のページに寄せているだけですからそれは同義語じゃないよねってデータも含まれるのですが、その辺の情報を精査するものは別途作る必要があるかと思います。
Wikipediaのデータから同義語となるデータを抽出する
使うのはWikipediaの記事データで、下記のURLに誰でもダウンロードできるように公開されています。
http://dumps.wikimedia.org/jawiki/latest/
で、この中で使えるデータがjawiki-latest-stub-articles.xml.gz(このファイルはあまりに巨大なので、今回使うのは分割しているjawiki-latest-stub-articlesN.xml.gz(Nは数字で2015.01時点では4分割)というファイルを使ってます)というファイルで、この中身はXML形式で記事のタイトルなどが含まれています。
<page>
<title>ヨーロッパ</title>
<ns>0</ns>
<id>32</id>
<revision>
<id>52739692</id>
<parentid>52357980</parentid>
<timestamp>2014-08-31T09:03:15Z</timestamp>
<contributor>
<username>Dexbot</username>
<id>620423</id>
</contributor>
<minor/>
<comment>Bot: Removing Link FA template</comment>
<text id="52784825" bytes="54708" />
<sha1>giuwvpewi1w7k2luxgwmyk2u7pyu08a</sha1>
<model>wikitext</model>
<format>text/x-wiki</format>
</revision>
</page>
<page>
<title>生物</title>
<ns>0</ns>
<id>42</id>
<revision>
<id>52108199</id>
<parentid>51054233</parentid>
<timestamp>2014-06-27T14:18:57Z</timestamp>
<contributor>
<ip>118.153.79.10</ip>
</contributor>
<comment>説明の具体化</comment>
<text id="52146183" bytes="13225" />
<sha1>gjcin6me04awp6g9z5rgwu563x30an8</sha1>
<model>wikitext</model>
<format>text/x-wiki</format>
</revision>
</page>
<page>
<title>コケ植物</title>
<ns>0</ns>
<id>43</id>
<revision>
<id>49410361</id>
<parentid>48129936</parentid>
<timestamp>2013-10-14T02:13:55Z</timestamp>
<contributor>
<username>Zqmykbvoh</username>
<id>140470</id>
</contributor>
<minor/>
<comment>「単相」 lk 修正</comment>
<text id="49421841" bytes="20742" />
<sha1>jp71dgglkk20tqoj11arwm02am8p856</sha1>
<model>wikitext</model>
<format>text/x-wiki</format>
</revision>
</page>
この中で、Wikipediaでよく見かけるリダイレクトをかけている項目を抽出します。
例えば、こういうページ
これが、元のWikipediaのデータだと下記のようになっています。
<page>
<title>リナックス</title>
<ns>0</ns>
<id>1016</id>
<redirect title="Linux" />
<revision>
<id>2169766</id>
<parentid>8081</parentid>
<timestamp>2003-03-02T19:47:50Z</timestamp>
<contributor>
<username>Okome</username>
<id>98</id>
</contributor>
<text id="2169766" bytes="19" />
<sha1>ow6122zxo9so312b35n5wtu21s92l0c</sha1>
<model>wikitext</model>
<format>text/x-wiki</format>
</revision>
</page>
redirectという要素があって、そこのtitle属性に転送先が書かれているわけですね。
ってことで、このデータを抽出して同義語ファイルを作るということです。
Solr用同義語ファイルを作成するPHPスクリプト
PHPで作った抽出スクリプトは下記のものです。
<?php
class CreateSynonymFile {
// Solrの辞書データのパス
public $dicPathIndex = "/path/to/solr/conf/synonyms.txt";
// Wikiの元ファイルがおかれたディレクトリ
public $wikiFileDir = "/tmp/articles/";
function execute() {
// Wikipediaの元ファイルのパスを取得
$wikiFiles = $this->getWikiFiles();
$fileCnt = count($wikiFiles);
for ($i = 0; $i < $fileCnt; $i++) {
$xml = simplexml_load_file($wikiFiles[$i]);
$xmlCnt = count($xml->page);
$fp = fopen($this->dicPathIndex, "w");
for ($j = 0; $j < $xmlCnt; $j++) {
$title = (string) $xml->page[$j]->title;
$redirect = (string) $xml->page[$j]->redirect[0]["title"];
if ($this->checkSynonym($title, $redirect)) {
$line = $title . " => " . $redirect . PHP_EOL;
fwrite($fp, $line);
}
}
fclose($fp);
}
}
/**
* 同義語として登録するかどうかチェックする
* @param string $title : 元の言葉
* @param string $redirect : リダイレクト先
* @return bool : 同義語登録対象の場合はTRUE、それ以外はFALSE
*/
protected function checkSynonym($title, $redirect) {
// 空白は全て除去
$title = str_replace(array(" ", " "), "", $title);
$redirect = str_replace(array(" ", " "), "", $redirect);
// データが空の場合は対象外
if (empty($title) || empty($redirect)) {
return FALSE;
}
// 対象の文字列が3文字未満なら対象外
if ((mb_strlen($title) < 3) || (mb_strlen($redirect) < 3)) {
return FALSE;
}
// Wikipedia関連記事のリダイレクトは対象外
if ((strstr($title, "Wikipedia") !== FALSE) || (strstr($redirect, "Wikipedia") !== FALSE)) {
return FALSE;
}
// 大文字・小文字の違いだけの場合は対象外
if (strtolower($title) == strtolower($redirect)) {
return FALSE;
}
// 「一覧」という言葉が含まれるものは抽象的な用語であるため対象外
if ((strstr($title, "一覧") !== FALSE) || (strstr($redirect, "一覧") !== FALSE)) {
return FALSE;
}
// 「曖昧さ回避」という言葉が含まれるものは抽象的な用語であるため対象外
if ((strstr($title, "曖昧さ回避") !== FALSE) || (strstr($redirect, "曖昧さ回避") !== FALSE)) {
return FALSE;
}
// ひらがな・カタカナの違いだけのものは対象外
if (mb_convert_kana($title, "h") == mb_convert_kana($redirect, "h")) {
return FALSE;
}
return TRUE;
}
/*
* Wikipediaの元ファイルを取得する
*/
protected function getWikiFiles() {
$dir = array();
if (is_dir($this->wikiFileDir) && $dh = opendir($this->wikiFileDir)) {
while (($file = readdir($dh)) !== false) {
if (preg_match("/jawiki-latest-stub-articles[0-9]+.xml/", $file)) {
$dir[] = $this->wikiFileDir . $file;
break;
}
}
}
closedir($dh);
sort($dir);
return $dir;
}
}
やってることはそんなに複雑じゃないんですけど、simplexml_load_file()をつかってXMLを解析し、redirectの項目の有無とその転送先(title属性)を抽出しています。
checkSynonym()では、同義語として登録するかどうかの関数ですが、この辺が最初に書いたようにまぁまぁな内容をどこまで精査できるかってところで、Wikipediaのデータには下記のような曖昧さ回避をする言葉もありますから、その辺は同義語として登録すべきかどうか判断が必要です(今回の場合は無視)。
出来上がったSolrの同義語ファイルは下記のような感じのもの。
森田一義 => タモリ .NET => .NET Framework OSI基本参照モデル => OSI参照モデル クーロン相互作用 => 電磁相互作用 場の理論 => 場の量子論
こういった例は全然いいんですけど、
エロス物 => エロス作品
これは、人の解釈によるんじゃないかとか、
錯乱坊 => うる星やつらの登場人物
そんな同義語必要ないんじゃないかとか、
将棋棋士 => 棋士 (将棋)
元の語のほうがわかりやすいと思うとか、まぁリダイレクトの処理からの抽出ですし、あくまでWikipedia上でのデータになりますから情報の精査は追加対応が必要そうです。
[PR]
[PR]