関数の値の補間方法


学生向け

「関数の値を補間するプログラムで遊ばせたら、補間のアルゴリズムが複雑だったせいか、伝えたかったことがまったく伝わらなかった」という話を聞いたので、ちょっとやってみました。

Manipulate[
 f = Function[{x}, 1 + Sin[a x]];
 start = -Pi;
 end = Pi;

 (*サンプリング*)
 samples = Table[{x, f[x]}, {x, start, end, 2 (end - start)/n}];

 (*補間*)
 g = Interpolation@samples;

 (*描画*)
 plot1 = Plot[f[ x], {x, start, end}, PlotLabel -> "Original"];
 plot2 = ListPlot[samples, PlotLabel -> "Samples"];
 plot3 = Plot[g[x], {x, start, end}, PlotLabel -> "Interpolating Function"];
 GraphicsGrid[{{plot1}, {plot2}, {plot3}}],
 {a, 1, 32, 1},
 {{n, 32}, 1, 64, 1}]

CDF Playerがインストールされていれば実際に動かして試せます。UMMでも動きます。

上から、オリジナルの関数・サンプリングデータ・補間結果です。

要は「サンプルをたくさん取ればいいってもんじゃないよ」という話で、標本化定理とかにつなげたかったのでしょう。そういう場合はMathematicaでやるのが簡単です。「補間」自体について学ばせたいときは、Javaのような低レベルな言語を使ってもいいでしょう。

はてなブックマークAtomAPIをOAuthで利用する方法(Java, scribe-java版)


はてなブックマークAtomAPIの使い方を紹介します。2010年末にOAuthをサポートしたので、ここでもそれを試します。言語はJava、ライブラリはscribe-javaを使います。前にoauth-signpostを使う方法を紹介しましたが、こちらのほうがおそらく簡単です(準備のためにクラスを1つ余計に作らなければなりませんが)。

アプリケーションの登録

アプリケーション登録ページでアプリケーションを登録し、Consumer keyとConsumer secretを取得します。

ライブラリの準備

4627847327scribe-バージョン番号.jarとcommons-codec-バージョン番号.jarを使えるようにします(このあたりの詳細については、拙著『Webアプリケーション』などを参照してください)。

APIを定義するクラス

TwitterのようなメジャーなAPIはあらかじめscribe-javaで定義されているので(一覧)、後のコードで「TwitterApi.class」などと書くだけでいいのですが、はてなはそこまでメジャーでもないようで、APIを定義するクラスを自分で作らなければなりません。

import org.scribe.builder.api.*;
import org.scribe.model.*;
import org.scribe.utils.OAuthEncoder;

public class HatenaApi extends DefaultApi10a {

  @Override
  public String getAccessTokenEndpoint() {
    return "https://www.hatena.com/oauth/token";
  }

  @Override
  public String getRequestTokenEndpoint() {
    String scope = "?scope=read_public%2Cwrite_public%2Cread_private%2Cwrite_private";
    return "https://www.hatena.com/oauth/initiate" + scope;
  }

  @Override
  public String getAuthorizationUrl(Token requestToken) {
    return String.format("https://www.hatena.ne.jp/oauth/authorize?oauth_token=%s",
            OAuthEncoder.encode(requestToken.getToken()));
  }
}

TwitterのOAuthと違うのは、どのような権利を求めるかを「score=...」という形で書いておくところです。

Access tokenとToken secretの取得

OAuthでユーザの権利を譲り受けるためのAccess tokenとToken secretを取得します。ここで紹介するコンソール上で行う方法の他に、ブラウザ上でリダイレクトを使う方法もありますが、両者の違いはTwitterの場合などと同じなので、Twitterでの方法を見れば、リダイレクトを使う方法も実現できるでしょう。

先に取得したConsumer keyとConsumer secretを下のコードに埋め込んでください。

import java.io.*;
import org.scribe.builder.*;
import org.scribe.model.*;
import org.scribe.oauth.*;

public class ScribeTokenCreator {

  public static void main(String[] args) throws Exception {
    // プロキシサーバの設定
    //System.setProperty("http.proxyHost", "proxy.example.net");
    //System.setProperty("http.proxyPort", "3128");
    //System.setProperty("https.proxyHost", "proxy.example.net");
    //System.setProperty("https.proxyPort", "3128");

    String consumerKey = ***** Consumer key *****;
    String consumerSecret = ***** Consumer secret *****;
    OAuthService service = new ServiceBuilder().provider(HatenaApi.class).apiKey(consumerKey).apiSecret(consumerSecret).build();

    Token requestToken = service.getRequestToken();
    //System.out.println(requestToken.getRawResponse());
    String authUrl = service.getAuthorizationUrl(requestToken);
    System.out.println("このURLにアクセスし、表示されるPINを入力してください。");
    System.out.println(authUrl);
    System.out.print("PIN:");

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String pin = br.readLine();

    Token accessToken = service.getAccessToken(requestToken, new Verifier(pin));
    System.out.println("Access token: " + accessToken.getToken());
    System.out.println("Token secret: " + accessToken.getSecret());
  }
}

上のコード実行するとURLが表示されるので、そのURLにブラウザでアクセスしてください。下のようなページが表示されます。scoreの記述によって内容は変わります。この例では、公開・非公開の両方の情報への読み書き権限を要求しています。

OAuthで許可を求められているところ

「許可」をクリックすると下のようなページが表示されるので、表示された文字列を先のコードからのプロンプトで入力してください。Access tokenとToken secretが得られます。

「許可」をクリックした結果

ブックマーク

譲り受けた権利を使って「http://b.hatena.ne.jp/atom/post」にURLをPOSTすれば、そのURLをはてなブックマークに登録できます。例として「http://www.google.com/」を登録するコードは次のようになります。先に取得したConsumer keyとConsumer secret、Access token、Token secretを埋め込んで実行してください。

