<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
   <title>nakatani @ cybozu labs</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/" />
   <link rel="self" type="application/atom+xml" href="http://labs.cybozu.co.jp/blog/nakatani/atom.xml" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13</id>
   <updated>2009-06-16T05:50:31Z</updated>
   
   <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.33-ja</generator>

<entry>
   <title>コンピュータはオー・ヘンリーとエドガー・アラン・ポーの文章を見分けられるか？(ブログ移転のお知らせ)</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/05/post_11.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2210</id>
   
   <published>2009-05-29T09:21:43Z</published>
   <updated>2009-06-16T05:50:31Z</updated>
   
   <summary> Perceptron の実装です、と言ってからずいぶん経ってしまいましたが、よ...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="自然言語処理" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[Perceptron の実装です、と言ってからずいぶん経ってしまいましたが、ようやくその続き……と思わせておいて、実はブログの移転のお知らせです。

サイボウズグループ全体の技術ブログとして <a href="http://developer.cybozu.co.jp/tech/">Cybozu Inside Out</a> が立ち上がり、こちらの "nakatani @ cybozu labs" で書いていたような記事も今後そちらで書かせていただくことになりました。

第１回目の記事として、Perceptron の実装編として、O.Henry と Edgar Allan Poe の文章を Perceptron で学習して正しく見分けられるか！？　という記事を書かせていただきましたので、よろしければごらんください。

<a href="http://developer.cybozu.co.jp/tech/2009/05/post-a7de.html ">コンピュータはオー・ヘンリーとエドガー・アラン・ポーの文章を見分けられるか？ - Cybozu Inside Out</a>

サイトは変わりますが、これからも引き続きよろしくお願いいたします。

なお、こちらのブログにある過去記事については、このまま保持される予定です。]]>
      
   </content>
</entry>
<entry>
   <title>Perceptron を手で計算して理解してみる</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/04/perceptron_1.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2188</id>
   
   <published>2009-04-24T09:59:02Z</published>
   <updated>2009-05-28T03:42:53Z</updated>
   
   <summary> Perceptron の実装とか見ると、ものすごく簡単なので、本当にこれで学習...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="自然言語処理" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="205" label="NLP" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="203" label="パーセプトロン" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="206" label="機械学習" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[Perceptron の実装とか見ると、ものすごく簡単なので、本当にこれで学習できちゃうの？　と不安になってしまいました(苦笑)。
こういうときは、実際にパーセプトロンが計算しているとおりに、紙と鉛筆で計算してみて、期待する結果が出てくることを確認してみたくなります。

参照する教科書は「パターン認識と機械学習・上」(PRML) の「 4.1.7 パーセプトロンアルゴリズム」。
短い節です。必要最低限のことを一通り書いてある感じかな。

計算に用いるサンプルですが、手で計算できる規模でないといけないので、論理演算の AND を試してみることにします。


<h2>簡単に勉強</h2>

ちゃんとした説明は PRML などを見て欲しいですが、とても簡単にまとめます。

２値の線形識別モデルは、N 次元空間内を (N-1) 次元の超平面(決定面)で分割することで、入力ベクトル x から得られる特徴ベクトル φ(x) が２つのクラスのどちらに属するかを分類します(２次元で言えば、座標平面を直線で分割している感じ)。

その超平面は重みベクトル w を使って、 w^T φ(x) = 0 と表します。
空間を w^T φ(x) ≧ 0 と w^T φ(x) < 0 に分割するわけですね。
重みベクトル w を決定する一般的なアプローチは、誤差関数を決め、それを w の関数と見なし、最小化する w を求める、となります。

パーセプトロンは、誤差関数としてパーセプトロン基準を用い、また最小化に確率的最急降下アルゴリズムを用いるのが特徴、と。

パーセプトロン基準は、目的変数として t ∈ {-1, +1} を用いつつ(通常は {0, 1})、誤差関数を次のように定義しています。

<pre>
E_P(w) = - Σ w^T Φ(x_n) t_n
</pre>

ここで Σは誤分類されたパターン全体を走るとします。
すると、w^T Φ(x_n) ≧ 0 のとき t_n は「間違って」 -1 になっているし、w^T Φ(x_n) < 0 のときは t_n は「間違って」 +1 になっているため、E_P(w) は常に０以上であることがわかります。

これを最小化するために、「誤分類された x_n １つずつに対して」次の式を使って逐次的に最小値に近づけていきます。
イメージとしては、ニュートン法(多次元版)って感じです。
このあたり、PRML には 3.1.3 節を見ろ、と書いてますが、5.2.4 節もあわせて見るといいかもしれません。

<pre>
w^(γ+1) = w^(γ) - η▽E_P(w) = w^(γ) + φ(x_n) t_n
</pre>

ややこしそうに書いてますが、アバウトにぶっちゃけると「ハズレのときは、そのハズレが出てしまった特徴ベクトルを重みベクトルに足すか引くかする」ということです。

こんな方法では最小値に本当に近づくのかどうか必ずしもわからないんですが、「解が存在する場合は」この計算を有限回繰り返すことで解が得られます、というのが「パーセプトロンの収束定理」で、Perceptron のキモです。

ね、ほんとにそんなんでうまく学習できるんですか？　という気になるでしょう。


<h2>手で計算してみる</h2>

論理演算 AND とは……は、さすがに略。

AND の入力ベクトルと目的変数は次のようになります(ベクトルは転置した状態で記述します)。

<pre>
x_0 = (0, 0), t_0 = -1
x_1 = (1, 0), t_1 = -1
x_2 = (0, 1), t_2 = -1
x_3 = (1, 1), t_3 = +1
</pre>

これを学習できるのか、手で計算してみましょう。

特徴関数 φ は x_n そのままでもいいんですが、「φ_0 はバイアスでしょ、jk」と PRML にもあるので、次のように定義します。

<pre>
φ( x_0 ) = (1, 0, 0)
φ( x_1 ) = (1, 1, 0)
φ( x_2 ) = (1, 0, 1)
φ( x_3 ) = (1, 1, 1)
</pre>

これより重みベクトル w も３次元、初期値は w^(0) = (0, 0, 0) となります。

さて、ここから計算していきますが、誤分類ベクトルは本来ランダムに選ぶべきところ、ここでは少々恣意的に選んでいくことにします(理由は後述)。

<hr />

■Step 1

x_0 は w^T Φ(x_0) = 0 ≧ 0 となり t_0 = -1 と合致しません。
よって、「次の w 」は次の式で求まります。

<pre>
w^(1) = w^(0) + φ(x_0) t_0 = (0, 0, 0) - (1, 0, 0) = (-1, 0, 0)
</pre>

ますます不安……この調子で大丈夫？

■Step 2

x_3 は w^T Φ(x_3) = -1 + 0 + 0 < 0 となり t_3 = +1 と合致しません。よって
<pre>
w^(2) = w^(1) + φ(x_3) t_3 = (-1, 0, 0) + (1, 1, 1) = (0, 1, 1)
</pre>

■Step 3

x_1 は w^T Φ(x_1) = 0 + 1 + 0 ≧ 0 となり t_1 = -1 と合致しません。よって
<pre>
w^(3) = w^(2) + φ(x_1) t_1 = (0, 1, 1) - (1, 1, 0) = (-1, 0, 1)
</pre>

■Step 4

x_2 は w^T Φ(x_2) = -1 + 0 + 1 ≧ 0 となり t_2 = -1 と合致しません。よって
<pre>
w^(4) = w^(3) + φ(x_2) t_2 = (-1, 0, 1) - (1, 0, 1) = (-2, 0, 0)
</pre>

■Step 5

x_3 は w^T Φ(x_3) = -2 + 0 + 0 < 0 となり t_3 = +1 と合致しません。よって
<pre>
w^(5) = w^(4) + φ(x_3) t_3 = (-2, 0, 0) + (1, 1, 1) = (-1, 1, 1)
</pre>

■Step 6

x_1 は w^T Φ(x_1) = -1 + 1 + 0 ≧ 0 となり t_1 = -1 と合致しません。よって
<pre>
w^(6) = w^(5) + φ(x_1) t_1 = (-1, 1, 1) - (1, 1, 0) = (-2, 0, 1)
</pre>

■Step 7

x_3 は w^T Φ(x_3) = -2 + 0 + 1 < 0 となり t_3 = +1 と合致しません。よって
<pre>
w^(7) = w^(6) + φ(x_3) t_3 = (-2, 0, 1) + (1, 1, 1) = (-1, 1, 2)
</pre>

■Step 8

x_2 は w^T Φ(x_2) = -1 + 0 + 2 ≧ 0 となり t_2 = -1 と合致しません。よって
<pre>
w^(8) = w^(7) + φ(x_2) t_2 = (-1, 1, 2) - (1, 0, 1) = (-2, 1, 1)
</pre>

<hr />

これで全ての w^T Φ(x_n) が目的変数と合致するようになり、w = (-2, 1, 1) が得られます。
いやあ本当に学習できるんですね。
めでたしめでたし。

もしよければ、各ステップごとに決定面がどのように遷移するか描いてみると面白いかもしれませんね。
w = (a, b, c)、φ = (1, x, y) とすると、w^T φ = a + bx + cy = 0 なので、座標平面の ( - a/b , - a/c ) を通る直線として表せるので。


こんな簡単な例でも、いろいろ問題点が見えてきます。


(1) 収束に時間がかかる。

明確に解があることがわかっている、これだけ低次元の例でも収束に８回かかっています。
オンライン学習ライブラリで「学習を１０回繰り返す」必要があるのも大納得。


(2) 学習データの偏りに強い影響を受ける。

上の計算で(恣意的に)選ばれた誤分類ベクトルを見ると、x_3 が多いことがわかります。これは、正例が x_3 しかないためです。
これを本当にランダムに選んでいたら(そして実際の計算ではおおむねそうせざるを得ない)、収束にはもっと回数を要したでしょう。
あるいは学習データに偏りがなくても、ランダムに選んだ順番がたまたま「ずっと正例でトレーニングした後、今度はずっと負例」なんてことになったら、間違った結果が確実に出てしまうでしょう。


このあたりの問題点を少しでも解消しようとするのが Averaged Perceptron その他の方式、ということでしょうか。
なんにせよ、オンライン学習で「ランダム！」「繰り返し！」と叫ばれる理由がよくわかって、一安心です。


次回は実装。]]>
      
   </content>
