ブログのタイトルを変更

ブログを始めた時に、"hotoku"でgoogle検索したらトップに出て来るようにしよう、と、思ってたんだけど、その当時は"hotoku"っていうのは「報徳」のローマ字表記であって、意外と激戦区なんだという事を知らんかった。報徳学園とか報徳会館とか、強くて勝てない。それでも、大体3位くらいには出てきてたので、まぁ良いか、と思ってたんだけど、最近、トップ10にも入らなくなってしまったので、タイトルに明示的に"hotoku"と入れる事にした。「とは」っていうのに特に意味はなし。

バッファの空行の数を均等にするelisp

自分のinit.elを編集していて思った事。新しいパッケージを追加した時に適当に設定を追加すると、場所毎に空行の間隔が変わっちゃってやだなぁと。で、練習がてら、空行の行数を調整するelispを書いてみた。
これまで、殆ど自分でelispを書く事はなかったけど、覚えると便利だ。

使い方

  • 上のファイルをload-pathの通った所に置いて(require 'equally-spaced)する
  • M-x equally-spaced-make-gap-bufferとすると、空行だけの部分が等間隔になる

こんな感じでバラバラに開いていた間隔が…beforeすっきり等間隔になる。after

open-junk-file + howmでサンプルコードのメモ

**の言語で〇〇する時はどうするんだっけ?みたいなメモをどう残すか。
こういう細かいメモは、新しい事を覚えた端から古い事を忘れてしまうので、かつて自分が試したコードは、出来るだけ手軽に検索出来る形で残しておきたい。という訳で、ただ置いておくだけで検索出来る場所、blogとかqiitaに、そういったメモを置いていた。しかし、これは「残す為の動作」がそれなりに重たくて、あんまりメモが溜まらない。大抵、M-x open-junk-file で一時ファイルを開いて、動作する事が確認出来たらもうそのメモは放っとかれてしまう。で、ハードディスクにファイル名の日付だけが手がかりとなったメモが大量に残る。
しかし、open-junk-fileでファイルを開く場所をhowmの管理下に設定しさえすれば、サンプルコードを書いてファイル保存するだけで、howmで検索できるようになるじゃん、と気が付いた。ついでに、拡張子から言語を判断して、ファイルの先頭に検索用のタグをコメントで入れておけば、一覧性も上がって便利なんではないかと。

;;; open junk file
(require 'open-junk-file)
;; howmの中にファイル作成
(setq open-junk-file-format "~/howm/junk/%Y/%m/%d_%H%M%S.")
;; howm用のタグを挿入
(defvar open-junk-ext-tags-alist
  '(("el" ";;" "ELISP")
    ("cpp" "//" "CPP")
    ("r" "#" "R")
    ("py" "#" "PYTHON")
    ("hs" "--" "HASKELL")
    ("scm" ";;" "SCHEME")))
(defadvice open-junk-file
  (after open-junk-file-insert-howm-comment-advice activate)
  "After open-junk-file, insert a tag into the opened buffer
to be searched by howm."
  (let* ((ext (replace-regexp-in-string "^.*\\.\\([^\\.]+\\)$" "\\1" buffer-file-name))
         (asc (assoc ext open-junk-ext-tags-alist))
         (prefix (cadr asc))
         (tag (caddr asc)))
    (insert prefix)
    (insert " %" tag)))

使い方(追記)

  • howmとopen-junk-fileが入っている事は前提。
  • 上のelispをinit.elに貼り付けて、通常通りにM-x open-junk-fileする。
  • 適当にメモを書いて保存した後、howmで検索する。
  • 下の画像は、howmの検索で"%elisp"を検索した所

howm画面

SchemeでScheme

2012年の目標の1つにSICPを読むというのがあったのだけれど、結局、完遂出来ずに年を越してしまった。スマホに入れて、通勤中(片道50分)に読んでいたんだけど、4章がちょっと重たくて、つまづいてしまったのである。4章はSchemeSchemeを実装するというのが主題なんだけど、これを脳内インタプリタだけで読みこなすのは俄リスパの自分には不可能なのであった。
という訳で、一応、自分でも実装してみたので、4.1節の内容は理解したという事にしたい。
https://gist.github.com/hotoku/4753593

データサイエンティストではない人に知っておいて欲しい事

統計を専門にしている訳ではない人と話していて感じた違和感があったので、書き留めておきたい。

疑うべき順番は モデル → 推定法

データ分析をしていれば、当然、期待を掛けたモデルのデータへの当てはまりそうが悪いという事が度々ある。こういう時、統計屋さんとして自然に浮かぶのは「モデルが間違っている」という発想である。と思うのだが、非統計屋さんと話していると、このような時に「別の推定法を試してみたらどうだろう」と言われる事がある。多分、目の前のモデルに対する過度の期待から来るのだろうと思うが、このような態度では統計的に見ると妥当性を欠いた分析をしてしまう危険を孕んでいる。

ひとつの事例

とある線型状態空間モデルのパラメータを推定した所、どうしてもデータに合わない部分があった。実は、それが合わない理由は簡単で、ある潜在変数は常に正であるはずなのだ。線型状態空間モデルでは、潜在変数の分布は正規分布であると仮定する(だから、負の値を取る事もある)。手元のデータでは、その潜在変数が0以下に推定されるような状態だったので、現実にそぐわない予測値が出てしまっていたのである。
この状況で、「カルマンフィルタ使って最尤推定しているのが駄目なんだな」と考える統計屋さんは居ないと思う。普通は、「非線型のモデル考えるか」と発想する。なんだけど、その線形モデルはかつて他所の会社で莫大な利益を生み出したという触れ込みで有名なモデルであったので、「モデルが間違っている」という考えは中々受け入れて貰えなかった。
自分の周りの非統計屋さん達は、次のように考える人が多い。観測値の一部分に予測値から大きく外れる部分があるのが気持ち悪いので、そこにもの凄く大きな重みを付けて、その部分の残差が小さくなるように推定しちゃえば良いんでは無いかと。
しかし、このような分析方法は、やはり統計的には妥当性を欠いた考えであると思う。
データに重みを付けるという事は、そのデータに含まれているノイズが小さい事を先験的に知っているという事を意味する。従って、本当は他のデータと同程度のノイズを含んでいるはずのデータに重みを無理に与えるというのは、ノイズによって偶然発生しただけの挙動を意味のある挙動として解釈せよと無理やりモデルに強制することに他ならない。だから、偶々その方法で該当部分の残差が小さくなったとしても、それはモデルの予測精度が上がっている事を意味するのではなく、ノイズへの過適合を起こしているだけである。
と、いうような事が昔あった。多分、そのモデルの伝説が強すぎて、こんな発想をしてしまうと思うのだが、上に書いたような理由で、それは禁じ手である。

仮説無しにデータ分析は始まらない

「〜〜という仮定を置いてよければ、**という手法が使えて、そうすると○○ということが言えますよ」という説明をすると、〜〜の部分が正しい事はどうやって分かるの?と言われる事がある。勿論、仮説検定だとかモデル選択だったりとかあるいは1つの仮定を置かずに複数の仮定を事前分布で混ぜちゃって分析するだとか、色々方法はある。なんだけど、そういった分析というのも実はよりメタな仮定を置いて議論を始めているだけで、データ分析において「〜〜という仮定を置くと」という部分が綺麗さっぱり消える事は無い。
そもそも、何ゆえにデータを分析するのかと云えばデータから有用な情報を取り出す為である。であるならば、少なくとも、何を有用ではないと見做すのかという立場をはっきりさせなくては始まらない。何が重要ではないかという事を設定して初めて、データを加工し、要らない部分を切り捨てて、解釈し易い情報を抽出することが出来るのだ。
「データを分析するには仮定が必要」だけど「どういう仮定が適切なのかはでーたを分析しないと分からない」というジレンマは、鶏と卵のような循環を成してしまう。だから、最後は何処かで人間がデータに依らずに「分析の出発点とする仮定を選ぶ」というステップが必ず必要になる。

自作リングノート

リングノートって、普通に綴じたノートに比べて割高なので、今まで全く使わなかったんだけど、先日とあるセミナーに参加したら、メモ用にA5版のリングノートをくれて、これを会社で使っていたら、とても便利と思った。
何が便利かというと、普通のノートは180度までしか開かないけど、リングノートは360度まで折り返せるという点。普通のノートだと見開き分の場所を取られるけど、リングノートだと半分の場所で済む。それと、360度折り返せると、机を使わないでノートを手に持った状態でもメモを取り易い。何なら、電車の中でもガリガリと計算出来るぐらい。
という訳で、リングノート熱が急上昇したんだけど、如何せん普段の勉強用メモとして使うにはリングノートは紙1枚の単価が高すぎる。で、自作出来ないんかとググってみたら、そんなのがあった。普通のルーズリーフが使えるので、すこぶる便利。

SICPのexcercise 3.21から3.27

  • どっかに記録を残しておかないとどこやったか忘れてしまうのでメモ。

3.16

listの中のペアの数を数える関数count-pairの間違った実装が与えられるので、間違った答えを出すような反例を作れという問題。

(define (count-pairs x)
  (if (not (pair? x))
      0
      (+ (count-pairs (car x))
         (count-pairs (cdr x))
         1)))

(define (f1)
  (define p1 '(1 . 2))
  (define p2 '(3 . 4))
  (define p3 '(5 . 6))
  (set-cdr! p1 p2)
  (set-cdr! p2 p3)
  p1)
(count-pairs (f1))

(define (f2)
  (define p1 '(1 . 2))
  (define p2 '(3 . 4))
  (define p3 '(5 . 6))
  (set-cdr! p1 p2)
  (set-cdr! p2 p3)
  (set-car! p2 p3)
  p1)
(count-pairs (f2))

(define (f3)
  (define p1 '(1 . 2))
  (define p2 '(3 . 4))
  (define p3 '(5 . 6))
  (set-cdr! p1 p2)
  (set-cdr! p2 p3)
  (set-car! p1 p2)
  (set-car! p2 p3)
  p1)
(count-pairs (f3))

(define (f4)
  (define p1 '(1 . 2))
  (define p2 '(3 . 4))
  (define p3 '(5 . 6))
  (set-cdr! p1 p2)
  (set-cdr! p2 p3)
  (set-cdr! p3 p1)
  p1)
(count-pairs (f4))

3.17

さっきのcount-pairsを正しくしろという問題。

(define (count-pairs x)
  (define (in? ls b)
    (cond ((eq? '() ls) #f)
	  ((eq? (car ls) b) #t)
	  (else (in? (cdr ls) b))))
  (define visited '())
  (define (iter a)
    (cond
     ((pair? a) (cond ((in? visited a) 0)
		      (begin
			(set! visited (cons a visited))
			(+ 1
			   (iter (car a))
			   (iter (cdr a))))))
     (else 0)))
  (iter x))

3.18

cdr cdr cdr と辿ると無限ループになってしまうようなリストかどうかを検査せよという問題。

(define (loop? ls)
  (define (in? ls b)
    (cond ((eq? '() ls) #f)
	  ((eq? (car ls) b) #t)
	  (else (in? (cdr ls) b))))
  (define visited '())
  (define (iter ls2)
    (cond ((eq? ls2 '()) #f)
	  ((in? visited ls2) #t)
	  (else
	   (begin
	     (set! visited (cons ls2 visited))
	     (iter (cdr ls2))))))
  (if (pair? ls) (iter ls) #f))

3.21

本文中のqueueの実装に合わせたプリンターを実装する問題

(define (print-queue q)
  (define (iter p)
    (if (null? p)
	()
	(cons (car p) (iter (cdr p)))))
  (iter (front-ptr q)))

3.27

schemeで以下のようにメモ化を実装。

(define (memoize f)
  (let ((table (make-table))) ; [b]
    (lambda (x) ; [c]
      (let ((previously-computed-result (lookup x table)))
        (or previously-computed-result
            (let ((result (f x)))
              (insert! x result table)
              result))))))

この時、

(define memo-fib
  (memoize (lambda (n) ; [a]
             (cond ((= n 0) 0)
                   ((= n 1) 1)
                   (else (+ (memo-fib (- n 1))
                            (memo-fib (- n 2))))))))

はきちんとメモ化された動作をするけど、

(define (fib n)
  (cond ((= n 0) 0)
        ((= n 1) 1)
        (else (+ (fib (- n 1))
                 (fib (- n 2))))))
(define memo-fib-2 (memoize fib))

は駄目な理由を考えようという問題。
memo-fibがどういう素性の物かを考える。

  • まず、[a]のmemoize呼び出しで、global environmentをenclosing environmentとするenvironment Aが作成される。
  • さらに、この中で、[b]のletによってAをenclosing environmentとするenvironment Bが作成される。
  • メモの実体となるtableは、environment Bの中に作成される。
  • memo-fibの実体=[a]のmemoize呼び出しの返り値=[b]のletの返り値=[c]のlambda式による関数オブジェクト。
    • このオブジェクトはenvironment Bの中に作成されている。
  • 従って、memo-fibの実際の呼び出しでは、environment Bをenclosing envirionmentとする環境が作成され、その中で評価が実行される。
    • 故に、environment Bの中のtableを参照してメモを読み出す事が可能。

同様に、memo-fib-2がどういう素性の物かを考える。

  • fibは、再帰呼び出しでfibを呼ぶ。
  • fibは、メモ化されずに定義されている関数である。
  • 従って、memo-fib-2を呼び出すと、最初の1回だけはテーブルを参照しに行くけれども、再帰的に呼び出されるfibの中ではメモの検索は行われないので、結局メモ化の有難味が無い。
    • 但し、(memo-fib-2 3) (memo-fib-2 3)と同じ引数で複数回評価すると、2回目以降の評価ではメモの値が返される。
    • 最上位のfib呼び出しだけはメモ化されているから。

memoizeを

(define (memoize f)
  (let ((table (make-table)))
    (lambda (x)
      (let ((previously-computed-result (lookup x table)))
	(if previously-computed-result (print x " is found.")
	    (print x " is not found"))
        (or previously-computed-result
            (let ((result (f x)))
              (insert! x result table)
              result))))))

のように書き換えると確かめられる。