2010年3月29日月曜日

gibbs sampling

3月に入って netwalker を購入しました。

電車通勤の時間を利用してコードを書きたいなと思っていたのですが、なかなかよろしい感じです。
といってもキーボードは使いやすいとは言い難く、長いコードは書きたくないですが...

ということで、netwalker で作成したプログラム第一号を公開します。
第一号は gibbs sampling を使ったモチーフ抽出アルゴリズムの実装です。

前々から gibbs sampling について調べてはいたのですが、適度な練習問題が無く実装はしていませんでした。
今月になって購入したバイオインフォマティクスの数理とアルゴリズムにちょうど良い例があったので、それを実装してみました。

そもそもモチーフって何という話ですが、一言で言うとタンパク質の配列パターンの事を指すそうです。
私は専門ではないので詳しい事は分かりませんが、ここでは与えられた複数のタンパク質配列から、特徴的な配列パターンを見つけてくる事をモチーフ抽出と言ってよさそうです。

具体的にどうモチーフを抽出するかですが、載っている例では、各タンパク質A、C、T、Gの出現確率をP(A)、P(C)、P(T)、P(G)、部分配列中の位置 j に各タンパク質が現れる確率を f_j(A)、f_j(C)、f_j(T)、f_j(G)とした時に、相対エントロピーを最大化するように部分配列を選んでくる事でモチーフを抽出します。

これをローカルマルチプルアラインメントと呼ぶそうですが、この問題はまともにやると NP 困難なので、gibbs sampling を使って効率的に解くよ、というお話しになっています。

という事で、アルゴリズムは以下の通り。

1. 各入力配列 s_k から, 長さ L の部分配列 t_k を一様ランダムに選択 (k = 1,...,n)
2. 入力配列から, s_i を一様ランダムに選択
3. f'_j(a) を t_i 意外の t_k から計算したモチーフ領域の j 番目の列に置ける文字 a の頻度とする ( f'_j(a) = (|{k|t_k[j] = a, k != i}|) / (n-1))
4. s_i から長さ L の部分配列 t'_i を, Πj f'_j(t'_i[j])/p(t'_i[j]) に比例する確率でランダムに選択
5. t_i を t'_i で置き換える
6. 2-5 を十分な回数繰り返す

ステップ 3. の頻度は確率の間違えかな?と思うのですが、一応そのまま書いておきます。
ステップ 4. では求めた事後分布に従って新しくモチーフ候補を選択し直すわけですが、ここはランダムで選択しないと初期値から動かなくなるので、まず計算したスコアの配列を確率分布として使えるように総和が1となるように正規化してから、PRMLの下巻に紹介されている棄却サンプリングを使いました。
提案分布には一様分布を使っています。

コードと実行例は以下。


#!/usr/local/bin/perl

use warnings;
use strict;

my $L = 5;
my @dataset;
my %wordset;
my $words = 0;

