PHPで日本文の“要約もどき”を行う

(1/1)
日本文の要約を自動的に行うことは、なかなか難しい。
今回は、Yahoo!JAPAN の「日本語係り受け解析」を利用し、ベースとなる述語と、それを修飾する文節を取り出すことで、“要約もどき”を行ってみることにする。

Yahoo!JAPAN 日本語係り受け解析

日本語係り受け解析」は、入力パラメータ(IN)として GET、POST の 2種類の方式を、出力結果(OUT)が XML で戻るという API である。
入力パラメータは翻訳したいテキストであるので、GET 渡しでは長くなりすぎてしまう。そこで、POST 渡しを使うことにする。
WebAPIのURL
URL
http://jlp.yahooapis.jp/DAService/V1/parse

入力パラメータ
項目名 フィールド名 内  容
アプリケーションID appid string Yahoo! DEVELOPER NETWORKのアプリケーションID。無料で入手できる。
要約対象テキスト sentence string 要約する日本語テキスト。UTF-8エンコード。
応答データ構造(xml) ResultSet Result ChunkList Chunk Id 文節ID Dependency 修飾するID MorphemList Morphem Surface 形態素の表記 Reading 形態素の読みがな Baseform 形態素の基本表記 POS 形態素の品詞 Feature 形態素の全情報

サンプル・プログラム

PHPで日本文の要約もどきを行う

サンプル・プログラムの解説:準備

0028: //Yahoo! JAPAN Webサービス アプリケーションID
0029: //http://help.yahoo.co.jp/help/jp/developer/developer-06.html にて登録のこと.
0030: define('APPLICATION_ID', '*****************************************');

Yahoo! ウェブ検索Web サービス」を利用するためには、アプリケーション ID を取得する必要がある。http://help.yahoo.co.jp/help/jp/developer/developer-06.html から無料で取得できる。
取得した ID は定数 APPLICATION_ID に格納する。

サンプル・プログラムの解説:パラメータ渡し

0098: /**
0099:  * HTTP通信を行う
0100:  * @param string $url "http://" から始まるURL
0101:  * @param string $method GET,POST,HEAD (省略時はGET)
0102:  * @param string $headers その他の任意のヘッダ (省略時は"")
0103:  * @param array  $post POST変数を格納した連想配列("変数名"=>"値") (省略時はNULL)
0104:  * @param string $cookie Cookie(利用するときは常に$method="POST") (省略時は"")
0105:  * @return string 取得したコンテンツ/FALSE 取得エラー
0106: */
0107: function http($url$method='GET', $headers='', $post=NULL$cookie='') {
0108:     if ($cookie != '')  $method = 'POST';
0109:     $URL = parse_url($url);
0110: 
0111:     $URL['query'] = isset($URL['query']) ? $URL['query'] : '';      //クエリ
0112:     $URL['port']  = isset($URL['port'])  ? $URL['port']  : 80;        //ポート番号
0113: 
0114:     //リクエストライン
0115:     $request  = $method . ' ' . $URL['path'] . $URL['query'] . " HTTP/1.1\r\n";
0116: 
0117:     //リクエストヘッダ
0118:     $request .= 'Host: ' . $URL['host'] . "\r\n";
0119:     $request .= 'User-Agent: PHP/' . phpversion() . "\r\n";
0120: 
0121:     //Basic認証用のヘッダ
0122:     if (isset($URL['user']) && isset($URL['pass'])) {
0123:         $request .= 'Authorization: Basic ' . base64_encode($URL['user'] . ':'. $URL['pass']) . "\r\n";
0124:     }
0125: 
0126:     //追加ヘッダ
0127:     $request .= $headers;
0128: 
0129:     //POSTの時
0130:     if (strtoupper($method) == 'POST') {
0131:         while (list($name$value) = each($post)) {
0132:             $POST[] = $name . '=' . $value;
0133:         }
0134:         $postdata = implode('&', $POST);
0135:         $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
0136:         $request .= 'Content-Length: ' . strlen($postdata) . "\r\n";
0137:         if ($cookie != '')  $request .= "Cookie: $cookie\r\n";
0138:         $request .= "\r\n";
0139:         $request .= $postdata;
0140:     } else {
0141:         $request .= "\r\n";
0142:     }
0143: 
0144:     //接続
0145:     $fp = fsockopen($URL['host'], $URL['port']);
0146:     //エラー処理
0147:     if (!$fp)    return FALSE;
0148: 
0149:     //リクエスト送信
0150:     fputs($fp$request);
0151: 
0152:     //応答データ受信
0153:     $flag = FALSE;
0154:     while (! feof($fp)) {
0155:         $s = trim(fgets($fp));
0156:         if (preg_match('/^\<\?xml/', $s$arr) != 0) {
0157:             $response = $s;
0158:             $flag = TRUE;
0159:         } else if ($flag && preg_match('/^[0-9|a-f]+$/iu', $s) == 0) {
0160:             $response .= $s;
0161:         }
0162:     }
0163:     fclose($fp);
0164: 
0165:     return $response;
0166: }

WebAPI にパラメータを POST 渡しするため、ユーザー関数 http を用意している。
http関数については、「PHP で住所から緯度経度を求める」で紹介しているものとほぼ同じである。

サンプル・プログラムの解説:パラメータ受け

0083: /**
0084:  * 指定したパラメータを取り出す
0085:  * @param string $key  パラメータ名(省略不可)
0086:  * @param bool   $auto TRUE=自動コード変換あり/FALSE=なし(省略時:TRUE)
0087:  * @param mixed  $def  初期値(省略時:空文字)
0088:  * @return string パラメータ/NULL=パラメータ無し
0089: */
0090: function getParam($key$auto=TRUE$def='') {
0091:     if (isset($_GET[$key]))     $param = $_GET[$key];
0092:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0093:     else                            $param = $def;
0094:     if ($auto)  $param = mb_convert_encoding($param, INTERNAL_ENCODING, 'auto');
0095:     return $param;
0096: }

