Search on the blog

2017年5月23日火曜日

Pythonのデフォルト値の罠

 Pythonのデフォルト値ではまったところがあったのでメモ。以下のプログラムを例に考える。
def add_key_value_to_dict(k, v, d={}):
  d[k] = v;
  return d

if __name__ == "__main__":
  x = add_key_value_to_dict("aa", "bb", {"xx": "yy"})
  print (x)

  y = add_key_value_to_dict("hoge", "fuga")
  print (y)
  
  z = add_key_value_to_dict("foo", "bar")
  print (z)
これを実行すると以下のような結果が出力される。
{'aa': 'bb', 'xx': 'yy'}
{'hoge': 'fuga'}
{'hoge': 'fuga', 'foo': 'bar'}

1行目と2行目の結果は予想どおりだが、3行目の結果はPythonに詳しくない人には意外かもしれない。

実はPythonのデフォルト値はdefステートメントが実行されるときだけ評価される。つまりデフォルト値がmutableな値の場合、関数内で実行した変更は副作用をもたらすことになる。上の例では、dict型の引数dのデフォルトは最初は{}だが、そのあとyを計算するときに{'hoge': 'fuga'}に更新され、さらにzの計算時に{'hoge': 'fuga', 'foo': 'bar'}に更新される。

デフォルト値にmutableな値を指定すると予想しない挙動になることがあるため、以下のようにimmutableな値を使用するとよい。

def add_key_value_to_dict(k, v, d=None):
  if d is None:
    d = {}
  d[k] = v;
  return d

if __name__ == "__main__":
  x = add_key_value_to_dict("aa", "bb", {"xx": "yy"})
  print (x)

  y = add_key_value_to_dict("hoge", "fuga")
  print (y)
  
  z = add_key_value_to_dict("foo", "bar")
  print (z)

上のコードの実行結果は以下のとおり。
{'aa': 'bb', 'xx': 'yy'}
{'hoge': 'fuga'}
{'foo': 'bar'}

2017年5月7日日曜日

Kさんの教え

 半年間一緒に働いたエンジニアのKさんの教え。

おれたちはソフトウェアを作っている
ハードウェアを作る場合は、綿密な計画、設計無しにものづくりを始めてはいけない。作り直しのコストが非常に大きいからね。でも、おれたちはソフトウェアを作っている。綿密な計画、設計をしているうちに作っちゃった方が早いよね。

必要以上に複雑にするな
ソフトウェアは使ってもらいながら進化していくものなので、最初から複雑にしすぎてはいけない。絶対必要なものに集中してとにかくシンプルなものからスタートせよ。

変更は小さく頻繁に
ソフトウェアの変更は細かく、高頻度に行うべき。一度に多くのものを変えようとするのはよくない。簡単な機能なのにすぐに変更、リリースができないなんて論外。

学びこそ至福の喜び
常に学び続けろ。新しいことを学ぶのがエンジニアの大きな喜びの一つだ。学ぶことをやめてしまったエンジニアに価値はない。

おまえはどうしたいのか
あの人がこう言った、あのチームの人たちはこう言っている、じゃない。
お前はどうしたいんだ?それが一番大事なことだ。

一心不乱に夢中になること
何かに夢中になっているヤツのまわりにいると、こっちもなんだか楽しい気分になってくるぜ。おれの会社の創業者もそうさせてくれるやつだった。

セキュリティと生産性のトレードオフ
セキュリティを厳しくしすぎると生産性が著しく低下する。両者はトレードオフの関係にあるけど、日本では生産性の問題が軽視される傾向にあるみたいだ。

(番外編)マラソン
マラソンはいろんなことを教えてくれる。精神状態をいかにコントロールするか、ペース配分をどうするかなど。

(番外編)やばたん
YABATAN知らないのか?日本語だぜ。若い女の子のことのこと分かってないんじゃないか?

2017年5月4日木曜日

便利な英語表現: To our best knowledge

 論文を読んでいるとよく出てくる表現。「我々が知るかぎり〜だ」と言うときに使える。