import org.scribe.builder.*;
import org.scribe.model.*;
import org.scribe.oauth.*;

public class ScribeBookmark {

  public static void main(String[] args) throws Exception {
    // プロキシサーバの設定
    //System.setProperty("http.proxyHost", "proxy.example.net");
    //System.setProperty("http.proxyPort", "3128");

    // これはユーザによらない
    String consumerKey = ***** Consumer key *****;
    String consumerSecret = ***** Consumer secret *****;
    OAuthService service = new ServiceBuilder().provider(HatenaApi.class).apiKey(consumerKey).apiSecret(consumerSecret).build();

    // これはユーザごとに異なる
    Token accessToken = new Token(
            ***** Access token *****,
            ***** Token secret *****);

    // ターゲット
    String target = "http://www.google.com/";
    String xml = String.format("<entry xmlns='http://purl.org/atom/ns#'>"
            + "<title>dummy</title>"
            + "<link rel='related' type='text/html' href='%s' />"
            + "<summary type='text/plain'></summary>"
            + "</entry>",
            target);

    // HTTPリクエスト(POST)
    OAuthRequest request = new OAuthRequest(Verb.POST, "http://b.hatena.ne.jp/atom/post");
    request.addHeader("Content-Type", "application/octed-stream");
    service.signRequest(accessToken, request);
    request.addPayload(xml);
    Response response = request.send();

    // 結果の表示
    System.out.println(response.getCode());
    System.out.println(response.getBody());
  }
}

Twitterの場合との違うのは、リクエストボディにXMLを書かなければならないこと、それを送信するためにContent-Typeをapplication/octed-streamにしておくことです。

ブックマークの編集

ブックマークを編集したいときは、上記のPOSTのレスポンス・ヘッダ (Location) に対して、GETで現状を取得し、PUTで新しいデータを送信します。例として、現在のコメントに「♡」を追記するコードは次のようになります(javax.xml.parsers.*とorg.w3c.dom.*、org.xml.sax.*をインポートしておく必要があります)。タイトルははてな全体で共有されるので、よほどの理由がない限りは変更しない方がいいでしょう。

// 編集のためのURL
String editUrl = response.getHeader("Location");

// 現コメントの取得
// HTTPリクエスト(GET)
request = new OAuthRequest(Verb.GET, editUrl);
service.signRequest(accessToken, request);
response = request.send();
System.out.println(response.getCode());

// 結果(XML)の処理
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(response.getStream()));
String comment = doc.getElementsByTagName("summary").item(0).getTextContent();

// コメントの更新(「♡」を付加する)
String newComment = comment + "♡";
xml = String.format("<entry xmlns='http://purl.org/atom/ns#'>"
        //+ "<title>Google 2</title>"
        + "<summary type='text/plain'>%s</summary>"
        + "</entry>",
        newComment);

// HTTPリクエスト(PUT)
request = new OAuthRequest(Verb.PUT, editUrl);
request.addHeader("Content-Type", "application/octed-stream");
service.signRequest(accessToken, request);
request.addPayload(xml);
response = request.send();

// 結果の表示
System.out.println(response.getCode());
System.out.println(response.getBody());

補足

アプリケーションに与えた許可を取り消したいときは、「Myはてな」→「ユーザ設定」→「外部アプリケーション認証」→「外部のアプリケーションから、はてなのサービスを利用する」の順にクリックしてください。「https://www.hatena.ne.jp/はてなID/config/auth/provider」にアクセスしてもいいでしょう。

はてなブックマークAtomAPIをOAuthで利用する方法(Java, oauth-signpost版)


より簡単な方法:scribe-java版

はてなブックマークAtomAPIの使い方を紹介します。2010年末にOAuthをサポートしたので、ここでもそれを試します。言語はJava、ライブラリはoauth-signpostを使います。

アプリケーションの登録

アプリケーション登録ページでアプリケーションを登録し、Consumer keyとConsumer secretを取得します。

ライブラリの準備

4627847327signpost-core-バージョン番号.jar とcommons-codec-バージョン番号.jarを使えるようにします(このあたりの詳細については、拙著『Webアプリケーション』などを参照してください)。

Access tokenとToken secretの取得

OAuthでユーザの権利を譲り受けるためのAccess tokenとToken secretを取得します。ここで紹介するコンソール上で行う方法の他に、ブラウザ上でリダイレクトを使う方法もありますが、両者の違いはTwitterの場合などと同じなので、Twitterでの方法を見れば、リダイレクトを使う方法も実現できるでしょう。

先に取得したConsumer keyとConsumer secretを下のコードに埋め込んでください。

import java.io.*;
import oauth.signpost.*;
import oauth.signpost.basic.*;

public class HatenaTokenCreator {

  public static void main(String[] args) throws Exception {
    // プロキシサーバの設定
    //System.setProperty("http.proxyHost", "proxy.example.net");
    //System.setProperty("http.proxyPort", "3128");
    //System.setProperty("https.proxyHost", "proxy.example.net");
    //System.setProperty("https.proxyPort", "3128");

    OAuthConsumer consumer = new DefaultOAuthConsumer(
            ***** Consumer key *****,
            ***** Consumer secret *****);

    String scope = "?scope=read_public,write_public,read_private,write_private";
    OAuthProvider provider = new DefaultOAuthProvider(
            "https://www.hatena.com/oauth/initiate" + scope,
            "https://www.hatena.com/oauth/token",
            "https://www.hatena.ne.jp/oauth/authorize");

    String authUrl = provider.retrieveRequestToken(consumer, OAuth.OUT_OF_BAND);
    System.out.println("このURLにアクセスし、表示されるPINを入力してください。");
    System.out.println(authUrl);
    System.out.print("PIN:");

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String pin = br.readLine();

    provider.retrieveAccessToken(consumer, pin);
    System.out.println("Access token: " + consumer.getToken());
    System.out.println("Token secret: " + consumer.getTokenSecret());
  }
}