while(<>)
{
chomp;
my $line = $_;
my @sequence = split(//,$line);
push @dataset,\@sequence;
}

# init words and probs
$words = &setwords(\%wordset, \@dataset);
&initwordprobs($words,\%wordset);
my $datasize = @dataset;

print "dataset\n";
foreach my $ar (@dataset)
{
print "@$ar\n";
}

# init motif set
my @pos;
foreach my $ar (@dataset)
{
my $p = int rand(@$ar-$L);
push @pos, $p;
#print $p,"\n";
}

# output first
print "init\n";
for (my $i = 0; $i < @dataset; $i++)
{
my $ar = $dataset[$i];
my $p = $pos[$i];
for (my $j = $p; $j < $p+$L; $j++)
{
print "$ar->[$j] ";
}
print "\n";
}

# iterration
for (my $iter = 0; $iter < 50; $iter++)
{
#print "epoch: $iter\n";
my $id = int rand($datasize);
my %wordsfreq;
# wordset * position
# +---+-------------+
# | |a|b|c|d|e|f|g|
# +---+-------------+
# | 0 | |
# | 1 | |
# | 2 | frequency |
# | . | |
# | L | |
# +---+-------------+
&initwordsfreq(\%wordset,$L,\%wordsfreq);
# count word frequency
&countwordfreq($id,\@dataset,\%wordsfreq,\@pos);
# pick up new motif i
my $ar = $dataset[$id];
$pos[$id] = &pickup($id,$ar,$L,\%wordsfreq, \%wordset);
}

# output
print "result\n";
for (my $i = 0; $i < @dataset; $i++)
{
my $ar = $dataset[$i];
my $p = $pos[$i];
for (my $j = $p; $j < $p+$L; $j++)
{
print "$ar->[$j] ";
}
print "\n";
}

# subroutine
sub pickup
{
my $id = shift;
my $ar = shift;
my $L = shift;
my $wordsfreqhr = shift;
my $wordsethr = shift;

my @scores;
my $total = 0;
for (my $p = 0; $p <= @$ar-$L; $p++)
{
my $score = 1;
my $index = 0;
for (my $j = $p; $j < $p+$L; $j++)
{
my $w = $ar->[$j];
$score *= $wordsfreqhr->{$index}->{$w}/$wordsethr->{$w};
$index++;
}
push @scores, $score;
$total += $score;
}
return int rand(@scores) if $total == 0;
for(my $i = 0; $i < @scores; $i++)
{
$scores[$i] /= $total;
}
#print "@scores\n";
my $flg = 1;
while($flg)
{
my $npos = int rand(@scores);
my $s = rand(1);
if ($s <= $scores[$npos])
{
$flg = 0;
return $npos;
}
}
}
sub countwordfreq
{
my $id = shift;
my $datasetar = shift;
my $wordsfreqhr = shift;
my $posar = shift;

for (my $i = 0; $i < @$datasetar; $i++)
{
next if ($i == $id);
my $ar = $datasetar->[$i];
for (my $j = $posar->[$i]; $j < $L+$posar->[$i]; $j++)
{
my $p = $j-$posar->[$i];
my $w = $ar->[$j];
$wordsfreqhr->{$p}->{$w}++;
}
}
for (my $l = 0; $l < $L; $l++)
{
foreach my $k (keys %{$wordsfreqhr->{$l}})
{
$wordsfreqhr->{$l}->{$k} /= (@$datasetar-1);
}
}
}

sub initwordsfreq
{
my $wordsethr = shift;
my $L = shift;
my $wordfhr = shift;
for (my $i = 0; $i < $L; $i++)
{
my %freqonpos;
foreach my $k (keys %$wordsethr)
{
$freqonpos{$k} = 0;
}
$wordfhr->{$i} = \%freqonpos;
}
}
sub initwordprobs
{
my $words = shift;
my $wordsethr = shift;

foreach my $k (keys %$wordsethr)
{
$wordsethr->{$k} = 1/$words;
}
}

sub setwords
{
my $wordshr = shift;
my $dataar = shift;
my $words = 0;

foreach my $ar (@$dataar)
{
foreach my $w (@$ar)
{
unless (defined $wordshr->{$w})
{
$wordshr->{$w} = defined;
$words++;
}
}
}
return $words;
}


サンプルデータ1

c a t c g a a a a a a
a a a a c a t c g a a
a a c a t c g a a a a
a a a a a a c a t c g


サンプルデータ2

t c a g a g a t t c g t a g
t t c a t t c g g g c g t
c g a t t c g c g a c t c
t g a a t t c g g a a


実行例1

# perl gibbs.pl < sample1.txt
dataset
c a t c g a a a a a a
a a a a c a t c g a a
a a c a t c g a a a a
a a a a a a c a t c g
init
g a a a a
a a a c a
a t c g a
a a a a a
result
c a t c g
c a t c g
c a t c g
c a t c g


実行例2

# perl gibbs.pl < sample2.txt
dataset
t c a g a g a t t c g t a g
t t c a t t c g g g c g t
c g a t t c g c g a c t c
t g a a t t c g g a a
init
t c a g a
c a t t c
t c g c g
a a t t c
result
a t t c g
a t t c g
a t t c g
a t t c g


上手い事特徴的な文字列を抽出できる事が確認できました。

2010年2月28日日曜日

Conditional Neural Fields on Google Code

CNF の著者の Jian Peng 氏に特許について質問をしてみたところ、問題ないということでしたので Google Code にプロジェクトを作成してコードを公開しました。

http://code.google.com/p/cnf/

あまりちゃんとした実装ではないので、使用は自己責任でお願いします。
間違ってるかもしれないので、間違いがあれば教えてくれると嬉しいです。

mercurial で管理しているので、以下のコマンドで落としてきて使用できます。

$ hg clone https://cnf.googlecode.com/hg/ cnf
$ cd cnf
$ make
$ ./src/cnflearn src/template data/conll2000/train.txt test.save
$ ./src/cnftagger src/template test.save data/conll2000/test.txt

cnflearn_main.cpp と cnftagger_main.cpp を見てもらえば分かる通り、実行用のプログラムはむちゃくちゃ手抜です。

logsumexp の計算部分には、NAIST の浅原先生の資料にあるコードを使わせて頂きました。

2010年2月16日火曜日

Conditional Neural Fields

年越ししてから既に2ヶ月が過ぎ、2月も終わりが見えかけてきた今日この頃です。
生存報告をかねて、少しだけ最近やっていた事を書いておきます。

去年の年末くらいに、面白そうな論文を見つけたのでそれを読みつつ、実装していました。
NIPS2009 で発表された論文です。

その名も Conditional Neural Fields 。
http://books.nips.cc/papers/files/nips22/NIPS2009_0935.pdf

名前から何か感じるところの有る人もいそうですが、これはCRFに隠れ層を加えて、非線形にした物になります。

自分のメモ用に、先に簡単に CRF についておきます。
--
CRF の説明はNLP2006のチュートリアル資料が割と分かりやすいです。
http://nlp.dse.ibaraki.ac.jp/~shinnou/lecture/nl/NLP2006.pdf

私はオンライン学習器でしかCRFを作った事が無いので、ここではそのつもりで書きます。
CRF では素性関数に対応するパラメータを学習するわけですが、その式は大雑把に書くと次のようになります。

Λ_t+1 = Λ_t + η * (正解の素性関数ベクトル - 期待素性関数ベクトル)

η は学習率、Λ は素性関数に対応するパラメータベクトルです。
正解の素性関数ベクトルは入力系列が与えられれば素性と正解のラベルのペアを取り出すだけで作れます。
要はどの素性とラベルのペアが何個有ったか、どのラベルとラベルの遷移が何回あったか、をカウントしてベクトルにしています。

期待素性関数ベクトルはちょっと計算が面倒ですが、現在のパラメータΛのもとで、入力系列から生起しうる全てのラベル系列を考えます。
各ラベル系列が生起した際の、各素性関数の期待値を計算し足し合わせた物が期待素性関数ベクトルです。
まともに全てのラベル系列を生成して試すともの凄い計算量になるので、これは Forward-Backward アルゴリズムを用いて計算するのが一般的です。
--

前置きが長くなりましたが、 CNF について書きます。
ちなみに間違ってるかもしれません。あまり期待しては行けません。
この記事は後でこっそり修正されてる可能性もあります。

CRF との違いは、観測素性の扱いになります。
CRF では、素性関数 f(x,y) は素性xとラベルy のペアを観測した際に1になるような関数です。

これが CNF では、f(h(X,t),y) になります。

数式は元論文のままだと比較しにくいので変えてます。
関数h はロジスティック関数で、X に対して非線形な値を取ります。
t は系列のカレントの位置を表しています。

f(h(X,t),y) は組み合わせ素性xとラベルy のペアを観測した際に、h(X,t)を返す関数と思ってください。
X が大文字になっている理由は、CNF では入力系列に対して隠れ層で組み合わせ素性を取り出すためです。
素性関数 f(h(X),y) は入力系列 X に対して、位置tに置ける組み合わせ素性 x とラベル y のペアに対して、h(X,t) を返します。

h(X,t) は中で何をしているのかと言うと、入力系列 X とパラメータθベクトルの内積を計算して、その値をロジスティック関数に乗せて非線形な値にしています。

こうする事で、単純に組み合わせ素性が観測されたというだけではなく、その素性に対して非線形な重みを与えています。

次に、パラメータの最適化ですが、実は素性関数が非線形になっているという事を無視すると、観測素性についてのパラメータと、遷移素性についてのパラメータの更新の式は一緒になります。

ただし、正解の素性関数ベクトルはどの素性とラベルのペアが何個有ったか、どのラベルとラベルの遷移が何回あったか、ではなく、素性に対して非線形な重みを計算し足し合わせたものになります。ラベルとラベルの遷移は相変わらずただのカウントです。
要は、カウントの仕方が1ずつ足していたのが、実数値ずつ足すようになった物です。

期待素性関数ベクトルも同じ要領です。 入力系列から生成されうる全てのラベル系列を考えて、各ラベル系列が生起した際の各素性関数の期待値を計算して足し合わせます。 ただし期待値を計算する際には素性に対して、これまで有った/無かったで済ませていたところにh関数の値が入ります。

パラメータ推定での1つ大きな違いは、これに加えて隠れ層のパラメータθについても更新することです。

θの更新は、次の式で行われます。
θt+1 = θt + Σt w_yt,g * ∂h/∂θ - E (Σt w_yt^,g * ∂h/∂θ)

wは素性関数に対応した重みパラメータです。
重みパラメータΛ = (W, U, θ) と思ってください。
Wは観測素性に対応する重みパラメータ
Uは遷移素性に対応する重みパラメータ
θは隠れ層のパラメータ
E は期待値です。

かなりやっつけな式なので詳しくは元論文を読んだ方が良いです。
要は、正解系列中の観測素性の重みベクトルと、入力系列から生成されうる全ラベル系列を考えた時の各観測素性の期待値ベクトルの差をなくすようにθを更新しています。

という事で、実装してみました。
実装は sgd + FOLOS です。

コード公開したいんだけど、権利関係やら何やら良く分からない。
論文の著者にメールして特許についてとか聞いといた方が良いと言われているのですが、そこで停止中。

--
ちなみに、CNF は組み合わせ素性、CRF は組み合わせ無し、のような印象を受けますが、CRF++やCrfSgdを使った事が有る方は分かると思いますが、CRF でも素性テンプレートを使って組み合わせ素性を扱えます。

素性テンプレートを使った組み合わせ素性の抽出はまさに CNF で言うところの中間層だと思っています。
なので、CNF と CRF の違いは実際のところロジスティック関数による非線形な値を観測素性に与えているところと、θの学習が入っているところだというのが私の理解です。

私の実装もそのまんまで、「ロジスティック関数による非線形な値を観測素性に与えているところと、θの学習を入れた」素性テンプレートを使える CRF です。

--
conll2000 のチャンキングで比較をしてみたので一応載せておきます。
一応素性テンプレートは揃えてありますが、内部での処理が微妙に違うため素性数は厳密に一緒ではありません。

まあ、見ての通りだと思いますが、恐らくロジスティック関数による非線形な値云々よりも、組み合わせ素性を使えるかどうかの方が重要で、CRF でもそれが出来れば問題ないんだろうなという感じでした。

元々Kernelなどを用いた非線形な分類の実体は、素性の組み合わせを考慮してより高次元空間へ写像して、その空間で線形分離しているわけで、素性テンプレートを使って組み合わせ素性を扱っている以上、あんま変わんない結果が出たとしても別段驚く事じゃない気もしますね。

* CrfSgd
[Epoch 50] -- wnorm: 9273.81 total time: 1664.69 seconds
Training perf: sentences: 8936 loss: 4473.67 objective*n: 9110.58
misclassifications: 794(0.375011%)
accuracy: 99.62%; precision: 99.32%; recall: 99.18%; FB1: 99.25
Testing perf: sentences: 2012 loss: 4429.27 objective*n: 5473.3
misclassifications: 1893(3.99561%)
accuracy: 96.00%; precision: 93.84%; recall: 93.62%; FB1: 93.73
Saving model file model.gz.
Done! 1664.69 seconds.

* CNF
epoch: 49 err:0.002985(632/211727)
./cnflearn template data/conll2000/train.txt testbb.save 3728.58s user 200.56s system 69% cpu 1:34:54.39 total

./cnftagger template testbb.save data/conll2000/test.txt | ./conlleval
processed 47377 tokens with 23852 phrases; found: 23718 phrases; correct: 22293.
accuracy: 96.02%; precision: 93.99%; recall: 93.46%; FB1: 93.73
ADJP: precision: 81.08%; recall: 75.34%; FB1: 78.11 407
ADVP: precision: 83.71%; recall: 80.72%; FB1: 82.19 835
CONJP: precision: 55.56%; recall: 55.56%; FB1: 55.56 9
INTJ: precision: 100.00%; recall: 50.00%; FB1: 66.67 1
LST: precision: 0.00%; recall: 0.00%; FB1: 0.00 0
NP: precision: 94.39%; recall: 93.88%; FB1: 94.14 12355
PP: precision: 96.81%; recall: 97.78%; FB1: 97.29 4859
PRT: precision: 83.15%; recall: 69.81%; FB1: 75.90 89
SBAR: precision: 87.72%; recall: 85.42%; FB1: 86.55 521
VP: precision: 93.95%; recall: 93.62%; FB1: 93.78 4642

2009年12月30日水曜日

PLSV

以前書いた plsv の perl script を晒しておく。
勾配法には sgd を使用した。
行数は320行。


#!/usr/bin/perl
# Probabilistic Latent Semantic Visualization
# Copyright (c) 2009, Kei Uchiumi

use warnings;
use strict;

# Usage
# perl plsv.pl corpus

our $dimension = 2;
our $topicsize = 2;
our $alpha = 0.01;
our $beta = 0.0001;
our $ganma = 0.0001 * $topicsize;
our $docnum = 0; # document size N
our $iteration = 50;

# for sgd parameters
our $rate = 0.1; # learning rate
our $input = shift;

my %W;

open(F, "$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);
&storeword(\%W, \@tokens);
$docnum++;
}
close(F);

our $wordsize = (keys %W);
$beta = $beta * $docnum;

# init parameters
our %theta;
our %phi;
our %xai;

# init phi
for (my $i = 0; $i < $topicsize; $i++)
{
my @position;
for (my $d = 0; $d < $dimension; $d++)
{
#$position[$d] = rand;
$position[$d] = 1;
}
$phi{$i} = \@position;
}
# init xai
for (my $i = 0; $i < $docnum; $i++)
{
my @position;
for (my $d = 0; $d < $dimension; $d++)
{
$position[$d] = 0;
}
$xai{$i} = \@position;
}
# init theta
for (my $i = 0; $i < $topicsize; $i++)
{
my @words;
my $denominator = 0;
for (my $j = 0; $j < $wordsize; $j++)
{
$words[$j] = -log(1.0 - rand);
$denominator += $words[$j];
}
for (my $j = 0; $j < $wordsize; $j++)
{
$words[$j] = $words[$j] / $denominator;
}
$theta{$i} = \@words;
}

our %prob_zpx;
our %prob_zpnm;

# learning start
for (my $i = 0; $i < $iteration; $i++)
{
&expectation($input);
&maximization($input);
}

# output
use Data::Dumper;
print "Result\n";
print "Phi\n";
print Dumper(%phi);
print "Xai\n";
print Dumper(%xai);
print "Theta\n";
print Dumper(%theta);

# functions
sub xaiupdate
{
my $docid = shift;
my $topic = shift;
my $grad = shift;

my $x = $xai{$docid};
my $p = $phi{$topic};

for (my $i = 0; $i < $dimension; $i++)
{
my $diff = $grad * ($x->[$i] - $p->[$i]) - $ganma * $x->[$i];
$x->[$i] += $rate * $diff;
}
return;
}

sub phiupdate
{
my $docid = shift;
my $topic = shift;
my $grad = shift;

my $x = $xai{$docid};
my $p = $phi{$topic};

for (my $i = 0; $i < $dimension; $i++)
{
my $diff = $grad * ($p->[$i] - $x->[$i]) - $beta * $p->[$i];
$p->[$i] += $rate * $diff;
}
return;
}

sub update
{
my $input = shift;

my $docid = 0;
open(F,"$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);

my $p_zpnm = $prob_zpnm{$docid};
for (my $i = 0; $i < @tokens; $i++)
{
my $p_znm = $p_zpnm->{$i};
for (my $j = 0; $j < $topicsize; $j++)
{
my $p_zpx = $prob_zpx{$docid}->[$j];
my $p_z = $p_znm->[$j];
my $grad = $p_zpx - $p_z;
&xaiupdate($docid,$j,$grad);
&phiupdate($docid,$j,$grad);
}
}
$docid++;
}
close(F);
}

sub thetaupdate
{
my $input = shift;
my $topic = shift;
my $word = shift;

my $numerator = 0;
my $denominator = 0;
my $docid = 0;
open(F,"$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);

my $p_zpnm = $prob_zpnm{$docid};
for (my $i = 0; $i < @tokens; $i++)
{
my $p_znm = $p_zpnm->{$i};
if ($tokens[$i] eq $word)
{
$numerator += $p_znm->[$topic];
}
$denominator += $p_znm->[$topic];
}
$docid++;
}
close(F);

return ($numerator+$alpha)/($denominator+$alpha*$wordsize);
}

sub maximization
{
my $input = shift;
# theta update
for (my $i = 0; $i < $topicsize; $i++)
{
for (my $j = 0; $j < $wordsize; $j++)
{
$theta{$i}->[$j] = &thetaupdate($input,$i,$j);
}
}
# xai, phi update
&update($input);

return;
}

sub euclid
{
my $topic = shift;
my $docid = shift;

my $docpositions = $xai{$docid};
my $topicpositions = $phi{$topic};

my $d = 0;
for (my $i = 0; $i < $dimension; $i++)
{
my $diff = $docpositions->[$i] - $topicpositions->[$i];
$d += $diff * $diff;
}

return $d;
}

sub dist
{
my $topic = shift;
my $docid = shift;

my $denominator = 0;
for (my $i = 0; $i < $topicsize; $i++)
{
$denominator += exp(-1/2 * &euclid($i, $docid));
}
my $numerator = exp(-1/2 * &euclid($topic, $docid));

return $numerator/$denominator;
}

sub posterior
{
my $docid = shift;
my $topic = shift;
my $word = shift;

my $p_zpx = $prob_zpx{$docid};
my $denominator = 0;
for (my $i = 0; $i < $topicsize; $i++)
{
$denominator += $p_zpx->[$i] * $theta{$i}->[$word];
}
my $numerator = $p_zpx->[$topic] * $theta{$topic}->[$word];

return $numerator/$denominator;
}

sub expectation
{
my $input = shift;
for (my $i = 0; $i < $docnum; $i++)
{
my @probs;
for (my $j = 0; $j < $topicsize; $j++)
{
my $prob = &dist($j,$i);
$probs[$j] = $prob;
}
$prob_zpx{$i} = \@probs;
}

my $docid = 0;
open(F,"$input") or die "Couldn't open $input $!";
while ()
{
chomp;
my $line = $_;
my @tokens = split(/\s/,$line);

my %probs_znm;
for (my $i = 0; $i < @tokens; $i++)
{
my @probs;
for (my $j = 0; $j < $topicsize; $j++)
{
my $p = &posterior($docid, $j, $tokens[$i]);
$probs[$j] = $p;
}
$probs_znm{$i} = \@probs;
}

$prob_zpnm{$docid} = \%probs_znm;
$docid++;
}
close(F);
return;
}

sub storeword
{
my $wh = shift;
my $ta = shift;

foreach my $w (@$ta)
{
unless (defined $wh->{$w})
{
$wh->{$w} = 1;
}
}
return;
}



入力ファイルの例は以下の通り。
1行が1文書に相当。
数値は単語ID。

0 1 2 3
0 1 2 3
4 5 6 7
4 5 6 7


使用例

# perl plsv.pl sample.txt
Result
Phi
$VAR1 = '1';
$VAR2 = [
'-0.581827405837318',
'-0.581827405837318'
];
$VAR3 = '0';
$VAR4 = [
'1.50610033709651',
'1.50610033709651'
];
Xai
$VAR1 = '1';
$VAR2 = [
'-0.42461538023816',
'-0.42461538023816'
];
$VAR3 = '3';
$VAR4 = [
'1.29019177243395',
'1.29019177243395'
];
$VAR5 = '0';
$VAR6 = [
'-0.393904728506928',
'-0.393904728506928'
];
$VAR7 = '2';
$VAR8 = [
'1.29484945144959',
'1.29484945144959'
];
Theta
$VAR1 = '1';
$VAR2 = [
'0.248699888255414',
'0.24869988511089',
'0.248699888504977',
'0.248699890098641',
'0.00130761840347527',
'0.00129605716166152',
'0.00129717365378877',
'0.00129959881115254'
];
$VAR3 = '0';
$VAR4 = [
'0.00128080308998399',
'0.00128080623499826',
'0.00128080284038186',
'0.00128080124646881',
'0.248711689079392',
'0.248723252125832',
'0.248722135459428',
'0.248719709923515'
];

2009年11月1日日曜日

ためして寒天

買い物中に何とも言えない名前のドリンクを発見。
このネーミングってどうなんだろう。

2009年10月7日水曜日

twitter

皆やってるし、勧められてもいたので twitter を始めてみた。
使い方が良く分からない。。

なんか最初からフレンドが20人とかになってるし、どうなってるんだろう。

私がそんなに大勢の友達を持っているはずが無いじゃないか。

2009年9月27日日曜日

入院+iPhone

9/16-9/25 まで入院していたのですが、その直前に iPhone を購入しました。

予約したのが10日で、申し込んだのが12日、3営業日で到着予定だったのですが、15日の午後3時過ぎに発送したようで16日の入院初日に手に入れられないという事態になりました。

佐川急便さんにお電話したところ、ソフトバンクに確認して、許可をもらった後に病院へ転送してくれると言ってくれたので安心していたのですが、その後落とし穴が待ってました。

16日に佐川急便さんからお電話が着て、ソフトバンクが許可をくれないので直接ソフトバンクと話をしてくださいと言われ、ソフトバンクにこちらから電話をかけてみるとなんと…

以下だいたいの流れ。

SBオペレータ:こちら一度発送したものの届け先住所の変更は出来ません。
SBオペレータ:違う住所へ届けるためには今の分をこちらでキャンセル致しますので、その後最初からやり直して頂く必要があります。
私:その場合新しく予約からになるんですか?
SBオペレータ:はい。予約からになります。
私:今発送されている iPhone はどうなるんでしょう。
SBオペレータ:キャンセルとなります。
私:予約の場合、クーポンとかどうなるんでしょうか。会社の特典クーポンが期限過ぎてしまうのですが。
SBオペレータ:申し訳ないのですがキャンセルとなります。
私:免許証とか画像でアップロードしたのですが、それはどうなりますか。
SBオペレータ:もう一度アップロードして頂くことになります。
私:入院中でそれが出来ないのですが…
SBオペレータ:お時間のある時に再度やり直してください。
SBオペレータ:キャンセル致しますか?
私:キャンセルしなかった場合、私は25日の退院後まで受け取れないのですが、取り置きなどは出来ますか?
SBオペレータ:出来ません。受取人不在で戻ってきた場合はキャンセルとなります。
私:それでは、今ここでキャンセルするか、受取人不在で戻ってきてキャンセルするかのどちらかしかないんでしょうか。
SBオペレータ:そうです。
私:...分かりました。どうもありがとうございました。

この後病院に外出許可を頂いて自宅に戻り、佐川急便さんに電話をして、電話後3時間以内に届けてとお願いをしました。
佐川急便さん、ほんと届けてくれてありがとうございます。

iPhone は入院生活中のネット閲覧に凄く役に立ってくれましたし、サービスそのものは悪くないと思うのですが、ソフトバンクはマニュアル体制が徹底しすぎていて、融通が効かない気がする。

送り先の住所を変更するだけで最初の予約からやり直さないと行けないし、受取人側で転送しようとしても転送不可だし、オペレータはキャンセル -> 最初からやり直し以外言わないし。。

iPhone のキャリア選択でソフトバンク以外に docomo が出ていたので、もし万が一キャリアフリーになったら速攻移ろうと思います。