この土日

少しいろいろとあって疲れたので旅に出ます。
連絡が取りにくい可能性があります。ご了承ください。
f:id:hideack:20090404071428j:image

iモード用メールアドレスの仕様の話

以前は@直前のドットや連続したドットを許容し、実際にトラブルの原因となるなどRFC的によろしくない状態だったドコモのiモードメールアドレスだが、4月以降新規取得(変更)するアドレスについてはこれらを許容しないこととなった
Slashdot - ドコモ、メールアドレスの仕様を修正

そういえば、大学時代、スパム防止のために

  • あえて@マークの前に.(ドット)を入れる
  • 英単語は使わない

というポリシーで友達がメールアドレスを換えたのを横で見ていたのを思い出した。
確か何かのホームページで見て参照したのであった。

PEAR::Cache_Lite_Function

以前も書いたPEARライブラリに登録されているキャッシュ処理のクラスに関数の呼び出し結果をそのままキャッシュできるものも含まれている事に気づいたので使ってみた。
メモリベースのキャッシュ機構がメジャーなのだろうけど、いま仕事で扱っている程度であればディスクキャッシュでも十分ということで。
とある箇所で、操作していると"つっかかる感"があって、その場所を調べていくとDBと連動してごにょごにょしているところであるので導入したが、結構効果があった。
メソッドの呼び出し方を変えるだけなので比較的楽に使えてみんなハッピー。

詳細はPEARのドキュメントを見てもらう事にしてメモがてら、サンプルを書いておく。*1

<?php
require_once('Cache/Lite/Function.php');
class CacheTest{
var $_cacheopts;
function CacheTest(){
$this->_cacheopts = array(
"lifeTime"               => 3600,// cacheの有効時間(秒)
"hashedDirectoryLevel"   => 2,  // cache階層
"debugCacheLiteFunction" => true,// デバッグ出力 (標準出力に "Hit" or "fail"表示)
);
}
function test($param = 0){
$cache  =& new Cache_Lite_Function($this->_cacheopts);
// 自身のメソッド"process"を呼び出す。引数は$param
$result = $cache->call(array(&$this, "process"), $param);
return $result;
}
/**
  * 比較的重い処理 (1度呼び出した結果を保管したい対象の処理)
  * @param $init 
  */
function process($init){
$resp = $init;
for($i=0; $i<5000000; $i++){
$resp++;
}
return $resp;
}
}
$cachetest = new CacheTest();
// 開始時間メモ
$stime = explode(' ', microtime());
$stime = $stime[1] + $stime[0];
print "start = ".$stime."\n";
// 処理実行
$resp = $cachetest->test($argv[1]);
// 終了時間から実行時間算出
$etime = explode(' ', microtime());
$etime = $etime[1] + $etime[0];
$time   = round(($etime - $stime), 4);
printf("%f seconds\n", $time);
printf("CacheTest::test response = %s\n", $resp);
?>

な具合である。
ここでは例として、コマンドライン引数で渡した値を初期値としてそこから500万回インクリメントするという重い処理の結果をキャッシュさせている。
で、これを実行すると1回目はキャッシュが効かないので普通に実行される。

/Users/hideack/tmp% php -q test.php 10
start = 1238689332.75
Cache missed !
1.634000 seconds
CacheTest::test response = 5000010

次にもう一度同じことを実行してみる

/Users/hideack/tmp% php -q test.php 10
start = 1238689377.62
Cache hit !
0.001300 seconds
CacheTest::test response = 5000010

今度はキャッシュから引かれたので実行時間が大幅に短くなっている。
こんな具合。

あぁ、久しぶりに技術的なこと書いた。

*1:もっともサンプルを書いて一番役に立てているのは自分かもしれない...。良く忘れてここを見て振り返る。

ご無沙汰しています

最近、友達から心配されたので更新します。
ちゃんとまだ生きてますよ。ご安心ください。
まだ、頑張れます。多分。

今日は仕事もあったのですが、日中は自由だったので写真を撮って回った。
ちょうど1年前は、東京は桜が満開だったのですが今年は遅れ気味ですね。
でも、ソメイヨシノ以外の桜は綺麗に咲いている。
f:id:hideack:20090329091859j:image
ふらふら。ふらふら。
あれこれ、あれこれ考え事しながら。
自転車で2時間くらい走り回って公園で休憩。天気も良くてベンチに座っていたら、目の前のリスの銅像と目が合った。
f:id:hideack:20090329100503j:image
でも、なんでリスを銅像にしたんだろう。
リスを見ながら、これからのことをいろいろ考えた。もっとも結論はでないのだけど。

後、全く話しは変わりますが、こないだから気づいていた事ですが、何気にこのblogも書いた日数が1500日を超えていたみたいです。
結構地味に書いたなぁ。

元気ですかぁ

また、突発的に誘われたので今日飲みに行った。
店の詳細の説明はいるまい。サラが頼まれるたびにイベントが発生するので面白かった。
ビールを5杯くらいと焼酎と日本酒と良く飲んだ。
f:id:hideack:20090314002543j:image
"とりあえず"が驚愕的な量ほど提供される。
f:id:hideack:20090314013013j:image

PHPでNaive Bayesを使ってみる

今月号のWEB+DB PRESS

WEB+DB PRESS Vol.49

WEB+DB PRESS Vol.49