TwitterのOAuthと違うのは、どのような権利を求めるかを「score=...」という形で書いておくところです。上のコード実行するとURLが表示されるので、そのURLにブラウザでアクセスしてください。下のようなページが表示されます。scoreの記述によって内容は変わります。この例では、公開・非公開の両方の情報への読み書き権限を要求しています。

OAuthで許可を求められているところ

「許可」をクリックすると下のようなページが表示されるので、表示された文字列を先のコードからのプロンプトで入力してください。Access tokenとToken secretが得られます。

「許可」をクリックした結果

ブックマーク

譲り受けた権利を使って「http://b.hatena.ne.jp/atom/post」にURLをPOSTすれば、そのURLをはてなブックマークに登録できます。例として「http://www.google.com/」を登録するコードは次のようになります。先に取得したConsumer keyとConsumer secret、Access token、Token secretを埋め込んで実行してください。

import java.io.*;
import java.net.*;
import oauth.signpost.*;
import oauth.signpost.basic.*;

public class SignpostBookmark {

  public static void main(String[] args) throws Exception {
    // プロキシサーバの設定
    //System.setProperty("http.proxyHost", "proxy.example.net");
    //System.setProperty("http.proxyPort", "3128");

    // これはユーザによらない
    OAuthConsumer consumer = new DefaultOAuthConsumer(
            ***** Consumer key *****,
            ***** Consumer secret *****);

    // これはユーザごとに異なる
    consumer.setTokenWithSecret(
            ***** Access token *****,
            ***** Token secret *****);

    // ターゲット
    String target = "http://www.google.com/";
    String xml = String.format("<entry xmlns='http://purl.org/atom/ns#'>"
            + "<title>dummy</title>"
            + "<link rel='related' type='text/html' href='%s' />"
            + "<summary type='text/plain'></summary>"
            + "</entry>",
            target);

    // HTTPリクエスト
    URL url = new URL("http://b.hatena.ne.jp/atom/post");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("POST");
    connection.addRequestProperty("Content-Type", "application/octed-stream");
    consumer.sign(connection);

    // POSTによる送信
    connection.setDoOutput(true);
    OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
    writer.write(xml);
    writer.flush();
    writer.close();

    // 結果の表示
    System.out.printf("%s %s\n", connection.getResponseCode(), connection.getResponseMessage());
    BufferedReader br = new BufferedReader(new InputStreamReader(
            connection.getResponseCode() / 100 == 2
            ? connection.getInputStream()
            : connection.getErrorStream(),
            "UTF-8"));
    String line = null;
    while ((line = br.readLine()) != null) {
      System.out.println(line);
    }
  }
}

Twitterの場合との違うのは、リクエストボディにXMLを書かなければならないこと、それを送信するためにContent-Typeをapplication/octed-streamにしておくことです。

ブックマークの編集

scribe-java版で紹介した方法と同様です(手抜き)。

補足

アプリケーションに与えた許可を取り消したいときは、「Myはてな」→「ユーザ設定」→「外部アプリケーション認証」→「外部のアプリケーションから、はてなのサービスを利用する」の順にクリックしてください。「https://www.hatena.ne.jp/はてなID/config/auth/provider」にアクセスしてもいいでしょう。

テキストからキーワードを抽出する方法(Yahoo! キーフレーズ抽出API)