</entry>
<entry>
   <title>Perceptron を勉強する前にオンライン機械学習ライブラリを試してみる</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/04/perceptron.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2187</id>
   
   <published>2009-04-22T17:08:12Z</published>
   <updated>2009-04-22T17:12:04Z</updated>
   
   <summary> 今度は CLUTO を試してみた話を書こうと思っていたのですけど、あまりふくら...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="自然言語処理" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[今度は <a href="http://www-users.cs.umn.edu/~karypis/cluto/">CLUTO</a> を試してみた話を書こうと思っていたのですけど、あまりふくらみそうにないので、保留。

オンライン学習(逐次学習)に興味があるので、まずは Perceptron 周辺を勉強し始めてます。
が、その前に動くものをさわっておこうということで、<a href="http://hillbig.cocolog-nifty.com/do/2008/05/oll_29e6.html">岡野原さんのオンライン機械学習ライブラリ</a>をちょっぴり試してみました。

<a href="http://code.google.com/p/oll/wiki/OllMainJa">oll プロジェクトページ(日本語)</a>


<h2>ビルド</h2>

Linux なら ./configure & make でＯＫ。

Windows の場合 oll.hpp の先頭のどこかに

<pre>
#include &lt;algorithm&gt;
</pre>

を追加すれば VC++ でもコンパイルできました。


<h2>サンプルデータ</h2>

サンプルデータには、プロジェクトページにも実験としてあがっている <a href="http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html#news20.binary">news20.binary</a> をまずは使ってみることにしましょう。
「シャッフルし、15000例の訓練データと4996例のテストデータに分けた」とあるので、その通りにします。

<pre>
$ sort -R news20.binary &gt; news20.random
$ head -15000 news20.random &gt; news20.train
$ tail -4996 news20.random &gt; news20.test
</pre>

実際に中谷が興味があるのは自然言語処理なので、NLP らしいデータはまた別途用意して試してみたいと思います。
まずはパーセプトロン。


<h2>学習＆テスト</h2>

ビルドが通ってデータの用意もできました。
実行。

<pre>
$ ./oll_train P news20.train news20.model.p -C=2.0 -b=1.0 -I=10
</pre>

15000 例の学習データを用いて、Perceptron(P) で１０回繰り返して学習(-I=10)、結果を news20.model.p に保存します。
学習手法は先頭の "P" を "AP" などに変更することで変えられます。そのあたり、コマンドラインの仕様はプロジェクトページで見てください。

「同じデータを１０回繰り返して学習する」というあたりが、収束(の可能性)がある線形識別器ならでは、でしょうか。
Naive Bayes とかだと同じデータで１０回学習なんて考えられないですから。


<pre>
$ ./oll_test news20.test news20.model.p

Accuracy 94.235% (4708/4996)
(Answer, Predict): (p,p):2425 (p,n):35 (n,p):253 (n,n):2283
</pre>

学習結果を用いて、テストデータを分類させてみて、その正解率を出しています。
(p|n,p|n) は positive|negative を positive|negative に分類した件数で、正解率は 94%。

プロジェクトページの例と大きく違っていないので、一応動いていることが確認できました。
これで自分で勉強＆実装したものとのベンチマークがとれますね。


次は、Perceptron を理解するために、「手で Perceptron を計算」してみます。]]>
      
   </content>
</entry>
<entry>
   <title>英単語タイピングゲーム iVoca を機能アップデートしました</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/04/_ivoca_3.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2177</id>
   
   <published>2009-04-13T06:52:31Z</published>
   <updated>2009-04-13T07:51:53Z</updated>
   
   <summary> 英単語タイピングゲーム iVoca にて、本日以下の機能をリリースしました。 ...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<a href="http://ivoca.31tools.com/">英単語タイピングゲーム iVoca</a> にて、本日以下の機能をリリースしました。

<ul>
<li>ブックに問題一覧(単語一覧)ページを追加しました。問題内容や、各単語ごとの「習得度」や「苦手度」を確認できます。</li>
<li>問題および解答の長さを 30文字から 60文字に拡張しました。</li>
<li>'¡'(逆さの！) や '¿'(逆さの？) を解答に使える文字に追加しました。入力はそれぞれ '!' と '?' です。スペイン語の感嘆文・疑問文に対応します。</li>
</ul>


ブック画面の右上に「問題一覧」リンクが追加されています。

<img src="/blog/nakatani/imgs/ivoca200904_1.png" />


問題一覧では、問題と解答の他に「出題数」「習得度」「苦手度」が各単語ごとに表示されます。
例えば<a href="http://ivoca.31tools.com/wordlist?id=600">「難読駅名(JR東日本 東京近郊編)」</a>の問題一覧は以下のようになります(ユーザ登録して遊んでいない場合は出題数・習得度・苦手度は空になります)。

<img src="/blog/nakatani/imgs/ivoca200904_2a.png" />


「習得度」には現在の習得レベルが 100点満点で表示されます。ブックのスコアは、この習得度の合計とほぼ一致するようになっています。
「苦手度」は、「その単語にどれだけ手こずったか」が星０個～５個で表示されます(多いほど苦手)。

問題一覧ではヘッダをクリックするとソートしますので、「苦手度」順に表示させて苦労した単語が確認するなど、今までに学んだことのあるブックについて、いろいろな楽しみ方や役立て方をしてもらえるようになりました。



ずいぶん前からご要望いただいていた「問題と解答の長さを長くして欲しい」にも、ようやく対応しました。

<img src="/blog/nakatani/imgs/ivoca200904_3.png" />

長～い問題や解答については、ご覧のようなイメージで２行で表示されます。



スローペースではありますが、iVoca では引き続きより楽しんで単語を覚えられる機能を追加していきたいと思いますので、よろしくお願いします。]]>
      
   </content>
</entry>
<entry>
   <title>HAC に使える feature selection を試す</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/03/hac_feature_selection.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2170</id>
   
   <published>2009-03-10T09:41:33Z</published>
   <updated>2009-03-10T09:53:17Z</updated>
   
   <summary> プチ間空きましたが、「IIR の「効果的な」階層的クラスタリング」の続き。 「...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="自然言語処理" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="168" label="HAC" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="166" label="IIR" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="177" label="feature selection" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<br />
