ヤフーは4日、開発者向けにYahoo! JAPANのサービスやデータベースへのアクセスを無料で公開するサービス「Yahoo!デベロッパーネットワーク」において、テキストから特徴的な表現(キーフレーズ)を抽出する「キーフレーズ抽出Web API」を公開した。

「キーフレーズ抽出Web API」は日本語のテキストを解析し、文章中からキーフレーズを抽出して回答するWeb API。テキストをパラメーターとして含むリクエストURLにアクセスすると、抽出されたキーフレーズと各キーフレーズの重要度を示す数値のリストが回答として返ってくる。

例えば、「東京ミッドタウンから国立新美術館まで歩いて5分で着きます」という文章からは、「東京ミッドタウン(重要度100)」「国立新美術館(重要度54)」「5分(重要度9)」といった解析結果が得られる。これにより、ニュースなどのテキストからメインテーマと考えられるフレーズを抜き出したり、キーフレーズを組み合わせた類似文検索といった用途が考えられるとしている。

ヤフー、「キーフレーズ抽出Web API」を公開

特徴語抽出ってやつかな?
とりあえず、サンプルをJavaで作って実行してみた。

サンプル実行結果


[解析対象のテキスト]
複数のキーワードをまとめて緯度経度に変換したいんだけど、Google とか Yahoo! のジオコーダはそういうのを許してくれるんだろうか。API的にまとめて送れないから連発投入という形になるけど。
[解析結果]
Yahoo!: 100
ジオコーダ: 66
緯度経度: 65
連発投入: 43
キーワード: 39
API的: 31
変換: 31
複数: 30
Google: 28
形: 16
 
[解析対象のテキスト]
ページ作って10分もたたないうちに1番上なんておかしいだろJK.
[解析結果]
JK.: 100
ページ: 35
うち: 25
1番上: 20
10分: 11
 
[解析対象のテキスト]
住所: 愛知県稲沢市稲沢中島都市計画事業尾張西部都市拠点地区 土地区画整理事業13街区2-2, 3画地 リーフウォーク稲沢 1F ( ゜Д゜) なんだこの住所・・・
[解析結果]
愛知県稲沢市稲沢中島都市: 100
リーフウォーク稲沢: 97
土地区画整理事業13街区: 66
住所: 66
3画地: 23
1F: 6
 
[解析対象のテキスト]
おそらく中央相互銀行が配布していた「日本語の常識辞典」という小冊子。小冊子といってもけっこう分厚かったりする。青い鳥症候群。20年前に専門家が「言ってみただけ」かもしれないけど。20年前の現代語「パーペキ」
[解析結果]
小冊子: 100
パーペキ: 91
青い鳥症候群: 75
現代語: 68
中央相互銀行: 54
常識辞典: 53
専門家: 48
日本語: 39
配布: 37
20年前: 26
 
[解析対象のテキスト]
こんな感じの分量でシェイクして完成。
・マスタード(粒じゃないやつ) 大さじ3
・オリーブオイル 大さじ3
・白ワインビネガー 大さじ3
・塩 適量
・コショウ 適量
・レモン(ポッカレモン) 適量
[解析結果]
大さじ: 100
適量: 85
大さじ3: 64
マスタード: 63
ポッカレモン: 63
白ワインビネガー: 59
オリーブオイル: 55
シェイク: 54
コショウ: 53
分量: 40
粒: 28
完成: 27
やつ: 24
塩: 22
感じ: 15
 
[解析対象のテキスト]
「高架下が見えるぐらい立体的」といううわさを聞いてさいしょに見たときは、ゲートタワービルの道路部分はぜんぜん空中に浮いてなくて、地表0mにくっついてて「立体的に見えるけどこれは地表部分に貼ってある航空写真の目の錯覚。高さデータないから0m地点に表示してるのかなー」みたいに言ってたけど、じつはちがうPCに変えたらちゃんと立体的な高架が見れたという。あのPC環境はもしかして Google Earth 4 だったんだろうか。たしかに、高架は地表を這っていたのに。。。
[解析結果]
地表: 100
航空写真: 64
高架下: 56
ゲートタワービル: 53
地表部分: 46
うわさ: 43
Google Earth 4: 40
空中: 38
錯覚: 37
道路部分: 34
さい: 32
地表0m: 32
高さデータ: 29
PC環境: 27
0m地点: 25
表示: 23
目: 11

サンプルコード


import java.io.*;
import java.net.*;
 
import javax.mail.internet.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
 
import org.w3c.dom.*;
import org.xml.sax.*;
 
/**
 * Yahoo!デベロッパーネットワーク - テキスト解析 - キーフレーズ抽出
 * http://developer.yahoo.co.jp/webapi/jlp/keyphrase/v1/extract.html
 */
public class KeyPhraseExtractSample {
 
