遥かへのスピードランナー

シリコンバレーでAndroidアプリの開発してます。コンピュータービジョン・3D・アルゴリズム界隈にもたまに出現します。

プロダクティブ・プログラマを実践して作業効率アップ

オライリーの新書「プロダクティブ・プログラマ」が良かった。

詳しい内容はこちらの目次とかを参照して欲しいのですが、すぐにでも使える実践的なTIPSが詰まっていて非常に良い本です。こんなのだいたい知ってるよー、という人も多いだろうけど、これだけあれば何かしら知らないことがあるんじゃないでしょうか。

この本の前半はいわゆるOA環境に特化した内容ですが、これだけでも十分実践する価値はあります。僕は自宅ではMac、会社ではWindowsXPを使っているのですが、会社のPC作業の効率に最近疑問を抱き初めていたのでこの本はちょうどよいタイミングでした。

いくつか実際に導入したTIPSを以下に挙げてみます。

仮想デスクトップの導入

Microsoftの提供するPowerToysの内の1つであるVirtual Desktop Managerを導入しました。(日本語化はこちら
このツールを導入すると仮想的なデスクトップを4つもつことができ、それぞれのデスクトップで起動しているアプリケーションを分けて管理することができます。また、ショートカットでそれぞれのデスクトップを簡単に切り替えることができます。

実際には4つも使うことはあまり無いと思うのですが、事務用と作業用に分けるだけでも相当便利です。ツールバーにメーラーやブラウザが存在しているのと、存在しないのとでは作業効率が全然違います。たとえ見る気はなくても、ツールバーに存在しているとついついメールチェックをしてしまったり、ネットサーフィンしてしまったりするものです。「メーラーやブラウザが完全に見えない」デスクトップを持つことで作業効率を大幅に上げることができます。

クリップボードの履歴管理ツールの導入

CLCLというクリップボードの履歴を管理できるツールを導入してみました。

地味な印象があるので必要性に疑問を感じる人も多いと思いますが、たとえば「クリップボードに新しいデータをコピーしたいけど、さっきコピーした内容が消えてしまうから一度テキストエディタに落としてからコピーしよう」という経験がある人には絶対に役立つツールと言えます。このツールを使えば、そんな心配をする必要がなくなるからです。遠慮無しにコピーしまくってから必要なものをペーストすればいいんです。

不要な通知をオフに

以前からやろうやろうと思っていたのですが、この本を読んでようやくメール通知をオフにする決心がつきました(笑)。最初の1日は、メールをリアルタイムで見ないと不安になったりもしたのですが、次の日からは気にならなくなりました。

仮想デスクトップで、作業用のデスクトップにはツールバーにも表示されないようにしたのでかなり効率が高まっている実感があります。


ほんのさわりくらいですが、まだまだ導入できることはたくさんあると思います。一度、日々の全ての作業を疑いの目で見てみると良いかもしれません。当たり前と思っている中に、改善すべき事象が潜んでいることが多いんじゃないでしょうか。

と、ここまで書いたところで、実は一番効率が悪いのは会社と自宅でOSが違うことなんではと思い至りました。。Macを許可してくれるほど柔らかい会社でもないしなー。なんとかならんものか。。。

mixiアプリカンファレンス 2009に行ってきました

定員に対して10倍の応募があったというmixiアプリカンファレンス 2009に当選したので行って参りました!

mixi笠原社長・Googleの辻野社長の講演を始めとした錚々たる顔ぶれで、プレスも多く詰めかけており、mixiアプリにかける周囲の期待の大きさを実感できただけでも大きな収穫でした。

僕個人が今現在mixiアプリに抱いている印象は、扱えるデータがまだまだ少ないので、アイデアだけで勝負!みたいなものが多いかなあと。もっと色々なデータ(レビューのデータとか)が使えるようになると、アイデアだけでなく特殊な技術を応用したアプリとかが出てきて、面白くなっていくのではと思ってます。あと、今回はほとんどクローズアップされなかったけどmixiコネクトには相当期待しています。
それと、mixiアプリのモバイル版があるとの発表がありました。これはまだ可能性が未知数。仕様が明日4/24に発表されるとのことでこちらも楽しみにしています。

懇親会では、毎度お世話になってるゴーガの小山さんに紹介して頂いて笠原社長や小山技術顧問とも名刺交換させて頂きました。人とのつながりは大事ですね、紹介してもらえるというのはありがたいことです。感謝!

最近の関心事

最近、学問にはまっています。はまっています、というのはちょっと語弊があるかもしれないんですが、自分のエンジニアとしての付加価値は何だろう?と考えたときに、マネージャ的な仕事やアーキテクト的な仕事よりも学問的なものに重心を置いていることの方が得意だし、面白いという結論に至ったわけです。そんなわけで大学卒業以来、およそ5年ぶりに線形代数の本を開いたりとかしています。5年が経ってようやく数学と現実との接点が見えてきた感がありとっても面白いです。

そんなわけで最近勉強している本。

Introduction to Information Retrieval
Christopher D. Manning Prabhakar Raghavan Hinrich Schutze
Cambridge University Press
売り上げランキング: 3684
内容は平易だけどやはり洋書という点で読むのに時間がかかります。
でも英語の勉強にもなって良い。あと内容が抽象的なので後述する「集合知プログラミング」で実践も合わせていくと良い、と思う。


パターン認識と機械学習 上 - ベイズ理論による統計的予測
C. M. ビショップ
シュプリンガー・ジャパン株式会社
売り上げランキング: 145292
おすすめ度の平均: 5.0
5 機械学習の新・定番教科書,待望の邦訳!
こちらは逆に日本語なので読みやすいのですが、数学的な内容がやや高度で読むのに時間がかかりますね。


集合知プログラミング
Toby Segaran
オライリージャパン
売り上げランキング: 11014
おすすめ度の平均: 4.5
4 プログラムはいまいち
4 ようこそ、Web 2.0の世界へ
5 知的好奇心が刺激された!!
5 機械学習の現実世界での応用
3 私には敷居が高かったかも・・・
理論的な背景については手薄だが、実践的な内容。すぐプログラム組んでみたくなっちゃう人向きですね。


そんなわけでこのブログにも成果を晒していこうかなと思います。
何ができるかわかんないけど色々遊んでみます。

はてなブックマークFirefox拡張を入れてみた

はてなブックマーク Firefox 拡張のベータテストを開始します】
http://hatena.g.hatena.ne.jp/hatenabookmark/20090402/firefox_beta

Firefoxローカルデータと同期する機能はよいですね。ローカルのブックマークと連携したりとかするといろいろ未来が広がりそうです。

個人的にはあと、人気エントリ・注目エントリに関する機能があってもよいかなあと思います。関連しそうな注目エントリをサイドバーに表示させてあげるとか。

ちなみに、ちゃんと公開はされていないようですが今開いているページのブックマーク数は、
http://b.hatena.ne.jp/entry.count?url=http%3A//d.hatena.ne.jp/
APIを利用して取得されていますね。

FireMobileSimulatorのIRCチャンネル作りました

FireMobileSimulatorの開発者を広げていくにはどうすればいいのか?という疑問をぶつけたところ、id:ZIGOROuさんからIRCチャンネルを作ってはどうかという案を頂いたので早速作ってみました。

#fms-devel@freenode

になります。
FireMobileSimulatorの開発に興味を持っている方、質問などお気軽にお願いします。
(その前にコードをリファクタリングした方がいいとは思いつつも・・・)

WEB+DBプレスの「[速習]レコメンドエンジン」のサンプルプログラムを訂正してみる

プリファードインフラストラクチャーid:tkngさんと岡野原さんがWEB+DBプレスvol.49に「[速習]レコメンドエンジン」という記事を書かれています。

WEB+DB PRESS Vol.49
WEB+DB PRESS Vol.49
posted with amazlet at 09.03.08

技術評論社
売り上げランキング: 359

レコメンドエンジンには前々から興味を持っていたので、早速サンプルコードを自分でも書いてみるか、と思い誌面のソースをXCodeに打ち込んで動かしていたのですが、コンパイルが通らない。

なにぶんC++なんてほとんど書いたことがないので、自分のやり方が悪いのかと疑ったのですが、代入する変数が間違っていたりミススペルがあったりなど、おそらくこれは誌面のソースが間違っているのだろうという結論に至りました。
例えば・・・
p130 リスト2より

#include <vector>
typedef vector<pair<int, int> > SparseDoc;Svec; // ←コンパイルとおりません。
typedef SparseVec::iterator Svec::iterator sit; // ←コンパイルとおりません。

p130 リスト3より

float calcInp(sparceVec& Svec& il1, sparceVec& SVec& il2) {
// ↑コンパイルとおりません。さらにtypedefではSparseVecとなっているはずなのにsparceVecです。
...
}

などなど。

僕のように、自分のかき方が悪いんじゃないかと疑って苦労している人もいるのではないかと思い、修正したソースコードをさらしておきます。

ちなみに、内容としては以下のような縦軸にユーザ番号、横軸にアイテム番号を取り、値としてユーザのアイテムに対する評価点を持った行列が与えられたときに、アイテム間の類似度を計算するというものです。

ユーザ番号\アイテム番号 0 1 2 3 4
0   1     3
1 2     3
2 3   5    
3   2 2    
4 3     4  

以下が修正したソースコード。WEB+DBプレスvol.49の130ページ〜131ページあたりが修正の対象で、アイテム番号0に対する各アイテムの類似度を計算して出力する箇所をmain()関数として追加しています。
[main.cpp]

#include <iostream>
#include <cmath>
#include <vector>

using namespace std;

typedef vector< pair<int,int> > SparseVec;
typedef SparseVec::iterator sit;

// 各ユーザごとにレビューしたアイテム番号とその結果を記録した結果
// idが昇順で並んでいる
vector<SparseVec> userLog;
// 各アイテムごとにレビューされたユーザ番号とその結果を記録した結果
vector<SparseVec> itemLog;

// il1とil2の内積を計算する
float calcInp (SparseVec& il1, SparseVec& il2) {
	sit it1 = il1.begin();
	sit it2 = il2.begin();
	int inp = 0;
	while (it1 != il1.end() && it2 != il2.end()) {
		int id1 = it1->first;
		int id2 = it2->first;
		if      (id1 < id2) ++it1;
		else if (id1 > id2) ++it2;
		else {
			inp += it1->second*it2->second;
			++it1;
			++it2;
		}
	}
	return inp;
}

// 各ベクトルの長さは前もって計算しておく
vector<float> norms;

void calcNorms () {
	for (size_t i = 0; i < itemLog.size(); i++) {
		float norm = sqrt(calcInp(itemLog[i], itemLog[i]));
		norms.push_back(norm);
	}
}

// i番目のアイテムとj番目のアイテム間のコサイン類似度を計算する
float calcCosSim (int i, int j) {
	float inp = calcInp(itemLog[i], itemLog[j]);
	return inp / norms[i] / norms[j];
}

// アイテムiと他のすべてのアイテムのコサイン類似度をまとめて計算してretに結果を返す
void calcMatchNum (int i, vector<float>& ret) {
	ret.resize(itemLog.size());
	SparseVec& sv(itemLog[i]);
	for (sit it = sv.begin(); it != sv.end(); it++) {
		// it->firstを購入したユーザを列挙する
		SparseVec sv2(userLog[it->first]);
		int val = it->second;
		for (sit it2 = sv2.begin(); it2 != sv2.end(); ++it2) {
			ret[it2->first] += val * it2->second;
		}
	}
	// 結果の正規化を行う
	for (size_t j = 0; j < itemLog.size(); j++) {
		ret[j] /= (norms[i] * norms[j]);
	}
	
	// ret[j]にはcalcCosSim(i,j)と同じ結果が入っている
}

int main (int argc, char * const argv[]) {	
	// itemLogを初期化
	itemLog.resize(5);
	itemLog[0].push_back(make_pair(2,3));
	itemLog[0].push_back(make_pair(4,3));
	itemLog[1].push_back(make_pair(0,1));
	itemLog[1].push_back(make_pair(1,2));
	itemLog[1].push_back(make_pair(3,2));
	itemLog[2].push_back(make_pair(2,5));
	itemLog[2].push_back(make_pair(3,2));
	itemLog[3].push_back(make_pair(4,4));
	itemLog[4].push_back(make_pair(0,3));
	itemLog[4].push_back(make_pair(1,3));
	
	// userLogを初期化
	userLog.resize(5);
	userLog[0].push_back(make_pair(1,1));
	userLog[0].push_back(make_pair(4,3));
	userLog[1].push_back(make_pair(1,2));
	userLog[1].push_back(make_pair(4,3));
	userLog[2].push_back(make_pair(0,3));
	userLog[2].push_back(make_pair(2,5));
	userLog[3].push_back(make_pair(1,2));
	userLog[3].push_back(make_pair(2,2));
	userLog[4].push_back(make_pair(0,3));
	userLog[4].push_back(make_pair(3,4));
	
	// 各ベクトルの長さを初期化
	calcNorms();
	
	// あるアイテムと他のアイテムとの類似度をそれぞれ個別に求める
	cout << "calcCosSim" << endl;
	for (size_t i = 0; i < itemLog.size(); i++) {
		cout << "itemNo:" << i << " similarity:" << calcCosSim(0,i) << endl;		
	}
	
	// あるアイテムと他のアイテムとの類似度をすべてまとめて求める
	cout << "calcMatchNum" << endl;
	vector<float> cosSimVec;
	calcMatchNum(0, cosSimVec);
	for (size_t i = 0; i < itemLog.size(); i++) {
		cout << "itemNo:" << i << " similarity:" << cosSimVec[i] << endl;
	}
	
    return 0;
}

ちなみに実行した結果は以下の通り。

calcCosSim
itemNo:0 similarity:1
itemNo:1 similarity:0
itemNo:2 similarity:0.656532
itemNo:3 similarity:0.707107
itemNo:4 similarity:0
calcMatchNum
itemNo:0 similarity:1
itemNo:1 similarity:0
itemNo:2 similarity:0.656532
itemNo:3 similarity:0.707107
itemNo:4 similarity:0

となり、calcCosSimでもcalcMatchNumでも同様の結果を得られていることが分かります。
またアイテム0と類似度が一番高いのはアイテム3だということも分かります。

なんだが揚げ足取りみたいになってしまいましたが、この記事自体はレコメンドのアルゴリズムを実際のソースコードや数式などを交えながら説明した数少ない一般紙での記事だと思いますし、LSHやSVD、RBMといった一歩踏み込んだレコメンドの手法も紹介されていて非常に面白いです。とってもすばらしい記事ですので、上のコードとかを参考により多くの人が理解を深めてくれるといいな、と思います。修正したら図書券とかもらえるかもしれないし。

id:tkngさん、岡野原さん、すばらしい記事をどうも有り難うございます!

Firefoxアドオンでちょっとコアにタブを扱う

Firefoxのアドオンで、ちょっとコアにタブを扱う処理のメモ。

http-on-modify-requestなどのトピック発生時に、HTTPリクエスト発生元のタブを取得するサンプルコード

getTabFromHttpChannelでtry-catchしている部分は、リクエスト元がDOMWindowじゃない場合にExceptionが発生するのでキャッチしているが、あんまり綺麗じゃないので他にうまいやり方はないだろうか。

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

//myHTTPListnerはhttp-on-modify-requestなどのトピックのObserverとして登録するオブジェクト
//Observer登録部分のソースは省略
function myHTTPListener() {};
myHTTPListener.prototype = {
  observe : function (subject, topic, data) {
    var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
    var tab = getTabFromHttpChannel(httpChannel);
    if (tab) {
      //リクエスト元のタブに対する処理
    }
  }
};

function getTabFromHttpChannel (httpChannel) {
  var tab = null;
  if (httpChannel.notificationCallbacks) {
    var interfaceRequestor = httpChannel.notificationCallbacks
        .QueryInterface(Ci.nsIInterfaceRequestor);
    try {
      var targetDoc = interfaceRequestor.getInterface(Ci.nsIDOMWindow).document;
    } catch (e) {
      return;
    }
    var webNav = httpChannel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
    var mainWindow = webNav.QueryInterface(Ci.nsIDocShellTreeItem)
        .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindow);

    var gBrowser = mainWindow.getBrowser();
    var targetBrowserIndex = gBrowser.getBrowserIndexForDocument(targetDoc);
    if (targetBrowserIndex != -1) {
      tab = gBrowser.tabContainer.childNodes[targetBrowserIndex];
    }
  }
  return tab;
};
タブごとに値を保存・取得するサンプルコード

タブごとに任意の値を保存するには、単に独自プロパティを1つ追加してもよいが、sessionStoreを使うとFireFoxを再起動してタブ復元しても保存される。詳細はこのへん

var tab = gBrowser.selectedTab; 
var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);

//タブに任意の値を保存
ss.setTabValue(tab, "your-attribute", "your-value");

//タブに保存した値を取得
var yourValue = ss.getTabValue(tab, "your-attribute");