プチ間空きましたが、<a href="http://labs.cybozu.co.jp/blog/nakatani/2009/02/iir_1.html">「IIR の「効果的な」階層的クラスタリング」</a>の続き。<br />
「次回は feature selection で次元を落とすのを試してみるべき」と書いたとおり、feature selection(特徴選択)を行ってみます。<br />
<br />
要は「２５文書しかないのに 8000 語とか多すぎる。文書増えてったらガクブル。よし減らそう。全部必要な訳ないしね。でも、どうやって？」という話です。<br />
<br />
IIR では、Chapter 13 にて feature selection を扱っており、
また Chapter 18 では LSI(latent semantic indexing)、乱暴に言えば固有ベクトルを求めることでその空間が本来持っている次元数(階数)を導いている。<br />
<br />
しかし、Ch.13 の内容は Bayesian のような「教師有り分類」の場合の feature selection しかカバーしておらず。<br />
固有ベクトルを求めるのは bag of words の世界で本質的に最も正しい気はしますが、追加学習のコストが高そう。<br />
Web などの文書も単語もあとからあとから追加されるシチュエーションでは、なかなかしんどそうです(現在の個人的な印象。いい方法があるのかもしれません)。<br />
<br />
というわけで
がちゃがちゃ検索して適当な論文を探してみました。<br />
<br />
<a href="http://www.csee.umbc.edu/cadip/2002Symposium/">2002 CADIP Research Symposium</a> で発表されたとおぼしき <a href="http://www.csee.umbc.edu/cadip/2002Symposium/kogan.pdf">"Feature Selection and Document Clustering"</a> が、k-Means など「教師無し分類」における feature selection の効果的な手法を取り扱っていたので、これを実装してみることにしました。<br />
<br />]]>
      <![CDATA[<br />
その論文で紹介されていた手法は大きく分けて３つ。<br />
<br />
<ul>
<li>Term variance quality</li>
<li>Same context terms</li>
<li>Spherical Principal Directions Divisive Partitioning</li>
</ul>
<br />
１つ目と２つ目はどちらも単語ごとにある種の評価値を計算して、上位××語を選ぶというもの。<br />
３つ目は分類の手法に依存していて、HAC では使えません。<br />
<br />
<br />
<h2>Term variance quality</h2>
<br />
<br />
Term variance quality (分散特性、かな？) では２種類の評価式があります。<br />
<br />
<pre>
q_0(t) = Σf^2 - (Σf)^2 / n_0
<br />
q_1(t) = Σf^2 - (Σf)^2 / n_1
</pre>
<br />
ここで f は各ドキュメントにおける単語 t の出現回数( term frequency )であり、Σ はドキュメント全体にわたってその和を取ることを表しています。また、n_0 は「全ドキュメント数」、n_1 は「 t が出現するドキュメント数」です。<br />
<br />
２つの式の違いは第２項の分母のみ。q_0(t) はもう一回 n_0 で割ればいわゆる分散そのものですね。<br />
<br />
これらを実装したものは例によって github に。<br />
<br />
<pre>
git://github.com/shuyo/iir.git
</pre>
<br />
<pre>
$ ./fselect.rb -n 1000 -s -tq0 4million.corpus
</pre>
<br />
とすれば、前回使用したコーパスファイルを読み込んで「 q_0 の式で評価し、上位 1000 語を抽出(<a href="http://www.dcs.gla.ac.uk/idom/ir_resources/linguistic_utils/">stop words</a> 除く)」して、同じフォーマットで 4million.corpus.q0 を生成します。<br />
あとはこの新たに作成されたコーパスを使って、前回の hac を呼び出せばＯＫ。<br />
<br />
<pre>
ruby hac.rb 4million.corpus.q0
</pre>
<br />
結果。<br />
<br />
<pre>
# without feature selection
$ ./hac.rb 4million.corpus
------------------------- TOBIN'S PALM
|| | ||             +---- BETWEEN ROUNDS
|| | |+------------------ THE SKYLIGHT ROOM
|| | |    || |   +------- THE FURNISHED ROOM
|| | |    || +----------- AN UNFINISHED STORY
|| | |    |+------------- THE GREEN DOOR
|| | |    |    +--------- THE CALIPH, CUPID AND THE CLOCK
|| | |    |       +------ BY COURIER
|| | |    +-------------- SPRINGTIME A LA CARTE
|| | +------------------- AN ADJUSTMENT OF NATURE
|| +--------------------- THE BRIEF DEBUT OF TILDY
|+----------------------- THE GIFT OF THE MAGI
|   |  |      |       +-- SISTERS OF THE GOLDEN CIRCLE
|   |  |      +---------- MEMOIRS OF A YELLOW DOG
|   |  +----------------- THE ROMANCE OF A BUSY BROKER
|   +-------------------- A COSMOPOLITE IN A CAFE
|       +---------------- MAN ABOUT TOWN
|           |   |    +--- FROM THE CABBY'S SEAT
|           |   +-------- LOST ON DRESS PARADE
|           +------------ MAMMON AND THE ARCHER
+------------------------ A SERVICE OF LOVE
  |      |         +----- THE COMING-OUT OF MAGGIE
  |      |             +- AFTER TWENTY YEARS
  |      +--------------- THE COP AND THE ANTHEM
  +---------------------- THE LOVE-PHILTRE OF IKEY SCHOENSTEIN


# q_0/1000 words
$ ./hac.rb 4million.corpus.q0
------------------------- TOBIN'S PALM
||  | |  |   |  +-------- THE CALIPH, CUPID AND THE CLOCK
||  | |  |   |       +--- BY COURIER
||  | |  |   +----------- BETWEEN ROUNDS
||  | |  |     |   +----- THE FURNISHED ROOM
||  | |  |     +--------- THE SKYLIGHT ROOM
||  | |  +--------------- THE ROMANCE OF A BUSY BROKER
||  | +------------------ A COSMOPOLITE IN A CAFE
||  +-------------------- AN ADJUSTMENT OF NATURE
|+----------------------- THE GIFT OF THE MAGI
|  | |     |        +---- SISTERS OF THE GOLDEN CIRCLE
|  | |     +------------- AN UNFINISHED STORY
|  | +------------------- MAN ABOUT TOWN
|  |   |  | | |   |   +-- FROM THE CABBY'S SEAT
|  |   |  | | |   +------ LOST ON DRESS PARADE
|  |   |  | | +---------- MEMOIRS OF A YELLOW DOG
|  |   |  | +------------ THE GREEN DOOR
|  |   |  +-------------- MAMMON AND THE ARCHER
|  |   +----------------- SPRINGTIME A LA CARTE
|  +--------------------- THE BRIEF DEBUT OF TILDY
+------------------------ A SERVICE OF LOVE
  |     |        +------- THE COMING-OUT OF MAGGIE
  |     |              +- AFTER TWENTY YEARS
  |     +---------------- THE COP AND THE ANTHEM
  +---------------------- THE LOVE-PHILTRE OF IKEY SCHOENSTEIN


# q_1/1000 words
$ ./hac.rb 4million.corpus.q1
------------------------- TOBIN'S PALM
|| |  | |    |  |    ||+- BETWEEN ROUNDS
|| |  | |    |  |    |+-- THE FURNISHED ROOM
|| |  | |    |  |    +--- THE SKYLIGHT ROOM
|| |  | |    |  +-------- BY COURIER
|| |  | |    +----------- THE CALIPH, CUPID AND THE CLOCK
|| |  | +---------------- MEMOIRS OF A YELLOW DOG
|| |  +------------------ THE COMING-OUT OF MAGGIE
|| +--------------------- THE ROMANCE OF A BUSY BROKER
|+----------------------- A COSMOPOLITE IN A CAFE
| | || |  ||   +--------- MAN ABOUT TOWN
| | || |  |+------------- AN UNFINISHED STORY
| | || |  |      +------- LOST ON DRESS PARADE
| | || |  +-------------- MAMMON AND THE ARCHER
| | || +----------------- AN ADJUSTMENT OF NATURE
| | ||      |     +------ THE BRIEF DEBUT OF TILDY
| | ||      +------------ SPRINGTIME A LA CARTE
| | ||              +---- THE GREEN DOOR
| | |+------------------- FROM THE CABBY'S SEAT
| | +-------------------- THE LOVE-PHILTRE OF IKEY SCHOENSTEIN
| +---------------------- A SERVICE OF LOVE
|        +--------------- THE COP AND THE ANTHEM
|                  +----- AFTER TWENTY YEARS
+------------------------ THE GIFT OF THE MAGI
              +---------- SISTERS OF THE GOLDEN CIRCLE
</pre>
<br />
何をもって良いと評価するかが例によって悩ましいわけですが、特にどちらかの結果の方が分類にまとまりがあって(主観的に)望ましいということもなさそうです。<br />
が、少なくとも「q_0 の結果の方が feature selection を行わなかった場合の結果に近い」ことは読み取れます。<br />
<br />
実は元論文では、全く種類の異なる３つのコーパス群を用意し、それらを機械分類させたときに３種類のコーパスをそれぞれに分類できたか、という方法で feature selection 手法の評価を行っているんですが、
そこでも q_0 の方が q_1 より再現率が高いという結果が出ています。<br />
<br />
論文は k-Means/Principal Direction Divisive Partitioning、ここでの実装は HAC と、全く異なる分類手法で同じ傾向を示したり(ここでの試行は件数少なすぎですが)、
式を見た瞬間は q_1 の方が筋がよいのではという印象がさっくり裏切られるのが、おもしろい。<br />
<br />
<br />
<h2>Same context terms</h2>
<br />
q_0/q_1 は term frequency から簡単に計算できましたが、
Same context terms では、単語ごとの近さ(same context = 同じ sentence 中で使われているかどうか)を表現した "term profile vector" なるものを定義して計算に使用します。<br />
全体は複雑なので、"term profile vector" のところだけ抜き出すとこんな感じ。<br />
<br />
<ul>
<li>term 全体 T = { t_1,  ... , t_m }</li>
<li>sentence 全体 { s_1, ... , s_n }</li>
<li>term t の profile P(t) とは、t が含まれる sentence に含まれる term 全体とする。</li>
<li>"term by sentence" matrix S とは、その要素 S_ij が sentence s_j に term t_i が出現する回数である (m x n) 行列。</li>
<li>t_i の profile vector PV(t_i) は対称行列 (SS^T) の第 i 行列。すなわち その第 j 成分は、t_i と t_j の出現回数の積を sentence 全体にわたって和をとったもの。</li>
</ul>
<br />
この "term profile vector" を用いて、最終的に各単語の評価を算出するわけですが、その式も補正＆ペナルティでガッチガチに構成してあって、俺がヒューリスティックの見本を見せてやる！　な感じです。<br />
頑張ってます。<br />
<br />
そして、論文では同様に分類の再現性でこの手法の評価をしていますが、残念ながら q_0 や q_1 よりずっと再現性が低いという結果が記されていました。<br />
empty document(残した単語が一つも含まれていない文書) も生じてしまっており、全くのダメダメとしか言いようがない状態です(なぜ補正＆ペナルティがいっぱいついているのか……わかりますねｗ)。<br />
<br />
<br />
というわけで。<br />
かなり計算量の多い面倒な手法＆結果はダメダメ、ということで実装のモチベーションが起きなくてスルーしました。すいません(苦笑)。<br />
<br />
<br />
ここで試した feature selection は「都合のいい単語を選ぶ」という一番シンプルな方針。<br />
「都合のいい」というのはイメージでは「できるだけバラバラ（イメージ）に選ぶ」ということで、「バラバラ（イメージ）」ということは偏りのある単語を選ぶのが有利な感じ。<br />
評価の高い単語と同じ sentence に含まれると評価の上がりやすい Same context terms や、全体にまんべんなく出現している(= n_1 が大きい)と有利になる q_1 が、どちらも悪い結果が出てしまったのは自然なことでもあるのかな。<br />
って、全て後知恵＆あくまで印象ですが。<br />
<br />
<br />
コーパスを<a href="http://www-users.cs.umn.edu/~karypis/cluto/">オープンソースのクラスタリングツール CLUTO</a> 用のフォーマットで吐いて遊ぶ、というのもやってみたんだけど、長くなったのでまた今度。<br />
<br />]]>
   </content>
</entry>
<entry>
   <title>英単語タイピングゲーム iVoca をアップデートしました</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/02/_ivoca_2.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2161</id>
   
   <published>2009-02-16T13:07:40Z</published>
   <updated>2009-02-16T13:49:23Z</updated>
   
   <summary> 本日、英単語タイピングゲーム iVoca をアップデートしました。 更新内容は...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[本日、<a href="http://ivoca.31tools.com/">英単語タイピングゲーム iVoca</a> をアップデートしました。
更新内容は次の通りです。

<ol>
<li>サイボウズ(R) ネットID のアカウントから、iVoca 独自のアカウントシステムに移行します。アカウントデータは移行されますので、ご利用のユーザーにおかれましては、別途アカウントを作成する必要はございません。<br />
また、mixi OpenID でご利用いただいていたユーザーにおきましては、従来通り mixi OpenID にて引き続きご利用いただけます。
</li>
<li>学習データを公開および API で取得できるようになります。</li>
<li>iKnow! 連係機能を追加します。第１弾として、iKnow の単語リストを iVoca で学習することができるようになります。</li>
</ol>

iVoca の認証はこれまで「外部認証のみ」だったわけですが、<a href="http://d.hatena.ne.jp/n_shuyo/20080929/openid">API をどうするか、などなどなどの頭の痛い問題</a>がありました。
今回、iVoca が利用していた外部認証の一つ「サイボウズ(R) ネットID」がサービスを停止することになったので、これを機に独自認証の機能を提供することにいたしました。
これで API を提供できる道筋がついたので、まずは第一弾として、個人の学習履歴データを取得できるようにしました。

iVoca アカウントを設定し、公開設定を「公開」に指定した後、

<pre>
http://ivoca.nsdev1/api/progresses/[iVoca アカウント名]
</pre>

この URL にアクセスすることで、熱心に学習している５件のブックについて、日々の学習履歴が json 形式で得られます(フォーマットについては後日解説します……すいません)。

なお、従来「サイボウズ(R) ネットID」をご利用いただいていた方のログインID(メールアドレス)とパスワードは、上の注意書きの通りそのまま iVoca アカウントのほうに移行させていただいていますので、「ログイン」をクリックした後、iVoca アカウントのログインフォームにてメールアドレスとパスワードを入れてログインしてください。

mixi OpenID でご利用いただいていた方はそのまま引き続きご利用いただけますし、setting メニューにて iVoca アカウントも設定して、併用することも出来るようになっています。


そして、今回の目玉機能は <a href="http://www.iknow.co.jp/">iKnow!</a> 対応です。

<a href="http://developer.iknow.co.jp/docs/api">iKnow の API</a> を利用して、<a href="http://ivoca.31tools.com/books?mode=iKnow">iKnow の「学習アイテムリスト」を iVoca で学ぶ(遊ぶ)ことができるようになりました</a>。
これにより iKnow の素晴らしい問題リストを iVoca で覚えられるようになっただけでなく、iKnow にて音声データのある単語はちゃんとゲーム中に発音してくれるようになっています。


iVoca で遊べる iKnow リストの一例：
<ul>
<li><a href="http://ivoca.31tools.com/book?id=331">基礎英語 ステップ１</a></li>
<li><a href="http://ivoca.31tools.com/book?id=338">【2008オバマ氏勝利演説：全音声付き】 </a></li>
<li><a href="http://ivoca.31tools.com/books?tag=TOEIC">タグ「TOEIC」の一覧</a></li>
</ul>


ここに挙げたリスト以外にも、iVoca のユーザであればどなたでも、iKnow のどのリストでも iVoca で学べる(遊べる)ように登録することができます。
iVoca の <a href="http://ivoca.31tools.com/books?mode=iKnow">iKnow メニュー</a> から、iKnow のリスト画面の URL ( http://www.iknow.co.jp/lists/.... という形式のもの) を指定するか、同じ画面の iKnow リスト検索を使って利用したいリストを探してください。
なお、初めて iVoca にて利用するリストの場合には、学習データ初期化のための処理が10秒～１分程度かかります。


これほど素晴らしいデータを API で提供してくれているセレゴさんには、本当に感謝します。ありがとうございます。
これを第１弾として、逆に iVoca のブック(単語帳)を iKnow に登録する機能や、iVoca と iKnow の学習データを一元化して見せる機能とか、まずは iVoca でざっと覚えて、特に苦手な単語だけを選んで iKnow のリストを自動的に作るとかとか、いろいろ夢はふくらむんですが、順番にちょっとずつ、ですね。
まずは「問題の長さをもうちょっと長くできると嬉しい」という要望をいただいているのに、まだほうったらかしなので、こちらをやっつけるところからでしょうかね。


なお発音機能は Flash Player にて、バックエンドで iKnow アイテム情報を順次取得して実現しています。そのため、音声を発音するようになるまで少し遅れたり、ごく一部の単語のみしか発音してくれない、という状態になる場合があります。iKnow が混雑する時間帯は特に起きやすいようです。
改善を図りたいと考えてはいますが、あらかじめご了承ください。

]]>
      
   </content>
