カレシの元カノの元カレ・・・をFacebookは知ってますよね


「カレシ、カレシの元カノ、カレシの元カノの元カレ・・・」というちょっと怖い広告がかつてありました。

あの広告が警告していたエイズは怖い病気ですが、もし、もっと怖い、たとえば、潜伏期間は人それぞれ、発症前に治療すれば100%治るが、発症すれば3日で死ぬ、そんな性感染症が発見されたら、すぐに検査を受けたくなりますよね。でも、(未だ収束していない)福島原発事故発生当時のような、「パニックを恐れる」エリートパニックによって、その事実が市民に知らされたときにはもう手遅れ、ということは十分あり得ます。

患者にインタビューしながら感染経路を調べて・・・なんてことをしてもやはり手遅れになるでしょう。

そこで、Facebookを使えないかなあ、とか思うわけです。Facebookでは、ユーザが自分で編集できる基本データに「恋愛ステータス」なる項目があって、交際相手を自己申告できるようになっています。自分の友人の登録状況は、次のアプリで一覧表示できます。

Friends Partner

表示させるには、交際ステータスへのアクセス許可が必要です。下のようなページが表示されたら、「インストール」をクリックしてください(http://www.facebook.com/settings/?tab=applicationsでアンインストールできます)。

私の友人たちの登録状況はこんな感じです。あまり登録していませんね(ウェブブラウザでなら友人以外の情報も見られるのですが、このアプリが利用しているAPIでは自分と友人の情報しか見られません)。

この交際相手、もちろん他人から見られるのは現在の相手だけですが、Facebookのサーバに、過去の相手が保存されていたら面白いですね(情報漏洩を恐れてちゃんと消しておくのが正しいポリシーでしょうが)。

最初に述べたような怖い病気が発生したときに、Facebookはその情報を公開するのだろうか、と思うのです。「公開する」と宣言すれば、人は将来の保険と思って登録するようになるでしょうか。「公開しない」と宣言すれば、情報漏洩を恐れる人も安心して登録するようになるでしょうか。あるいは逆の結果に?

「そういう病気が見つかって初めて過去の恋人たちを登録できるようなフォームが作られるだろうから今は何もしない」という人はまったく冷静ですばらしいです。そのペースで対応できる病気ならよいのですが。

Googleはそのミッションを変更する時期に来ているのでは?


Googleの現在のミッションは、

世界中の情報をアクセス可能にし、人々の役に立てる

だとされています。しかし、この「世界中の情報」に「Googleの検索アルゴリズムの詳細」は含まれていません。「Googleで検索して出てこないものは存在していないに等しい」と言われる時代、Googleのアルゴリズムは世界の成り立ちを決めていると言ってもいい時代です。検索結果に「Googleのアルゴリズム」が含まれていないのなら、それは「世界中の情報」とは呼べないでしょう。近いうちに出てくるということもないでしょう。ミッションはインポッシブルなのです。

Googleも営利企業ですから、自社の優位を保つために秘密を持つのはかまいません。そのこと自体を邪悪だとも思いません。でも、実現する気のないミッションを掲げたままにしておくのはいけません。邪悪です。

ついでに言うと、「アルゴリズムの詳細を公開するとチートされる」という言い方もどうかと思います。単純に「自社の優位を保つため」でいいと思いますが、それでは足りないというなら、せめて「世界中から優秀な人材を集めているが、公開してもチートされないようなアルゴリズムはまだ思いついていない」くらいにしておいてほしいものです。アルゴリズムとデータが分離できないような方式とか、いろいろやりようはあると思うのでがんばってください。

4484111160こんなことを考えたのは、レヴィ『グーグル ネット覇者の真実』(阪急コミュニケーションズ, 2011)を読んだからです。そこで紹介されていたジャック・ロマノス(サイモン&シェスターの元CEO)の発言が、最近のGoogleをよく表していると思います。

「ひたすら理想を追求しているかのような態度を取り、世界の知を拡大することだけが目的なのだと言うくせに、次の瞬間には、自分たちの流儀を受け入れなければ、今回の話はなかったことにすると言う」(p.565)

こんなことを言われない古き良きGoogleが私は好きでした。

http://rickwebb.tumblr.com/post/14467269283/god-i-am-getting-so-fed-up-with-google-this-is

グーグル検索の変化–問われる検索結果の関連性

関数の値の補間方法


学生向け

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

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のような低レベルな言語を使ってもいいでしょう。

「ポスドクからポストポスドクへ」


私は芥川賞を純文学の新人賞だと思っているので、すでにSF作家として広く認知されている円城塔さんが受賞したのにはちょっと不思議な感じがしました。まあ、円城塔さんの書きたいことを表現する方法として「SF」が最適かどうかはちょっと疑問があるので(SFの枠組みを広げているという言い方もできますが)、この受賞を機に読者層が広がって、もっとぴったりした方法をが見つかるかも、という期待はしています(僭越ながら)。

不可解な落選が過去にあったので、この受賞でみんなスッキリという効果もあるでしょうか(村上龍さんは選考会を欠席したそうですが)。

4150503648「円城塔」という名前の由来である、金子邦彦『カオスの紡ぐ夢の中で』が読まれて、金子さんの研究分野の周辺領域に興味を持つ人が増えると楽しいかも、と思っていたら、先に注目を浴びたのは、日本物理學會誌にかつて掲載された「ポスドクからポストポスドクへ」でした。あらあら。

アカデミズム版「政策より政局」みたいな話ではありますが、単純に言えば、

  • 若手の大学教員や研究者の一部、ポスドクと呼ばれる立場の人は「ワーキングプア」になっている。助手や助教はプアではないが、数年という短い任期が過ぎれば追い払われる立場にいる
  • (准)教授になってから時間が経った人の一部は、まともに教育・研究できないのはもちろん、もし首になったら再就職なんてとてもできない「使えないヤツ」になっている

ということなのですが、このような現実をこの芥川賞騒ぎの中で初めて知ったという大学生ももしかしたらいるかもしれません。大学院生がそうだととてもまずいでしょうね(あとで「自己責任」と言われるから気をつけて!)。

「ふだん偉そうにしている先生が実は・・・」ということを、時間的金銭的投資をしている学生たちはちゃんと知っておくといいでしょう。もちろん、それによって大学教員の権威は揺らぐわけですが、それくらいのことで話を聞いてもらえなくなる教員は、どうせたいしたことはないのです(ブーメランってやつです)。

『カオスの紡ぐ夢の中で』のほうが、やっぱり面白いですね。

はてなブックマーク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, 炎上ビジネス, 可視化, 岩上, 岩上さん上杉, 岩上安身, 記者クラブ, 恒次, 黒い池上彰, 今西, 佐々木俊尚, 佐藤栄作久, 自由報道協会, 社団法人, 小沢, 小渕, 上杉, 上杉リークス, 上杉隆, 上杉隆ファイナルインタビュー, 上杉隆氏ファイナルインタビュー, 上杉隆氏ラストインタビュー, 神々しい岩神, 世川行介, 孫崎, 孫碕, 大批判, 池田信夫, 朝生, 田中真紀子, 読売ツネツグ, 読売新聞, 日隅, 畠山, 鳩山, 鳩山ゆきよ, 鳩山由紀夫, 福島県民, 立花隆, 鈴木宗男

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

いちばんよくないのは、無関心だ


4822248763ステファン・エセル『怒れ!憤れ! 』(日経BP, 2011)

私はすべての人に訴えたい。一人ひとりが怒るべき理由を見つけてほしい。怒りは貴重だ。(p.25)

たしかに今日の世界では、怒る理由が昔ほどはっきりしなくなっている。言い換えれば、世界はあまりに複雑になった。(p.43)

だがこの世界にあっても、許しがたいことは存在する。それを見つけるためには、目をよく見開き、探さなければならない。若者よ、探しなさい。そうすれば、きっと見つかる。いちばんよくないのは、無関心だ。「どうせ自分には何もできない。自分の手には負えない」という態度だ。そのような姿勢でいたら、人間を人間たらしめている大切なものを失う。その一つが怒りであり、怒りの対象に自ら挑む意志である。(p.45)

一番いけないのは、おなかが空いていることと、一人でいること

米国製の計算機と日本製の計算機の違い


AppleとSonyの一番の違い part IIという記事を見て、「なるほど」と思ったのですが、こういう「たとえ」を使わなくても、そのままずばりのわかりやすい例がありました。

「高さが3の正三角形の面積は?」と訊かれたら、たいていの人は答えられると思いますが、ウェブで検索するより早く答えられる人は少ないでしょう。実際に、ウェブで解決しようとするとき、米国製の計算機と日本製の計算機、どっちを使いますか?

米国製の計算機

日本製の計算機

米国製の計算機、WolframAlphaの場合

「equilateral triangle height=3」と入力すれば答えが得られます。簡単に答えがわかるのはもちろん、結果のURLが得られるのもいいですね。

日本製の計算機、高精度計算サイトkeisanの場合

  1. 「数学・物理の計算」をクリックする
  2. 「三角形」をクリックする
  3. 「正三角形」をクリックする
  4. 入力指定を「高さ」にする
  5. 高さを「3」にする
  6. 「計算」ボタンをクリックする

これでやっと答えが得られます。初めて行く人は、どこに何があるかがわからないので、「手で計算した方が早い」ということになるでしょう。

keisanがWolframAlphaになる必要はありませんが、「英語がわからなくても大丈夫」以外のメリットを打ち出せるようになるといいですね。

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


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)