一方、このスクリプト自身もパラメータを受け取る必要があるので、ユーザー関数 getParam を定義し、GET/POST いずれの方法でもパラメータを受け取れるようにしている。

サンプル・プログラムの解説:テキストの分解

0290: /**
0291:  * 複数文要約インターフェース
0292:  * @param string $sentence 要約するテキスト
0293:  * @return string 要約もどき
0294: */
0295: function pseudodigest($sentence) {
0296:     $arr = mb_split("[。.]", $sentence);
0297:     $pseudodigest = '';
0298:     foreach ($arr as $str) {
0299:         if ($str == '')    continue;
0300:         if (($res = pseudodigest_sub($str . '')) == FALSE)  return FALSE;
0301:         $pseudodigest .= $res;
0302:     }
0303: 
0304:     return $pseudodigest;
0305: }

日本語係り受け解析」は 1 文しか解析できないため、入力されたテキストを句点で分解する。

サンプル・プログラムの解説:要約もどき

0196: /**
0197:  * 「Yahoo!JAPAN 日本語係り受け解析」を用いて日本文を要約する
0198:  * @param string $sentence 要約するテキスト(1文)
0199:  * @return string 要約もどき
0200: */
0201: function pseudodigest_sub($sentence) {
0202: //WebAPIにパラメータをPOST渡しする
0203:     $url = REQUEST_KAKARIUKE_URL;
0204:     $sentence = urlencode($sentence);
0205:     $post = array(
0206:         'appid'        => APPLICATION_ID,
0207:         'sentence'     => $sentence
0208:     );
0209: 
0210: //係り受け解析
0211:     $res = @http($url, 'POST', '', $post);
0212:     if ($res == FALSE)  return FALSE;
0213:     $items = array();
0214: 
0215: //PHP4用; DOM XML利用
0216:     if (isphp5over() == FALSE) {
0217:         $dom = @domxml_open_mem($res);
0218:         if ($dom == FALSE)  return FALSE;
0219:         if (($ResultSet = $dom->get_elements_by_tagname('ResultSet')) == NULL)   return FALSE;
0220:         $Result = $ResultSet[0]->get_elements_by_tagname('Result');
0221:         foreach ($Result as $val1) {
0222:             $ChunkList = $val1->get_elements_by_tagname('ChunkList');
0223:             foreach ($ChunkList as $val2) {
0224:                 $Chunk = $val2->get_elements_by_tagname('Chunk');
0225:                 foreach ($Chunk as $val3) {
0226:                     if (($node = $val3->get_elements_by_tagname('Id')) != NULL) {
0227:                         $id = (int)$node[0]->get_content();
0228:                     } else {
0229:                         $id = -1;
0230:                     }
0231:                     if (($node = $val3->get_elements_by_tagname('Dependency')) != NULL) {
0232:                         $dependency = (int)$node[0]->get_content();
0233:                     } else {
0234:                         $dependency = -1;
0235:                     }
0236:                     $phrase = '';
0237:                     $MorphemList = $val3->get_elements_by_tagname('MorphemList');
0238:                     foreach ($MorphemList as $val4) {
0239:                         $Morphem = $val4->get_elements_by_tagname('Morphem');
0240:                         foreach ($Morphem as $val5) {
0241:                             $node = $val5->get_elements_by_tagname('Surface');
0242:                             $phrase .= (string)$node[0]->get_content();
0243:                         }
0244:                     }
0245:                     $items[$id]['dependency'] = $dependency;
0246:                     $items[$id]['phrase']     = $phrase;
0247:                 }
0248:             }
0249:         }
0250: 
0251: //PHP5用; SimpleXML利用
0252:     } else {
0253:         $ResultSet = simplexml_load_string($res);
0254:         if (! isset($ResultSet->Result->ChunkList))    return FALSE;
0255:         foreach ($ResultSet->Result->ChunkList as $ChunkList) {
0256:             foreach ($ChunkList->Chunk as $Chunk) {
0257:                 $id = isset($Chunk->Id) ? (int)$Chunk->Id : (-1);
0258:                 $dependency = isset($Chunk->Dependency) ? (int)$Chunk->Dependency : (-1);
0259:                 $phrase = '';
0260:                 foreach ($Chunk->MorphemList as $MorphemList) {
0261:                     foreach ($MorphemList->Morphem as $Morphem) {
0262:                         $phrase .= (string)$Morphem->Surface;
0263:                     }
0264:                 }
0265:                 $items[$id]['dependency'] = $dependency;
0266:                 $items[$id]['phrase']     = $phrase;
0267:             }
0268:         }
0269:     }
0270: 
0271: //要約
0272:     $base;
0273:     foreach ($items as $id=>$item) {
0274:         if ($item['dependency'] < 0) {
0275:             $base = $item['phrase'];
0276:             break;
0277:         }
0278:     }
0279:     $pseudodigest = '';
0280:     foreach ($items as $item) {
0281:         if ($item['dependency'] == $id) {
0282:             $pseudodigest = $pseudodigest . $item['phrase'];
0283:         }
0284:     }
0285:     $pseudodigest .= $base;
0286: 
0287:     return $pseudodigest;
0288: }

要約を行うのはユーザー関数 pseudodigest_sub である。
前半で、「日本語係り受け解析」を呼び出し、文節と修飾ID を配列 $items に格納する。

後半が要約もどきの処理である。
修飾ID が無い(-1 が格納されている)文節がベースとなる述語である。これを探しだし、それを修飾している文節を並べる。

参考サイト

(この項おわり)
header