</entry>
<entry>
   <title>iVoca メンテナンスのお知らせ</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/02/ivoca_4.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2154</id>
   
   <published>2009-02-10T09:54:56Z</published>
   <updated>2009-02-10T10:05:42Z</updated>
   
   <summary> 英単語タイピングゲーム iVoca につきまして、以下の日程でメンテナンスを実...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="171" label="iKnow" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="153" label="iVoca" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="174" label="サイボウズ(R) ネットID" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="172" label="メンテナンス" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[英単語タイピングゲーム iVoca につきまして、以下の日程でメンテナンスを実施させていただきます。

・2009年2月16日(月) 18:00 ～ 20:00

メンテナンス中は iVoca をご利用していただくことが出来ませんので、ご了承ください。
メンテナンスが終了次第、サービスを再開いたします。

今回のメンテナンスにおきまして、以下の更新を予定しております。

<ul>
<li>サイボウズ(R) ネットID のアカウントから、iVoca 独自のアカウントシステムに移行します。アカウントデータは移行されますので、ご利用のユーザーにおかれましては、別途アカウントを作成する必要はございません。<br />
また、mixi OpenID でご利用いただいていたユーザーにおきましては、従来通り mixi OpenID にて引き続きご利用いただけます。
</li>
<li>学習データを公開および API で取得できるようになります。</li>
<li>iKnow 連係機能を追加します。第１弾として、iKnow の単語リストを iVoca で学習することができるようになります。</li>
</ul>
]]>
      
   </content>
</entry>
<entry>
   <title>IIR の「効果的な」階層的クラスタリング</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/02/iir_1.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2146</id>
   
   <published>2009-02-03T10:26:46Z</published>
   <updated>2009-02-04T03:26:52Z</updated>
   
   <summary> IR の階層的クラスタリングを試すの続きです。 &quot;efficient&quot; な H...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="自然言語処理" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="168" label="HAC" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="166" label="IIR" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="169" label="centroid" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<a href="http://labs.cybozu.co.jp/blog/nakatani/2009/01/iir.html#more">IR の階層的クラスタリングを試す</a>の続きです。
"efficient" な HAC(hiererachical agglomerative clustering) を実装してみます。
今回は、コード全体をぺたぺた貼り付けるのも見にくいし面倒だしということで、github に置いてみました。

<pre>
git://github.com/shuyo/iir.git
</pre>

前回作った corpus パックも commit してありますので、 clone すればいきなり動く、はず。

<pre>
git clone git://github.com/shuyo/iir.git
cd iir/hac
ruby hac.rb 4million.corpus
</pre>

おのおの手元でちょこちょこ改変して試してみるには CodeRepos より git の方が向いてるんじゃあないかなあと思ったんですが、git まだ使いこなせてないのでなんか色々間違ってるかも。


実装の説明に行く前に、まず前回の naive HAC (=single-link clustering) はなぜ inefficient だったか、という話を簡単に。

クラスタを近い(=類似度が高い)順に結合していくのが HAC の基本ですが、そうなると当然「クラスタ同士の類似度」を計算しなくてはならなくなります。
一番素直で簡単のだと、「クラスタ間で最も近い(類似度が高い)ベクトル同士の類似度を、クラスタ間の類似度とする」という方法があるのですが(これが single-link clustering)、これが「大きいクラスタほど有利」な計算方法であることは明らかでしょう。
このため、「大きいクラスタが、対象を一つずつ拾っていってしまう」という現象(chaining)が発生してしまい、クラスタリングとして役に立たなくなってしまうことがあるわけです。
それを解消するための方法として、IIR の 17.2～17.4 では single-link clustering 以外に complete-link clustering, centroid, group-average など、チェインニングが起きにくい「クラスタ同士の類似度」を紹介しています。これらの詳細については、ここでは省きますね。
前回も紹介した<a href="http://www.kamishima.net/jp/clustering/">「クラスタリングとは (クラスター分析とは)」</a>にもこれらの方法が載っています。ただし、こちらは類似度として(ユークリッド)距離を使用しているので、IIR の cosine similarity とは大小が逆だったり、距離の場合に有効な「ウォード法」が紹介されていたりします。


また、IIR 17.2.1 では、前回の naive HAC の計算量は Θ(N^3) だったのですが、priority queue を導入すると Θ(N^2 log N) にできるよ、という話をしています。