テキストからキーワードを抽出しようとするとき、形態素解析して単語の頻度を数えるくらいのことではうまくいかないという話をしました。(テキストからキーワードを抽出する方法(非推奨)

自然言語処理の複雑なアルゴリズムを試すのも勉強にはなるのですが、「今すぐ答えがほしい」という状況を想定して、Yahoo!のキーフレーズ抽出APIを使ってみましょう。

4627847327お約束ですが、APIの使い方の基本的なところから学びたいという場合は、拙著『Webアプリケーション構築入門』を参照してください。

準備

Yahoo!のAPIを使うために、アプリケーションIDを登録します。表現は「登録」ですが、実際には「取得」です(参考)。

アプリケーションIDを取得したら、「appid=アプリケーションID」という内容(右辺は適当に置き換える)のファイルyahoo.appidを作っておきます。

練習(APIの使い方の確認)

適当な文章をUTF-8で記述したファイルsentence.txtを用意し、以下のコマンドでAPIを呼び出します。

curl -d @yahoo.appid -d output=json --data-urlencode sentence@sentence.txt\

http://jlp.yahooapis.jp/KeyphraseService/V1/extract

結果はJSON形式で返ってくるので、それを処理する簡単なスクリプトyahookeywords.pyを用意します。

import sys, json

for line in sys.stdin:
    keywords = json.loads(line)
    for keyword, score in sorted(keywords.items(), key=lambda x:x[1], reverse=True):
        print score, keyword.encode('utf-8')

先のコマンドとパイプでつなげば完成です。

curl -d @yahoo.appid -d output=json --data-urlencode sentence@sentence.txt\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
 python yahookeywords.py

日本国憲法の前文をsentence.txtに書いておいた場合の結果は以下の通りです。

100 われら
71 諸国民
52 代表者
51 憲法
47 主権
43 自国
43 他国
42 日本国民
41 わが国全土にわ
41 原理
41 恵沢
38 法則
38 隷従
37 詔勅
34 国家
34 惨禍
34 専制
33 いづれ
33 協和
33 理想

本番

日本語の文章がtweets.txtに書かれていると仮定します。ここでは例によって、Streaming APIで取得したつぶやきの処理方法で紹介したもの、つまり「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のもの(本文のみ)を使います。

Yahoo! キーフレーズ抽出APIは、1回のリクエストのサイズが100KBまで(URLエンコードした状態で?)に制限されているので、テキストから「( #iwakamiyasumi live at http:…)」と「http…」という文字列を削除しましょう。

cat tweets.txt | sed -e "s/(.*live.at.*)//g" | sed -e "s/http.*[:space]//g" > tmp

補足:Ustream上でつぶやかれたものだけをとりだすなら、「( #iwakamiyasumi live at http:…)」が含まれるものだけを残してください。

方針1:ランダムサンプリング

ここで利用するテキストは、上述の準備をした後でもサイズ制限を超えているので、小さくなるようにランダムサンプリングをしてからAPIを呼び出してみましょう(「3/13」がサンプリングレートです。この値は適当に調整してください)。

cat tmp | awk '{ if (rand() < 3/13) print $0; }' |\
curl -d @yahoo.appid -d output=json --data-urlencode sentence@-\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
 python yahookeywords.py

結果は以下のようになり、以前の結果と比べればずいぶんよくなっています(ランダムサンプリングなので、結果は実行ごとに変わります)。

100 上杉隆
99 岩上
66 uesugitakashi
61 iwakamiyasumi
46 IWJ・UST
46 上杉隆氏ファイナルインタビュー
43 ジャーナリスト休業直前
41 IWJ_UESUGI
38 ジャーナリズム
38 kfuruta777
36 ch1
35 T7OSEDyz
35 インターン
34 journalist
33 岩上安身
32 ホロスコープネイタル
32 自由報道協会
31 Toraji Tachiki
31 プレゼンテーター
31 佐々木俊尚

方針2:テキストの分割

テキストを分割し、APIを複数回呼び出すことも考えられます(実行前にresult.txtやx*を削除してください)。

split --line-bytes 30k tmp

for file in x*
do
  curl -d @yahoo.appid -d output=json --data-urlencode sentence@$file\
   http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
   python yahookeywords.py >> result.txt
done

cut -d ' ' -f 2 result.txt | sort | uniq

これによって得られるキーフレーズは以下のようになります(APIが変わらなければ、結果はいつも同じになります)。

DEMA, GGB9500, IWJ, IWJ1ch, IWJ_UESUGI, IWJ・UST, Journalist, NHKスペシャル, P8B88dXY, T7OSEDyz, Toraji, U-st, Ust, Web, asd59, ch1, genpatsu, higahayato, iwakamiyasumi, iwakamiyasumi&quot, iwakamiyasumi2, iwakamiyasumi3, iwj_uesugi, kfuruta777, kinoryuichi, komatsunotsuma, ky00950, kyokonoguchi, pkashima, uesugitakashi, w, おしどりマコ, つねつぐ氏, アクセス権, アワード, インターン, インタビュー, ウギリークス, ガサ入れ, ガンバッテ, グリニッジ, ゴルフジャーナリスト, ジャーナリスト, ジャーナリスト休業直前, ジャーナリズム, デマYAROU, デマYallo, デマYaroo, デマyallo, デマ野郎, ニューヨークタイムズ, ハイパーメディアキュリエーター, ヒマナイヌ川井, ピューリッツアー賞, ピューリッツア賞もの, ピュリッツァー, プチ鹿島, プルト君, プレゼンテーター, ホロスコープネイタル, マイク, メディア, メルマガハ, 安川@shinfonic, 炎上ビジネス, 可視化, 岩上, 岩上さん上杉, 岩上安身, 記者クラブ, 恒次, 黒い池上彰, 今西, 佐々木俊尚, 佐藤栄作久, 自由報道協会, 社団法人, 小沢, 小渕, 上杉, 上杉リークス, 上杉隆, 上杉隆ファイナルインタビュー, 上杉隆氏ファイナルインタビュー, 上杉隆氏ラストインタビュー, 神々しい岩神, 世川行介, 孫崎, 孫碕, 大批判, 池田信夫, 朝生, 田中真紀子, 読売ツネツグ, 読売新聞, 日隅, 畠山, 鳩山, 鳩山ゆきよ, 鳩山由紀夫, 福島県民, 立花隆, 鈴木宗男

先の結果よりは冗長ですが、もともとの映像がどういうものだったのか、だいたいわかるようなものになっています。

テキストからキーワードを抽出する方法(非推奨)


Streaming APIで大量のつぶやきをリアルタイムに保存する方法(cURL編)で述べたように、Ustreamなどで重要な映像が配信されるときには、Twitterなどでその内容をtsudaってくれる人がいます。そうやって生み出される大量のテキストが、映像のメタ情報としてもっと活用されるとうれしい、という話です。

最初に試したいのはキーワードの抽出です。

テキストファイルからキーワードを取り出そうとするとき、多くの学生はまず「形態素解析」を試みます。自然言語処理についてちゃんと学びたいときは、こういうところからじっくり勉強するといいのでしょうが、単に形態素解析するだけではあまりいい結果は得られません。

実際にやってみましょう。

4873114705形態素解析システムの使い方は、『入門 自然言語処理』(オライリー, 2010)などで紹介されていますが、環境によってはもう少し簡単です。たとえば、Ubuntuの場合、有名な日本語形態素解析システムMeCabをPythonで使うための準備は、次のようにとても簡単です。

sudo apt-get install python-mecab mecab-jumandic-utf8

インストールが終わったら、Pythonのインタラクティブシェルで動作を確認します。

import MeCab
mecab = MeCab.Tagger()
sentence = "すもももももももものうち"
print mecab.parse(sentence)

実行結果は次の通りです。

す	接頭辞,名詞接頭辞,*,*,す,す,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
の	助詞,接続助詞,*,*,の,の,*
うち	名詞,副詞的名詞,*,*,うち,うち,*
EOS

というわけで、うまくいってませんが先に進みましょう。形態素ごとに何らかの処理を行う練習です。

node = mecab.parseToNode(sentence)
node = node.next
while node:
    print node.surface, node.feature
    node = node.next

実行結果は先の場合とほとんど同じになります。形態素がnode.surfaceに、その素性がnode.featureに格納されます。

PythonでMeCabを使う方法がわかったので、形態素の出現回数を数えてみましょう。出現回数た多い単語がキーワードであろう、という発想です。対象テキストは、Streaming APIで取得したつぶやきの処理方法で紹介したもの、つまり「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のものです(本文のみ)。そのテキストが、ファイルtweets.txtに格納されていると仮定します。

次のようなコード(wordcounter.py)は、名詞と見なされたものの出現回数を数え、大きいものから順に出力します(ASCII数字のみからなる単語は除く)。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import MeCab, sys

mecab = MeCab.Tagger()
counter = {}

for line in sys.stdin:
    node = mecab.parseToNode(line)
    node = node.next
    while node:
        if node.feature.startswith("名詞,"):
            if not node.surface.isdigit():
                counter[node.surface] = counter.get(node.surface, 0) + 1
        node = node.next

for surface, count in sorted(counter.items(), key=lambda x:x[1], reverse=True):
    print count, surface

cat tweets.txt | python wordcounter.py | head -n 20」などとして、出現回数が多いものから順に20個抽出すると次のようになります。


1509 iwakamiyasumi
1433 t
1406 co
1406 http
1301 live
1301 at
533 上杉
261 隆
157 岩上
121 記者
112 福島
106 ジャーナリスト
81 IWJ
73 クラブ
70 報道
64 uesugitakashi
63 こと
62 w
57 bCljBsCQ
54 ー

というわけで、あまりうまくいっていませんが、形態素解析システムの使い方がわかったので、とりあえずこれでよしとしましょう。

もう少し楽な方法を後で紹介します。

テキストからキーワードを抽出する方法(Yahoo! キーフレーズ抽出API)

Streaming APIで取得したつぶやきの処理方法


かつて、Streaming APIで大量のつぶやきをリアルタイムに「保存する方法」を紹介しました(cURL編Python編)。つぶやきはJSON形式で保存していましたが、そこから何かを見出すためにはまず、JSON形式のデータを処理できなければなりません。ここではその方法を確認します。

Pythonで簡単に処理する方法を、@nokunoさんが紹介しています(Twitter Streaming APIの使い方)。cURLで取得したつぶやきは、1行に1つずつファイルに格納されるので、次のように1行ずつ取り出してJSONデータをパースすればいいようです(ここではtweet['text']、つまりつぶやきの本文だけを取り出しています)。

#!/usr/bin/env python
import sys, json
for line in sys.stdin:
    tweet = json.loads(line)
    if 'text' in tweet:
        print tweet['text'].encode('utf-8')

cURL編Python編でつぶやきを保存したファイルがresult.datだとすると、「python parse.py < ressult.dat」などとして本文だけを取り出すことができそうです。

Streaming APIのsample.jsonならこれでもいいのですが、filter.jsonを使うときには、条件に合うつぶやきが途絶えることがあります。そういうときには、30秒ごとにCR LFが送信されてきて、それがファイルに書かれることになるので、上のように1行ずつ処理しようとすると、ValueErrorが発生して処理が途中で止まってしまいます(cURLの場合)。ですから、次のようにValueErrorを無視しなければなりません(PythonのコードではJSON形式だけを出力していたので問題ありません)。

#!/usr/bin/env python
import sys, json
#from pprint import pprint

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        #print type(tweet['text'])
        print tweet['text'].encode('utf-8')
    except ValueError:
        pass

入力がstr型でも出力の一部はunicode型になったりするのが紛らわしい。Pythonを使うのはバージョン3が主流になってからにしたいですね、細かいことですが。

つぶやかれた日時はtweet['created_at']で取得できるのですが(このあたりのことはpprint()で確認できます)、この値は「Thu Dec 29 04:27:28 +0000 2011」のように標準時なので、日本ではちょっと使いにくいです。そこで、日本時間の年月日時分秒(20111231000000)のような表現に変換する関数を用意します。

#!/usr/bin/env python
import sys, json, time, calendar
#from pprint import pprint

def YmdHMS(created_at):
    time_utc = time.strptime(created_at, '%a %b %d %H:%M:%S +0000 %Y')
    unix_time = calendar.timegm(time_utc)
    time_local = time.localtime(unix_time)
    return int(time.strftime("%Y%m%d%H%M%S", time_local))

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        print YmdHMS(tweet['created_at']), tweet['text'].encode('utf-8')
    except ValueError:
        pass

リツイートを除外したいときは、次のようにすればいいでしょう。

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        if 'retweeted_status' not in tweet:
            print YmdHMS(tweet['created_at']), tweet['text'].encode('utf-8')
    except ValueError:
        pass

cURL編で書いたように、こういうことをしているのは、Ustreamなどで配信された動画の整理のためなので、保存したつぶやきは、時間を指定してとりだせるようにしておきましょう。

以下のようなスクリプト(parse.py)を使えば、「python parse.py 20111230150000 20111230174600 < result.dat」などとして、日本時間の2011年12月30日15時から2011年12月30日17時46分までのつぶやきだけを取得できます(日時の指定は省略してもかまいません)。

#!/usr/bin/env python
import sys, json, time, calendar
#from pprint import pprint

def YmdHMS(created_at):
    time_utc = time.strptime(created_at, '%a %b %d %H:%M:%S +0000 %Y')
    unix_time = calendar.timegm(time_utc)
    time_local = time.localtime(unix_time)
    return int(time.strftime("%Y%m%d%H%M%S", time_local))

argv = sys.argv
start_time = 0
end_time = 99999999999999
if 1 < len(argv):
    start_time = int(argv[1])
    end_time = int(argv[2])

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        if 'retweeted_status' not in tweet:
            tweet_time = YmdHMS(tweet['created_at'])
            if start_time <= tweet_time and tweet_time <= end_time:
                tweet_sec = tweet_time-start_time
                screen_name = tweet['user']['screen_name']
                text = tweet['text'].encode('utf-8')
                url = "https://twitter.com/#!/%s/status/%s"\
                    % (screen_name, tweet['id_str'])
                #print tweet_sec, url, text
                #print text
                t = time.strptime(str(tweet_time), "%Y%m%d%H%M%S")
                print time.strftime("%H:%M:%S", t), text
    except ValueError:
        pass

どのような形式で出力するかは、後で何に使いたいかで決まります。たとえばyouTube上でキャプションを付けたいなら、YouTubeがサポートする形式(参考)にあわせるといいでしょう。

実際に、「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のものを抜き出すと下のようになります(スクロールバーを表示できない環境では閲覧できないかもしれません)。

Streaming APIで大量のつぶやきをリアルタイムに保存する方法(Python編)


なぜこんな技術が必要なのかは、cURL編で書きました。

Streaming APIを使うときは、接続が切れてしまったときにいかに接続し直すかというのがキモなわけですが、cURLで簡単に実現する方法がわからなかったので、240秒ごとに強制的に接続し直すという方法を採用しました。この方法には、(1)再接続のときにつぶやきを取りこぼす危険と重複して取得する可能性があること、(2)接続が切れてから再接続を試みるまでに平均で120秒かかる、という問題がありました。

Streaming APIは、つぶやきが無いときには「CR LF」を30秒おきに送信してくるので、これをチェックすれば切断を検出できるのですが、検出と再接続の機能を自分で実装するのは大変なので、ライブラリに頼りましょう。

ここでは、PythonでTwitterを利用するためのライブラリ、Tweepyを使います。Ubuntuなら次のように簡単にインストールできます。

#sudo apt-get install python-setuptools
#sudo easy_install tweepy

Tweepyは切断の検出と再接続の機能を持っているので、Streaming APIで受け取ったデータを出力するプログラム(stream.py)は次のように簡単に書けます。StreamListenerで、つぶやきに対応するon_status()ではなくon_data()をオーバーライドしているのは、データをそのまま(JSON形式で)出力したいからです。とりあえず全部保存しておいて、必要なものをあとで取り出すというわけです。JSON以外のものがdataとして来る場合もあるので、最初が「{」のものだけをJSONデータと見なして出力するようにしています(ちょっとかっこわるい)。

#!/usr/bin/env python

import tweepy

class StreamListener(tweepy.StreamListener):
    def on_data(self, data):
        if data.startswith("{"):
            print data

username = "Twitterのユーザ名"
password = "Twitterのパスワード"
keyword = "つぶやきを絞り込むためのキーワード"
auth = tweepy.auth.BasicAuthHandler(username, password)
stream = tweepy.Stream(auth, StreamListener())
#stream.sample()
stream.filter(track=[keyword])

Twitterのユーザ名とパスワード、つぶやきを絞り込むためのキーワード(例:「#iwakamiyasumi,#iwakamiyasumi2」。OR条件はカンマ、AND条件はスペースで連結)を入力して、「python stream.py >> result.dat」などとして実行すれば、条件に合うつぶやきがresult.datに書き込まれます。

「せっかくPython使っているんだから、この場でパースして・・・」と思うかもしれませんが、後で何が必要になるかわからないので、とりあえず生データを保存しておくのがいいでしょう(リアルタイムでアウトプットしたいという場合は別です)。ファイルではなくデータベースに保存してもいいのですが、話が複雑になるのでやめておきます(データベースを使う場合でも、後で何が必要になるかわからないので、全データを保存しておくのがいいでしょう)。

再接続できるからといって、通信の不安定な場所での利用はおすすめできません。通信の安定した環境に置いたサーバで動かしっぱなしにするのがいいでしょう。何らかの原因でプロセス自体が落ちたときのために、以下のようなスクリプトを使うといいかもしれません(通常の切断ではプロセスは落ちません)。

#!/usr/bin/env bash

while :
do
  python stream.py >> result.dat
  sleep 240
done

Streaming APIで取得したつぶやきの処理方法

Streaming APIで大量のつぶやきをリアルタイムに保存する方法(cURL編)


つぶやきを大量に取得したいときには、TwitterのふつうのAPIではなく、Streaming APIを使います。ふつうのAPIは1時間あたり350回しか使えないので、対象となるつぶやきが、APIを目一杯使って取得できる数を超えるとどうしようもありません。Streaming APIならこのような心配は無用で、条件を設定して一度接続すれば、設定した条件に合うつぶやきを大量に取得できます。厳密に言えば、これは程度の問題でしかなく、Streaming APIでも、すべてのつぶやきを取得できるわけではないのですが、ここでは、「ふつうのAPIに比べれば遙かにたくさん取得できる」と考えてください。

cURLを使うのが簡単です。次のようなシェルスクリプト(stream.sh)を作成し、「bash stream.sh >> result.dat」などとして実行すれば、指定したキーワードを持つつぶやきを、result.datに保存することができます(「sudo apt-get install curl」などとしてcURLをインストールする必要があるかもしれません)。

キーワードはURLエンコーディングが必要です(ハッシュは「%23」、OR検索のためのカンマは「%2C」、AND検索のためのスペースは「%20」とすればいいのですが、日本語は現時点では難しそうです)。#iwakamiyasumiと#iwakamiyasumi2のどちらかを含むつぶやきを取得したいときは「%23iwakamiyasumi%2C%23iwakamiyasumi2」となります。

#!/usr/bin/env bash

username=Twitterのユーザ名
password=Twitterのパスワード
keyword=つぶやきをしぼりこむためのキーワード(URLエンコーディングが必要)

url=https://stream.twitter.com/1/statuses/filter.json
while :
do
  curl -d track=$keyword -m 240 -u $username:$password $url
done

240秒ごとにcURLの接続をリセットしています。この方法には、途中で接続が切れても最大240秒待てば自動的に再接続されるというメリットがありますが、リセット時につぶやきを取りこぼす危険や、つぶやきを重複して取得する可能性があります。この間隔(240)を小さくすると、Twitterから接続を断られるかもしれないので注意して下さい。この数字は再接続の最大待ち時間です(参考:Streaming API Concepts)。つぶやきがないときは、30秒ごとに「CR LF」が送信されてくるので、これをチェックすれば切断を検出できるのですが、自分で実装するのは大変なので、あとで別の方法を紹介しましょう。(30秒で2バイトなので、cURLの「-y」や「-Y」では対応できないのが残念です。)

取りこぼしを少なくするためにこのスクリプトは、インターネット環境が不安定な出先ではなく、インターネット環境が安定したサーバで動かしたままにしておくといいでしょう。とりあえず全部取っておいて、あとで必要な部分だけ抜き出すというわけです(抜き出し方はあとで紹介しましょう)。

以下の2点については後で書きます。

  • 接続が切れたときにスマートに再接続する方法Python編で紹介しました)
  • 保存したデータから必要な部分だけを取り出す方法書きました

なぜこういう技術が必要なのでしょう。

Ustreamのような動画配信サービスが登場し、それを活用するジャーナリストの活躍を見ると、「記者クラブメディアが諸悪の根源だ」という状況も少しずつ改善するのではないか、と期待してしまいます(最近の面白かった映像は「読売新聞記者vs上杉隆氏と岩上安身さん」)。たとえば、岩上安身(@iwakamiyasumi)さんが率いるIndependent Web Journal (IWJ)のおかげで私たちは、これまでなら記者クラブメディアの能力不足とフィルタリングのせいで人目に触れずに消えていたであろう映像を見ることができます。

しかし、IWJのUstreamスケジュールを見てもわかるように、その量は膨大で、全部見るのはほとんど不可能です。自分にとって重要な映像がすぐに見つかるようになっていればいいのですが、現在の映像検索技術では難しいでしょう。よくやられるのは映像に付与されたメタ情報(タイトルやタグ)などを使って検索することですが、タイトルはともかく、タグを適切に付けるのもかなりの労力だと思います(Togetterもしかり)。ソーシャルメディア上で推薦システムを動かして・・・ということも考えられますが、自分が見た映像の記録がすべて保存され、活用されるのをいやがる人もいるかもしれません(話がそれました)。

幸運なことに、著名なジャーナリストが配信する映像は、一般的な映像とは違います。特に記者会見の映像には、「tsudaる」というボランティア行為による膨大な文字情報が付随します。これを活用するために、まずはその文字情報を保存しよう、というのが今日のお話。

Ustreamには、動画に対する複数のソーシャルメディアからのコメントをまとめて表示する「ソーシャルコメント」という機能があるのですが、とりあえず、Twitterのつぶやきだけを保存することにします。こういうことの大切さが理解され、Ustreamが保存してあるソーシャルコメント()を使いやすい形で提供してくれることを願います。

OAuth認証でTwitterを利用するWebアプリケーション(PHPの場合)


OAuth認証が必要なAPIでは、Twitter APIとFacebook APIが有名ですが、ちょっと仕様が違うので、使い方を簡単に紹介しようとすると、記事を分けなければなりません。さらに、JavaとPHPの両方を学べるというお得な本を書いたこともあって、その読者をサポートするために、JavaとPHPの両方で記事を書かなければなりません。都合、以下の4パターンが必要です。

今回は、「PHPでTwitter API」です。「JavaでFacebook API」はまた別の機会に。

アプリの登録

http://dev.twitter.com/appsでアプリケーションを登録し、Concumer keyとConsumer secretを取得してください。その際、クライアントアプリケーションではなくブラウザウェブアプリケーションとして登録するように注意してください。

OAuthのためのライブラリ

PHPには、標準の「PECL/oauth」や準標準と言える「Pear HTTP_OAuth」、Twitterに特化した「Pear Services_Twitter」がありますが、例によって、PECLはWindowsで使いにくいし、HTTP_OAuthはベータ版、Services_Twitterはアルファ版なので、さっさと見限って「abraham / twitteroauth」を使いましょう。

OAuth.phpとtwitteroauth.phpをダウンロードして、ウェブサーバで配信できる場所に置きます。

cURL

XAMPP

PHPでcURLを使うために、php.iniに以下のように記述して、Apacheを再起動します。

extension=php_curl.dll

Ubuntu

次のコマンドを実行してcURLモジュールをインストールしてからApacheを再起動します。

sudo apt-get install php5-curl

4627847327お約束ですが、このあたりでもうついて行けないという場合は、拙著『Webアプリケーション構築入門』などを参照してください。以下のPHPプログラミングで必要な知識も本書にまとめてあります。

OAuth認証

以下の3画面で認証することにします(oauth-start.phpとoauth-end.phpは一つのファイルにまとめることもできますが、ここではわかりやすいように二つに分けています)。

  1. OAuth認証のためのURIを生成し、それにアクセスするためのリンクを表示するoauth-start.php
  2. アプリケーションを許可するTwitterページ
  3. Twitterからのコールバックを受信し、access tokenとtoken secretを取得するoauth-end.php

実装

oauth-start.php

OAuth認証の入り口となるoauth-start.jspは以下のようになります。「Consumer key」や「Consumer secret」、コールバックURIの部分は、状況に合わせて書き換えてください。

<?php
require_once('twitteroauth.php');

session_start();

//セッションを節約したい場合は別ファイルに書いておく。
$_SESSION['consumer_key'] = Consumer key;
$_SESSION['consumer_secret'] = Consumer secret;
$callbackUri = 'http://localhost/twitter/oauth-end.php';

$connection = new TwitterOAuth($_SESSION['consumer_key'], $_SESSION['consumer_secret']);
$request_token = $connection->getRequestToken($callbackUri);
$_SESSION['oauth_token'] = $token = $request_token['oauth_token'];
$_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];

$authUrl = $connection->getAuthorizeURL($token);
?>
<!doctype html>
<html>
  <head>
    <title>OAuth Start</title>
  </head>
  <body>
    <p><a href="<?php echo $authUrl; ?>">Twitter OAuth認証開始</a></p>
    <p><a href="logout.php">logout</a></p>
  </body>
</html>

oauth-end.php

コールバック後の処理を実装するoauth-endは以下のようになります。

<?php
require_once('twitteroauth.php');

session_start();

$connection = new TwitterOAuth($_SESSION['consumer_key'], $_SESSION['consumer_secret'],
                $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']);
$access_token = $connection->getAccessToken($_GET['oauth_verifier']);
unset($_SESSION['oauth_token']);
unset($_SESSION['oauth_token_secret']);

//セッションに記録する。データベースなどを使ってもよい。
$_SESSION['access_token'] = $access_token;
?>
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Twitter OAuth認証完了</title>
  </head>
  <body>
    <pre><?php print_r($access_token); ?></pre>
    <p>access_tokenをセッションに保存した</p>
    <p><a href="post.php">送信テスト</a></p>
    <p><a href="logout.php">logout</a></p>
  </body>
</html>

つぶやく

認証が終わったら、以下のようなコードでつぶやけます。

<?php

header('Content-type:text/plain; charset=utf-8');
require_once('twitteroauth.php');

session_start();

$access_token = $_SESSION['access_token'];
$connection = new TwitterOAuth($_SESSION['consumer_key'], $_SESSION['consumer_secret'],
                $access_token['oauth_token'], $access_token['oauth_token_secret']);

$message = "テスト at " . time();
$parameters = array('status' => $message);
$status = $connection->post('statuses/update', $parameters);
print_r($status);

ブラウザを再起動すればはじめに戻りますが、次のようなlogout.phpを作っておいてもいいでしょう。

<?php
session_start();
session_destroy();
?>
<!doctype html>
<html>
  <head>
    <title>Logout</title>
  </head>
  <body>
    <p><a href="oauth-start.php">start</a></p>
  </body>
</html>

Wolfram CDF PlayerをMathematicaとして使う方法


http://www.unfindable.net/umm3/

計算可能ドキュメント形式(Computable Document Format, CDF)を閲覧するためのソフトウェアWolfram CDF PlayerとMathematicaの関係は、Portable Document Format (PDF)を閲覧するためのソフトウェアAdobe ReaderとAcrobatの関係に似ています。Wolfram CDF Playerで閲覧可能なCDF文書を作るにはMathematicaが、Adobe Readerで閲覧可能なPDF文書を作るにはAcrobatが必要です。

しかし、CDFとPDFには大きな違いがあります。PDF文書は内容が固定された静的な文書であるのに対して、CDF文書は内容を変化させられる動的な文書です。下はCDF文書の簡単な例です。Wolfram CDF Playerがインストールされている環境なら、aの値を変えながらSin[a x]をプロットしてみることができます。CDF文書の内容は計算によって変化するのです。

Sin[a x]のaの値を変えられるなら、もっと大胆に「Sin[a x]」という式全体を変えられるのではないかと考えるのは自然でしょう。Mathematicaの式を処理できるCDF文書、それはMathematicaとして使えるCDF文書です。使い勝手は多少悪くても、「Mathematicaを使いたいけれど高すぎて買えない」という人にとっては有用でしょう。みんな大好きWolframAlphaも、Mathematicaのすべての機能を使えるわけではありませんし。

残念ながら、直接的な方法はうまくいきません。CDF文書に入力できるのは数値だけであり、「Sin[a x]」のような文字列は入力できないからです。しかし、コンピュータ上で表現されるすべてのものは、メモリ上では数で表現されています。「Sin[a x]」のような式ももちろん数で表現されています(メモリのことがよくわからない人は、ゲーデル数を思い出してもいいでしょう)。ですから、Mathematicaの式を一度数値に変換してからCDF文書に入力し、CDF文書内でそれを元に戻すというような工夫をすれば、CDF文書で式を扱えます。このアイディアを実現したのが、以前紹介したUniversal Mathematica Manipulator (UMM)です。

UMMには、Mathematicaの式を変換してできる数値が長大なため入力に手間がかかるという問題がありました。Wolfram CDF Playerには、文書上でクリップボードからの貼り付けができないという制約があるため、長大になる数値をCDF文書上で入力するための面倒な仕掛け(VBScriptやAppleScript)が必要でした。(Mathematicaに付属するCDF Playerなら貼り付けられます。このように仕様がばらばらなことが後で混乱を生まないことを祈ります。)

ここでは別の方法を紹介します。

まず、Wolfram CDF Playerをインストールします。これがなくては始まりません。

次に、PHPを使えるウェブサーバをlocalhostに用意します。WindowsならXAMPPを導入するのが簡単でしょう。

下のような内容のexpression.phpをhttp://localhost/umm3/expression.phpというURLで呼び出せるようにしておきます。ディレクトリumm3は、ウェブサーバから書き込めるようにしてください。

<?php

$file = 'expression.txt';

if (isset($_GET['body'])) {
  file_put_contents($file, $_GET['body']);
  echo $_GET['callback'].'()';
} else {
  if (is_file($file)) echo file_get_contents($file);
  echo '(* OK *)';
}

PHPのmagic_quotes_gpcがOffであることを確認します。XAMPPの場合はデフォルトでOffになっています。Macの場合はphp.iniを編集してApacheを再起動する必要があるかもしれません。

4627847327お約束ですが、上の手順がよくわからない人は、拙著『Webアプリケーション構築入門』などを参照してください。

以上の準備ができたらhttp://www.unfindable.net/umm3/にアクセスします。ピタゴラス3体問題のような比較的大きなプログラムも実行できることを、Wolfram CDF Player 8.0.4(Win, Mac)で確認しています。