  public static void main(String[] args) throws Exception {
 
    // 入力パラメータ
    String appid = ""; // <- アプリケーションIDを取得して設定
    String[] sentence = {
      "複数のキーワードをまとめて緯度経度に変換したいんだけど、Google とか Yahoo! のジオコーダはそういうのを許してくれるんだろうか。API的にまとめて送れないから連発投入という形になるけど。",
      "ページ作って10分もたたないうちに1番上なんておかしいだろJK.",
      "住所: 愛知県稲沢市稲沢中島都市計画事業尾張西部都市拠点地区 土地区画整理事業13街区2-2, 3画地 リーフウォーク稲沢 1F ( ゜Д゜) なんだこの住所・・・",
      "おそらく中央相互銀行が配布していた「日本語の常識辞典」という小冊子。小冊子といってもけっこう分厚かったりする。青い鳥症候群。20年前に専門家が「言ってみただけ」かもしれないけど。20年前の現代語「パーペキ」",
      "こんな感じの分量でシェイクして完成。\n・マスタード(粒じゃないやつ) 大さじ3\n・オリーブオイル 大さじ3\n・白ワインビネガー 大さじ3\n・塩 適量\n・コショウ 適量\n・レモン(ポッカレモン) 適量",
      "「高架下が見えるぐらい立体的」といううわさを聞いてさいしょに見たときは、ゲートタワービルの道路部分はぜんぜん空中に浮いてなくて、地表0mにくっついてて「立体的に見えるけどこれは地表部分に貼ってある航空写真の目の錯覚。高さデータないから0m地点に表示してるのかなー」みたいに言ってたけど、じつはちがうPCに変えたらちゃんと立体的な高架が見れたという。あのPC環境はもしかして Google Earth 4 だったんだろうか。たしかに、高架は地表を這っていたのに。。。",
    };
    
    for(int i=0; i<sentence.length; i++){
      doit(appid, sentence[i]);
    }
  }
  
  private static void doit(String appid, String sentence){
    try{
      // リクエストURL
      String requesturl = "http://jlp.yahooapis.jp/KeyphraseService/V1/extract";
      
      // キーフレーズ抽出APIを使う
      String parameters = getParametersString(appid, sentence);
      String xmlContent = getContent(new URL(requesturl), parameters);
      Document doc = getDocument(xmlContent);
      ResultSet rs = getResultSet(doc);
      
      // 結果を出力
      System.out.println("[解析対象のテキスト]");
      System.out.println(sentence);
      System.out.println("[解析結果]");
      for(int i=0; i<rs.result.length; i++){
        System.out.println(rs.result[i].Keyphrase + ": " + rs.result[i].Score);
      }
      System.out.println();
      
    }catch(Exception e){
      e.printStackTrace();
    }
  }
  
  private static String getParametersString(String appid, String sentence) throws UnsupportedEncodingException{
    String parameters =
      "appid=" + appid +
      "&" +
      "sentence=" + URLEncoder.encode(sentence, "UTF-8");
    return parameters;
  }
 
    private static int getLength(XPath xpath, Document doc, String expression) throws XPathExpressionException{
      NodeList nodelist = (NodeList)xpath.evaluate(expression, doc, XPathConstants.NODESET);
      if(nodelist != null){
        return nodelist.getLength();
      }else{
        return 0;
      }
    }
    
    private static String getString(XPath xpath, Document doc, String expression) throws XPathExpressionException{
      return xpath.evaluate(expression, doc);
    }
    
    // XML 文書文字列からDocumentオブジェクトを生成
    private static Document getDocument(String xmlContent) throws IOException, SAXException, ParserConfigurationException {
      StringReader sr = new StringReader(xmlContent);
      InputSource is = new InputSource(sr);
      Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
      return doc;
    }
    
    // URLからコンテンツ(HTML/XMLページの文字列)を取得
    private static String getContent(URL url, String parameters) throws IOException, ParseException {
 
      HttpURLConnection con = (HttpURLConnection)url.openConnection();
      con.setRequestMethod("POST");
      con.setDoOutput(true);
      con.connect();
      
      OutputStream os = con.getOutputStream();
      OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
      BufferedWriter bw = new BufferedWriter(osw);
      bw.write(parameters);
      bw.flush();
      bw.close();
      
      // ex. String ct = "text/xml; charset=\"utf-8\"";
      String ct = con.getContentType();
      String charset = "UTF-8"; // Content Type が無ければ UTF-8 KIMEUCHI
      if(ct != null){
        // JavaMail なクラス ContentType
        // Java SE にあればいいのに……
        String cs = new ContentType(ct).getParameter("charset");
        if(cs != null){
              charset = cs;
          }
      }
 
      InputStream is = con.getInputStream();
      InputStreamReader isr = new InputStreamReader(is, charset);
      BufferedReader br = new BufferedReader(isr);
      StringBuffer buf = new StringBuffer();
      String s;
        while ((s = br.readLine()) != null) {
          buf.append(s);
          buf.append("\r\n"); // 改行コードKIMEUCHI
        }
      br.close();
      con.disconnect();
      
      return buf.toString();
    }
 
  private static ResultSet getResultSet(Document doc) throws Exception {
    
    XPath xpath = XPathFactory.newInstance().newXPath();
    
    int length = getLength(xpath, doc, "ResultSet/Result");
    Result[] r = new Result[length]; 
    for(int i=0; i<length; i++){
      r[i] = new Result();
      r[i].Keyphrase = getString(xpath, doc, "ResultSet/Result[" + (i+1) + "]/Keyphrase");
      r[i].Score     = getString(xpath, doc, "ResultSet/Result[" + (i+1) + "]/Score");
    }
    
    ResultSet rs = new ResultSet();
    rs.result = r;
    return rs;
  }
  
  public static class ResultSet{
    public Result[] result;
  }
  
  public static class Result{
    public String Keyphrase; // キーフレーズ
    public String Score; // キーフレーズの重要度
  }
 
}

さいしょは HTTP GET でやってたけど解析対象の入力テキストが長すぎて 414 Request-URI Too Long なエラーがよく出てたので POST を使うようにした。

Ref. Yahoo!デベロッパーネットワーク - テキスト解析 - キーフレーズ抽出

tags: zlashdot WebServices Java WebServices

Posted by NI-Lab. (@nilab)