それらを合わせたアルゴリズムの pseudo code が掲載されているので、今回はそれに沿って実装してみるわけです。


]]>
      <![CDATA[<h2>実装</h2>

まず。
前回は余計なキャッシュとかして追いかけにくいコードになってしまって反省なので、今回は「対象な２値ハッシュ」のようなものをこしらえてみました。

<pre>
a = SymmetricHash.new
a[1, 2] = 3
puts a[2, 1] # => 3
</pre>

このように、キーとして２つの値を取りつつ、それを入れ替えたものも同じ値へのアクセスになる、というもの。
前回より読みやすくなってると思います。

あと、priority queue ですが……それっぽいインターフェースの「自動ソート配列」で簡易にやっつけました。手抜きです(汗)。
誰かが高速な priority queue の実装を教えてくれるのを期待(苦笑)。


HAC のキモ、IIR の pseudo code にちょうど対応する部分のソースです。

<pre>
def pq_hac(docs)
  sim = SymmetricHash.new  # 類似度
  clusters = Hash.new      # クラスタに属するドキュメント。centroid などの計算に使用
  pqueue = Hash.new        # priority queue

  # calcurate similarity & priority queue
  docs.each do |d1|
    t1 = d1[:title]
    clusters[t1] = [d1]
    pqueue[t1] = PriorityQueue.new
    docs.each do |d2|
      next if d1 == d2
      v = sim[t1, d2[:title]]
      unless v
        sim[t1, d2[:title]] = v = calc_sim(d1[:vector], d2[:vector])
      end
      pqueue[t1].insert( [v, d1, d2] )
    end
  end

  # hac
  merged = []    # マージ列。dendrogram 生成に使用
  while docs.size > 1
    # 類似度最大のクラスタの組を取得
    maxsim = [0]
    docs.each do |d1|
      t1 = d1[:title]
      maxsim = pqueue[t1].max if pqueue[t1].max[0] > maxsim[0]
    end
    v, d1, d2 = maxsim
    t1, t2 = d1[:title], d2[:title]

    # マージ
    merged << [t1, t2, v]
    docs.delete(d2)
    clusters[t1] += clusters[t2]
    pqueue[d1[:title]].clear      # ←★問題有り！★(後述)
    docs.each do |d|
      next if d==d1
      t = d[:title]
      pqueue[t].delete_if_3rd(d1)
      pqueue[t].delete_if_3rd(d2)
      v = yield sim, clusters, d, d1, d2   # clustering algorism はブロックで与える
      pqueue[t].insert( [v, d, d1] )
      pqueue[t1].insert( [v, d1, d] )
    end
  end
  merged
end
</pre>

今回は pseudo code とかなり１対１に対応するので、見比べやすいです。
ポイントは、clustering algorism はブロックで与えられるようにしているところ。
clustering algorism を色々切り替えて楽しめるようになっています。

<pre>
# complete_link
clusters = pq_hac(docs) do |sim, clusters, d, d1, d2|
  t, t1, t2 = d[:title], d1[:title], d2[:title]
  v = sim[t, t1]
  v = sim[t, t2] if v > sim[t, t2]
  v
end

# centroid
centroid = Proc.new do |sim, clusters, d, d1, d2|
  calc_sim(calc_centroid(clusters[d1[:title]]), calc_centroid(clusters[d[:title]]))
end
clusters = pq_hac(docs, &centroid)
</pre>

<h2>実行</h2>

前回と同じ "4 Millions" の 25 短編を "Group-average agglomerative clustering" で分類させてみると次のような結果が得られました。

<pre>
------------------------- TOBIN'S PALM
|| |||||| |  | | |  | |+- BETWEEN ROUNDS
|| |||||| |  | | |  | +-- THE FURNISHED ROOM
|| |||||| |  | | |  +---- THE SKYLIGHT ROOM
|| |||||| |  | | +------- AN UNFINISHED STORY
|| |||||| |  | +--------- LOST ON DRESS PARADE
|| |||||| |  +----------- MAMMON AND THE ARCHER
|| |||||| +-------------- MEMOIRS OF A YELLOW DOG
|| |||||+---------------- AN ADJUSTMENT OF NATURE
|| |||||    |     +------ THE BRIEF DEBUT OF TILDY
|| |||||    +------------ SPRINGTIME A LA CARTE
|| |||||           +----- THE GREEN DOOR
|| ||||+----------------- BY COURIER
|| |||+------------------ A COSMOPOLITE IN A CAFE
|| |||     +------------- MAN ABOUT TOWN
|| ||+------------------- THE LOVE-PHILTRE OF IKEY SCHOENSTEIN
|| |+-------------------- THE ROMANCE OF A BUSY BROKER
|| +--------------------- FROM THE CABBY'S SEAT
|+----------------------- THE GIFT OF THE MAGI
|               +-------- SISTERS OF THE GOLDEN CIRCLE
+------------------------ A SERVICE OF LOVE
  +---------------------- THE COMING-OUT OF MAGGIE
         |    |      +--- AFTER TWENTY YEARS
         |    +---------- THE COP AND THE ANTHEM
         +--------------- THE CALIPH, CUPID AND THE CLOCK
</pre>

前回の naive HAC の結果よりは改善の兆しが見られますね。 "AFTER TWENTY YEARS" と "THE COP AND THE ANTHEM" が独立したクラスタに属しているあたりなんか、ちょっとは進歩したな、という感じです。
でも、まだまだ不満足。

これはやっぱり 8000次元もあるせいでしょうか……(次元の呪い、というやつ？)。
少なくとも「8000次元もあるせい」で、これがデータ由来の自然な結果なのかどうかを検証するのが難しくなっているので、次回は feature selection で次元を落とすのを試してみるべきですね。


<h2>問題(ばぐ？)</h2>

IIR の pseudo code には１つ問題があるように思えます。【追記：すいません勘違いでした→後述】

前回の naive HAC と、今回の priority queue + single-link は同じ結果になることが期待されるわけですが、実はそうはなりません。
上のコードで「★問題アリ★」と書いた部分で t1 (=マージ先のクラスタ) の類似度をため込んでいる priority queue をクリアしてしまっているんですが、これによって以下のような現象が起きてしまいます。

<ul>
<li>A, B, C, D の４つのドキュメントがある</li>
<li>各の類似度が (A, B)=0.9, (A, C)=0.6, (A, D)=0.2, (B, C)=0.5, (B, D)=0.4, (C, D)=0.1 とする</li>
<li>このとき HAC により (((A+B)+C)+D) の順にマージされる</li>
<li>C までマージされたときの (A+B+C) と D の類似度は max(*, D)=(B, D)=0.4 のはず(naive HAC ではこの値になります)</li>
<li>だが priority queue を用いた方では、C をマージしたときに queue に格納されていた 0.4 が消えてしまい、(A, D) と (C, D) のうち大きい方の 0.2 という評価になってしまう</li>
</ul>

centroid と group-average では、クラスタ内の全てのベクトルを使って計算しているのでこの問題は起きません。
ので、single-link や complete-link であっても、同じようにクラスタ内の全てのベクトルで再計算すれば問題ないんですが、"max(sim(i, k1), sim(i, k2))" のように２つの値だけの比較で済ませようとするなら、priority queue を単純にクリアするだけでは上述のような問題が起きます。

まあ、single-link を使うなら、同じ 17.2 で紹介されている NBM(next-best-merge) array を使った algorism を当然採用するんでしょうから(計算量 Θ(N^2))、実用上は問題ないと思いますけどね。

【追記】
pseudo code の18行目、20行目の解釈が抜けていました。これらをちゃんと実装すれば、B をマージしたときに (A, D) が 0.2 から 0.4 に更新されるので問題なくなります。すいません……。
逆に言えば、18行目、20行目は centroid や group-average を採用する場合には不要ですね。と言って少しでも取り繕ってみる(苦笑]]>
   </content>
</entry>
<entry>
   <title>IIR の階層的クラスタリングを試す</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2009/01/iir.html" />
   <id>tag:labs.cybozu.co.jp,2009:/blog/nakatani//13.2143</id>
   
   <published>2009-01-30T06:27:26Z</published>
   <updated>2009-05-28T03:22:12Z</updated>
   
   <summary> Pathtraq で Web ページの自動分類を手がけてみて。 Web ページ...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="自然言語処理" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="168" label="HAC" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="166" label="IIR" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[Pathtraq で Web ページの自動分類を手がけてみて。

Web ページは日々どんどん変わっていくのでフィルタは常に更新されなければいけないんですが、そのためには適切なタイミングに、適切な学習データを用意しなければならない。大変。
メンテナンスフリーが理想ですが、もちろん難しい。
現実的なところとしては「追加学習が必要なことを検知して、適切な学習データの候補を提案してくれる」というものが作りたいなあ……などなど考えているわけです。


そこらへんも含めて、自然言語処理とか機械学習とかそこら辺のお勉強をしてるんですが、実際に手を動かさないとわかんないですよねー。

というわけで、 "Introduction to Information Retrieval" の Chapter 17 "Hierarchical clustering" に沿って、ドキュメントの分類器を作ってみました。
ポイントは、教師無し分類でどれくらいのことができるのか。
なので Chapter 16 の K-means でも良かったんですが、dendrogram で結果が得られるのが楽しそうだったので、手始めに 階層的クラスタリング にしました。
K-means も別途試してみるつもり。


"Hierarchical clustering"(階層的クラスタリング) については、日本語での説明だとこちらのページがわかりやすいです。

<ul>
<li><a href="http://www.kamishima.net/jp/clustering/">クラスタリングとは (クラスター分析とは)</a></li>
</ul>

「似ているもの」同士を結んで、全体を１つの２分木のようなもの（デンドログラム）に分類する手法です。おのおのの枝分かれについて閾値が明確なので、この結果から任意の個数のクラスタを得ることが出来るのが特徴です。
というわけで「デンドログラム」を描くのがひとまず目指すゴール。

]]>
      <![CDATA[<h2>データ</h2>

自然言語処理を試すには、とにもかくにもデータが必要。
学術的には Reuters-RCV1 とかとかとかとか使う方が正しいんでしょうけど。
突っ込みを入れてもらいやすいよう、できるだけ検証しやすい形でサンプルコード＆データを見せていきたいな、とか企んでいるので、Reuters-RCV1 とかだとちょっと尻が重い……。

というわけで Project Gutenberg から O.Henry の短編集を借りてくることにしました。
各短編がおおむね 3000 語前後、ほど良さげな長さで揃っていて、かつ全部持ってくれば 200 編以上とボリュームもそこそこというあたりが決め手。

え？　もっとカテゴリがはっきりしているデータの方がいいんじ ゃないかって？
うーんそうかも。でも、自分の興味のあるデータの方が楽しいですよね(笑)。ま、それにデータは入れ替えられますから。


Project Gutenberg はフォーマットがぐちゃぐちゃ。
<a href="http://d.hatena.ne.jp/n_shuyo/20081118/gutenberg">Project Gutenberg のテキストデータから本文を抽出する</a> を使ってまず本文部分を抽出しつつ、短編ごとに切り出し(もちろんこれもフォーマットバラバラ)、さらにラベルに使いたいのでタイトルも抽出(もち以下略)、ということをしなくちゃなりません……

以下がそのための Ruby コードです。

<pre>
require 'rubygems'
require 'stemmer'

docs = Array.new
terms = Hash.new
ARGV.each do |path|
  # ファイルを開いて本文を取得
  file = path.dup
  file = $1 if path =~ /\/([^\/]+)$/
  text = if path =~ /\.zip$/i
    file.sub!(/\.zip$/i, ".txt")
    `unzip -cq #{path} "*.txt"`
  else
    open(path){|f| f.read}
  end
  text = extract_gutenberg_body(text)

  # 短編ごとに分割
  list = text.split(/^[IVX]+\s*\.?$/)[1..-1]
  list = text.split(/^\n{4}$/) if list.size<=1
  list.each do |x|
    next unless x =~ /^(.+)$/
    title = $1

    words = x.scan(/[A-Za-z]+(?:'t)?/)
    next if words.size &lt; 1000

    words.each do |word|
      word = word.downcase.stem # 単語の原形(簡易版)
      terms[word] ||= Hash.new(0)
      terms[word][docs.size] += 1
    end
    docs &lt;&lt; {:title=>title, :n_words=&gt;words.size}
  end
end

open('corpus', 'w'){|f| Marshal.dump({:docs =&gt; docs, :terms =&gt; terms}, f) }
</pre>

extract_gutenberg_body() は <a href="http://d.hatena.ne.jp/n_shuyo/20081118/gutenberg">Project Gutenberg のテキストデータから本文を抽出する</a> の抽出コードを呼び出すものです（上記サンプルコードでは省略）。
これに <a href="http://www.gutenberg.org/etext/2776">The Four Million by O. Henry</a> などからダウンロードした zip アーカイブされた ascii ファイルを渡すと、各短編の抽出と使用語の解析が行われて corpus という marshal ファイルに出力されます(全ての Gutenberg ドキュメントに対応するわけではもちろんありません……)。

<pre>
$ ./extract_corpus.rb 2776.zip 2777.zip ...
</pre>

使用語については原形に戻してます。精度が良くなることを期待するのと(必ずしも上がるわけではないのですが)、計算時間を減らすため。
サンプルコードでは stemmer モジュールを使って簡便に済ませてしまってます。
手元のコードでは、ちょっとでも精度を上げようと、<a href="http://d.hatena.ne.jp/n_shuyo/20071128/vocabulary">ここに書いてあるやり方で Linguistics モジュールも組み合わせつつ</a>、さらに手作り辞書とかごにょごにょしてます。
Pathtraq のカテゴリ分類での経験からすると、ここの精度が最終的に結構効いてくるんだろうなあ、という予感はします……まあ今のところは技術検証なのでシビアにやる必要はないですけど。


データの準備は本題ではないので、このへんで。
データだけでこんなに面倒じゃあ、検証とか無理無理……と思われると嬉しくないので、ここで作った corpus ファイルをさっくり公開します。

<a href="/blog/nakatani/downloads/corpus.gz">O.Henry の短編 109 データのダウンロード</a>

これを利用するには以下のように書けばＯＫ。

<pre>
data = open('corpus'){|f| Marshal.load(f) }
docs = data[:docs]
terms = data[:terms]

#docs = [{:title=&gt;"THE GIFT OF THE MAGI", :n_words=&gt;2105}, ...]
#terms = {"cat"=&gt;{49=&gt;2, 22=&gt;1, 55=&gt;2, 39=&gt;1, 50=&gt;1, ..}, ...}
</pre>

docs には短編のタイトルとその語数が格納されています。
terms は Hash 形式の inverted index になっていて、キーの term に対して、ドキュメントID とそのドキュメントにおけるその term の出現回数(= term frequency) の Hash を返します(ドキュメントID は docs におけるインデックス)。
term には原形の語幹が入っています。
この形式のデータがあれば、いきなりいろいろ遊べますよね！

<h2>simple HAC の実装</h2>

IIR Ch 17.1 の simple, naive, but inefficient な hiererachical agglomerative clustering(HAC) を実装してみましょう。

<pre>
# tf-idf を計算(一番オーソドックスな式)
def calc_tdidf(tf, df, n_docs)
  tf * Math.log(n_docs / df)
end

# similarity(ドキュメントの類似度、内積) を計算
def calc_sim(v1, v2)
  sim = 0
  v1.each_with_index {|x, i| sim += x * v2[i] }
  sim
end

# similarity(ドキュメントの類似度) を計算
class Similarity
  def initialize
    @memo = Hash.new # ドキュメント名に対して結果をキャッシュ
  end
  def calc(d1, d2)
    key = [d1[:title], d2[:title]]
    @memo[key] || @memo[key.reverse] || (@memo[key] = calc_sim(d1[:vector], d2[:vector]))
  end
  def include?(pair, title) # ペアに含まれていれば片割れを返す
    return pair[0] if pair[1] == title
    return pair[1] if pair[0] == title
  end
  # ２つのクラスタをマージ(inefficient なのはこいつのせい)
  def merge(d1, d2)
    @memo.each do |key, value|
      if (title2 = include?(key, d1[:title])) && title2 != d2[:title]
        value2 = @memo[[title2, d2[:title]]] || @memo[[d2[:title], title2]]
        @memo[key] = value2 if value2 &gt; value
      end
    end
  end
end

# corpus を読み込み
data = open(ARGV[0] || 'corpus'){|f| Marshal.load(f) }
docs = data[:docs]
terms = data[:terms]

# 軸
#axes = terms.keys # all terms
axes = terms.keys.select{|t| n=terms[t].size; n&gt;1 && n&lt;docs.size } # 2～n_docs-1

# 各ドキュメントの特徴ベクトルを計算
docs.each_with_index do |doc, doc_id|
  l2 = 0
  v = axes.map do |term|
    rev_index = terms[term]
    docfreq = rev_index.size
    termfreq = rev_index[doc_id]
    x = calc_tdidf(termfreq, docfreq, docs.size)
    l2 += x * x
    x
  end
  l = Math.sqrt(l2)
  doc[:vector] = v.map{|x| x / l} # 単位ベクトルに
end

# HAC algorism(ここが本体)
sim = Similarity.new
clusters = []
while docs.size &gt; 1
  maxsim = 0
  maxsim_pair = nil
  docs.each do |d1|
    docs.each do |d2|
      break if d1==d2
      s = sim.calc(d1, d2)
      if maxsim &lt; s
        maxsim = s
        maxsim_pair = [d1, d2]
      end
    end
  end

  clusters &lt;&lt; [maxsim_pair[0][:title], maxsim_pair[1][:title], maxsim]
  docs.delete(maxsim_pair[1])
  sim.merge maxsim_pair[0], maxsim_pair[1]
end

# dendrogram を出力
tree = Tree.new(clusters)
puts tree
</pre>

IIR に掲載されている pseudo code では 13行しかないんですけどね(苦笑)。
しかもこれでも dendrogram を出力するための Tree クラスはアルゴリズムに直接関係ない＆でかいので省略してあるんですよ……（後掲してあるのでご安心を）。

細かい説明は後にして、まずは実行。
以下のような dendrogram が得られます(テキストだから枝分かれの表現が貧弱ですが)。

<pre>
------------------------- THE BRIEF DEBUT OF TILDY
||||||||| ||    |    +--- AN ADJUSTMENT OF NATURE
||||||||| ||    +-------- A COSMOPOLITE IN A CAFE
||||||||| |+------------- FROM THE CABBY'S SEAT
||||||||| |  +----------- THE GREEN DOOR
||||||||| |      +------- SPRINGTIME A LA CARTE
||||||||| +-------------- THE FURNISHED ROOM
|||||||||   | |   | +---- BETWEEN ROUNDS
|||||||||   | |   |    +- TOBIN'S PALM
|||||||||   | |   +------ AN UNFINISHED STORY
|||||||||   | +---------- THE SKYLIGHT ROOM
|||||||||   +------------ LOST ON DRESS PARADE
||||||||+---------------- MAN ABOUT TOWN
|||||||+----------------- MEMOIRS OF A YELLOW DOG
||||||+------------------ THE CALIPH, CUPID AND THE CLOCK
||||||         +--------- THE COP AND THE ANTHEM
|||||+------------------- MAMMON AND THE ARCHER
||||+-------------------- BY COURIER
|||+--------------------- AFTER TWENTY YEARS
|||      |            +-- THE COMING-OUT OF MAGGIE
|||      +--------------- A SERVICE OF LOVE
||+---------------------- SISTERS OF THE GOLDEN CIRCLE
||                 +----- THE GIFT OF THE MAGI
|+----------------------- THE LOVE-PHILTRE OF IKEY SCHOENSTEIN
+------------------------ THE ROMANCE OF A BUSY BROKER
</pre>

109 ドキュメントだとダイアグラムが大きすぎるので、短編集 The Four Million に含まれる 25 編のクラスタリング結果です。
本当は閾値も出したいんだけど、それは宿題ということで(Graphviz で書けるかなぁ？)。

この結果を見ても、コーパスの性質を知らないと「？？？」でしょうけれど（だからもっとわかりやすいデータをって！）。
それでもクラスタリングとしては到底満足できない結果であることはわかります。
例えば左から３列目までの枝分かれを見ることで、全体を４つのクラスタに分けることが出来ますが、「１個、１個、２個、２１個」というクラスタリングになってしまいます。本当に１個しか含まれないクラスタが存在すべきデータならともかく(１つだけイタリア語とかｗ)、そうでないならこの割り方は不自然です。
このように枝分かれが階段状になってしまうことを「チェイニング効果」というそうですが、それが起きてしまっているわけですね。

ここで実装した simple unefficient な HAC ではチェイニング効果が発生しやすいことはわかっていたんですが(だから inefficient!)、それが確認できた、ということですね。
17.2 以降の efficient な HAC ではそれが抑えられるはずなので、次回はそちらに挑戦します。


<h2>コードについて</h2>

簡単に。

ドキュメントの特徴ベクトルは、基準となる terms をあらかじめ選んでおき、term ごとの tf-idf を計算、それを並べたものになります(後のために単位ベクトル化しておく)。
ドキュメント間の類似度 (similarity) は、その特徴ベクトルの内積(=単位ベクトルなので、２つのベクトルのなす角の余弦)と定義します。0 から 1 の間の値となり、大きいほど類似度が高いと見なすことが出来ます。
このあたりは IIR の Chapter 6 に書いてあります。

特徴ベクトルは (terms の個数)次元の vector space の元となるわけですが、109 ドキュメントでの使用語数は 13000 くらいあるので 13000 次元の vector space ！
……なんて考えただけでもうんざりするので(しかもテスト用の小さいコーパスに過ぎないのに!)、サンプルコードでは恣意的に減らして document frequency が１だったり、ドキュメント数と一致(=全ドキュメントに出現)というものについては除外してみました。それでもまだ 8000 あるんですが。
正しくは Chapter 13 の feature selection などを駆使するべきなのでしょうが、そこは後回し。

あとは基本的に IIR の pseudo code に従って。
ただ、対称な similarity を２回計算するのを嫌って、結果のキャッシュとかしてます。


次回は上で書いたとおり、このコードを efficient HAC に発展させたいです。
おっとそうそう。dendrogram 出力クラスも載せておきます。だらっと書いたので長いです(汗)。

<pre>
class Tree
  def initialize(clusters)
    @tree = Hash.new
    clusters.each do |d1, d2, s|
      branch d1, d2
    end

    @lines = Array.new
    @index = Hash.new
    gen_lines @tree.values[0]
    clusters.each do |d1, d2, s|
      connect d1, d2
    end
  end

  def get_tree(d)
    if @tree.key?(d)
      @tree.delete(d)
    else
      d
    end
  end
  def branch(d1, d2)
    @tree[d1] = [get_tree(d1), get_tree(d2)]
  end

  def gen_lines(branch)
    if branch.instance_of?(Array)
      gen_lines branch[0]
      gen_lines branch[1]
    else
      @lines &lt;&lt; "- " + branch
      @index[branch] = @lines.size - 1
    end
  end

  def connect(d1, d2)
    i1 = @index[d1]
    i2 = @index[d2]
    i1, i2 = i2, i1 if i2 &lt; i1
    @lines.each_with_index do |line, i|
      if i==i1
        @lines[i] = "-" + line
      elsif i==i2
        @lines[i] = "+" + line
      elsif i&gt;i1 && i&lt;i2
        @lines[i] = "|" + line
      elsif line =~ /^-/
        @lines[i] = "-" + line
      else
        @lines[i] = " " + line
      end
    end
  end
  def to_s
    @lines.join("\n")
  end
end
</pre>
]]>
   </content>
</entry>
<entry>
   <title>iVoca が日本語(ローマ字入力)に対応、なんでも暗記サービスになります！</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2008/12/ivoca_3.html" />
   <id>tag:labs.cybozu.co.jp,2008:/blog/nakatani//13.2110</id>
   
   <published>2008-12-10T02:49:24Z</published>
   <updated>2008-12-10T03:01:21Z</updated>
   
   <summary> 単語をタイピングで覚える iVoca(アイボキャ)。 問題が上から落ちてくるの...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="164" label="ローマ字入力" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="163" label="日本語" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<a href="http://ivoca.31tools.com/home">単語をタイピングで覚える iVoca(アイボキャ)</a>。
問題が上から落ちてくるので、どんどん答えていくだけで単語を覚えてしまえるサービスです。

タイピングということはやっぱり英語だけなのか……
これが日本語でもできたら、英語から日本語もできるし、それこそ英語以外のいろいろなことを覚えるのに使えるのになあ。

と思っていたあなたに朗報！
iVoca が日本語入力に対応しました。

<script src='http://ivoca.31tools.com/widget?id=142' type='text/javascript'></script>

<a href="http://ivoca.31tools.com/book?id=142">「iVoca 難読漢字」</a>です。
ちょっぴり難しめの漢字や熟語が降ってきますので、読みを「ローマ字入力で」答えてください。
もちろん今までの iVoca と同じで、ユーザ登録すれば漢字ごとの成績を覚えてくれて、苦手な問題を重点的に暗記できます。


仮名漢字混じりの問題ももちろんＯＫ！

<script src='http://ivoca.31tools.com/widget?id=164' type='text/javascript'></script>

<a href="http://ivoca.31tools.com/book?id=164">「基本英単語100！(意味編)」</a>は、iVocaで楽しんでもらっていた「基本英単語100！」の意味を答えるバージョンです。
例えば問題で "see" が落ちて来たら "見る" と答えるわけですね。

漢字の入力はどうするのかって？　変換とか面倒でしょう？

いえいえご心配なく！
なんと "mi" と入力した時点で "見" が表示されます。
変換とか全くいらないので、とっても楽ちん。覚えることだけに集中できます。

他にも<a href="http://ivoca.31tools.com/book?id=168">「ハリー・ポッター１巻を読むのに必要な英単語(意味編)」</a>や、<a href="http://ivoca.31tools.com/book?id=159">「都道府県の県庁所在地」</a>など、日本語入力に対応したブックはどんどん増えていきます。


もちろん iVoca なので、ユーザが自由に問題を作ることができます。


これからは暗記ものならだいたいなんでも iVoca を使ってもらえますね。
英語だけじゃなくて、様々な Vocabulary (語彙)を増やすのに iVoca を活用してもらえると嬉しいです。


そうそう。
とりあえず覚えなきゃいけないことがない人でも、「全く変換とかしないのにどんどん漢字が表示されていく」という新感覚はぜひ一度楽しんでもらいたいです(笑)。
なかなか気持ちいいですよ！


続きには「日本語対応ブックの作り方」。

]]>
      <![CDATA[【日本語対応ブックの作り方】

iVoca でのブック(問題)作成手順については<a href="http://ivoca.31tools.com/help.html#a5">ヘルプ</a>を参照してください。
ここでは特に日本語入力に対応した単語データの作り方について説明します。

今までは主に問題が日本語、答えが英語でしたので、「日本語,英語」として問題を作成してもらっていました。

<pre>
猫,cat
犬,dog
</pre>

新しく日本語入力の問題を作ってもらう場合、答えがひらがなやカタカナなら、今まで英単語を書いていたところにそのままその答えを書いてください。

<pre>
子,ね
丑,うし
寅,とら
</pre>

<pre>
鹿島,アントラーズ
川崎,フロンターレ
名古屋,グランパス
</pre>

漢字が入っている場合は、漢字の読みを教えてあげる必要があります。
漢字の部分を "/" で区切って、その読みを ":" の後ろに書いてください。
送りがなのように漢字とかなが混じっている場合は、漢字とかなの間も同じく "/" で分けてください。かなの方はそのまま残しておけばＯＫです。

<pre>
he,彼:かれ
she,彼:かの/女:じょ
think,考:かんが/える
red car,赤:あか/い/車:くるま
</pre>

<pre>
「吾輩は猫である」の作者,夏:なつ/目:め/漱:そう/石:せき
金閣寺を建立した,足:あし/利:かが/義:よし/満:みつ
弘法大師,空:くう/海:かい
</pre>

複雑なようですが、慣れれば難しくないですよ。

実は、この機能は単に漢字を変換しなくても入力できる機能ではなくて、「入力文字列に自由な表示文字列を割り当てる機能」なのです。
だから、漢字部分は何文字でも書けますし、それこそ漢字じゃなくても大丈夫。読みもかなだけじゃあなくて iVoca で使えるものならアルファベットや記号もＯＫ、と自由度が高いので、こんなこともできます。

<pre>
二十歳:はたち
竜:ドラグ/破斬:スレイブ
禁書目録:index
♂:おす
</pre>
]]>
   </content>