To our best knowledge, this is the first time that ....
我々が知るかぎり、....したのはこれが初めてだ。

To our best knowledge, our model is better than all previously published methods.
我々が知るかぎり、我々の手法は過去に発表された他の手法より優れている。

2017年4月30日日曜日

はじパタ第5章 k最近傍法(kNN法)

 平井有三先生の「はじめてのパターン認識」第5章を読んだ。
kNN自体はもちろん知っていたが、理論的な考察についてはほとんど知らなかったので勉強になった。

面白かったところ
  • 式を使ったボロノイ図の定義
  • kNNとベイズ誤り率の関係
  • kNNにおける次元の呪いの話
理解度チェックリスト
  • 最近傍法とは?
  • k最近傍法とは?
  • ボロノイ図とは?
  • kNNを使うときに問題となる次元の呪いについて説明せよ。
    • 予測データと学習データの距離はどうなる?
  • kNNの計算量は?
  • kNNの計算量を減らすための工夫をいくつか説明せよ。

2017年4月27日木曜日

便利な英語表現: get back to

「あとで〜するね」というときに使える便利なフレーズ。

ミーティングなどで「あとで確認して連絡します。」という場合は、
I'll get back to you later.

「金曜までに具体的な数字を教えてくれますか?」とお願いするときは
Can you get back to me with some figures by Friday?

何かを注文しようとして、その場で即決できず「ちょっと考えてあとで連絡するよ」と言いたいときは
Thank you. Let me think about it and get back to you.

2017年4月22日土曜日

単位超球から一様サンプリング

 誰もが一度はモンテカルロ法を使って円周率を計算してみたことがあると思います。二次元の場合は、点が円の中に入ったり、入らなかったりするのですが、次元が大きくなると円の中に入る頻度が極端に少なくなります。
 
 じゃあ、もし高次元の単位超球内に一様分布する点をサンプリングしたくなったらどうすればよいでしょうか?

 以下自分がやってみたこと。

分散を計算するときの桁落ち対策

分散 = 二乗平均 - 期待値の二乗

という式を使って分散を計算すると、右辺の2つの項の値が非常に近い場合、桁落ちが発生する。

たとえば以下のようなデータを考える。

x1 = 1010
x2 = 1010 + 1
x3 = 1010 + 2
x4 = 1010 + 3
x5 = 1010 + 4

この場合、二乗平均も期待値の二乗も1020程度である。
両者の差は2だが、倍精度浮動小数点の有効桁数は15桁程度なので、この差の情報は失われてしまう。

分散は平行移動しても変わらないので、平均が0になるように中心化を行う。

x1 = -2
x2 = -1
x3 = 0
x4 = 1
x5 = 2

すると、二乗平均は2、期待値の二乗は0となり、差の情報は失われない。

以下のプログラムを実行してみると、桁落ちの様子を確認できる。

#include <iostream>
#include <vector>
#include <iomanip>

using namespace std;

// データを中心化する
vector<double> centerize(const vector<double> &v) {
  int n = v.size();
  double avg = 0.0;
  for (auto &x : v)
    avg += x;
  avg /= n;
  vector<double> w(n);
  for (int i = 0; i < n; i++)
    w[i] = v[i] - avg;
  return w;
}

// 分散を計算する
double calc(const vector<double> &v) {
  int n = v.size();
  double avg_sq = 0.0;
  double avg = 0.0;
  for (auto &x : v) {
    avg += x;
    avg_sq += x * x;
  }
  avg_sq /= n;
  avg /= n;

  return avg_sq - avg * avg;
}

// データ生成
vector<double> gen() {
  vector<double> v;
  for (int i = 0; i < 5; i++)
    v.push_back(1e10 + i);
  return v;
}

int main(int argc, char *argv[])
{
  vector<double> v = gen();
  cout << fixed << setprecision(15);
  cout << calc(v) << endl;
  cout << calc(centerize(v)) << endl;
  return 0;
}

以下実行結果。
$ ./main       
0.000000000000000
2.000000000000000