はてなブックマークのリニューアルに際しての特集記事があったり、レコメンドエンジンの解説記事があったりと非常に読み応えがあっていつもの3割増でおすすめ。
で、ブックマークのカテゴリ自動判定システムで使われているアルゴリズムはComplement Naive Bayesで、このアルゴリズムの元となっているアルゴリズムはNaive Bayes(単純ベイズ分類機)と呼ばれるもの。
Perlでは、記事でも紹介されている通り、Algorithm::NaiveBayesというライブラリがCPANにあるので利用するとアルゴリズムが比較的簡単に利用することができる。
このアルゴリズムを使ってみたいと思ったのだけど、あいにくPHPでは似た形で利用できるライブラリがすぐに見つからなかったので、突貫でこのPerlのライブラリを移植してみた。
Perl版だと、スコアを計算する方法を"frequency", "discrete", "gaussian"の3通りから選べたり、学習させた結果を保管できるのだけど、このたびのものは無し。
記事に記載のサンプルに倣って試してみる。
PHP実装もPerlのインターフェースに併せている。addInstanceメソッドの第一引数に学習対象となる文書の単語の出現数をarrayで与え、その文書が所属するカテゴリを第2引数に与える。
trainメソッドで学習を実行して、predictメソッドで分類を推定する文書中の単語とその出現数を与えると、カテゴリに所属する確率を推測してくれる。

<?php
$bayes = new NaiveBayes();
$bayes->addInstance(array("はてな" => 5, "京都" => 2), array("it"));
$bayes->addInstance(array("引っ越し" => 1, "" => 1), array("life"));
$bayes->train();
$resp = $bayes->predict(array("はてな" => 1, "引っ越し" => 1, "京都" => 1));
print_r($resp);
?>

上記のソースを実行すると、

Array
(
[it] => 0.825130233192
[life] => 0.564942561923
)

Perl版と同じ結果になるので大丈夫かな。。。と。
この場合だと、ITというカテゴリに所属する確率が高いと判定されたということになる。
ソースコードは以下参照。おそらく逐次直します。変なところもあるだろうし。きっと。

<?php
/**
 * Naivebayes.php 
 *
 * This package was ported from Perl's Algorithm::NaiveBayes (frequency model only)
 * http://search.cpan.org/~kwilliams/Algorithm-NaiveBayes-0.04/lib/Algorithm/NaiveBayes.pm
 * 
 * @category  Algorithm 
 * @package   Naivebayes
 * @author    hideack
 * @license   http://www.php.net/license/3_01.txt The PHP License, version 3.01
 * @version   0.1 
 */
class Naivebayes{
private $modeltype;
private $instances;
private $trainingdata;
private $model;
public function __construct(){
$this->trainingdata = array(
"attributes" => array(),
"labels"     => array(),
);
$this->instances = 0;
$this->modeltype = "";	// Perl版では切り替え可能
}
public function addInstance($attributes, $label){
$this->instances++;
foreach($attributes as $keyword => $count){
if(isset($this->trainingdata['attributes'][$keyword])){
$this->trainingdata['attributes'][$keyword] += $count;
}
else{
$this->trainingdata['attributes'][$keyword] = $count;
}
}
foreach($label as $labelword){
if(isset($this->trainingdata['labels'][$labelword]['count'])){
$this->trainingdata['labels'][$labelword]['count']++;
}
else{
$this->trainingdata['labels'][$labelword]['count'] = 1;
}
foreach($attributes as $keyword => $count){
if(isset($this->trainingdata[$keyword])){
$this->trainingdata['labels'][$labelword]['attributes'][$keyword] += $count;
}
else{
$this->trainingdata['labels'][$labelword]['attributes'][$keyword] = $count;
}
}
}
}
public function train(){
$m = array();
$labels = $this->trainingdata['labels'];
$m['attributes'] = $this->trainingdata['attributes'];
$vocab_size = count($m['attributes']);
foreach($labels as $label => $info){
$m['prior_probs'][$label] = log($info['count'] / $this->instances);
$label_tokens = 0;
foreach($info['attributes'] as $word => $count){
$label_tokens += $count;
}
$m['smoother'][$label] = -log($label_tokens + $vocab_size);
$denominator = log($label_tokens + $vocab_size);
foreach($info['attributes'] as $attribute => $count){
$m['probs'][$label][$attribute] = log($count + 1) - $denominator;
}
}
$this->model = $m;
}
public function predict($newattrs){
$scores = $this->model['prior_probs'];
foreach($newattrs as $feature => $value){
foreach($this->model['probs'] as $label => $attribute){
$tmpscore = 0.0;
if($attribute[$feature] == 0.0){
$tmpscore = $this->model['smoother'][$label];
}
else{
$tmpscore = $attribute[$feature];
}
$scores[$label] += $tmpscore * $value;
}
}
$scores = $this->rescale($scores);
return $scores;
}
public function labels(){
$labels = array();
foreach($this->trainingdata['labels'] as $label => $value){
$labels[] = $label;
}
return $labels;
}
public function doPurge(){
// 未実装...
}
private function rescale($scores){
$total = 0;
$max  = max($scores);
$rescalescore = $scores;
foreach($rescalescore as $key => $val){
$val = exp($val - $max);
$total += pow($val, 2);
$rescalescore[$key] = $val;
}
$total = sqrt($total);
foreach($rescalescore as $key => $val){
$rescalescore[$key] /= $total;
}
return $rescalescore;
}
}
?>