</entry>
<entry>
   <title>iVoca が Yahoo! ブログへの貼り付けに対応しました＆RSS＆苦手単語ランキング</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2008/11/ivoca_yahoo_rss.html" />
   <id>tag:labs.cybozu.co.jp,2008:/blog/nakatani//13.2089</id>
   
   <published>2008-11-19T14:22:10Z</published>
   <updated>2008-11-19T14:46:51Z</updated>
   
   <summary> ゲームで英単語を覚える iVoca に新しい機能を追加しましたのでご紹介。 ■...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="160" label="RSS" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="156" label="Yahoo! ブログ" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="157" label="ブログパーツ" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="158" label="苦手単語" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<br />
<a href="http://ivoca.31tools.com/">ゲームで英単語を覚える iVoca</a> に新しい機能を追加しましたのでご紹介。<br />
<br />
<br />
<h3>■ Yahoo! ブログにブログパーツを貼り付けられるようになりました。</h3>
<br />
埋め込み用タグに "Yahoo" を追加！<br />
<a href="http://blogs.yahoo.co.jp/">Yahoo! ブログ</a>の Wiki 文法を使って iVoca を貼り付けられるようになりました。<br />
<br />
<img src="http://ivoca.31tools.com/img/blogs/yahoo_blog.png" /><br />
<br />
ただ残念ながら、Yahoo! ブログのブログパーツは、パーツをクリックして他のページに遷移することを禁止しているため、ブログパーツからの直接ログインなどは現状できなくなってしまっております。こちらについては対策を検討したいと考えています。<br />
<br />
あとは……そうですね、mixi に貼り付けられるようになったらとっても嬉しいんですがｗ<br />
<br />
<br />
<h3>■ 新着ブック RSS 配信</h3>
<br />
新しく作成されたブックや更新されたブックについて、RSS での配信を開始しました。<br />
RSS リーダーなどで <a href="http://ivoca.31tools.com/api/booksrss">http://ivoca.31tools.com/api/booksrss</a> を購読してもらえば、興味のあるブックを見逃しません。<br />
<br />
<br />
<h3>■ 「苦手な問題ランキング」を追加</h3>
<br />
みんながどういう単語を苦手としているか、ブックごとにランキングを表示しています（まだ遊んでいる人が少ないブックでは表示されません）。<br />
<br />
<img src="http://ivoca.31tools.com/img/blogs/nigate.png" /><br />
<br />
どれくらいの難易度かとか、ブックにどういう雰囲気の単語が含まれているとか、みんなこんな単語が苦手なんだ～とか、いろいろ楽しんでもらえるかと。<br />
問題を作ってくれた人にはどの単語や表現が難しいかとか意外とわからないものなので、ブック作成者にはなおさら興味深く感じてもらえるんじゃあないかと思っています。<br />
<br />
<br />
<br />
今後もまだまだ機能追加拡充を予定していますので、お楽しみに！<br />
<br />
]]>
      
   </content>
</entry>
<entry>
   <title>「英単語」タイピングゲーム iVoca が英語以外にも対応しました </title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2008/11/_ivoca_1.html" />
   <id>tag:labs.cybozu.co.jp,2008:/blog/nakatani//13.2068</id>
   
   <published>2008-11-04T09:30:36Z</published>
   <updated>2008-11-04T10:16:41Z</updated>
   
   <summary> iVoca(アイボキャ) はユーザが自由に問題を作成することができる「英単語を...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<br />
iVoca(アイボキャ) はユーザが自由に問題を作成することができる「英単語を覚える」タイピングゲームです。<br />
<br />
といっても、使われる文字がアルファベットに限ってよければ、あるいは問題文の方にもってくるのなら、以前から英語以外の問題を作成できていました（例:<a href="http://ivoca.31tools.com/book?id=50">「ポルトガル語基礎会話とサバイバル単語」</a>、<a href="http://ivoca.31tools.com/book?id=51">「動物の単語(ハングル版)」</a>）。<br />
<br />
今回は！<br />
イタリア語やフランス語、ドイツ語などで使われるアクセント付きアルファベットなどの問題として使えるようになりました。<br />
例えばイタリア語で「木曜日」は "giovedì" のように i にアクセントが付いた文字が使われるのですが、これをそのまま問題として登録できるわけです。<br />
ゲームの中でユーザが入力するときは、アクセントを無視して「Ｉ」キーでタイプ。アクセント付き文字をどうやって入力するかじゃあなくて、単語を覚えるためのゲームなので、最も簡単な方法で遊んで覚えて欲しいですからね！<br />
<br />
他にもどういう文字が使えるかは、ヘルプの<a href="http://ivoca.31tools.com/help.html#char">「問題に使える文字の種類」</a>を参照してください。<br />
<br />
早速イタリア語のブックを作ってみました。<br />
イタリアと言えばやっぱり！　おいしいごはん。というわけで第１弾は「イタリア語の食べ物」。<br />
<br />
<script src='http://ivoca.31tools.com/widget?id=104' type='text/javascript'></script>
<br />
iVoca のルーツは、素人時代に作ったイタリア語のタイピングゲーム。<br />
その頃から「このゲームを、学習度とか覚えてくれる、ちゃんとしたサービスにしたいな」とずっと思ってきたのが iVoca になりました。<br />
でも、やっぱり多くの人に受け入れてもらうためには、最初は英語を優先せざるをえず……<br />
こうしてイタリア語の問題も作れるようになって、夢の一つがようやく叶いました、ね。<br />
<br />
というわけで、イタリア語の問題のストックはまだまだあります。第２弾第３弾もお楽しみに！<br />
<br />
]]>
      
   </content>
</entry>
<entry>
   <title>IE &amp; Javascript で英語をしゃべらせる方法 </title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2008/10/ie_javascript.html" />
   <id>tag:labs.cybozu.co.jp,2008:/blog/nakatani//13.2061</id>
   
   <published>2008-10-28T08:23:50Z</published>
   <updated>2008-10-28T09:37:54Z</updated>
   
   <summary>「（ちょっと設定しないといけないけど）何もインストールしないで、ブラウザに英語を...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="152" label="SAPI" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="153" label="iVoca" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="112" label="英語" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="154" label="音声合成" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[「（ちょっと設定しないといけないけど）何もインストールしないで、ブラウザに英語を喋らせるサンプル」を作ってみました。IE 限定。<br />
元ネタは<a href="http://builder.japan.zdnet.com/member/u336453/blog/2008/10/22/entry_27017272/">「JavaScript などで英語を喋らせる方法」</a>。<br />
<br />
<dl>
<dt>iVoca おしゃべりモード (IE & Javascript で英語をしゃべらせるサンプル)</dt>
<dd><a href="http://ivoca.31tools.com/speak_text.html">http://ivoca.31tools.com/speak_text.html</a></dd>
</dl>
<br />
任意の英文を喋らせてみるサンプルと、<a href="http://ivoca.31tools.com/">英単語タイピングゲーム iVoca</a> を SAPI に対応させて、タイピングした英単語をしゃべってくれる「おしゃべりモード」を試験的に実装したものを紹介しています。<br />
<br />
細かい手順やセキュリティに関する注意事項などはリンク先をご覧いただくとして、ここでは仕組みを簡単に説明。<br />
<br />
Windows XP や Vista では SAPI (Microsoft Speech API) が標準搭載されていて(Windows 2000 にも入っているとか。手元に環境がないので確認できませんが)、そいつが音声合成を行う ActiveX コンポーネントを持っているので、それをブラウザ上の Javascript から呼び出せれば OK。<br />
なのですが、残念ながらその ActiveX はブラウザから呼べるようになっていません（「実行しても安全とマーク」されていない）。<br />
<br />
それでもなんとかして呼び出したいなら、セキュリティの設定の方をいじる必要があります。<br />
具体的には Internet Explorer のセキュリティオプションで「スクリプトを実行しても安全だとマークされていない ActiveX コントロールの初期化とスクリプトの実行」を「有効にする」に設定することで SAPI の ActiveX コンポーネントをブラウザの Javascript から呼び出せるようになります。<br />
もちろん、この設定項目はどこからどう見ても危険。せめて「信頼済みサイト」で行っておくべき。というあたりの手順はサンプルのページを参照のこと。<br />
間違っても「インターネットゾーン」でこの設定を行わないように……！！！！<br />
<br />
SAPI を呼び出せるようにしてしまえば、後は簡単。<br />
SAPI の音声合成コンポーネントを取ってきて、その Speak メソッドに発音させたい英文を渡すだけです。<br />
<br />
<pre>
var spkr = new ActiveXObject('SAPI.SpVoice');
spkr.Speak("Hello iVoca!");
</pre>
<br />
サンプルの方では、try ～ catch で囲んで SAPI を呼び出せない場合の手当をしたり、Flash (iVoca のゲーム本体) から呼び出すためにほげほげしたりしてます。<br />
<br />
XP と Vista でインストールされている SAPI のバージョンが違っていて、実は Vista (SAPI 5.3) の方が女性の声で音質も良く、発音も自然できれい。合成じゃなくて録音じゃないかというくらい。<br />
XP (SAPI 5.1) のももちろん十分すごいんですが、音質のかなり悪い男性の声、発音もくせのある感じ……。Vista のを聞いた後だと、正直聞くに堪えないと言わざるを得ない。<br />
Vista にしてしまって、「あーなんか一つでもいいから Vista にして良かったと思えることないかなあ。もう何でもいいんだけど……」と日頃嘆いている方には、ぜひ Vista の発音を聞いて欲しいですね！！！<br />
もうほんと、このことだけで Vista にして良かったと思えること請け合いです……気の持ちようによっては。<br />
(ちなみに Vista の女性は "Microsoft Anna"、XP の男性は "Microsoft Sam" というお名前です）。<br />
<br />
サンプルのページでは<a href="http://ivoca.31tools.com/book?id=90">「アメリカ合衆国の５０州」</a>を取り上げてみたんですが、こういうのは特に正しい発音を聞きながら勉強したいからピッタリですよね。<br />
あと、サンプルにするのをつい自重してしまったんですが、<a href="http://ivoca.31tools.com/book?id=76">「ガンダムの機動兵器(型式→英語名)」</a>もかなり強烈にオススメ。想像以上にまともに発音してくれるのでウケます。<br />
<br />
iVoca ではユーザさんに単語帳を自分で作ってもらえるのですが、テキストなら誰でも作れるけど、音声はなかなかそういうわけにはいかない。<br />
でも、英文や単語を与えるだけでこれほど正確に発音してくれる SAPI がブラウザから普通に使えるようになれば、ユーザさんが作ってくれた問題の価値が何倍にも増しますし、他にもいろんな利用シーンが考えられて、いろいろとっても楽しそうな想像がふくらむんですが……音声合成だけでもブラウザから「安全に」使えるようにならないかなあ。
<br />
実際、発音している間はブラウザや Flash の動作が止まってしまうのさえなんとかなれば、個人的には常用したい。<br />
setTimeout でくるんだり、iframe 内で実行させるようにしてみたり、といくつか試してみたのですが、今のところこれを回避できずにいます。うーん。<br />
<br />
]]>
      
   </content>
</entry>
<entry>
   <title>iVoca でブック(出題用の単語帳)を作るには </title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2008/10/ivoca_2.html" />
   <id>tag:labs.cybozu.co.jp,2008:/blog/nakatani//13.2055</id>
   
   <published>2008-10-21T07:37:19Z</published>
   <updated>2008-10-21T08:40:07Z</updated>
   
   <summary> 英単語タイピングゲーム iVoca は楽しんでいただけましたか？ 自分の興味の...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<br />
<a href="http://ivoca.31tools.com/">英単語タイピングゲーム iVoca</a> は楽しんでいただけましたか？<br />
自分の興味のあるブック(単語帳)は一通りクリアしてしまったから、もう勉強する（遊ぶ）ものがないよう、という人もいらっしゃったりします？<br />
そういうとき、iVoca トップページの上にある「人気のタグ」からまだ勉強していないブックを探すのもいいですが、iVoca にはブックを作る機能があるので、自分で好きなブックを作ってしまうのも一つの手。<br />
<br />
「自分で作ったブックなんて、全部答え知ってるからおもしろくないでしょう」って？<br />
いえいえ、<a href="http://d.hatena.ne.jp/nishiohirokazu/20081016/1224127055">「他人が作ったブックで遊ぶよりも自分で作ったブックで遊ぶ方が面白いかも」</a>という意見もありますよ。<br />
ここはだまされたと思って一度作ってみちゃいましょう！<br />
<br />
というわけで、iVoca ブックの作り方をご紹介。とっても簡単です。<br />
<br />
iVoca の画面の右上にあるメニューの中から、"create book" をクリックするとブック作成画面が開きます。<br />
<img src="/blog/nakatani/imgs/create_book.png" /><br />
<br />
入力項目がいくつかありますが、最低限記入が必要なのは「ブック名」と「問題」です。<br />
ブック名は内容がわかるよう適当に短めに記入しましょう。<br />
<img src="/blog/nakatani/imgs/book_name.png" /><br />
<br />
問題＆解答は半角カンマ区切り、「問題,解答」か「解答,問題」のどちらでもお好きな形式で、問題の個数だけ改行区切りで並べればＯＫ。<br />
最低５問あれば登録して遊べちゃいます（さすがに５問くらいないと、１ゲームいかないうちに確実に終わってしまいます……）。<br />
<img src="/blog/nakatani/imgs/question.png" /><br />
<br />
適当に作ってみただけだからいきなり他の人に遊んでもらうのは恥ずかしいなあ……という場合は「公開範囲」を「自分専用」に設定すれば、そのブックを見えるのも遊べるのも自分だけになりますのでとっても安心。<br />
ブックが充実して、みんなに遊んでもらいたくなった場合はもちろん「公開範囲」を「全体に公開」などに変更するのを忘れずに！<br />
<img src="/blog/nakatani/imgs/accessible.png" /><br />
<br />
登録したブックはトップページの「新着ブック」に表示されます！<br />
<img src="/blog/nakatani/imgs/arrival.png" /><br />
<br />
ブックを登録した後に、単語の追加とか変更とかも簡単簡単。<br />
「新着ブック」に表示されているブック名をクリックするとかしてブック画面を開くと、右上のブック作者のアイコンの横に「このブックを編集」ボタンがあるので、クリック。<br />
すると新規登録の時と同じブック作成画面が開くので、同じように単語の追加や修正をしてください。<br />
<img src="/blog/nakatani/imgs/edit_book.png" /><br />
<br />
<br />
<h3>どんなブックを作ろうかな？</h3>
<br />
もちろん誰でも自由に好きなブックを作って勉強したり楽しんじゃったりしていいんですが、例えばどんなブックが楽しそうかなあ。<br />
<br />
<dl>
<dt>英語の文章、小説、漫画を読んでいて、わからなくて辞書引いちゃった単語を覚えちゃう</dt>
<dd><a href="http://ivoca.31tools.com/book?id=69">ハリー・ポッター１巻を読むのに必要な英単語</a>、<a href="http://ivoca.31tools.com/book?id=84">The Great Gatsby を読むのに必要な英単語</a></dd>
<dt>英語の映画やTVを見たり曲を聴いたりしていて、セリフや歌詞に出てきた単語を覚えちゃう</dt>
<dd><a href="http://ivoca.31tools.com/book?id=89">リトルチャロ Episode 1～3</a></dd>
<dt>スポーツとか経済とか、興味のある範囲の言葉を用語集的に集める</dt>
<dd><a href="http://ivoca.31tools.com/book?id=56">レシピによく出てくる単語</a>、<a href="http://ivoca.31tools.com/book?id=79">OpenID Authentication 2.0 に関連する英単語</a></dd>
<dt>試験対策とかで出題範囲の単語を効率よく暗記したい</dt>
<dd><a href="http://ivoca.31tools.com/book?id=73">工業英検3級対策 Vocabulary "M-R"</a></dd>
<dt>全然あんまり英語と関係ないブックを作って遊んじゃう</dt>
<dd><a href="http://ivoca.31tools.com/book?id=76">ガンダムの機動兵器(型式→英語名)</a>、<a href="book?id=70">タイ語で数字</a></dd>
</dl>
<br />
iVoca に実際に登録されてるブックの傾向と、どんなブックがあるかをさらりとあげてみました。<br />
勝手に整理してみただけなので、もちろんこの範囲に収まってなきゃいけないなんてことないので、好きなブックを作って勉強してね！（遊んでね！）。<br />
<br />
そうそう、ちょいと単純な雑学クイズっぽいのもいいなあ、なんて思って、今回のブックの作り方ご紹介を用意するついでにこんなブックを作ってみました。<br />
Have fun!<br />
<br />
<script src='http://ivoca.31tools.com/widget?id=90' type='text/javascript'></script>

http://labs.cybozu.co.jp/blog/nakatani/imgs/blogparts.png]]>
      
   </content>
</entry>
<entry>
   <title>iVoca ブログパーツを貼り付けてみよう</title>
   <link rel="alternate" type="text/html" href="http://labs.cybozu.co.jp/blog/nakatani/2008/10/ivoca_1.html" />
   <id>tag:labs.cybozu.co.jp,2008:/blog/nakatani//13.2051</id>
   
   <published>2008-10-17T05:25:57Z</published>
   <updated>2008-10-17T05:28:59Z</updated>
   
   <summary> 10/15 にリリースしました英単語タイピングゲーム iVoca。 その iV...</summary>
   <author>
      <name></name>
      <uri>nakatani</uri>
   </author>
         <category term="iVoca" scheme="http://www.sixapart.com/ns/types#category" />
   
   
   <content type="html" xml:lang="ja" xml:base="http://labs.cybozu.co.jp/blog/nakatani/">
      <![CDATA[<br />
10/15 にリリースしました<a href="http://ivoca.31tools.com/">英単語タイピングゲーム iVoca</a>。<br />
<br />
その iVoca のゲーム部分をブログパーツとして、あなたのサイトやブログに貼り付けることが出来るようになっています。<br />
ブログパーツ貼り付けは iVoca にユーザ登録しなくてもできます！<br />
<br />
<script src='http://ivoca.31tools.com/widget?id=69' type='text/javascript'></script>
<br />
やり方は簡単。<br />
まず iVoca で貼り付けたいブックを選びます。<br />
iVoca のトップページに「人気のブック」が出ていますし、「人気のタグ」をクリックすれば(例えば<a href="http://ivoca.31tools.com/books?tag=%E5%88%86%E9%87%8E%E5%88%A5">「分野別」をクリックしてみる</a>)、そのタグで分類されたブックの一覧が表示されるので、そのあたりから探してみましょう。<br />
<br />
<a href="http://ivoca.31tools.com/book?id=69">「ハリー・ポッター１巻を読むのに必要な英単語」</a>を貼り付けるなら、ブック名をクリックして開いたブック画面の右下にある「埋め込み用タグを表示」ボタンを押します。<br />
<br />
<img src="/blog/nakatani/imgs/blogparts.png" /><br />
<br />
ブログパーツの貼り付け方の選択肢が出てきますので、あなたの使っているブログサービスに合わせて「script 形式」または「Google Gadgets 形式」を選んでください。<br />
script タグを使えるブログなら「script 形式」を選びましょう。はてなダイアリーの場合は「Google Gadgets 形式」を選んでくださいね。<br />
ブログでどのような形式のブログパーツが使えるかわからない場合は、ブログのサービス元にてご確認ください。<br />
<br />
<img src="/blog/nakatani/imgs/blogparts2.png" /><br />
<br />
形式を選ぶと、貼り付け用のタグがそのすぐ上の欄に表示されるのでコピーして（クリックすればタグ全体が選択されるので、右クリックしてコピーを選ぶだけでＯＫ）、ブログの本文書き込み欄などにペースとしてください。<br />
これでゲームをブログに貼り付けられました。かんたん！<br />
<br />
]]>
      
   </content>
</entry>

</feed>

