さくらんぼのlambda日記

lambdaちっくなことからゲーム開発までいろいろ書きます。

機械学習の勉強をしてみた時のまとめ

ちょっと一念発起して機械学習とか統計の勉強をしてみたのでその時の勉強に使った本とか の記録です。

とりあえずこの勉強で以下のような状態には辿りつけました

これから勉強するものとしては以下のものが残っているような状態です

などなどです。 まだ知りたいことは大量にあるので適宜勉強していきます。

モチベーション

そもそもなんで勉強するのか?

  • 最近データ分析の分野が注目されている
  • 計算機の性能向上

最初の理由は最近ニュースなどでもよく目にする「ビッグデータ」というやつですね。 巨大なデータからパターン認識などを見つけて新しいビジネスを見つけていこうとかそういう感じのものです。

2つ目の理由は、自分が大学生の頃はまだまだ個人で巨大なデータ解析をするというのは難しかったのですが、 最近の計算機は性能向上やHadoopやmahoutといった技術がだいぶこなれてきていてお家でも手軽にデータ解析できるようになった ということです。

というのが建前です。

  • 流行りのアニメから自分好みの物を見つける
  • 自分の好きそうなキャラの画像を探す
  • Webサーフィン楽したい

というのを自動化したいというのがモチベーションとしてあります。 特に最近自分が好きそうなアニメを録画リストに入れ損ねているという事件が頻発しているので対策を早めに 実施しないと自我崩壊してしまう危険性があります。

画像探したりアニメ探したりというのはtwitterのTLをデータとして与えればなんとかなるかなぁと思っています。 Webサーフィン楽したいというのは、自分は普段はてなブックマークのホットエントリをよく読んでいるのですが 記事の好みが自分と微妙に合わなかったり、「〇〇を〇〇する〇〇個の方法」みたいなのは外したいとかあるので そういうフィルタが書けたら良いなという感じです。

やったこと

ざっくりまとめると

この2つだけです。 書籍で勉強した感じです。

最初に読んだ物

最初に勉強したのはどれだったかもうあまり覚えていないのですが

入門統計学

http://www.amazon.co.jp/dp/4274068552

データサイエンティスト養成読本 [ビッグデータ時代のビジネスを支えるデータ分析力が身につく!]

http://gihyo.jp/book/2013/978-4-7741-5896-9

機械学習 はじめよう

http://gihyo.jp/dev/serial/01/machine-learning

銀座で働くデータサイエンティストのブログさんのいくつかの記事

http://tjo.hatenablog.com/entry/2013/05/01/190247

この辺りを取っ掛かりにして機械学習のイメージがつかめてきました

慣れてきた辺りで読んだ物

最初の書籍などでどんなことができるのかはイメージがついてきたので、 実際にどのような手法があるのかを具体的に知りたくなったので以下の書籍やwikiなどを 読んでみました。

「入門 機械学習

http://www.oreilly.co.jp/books/9784873115948/

朱鷺の杜Wiki

http://ibisforest.org/index.php?%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92

オライリー本は期待してた内容とマッチしていたのですが、載ってるプログラムが今ひとつ。 Rを使って正規表現処理とか頑張ってるのですが、正直自分で書くならPythonとかRubyでデータ整形して その結果をRに渡す or Scipyとかで自前で処理すると思うので参考にならなかったです。 ただ、理論面に関してはかなり丁寧に書いてくれているので各種アルゴリズムがどうして上手く動いてくれるのか という観点ではためになりました。

Wikiに関しては、この分野が概観できるものになっていてオライリー本には載っていない分野なども知ることができて 参考になりました。

さらに理論面を強化するために

言語処理のための機械学習入門 (自然言語処理シリーズ)

http://www.amazon.co.jp/dp/4339027510

を現在読んでいます。さくさく読めて良い本だと思います。 実際に機械学習をやるにはどうしても日本語がネックになると思ったので丁度良い本ですね。

あとはバスケット分析とか協調フィルタリングとか言われている分野の勉強をしていけば良いかなぁと思っていますが なかなか手頃な入門書が見当たらないというのが現状です。

この辺りが参考になると良いなぁという感じです。

http://gihyo.jp/dev/serial/01/recommend_hadoop

http://hivecolor.com/id/47

http://d.hatena.ne.jp/EulerDijkstra/20130407/1365349866

Expressのhjsでpartialが使えなかったのを修正した話

最近nodeで遊んだりしてました。

nodeで広く使われているフレームワークにexpressというモノがあります。 http://expressjs.com/

いまどきのフレームワークらしく生のHTMLではなくテンプレートを使うことができます。 express --helpでどれに対応しているのかある程度わかるだろーと思って眺めてみます。

  Usage: express [options] [dir]

  Options:

    -h, --help          output usage information
    -V, --version       output the version number
    -s, --sessions      add session support
    -e, --ejs           add ejs engine support (defaults to jade)
    -J, --jshtml        add jshtml engine support (defaults to jade)
    -H, --hogan         add hogan.js engine support
    -c, --css <engine>  add stylesheet <engine> support (less|stylus) (defaults to plain css)
    -f, --force         force on non-empty directory

Usageによると

  • ejs
  • jshtml
  • hogan

あたりが使えるようです。 そこで、twitterが使ってるしと軽い気持ちでHoganを選択した所大ハマリしてしまいました。

Hogan.jsで使っているテンプレートの文法はmustacheと同じなのですが

http://mustache.github.io/mustache.5.html

この中のpartialを使おうとしたら、 partialを認識しないという状況になっていました。 テンプレートを分割して管理できないのは生きて行くのが辛いので対処しないといけません。

expressとhogan.jsを組み合わせるためにデフォルト('''express -H''')で使われているhjsのコード を眺めてみると、なんと引数としてpartialを使ってもいなそうということがわかりました。

適当にpartialsとして渡された引数を評価して、分割されたpartialとして認識されるようにし その中身をHoganでHTMLにするという所を追加して事無きをえました。

githubに適当においておきます。 expressでhogan.js使いたい人は参考までに。

https://github.com/lambdasakura/hjs/tree/fix-partial

折角なので、本家にもpull requestを投げておきました。

Yahooを語るフィッシングメールが来たのでみなさんも注意しましょう

なんか登録した覚えすらないYahooからアカウント情報更新のメールが来たので不審に思って調べてみたら見事にフィッシングでした。

こんな内容

いつもYahoo! JAPANをご利用いただきありがとうございます。 YAHOOプレミアム更新の時期がきましたので更新の手続きを行ってください。

◆更新しましょう!

http://yahoojapan.cher-ish.net/

上記の更新ページの必要事項を記入し更新ボタンで更新手続き完了となります。

◆ヤフオク!HAPPY! WEEK開催のお知らせ http://topic.auctions.yahoo.co.jp/notice/special/happy_week_2/

最後のヤフオクの宣伝で本物なのですが 更新しましょうと書いてあるyahoojapan.cher-ish.netの方は全然関係ないサイトのようです。

whoisの結果を見てみると http://www.mfro.net/service/quicca.php こちらのサーバで公開されているようです。

Yahooさんが自前のサーバでやらない理由が思いつかないのでフィッシングなのでしょう。 みなさんもダマされない様に注意してください。

sublime text 2を導入してみました

巷で話題の、sublime text 2を導入してみました。 @nekorukiaさんがRuby編集環境を物色していたので薦めるついでに自分も使ってみたら 使いやすくて気に入ってしまいました。

具体的には

  • 起動時間が短い
  • 多くの言語に標準で対応している
  • Windows/Mac/Linuxで動作する
  • UIが綺麗

あたりが気に入った理由です。

自分は10年以上Emacsで生活してますが、いくつか気に入らない点がありました。

  • 設定が面倒くさい点
  • 起動が遅いという

これらの問題をSublime Textは見事に解決してくれました。

今までのEmacsの生活

Emacsはとても強力な開発環境を提供してくれます。 しかし、それを手に入れるには様々なEmacs Lispを導入しなければなりません。 拡張機能の存在のあるなしも含めて調査から開始です。

例えば、Haskellを始めようと思ったら、

  1. Haskell向けのシンタックスハイライトがないか調べる
  2. ghciがemacs上で対話的に操作できないか調べる
  3. シンボルからドキュメントを引くことができるかどうかを調べる
  4. 補完ができるかどうかを調べる
  5. emacs上でhaskellの実行ができないかどうかを調べる
  6. コードナビゲーション機能があるかを調べる

といったことをついついやってしまいます。 要は、Emacs上にすでに構築してある他の言語環境と同等のものを設定したくなってしまうのですね。

実際のところHaskellを始めるには

  1. Haskell向けのシンタックスハイライトがないか調べる
  2. 補完ができるかどうかを調べる
  3. emacs上でhaskellの実行ができないかどうかを調べる

程度ができれば十分実用になるはずです(これでも面倒くさいですが...)。

また、起動速度に関しては、様々な環境を構築しきっている状態ではEmacsの起動はとても遅いです。 言語を始めたての頃は気軽に起動して実行して試したいはずですが、時間がかかり過ぎてしまいます。

emacs-serverとemacs-clientという仕組みがありますが、これがベストな解決には思えません。 常時Emacsを起動していないといけないので、邪魔です。Alt+Tabとかに常にEmacsがいるような状態は嫌です。 手軽に起動できて終了したいです。

これらの欠点のせいで、新しい言語を始める場合や書き捨てのファイルを作るのに億劫になる場面が多々ありました。 なので、

  • 高速に起動できる
  • 多くの言語に標準で対応している
  • Emacsと同じOSで動作する

というエディタが欲しくなっていました。

Sublime Textの生活

そんな自分にとって、sublime textは魅力的なエディタです。

  • 起動は早い
  • 標準の機能で多くの言語に対応し開発可能
  • Windows/Mac/Linuxで動作する
  • UIが綺麗

だったからです。

標準だと日本語入力に難があったので

を入れて

  • IMESupport

を入れるだけで多くの言語において十分実用になる環境(シンタックスハイライトされ、ある程度の補完をしてくれて、実行がエディタから行える環境)が手にはいりました。

使っていて足りないかも思う機能としては

  • コードナビゲーション
  • 対話環境
  • ドキュメント引き

の機能が挙げられますが、多くの場合プラグインがありそうです。 それにEmacsを別に捨てるつもりもないので、しっかりした環境が欲しい場合はEmacsの環境を構築すれば良いと考えています。 (例えば、Common LispのSLIME環境と同等のものがSublime Text上に構築できるとはとても思えませんから、当分Common LispEmacsで開発するでしょう。)

こんな感じで、Sublime TextとEmacsの合わせ技でしばらく生活してみようかと思います。

おまけ

HTMLで少し遊ぶかと思って以下のものも入れてみましたが なくてもあってもどちらでもよさそうです。

CommonLispで家庭内メモ共有システム(MyEvernote)を構築した話

自宅でEvernoteのようなメモ管理システムを構築してみました。
そのバックエンドとしてCommonLispを採用したので記録も兼ねて記事にしておきます。
ソースはgithubで公開してます。

動作している画面はこんな感じです。

f:id:sakura-1:20130606140606p:plain

開発動機

外部のサービスに依存していないメモ管理システムが欲しいというのが開発動機です。
Evernoteなどのクラウドシステムにはプライベートな情報を記述しておきたくないです。

個人用途なので以下の機能はばっさり切り捨てています

  • 認証システム
  • タグ付けシステム
  • リマインダ

それぞれの理由としては、

認証システム
Apacheなどで認証してもらえば十分である。:
タグ付け
知識を蓄えておくためのものではないのでタグ付けが必要になるほどの大量のメモは取らない。:
リマインダ
タスク管理システムやスケジューラではない。:

と言った感じです。

開発環境

CommonLispの処理系はSBCLを選びました。Emacs+SLIMEで開発してます。

使用したライブラリ

  • clack
  • cl-dbi
  • cl-json

マシン構成

サーバとして動いているマシンは以下のスペックです。今回のために用意したマシンではなくもともと自宅で動いているサーバマシンです。

OS Debian 6.0
CPU Core i3-540
メモリ 4GB
動いてるもの nginx,Apache2,SQLite3

開発方法

サーバマシンにSSHでログインしてscreen上のSLIMEで開発してます。
この環境だとログアウトしてもSBCLは動作し続けてくれるのでデーモン化などしなくても
手軽に使えます。SLIMEのバッファで評価してあげれば即座にサーバの動作も変わってくれて
楽です。

MyEvernoteサーバの詳細

サーバ内部のプロセス同士の連携は以下のようなイメージになります。
認証はApacheにDigest認証をやってもらうことにしました。
ツッコミが入りそうなところとしては、リバースプロクシが多段になっているのは、最近nginxを入れたばかりなため手が回らなかったためですね。

f:id:sakura-1:20130606144818p:plain


サーバの内部のクラス間の連携はこんな感じで実装してます。
今回はこのニコニコ動画この開発手法に感銘を受けたので
キャラクター名がクラス名です。

f:id:sakura-1:20130606142926p:plain

どうみてもゲームのキャラ紹介にしか見えませんが仕様です。
基本的に難しいことはおこなっていないのでソースを読んでもらえば何をしているのかすぐわかるかと思います。

ややこしいことを行なっている箇所としては

  • ユーザ名をHTTPのリクエストから取り出している所
  • clackのPOSTメッセージから本体を取り出している所

くらいでしょうか。

MyEvernoteクライアントの詳細

サーバが適当にJSONを返してくるので、それをDataTableに整形して表示するだけです。
特に新しい試みはおこなっていないので、詳細は省きます。

こっちはものすごくコードが汚いので読まないほうが良いと思いますw

ハマった点

SQLiteがやたらとエラーを返して落ちる。

どうもupdateとかisertの処理の直後にselectを発行すると、busy_errorを返すようです。
SQLiteはこのあたりを呼び出す側に押し付けるような仕組みとなっているので仕方がないですが
使い物になりません。

ソースをいろいろ眺めているとcl-sqliteにはこれのための対処のオプションがあるようです。
ただ、cl-dbiからはこれを使うことが想定されていないようなので、独自に拡張して対処しました。
なお、この修正は本家にも取り込まれました。

Datatablesのtwitter bootstrap対応

メモ表示の部分にDatatablesを使ったのですがtwitter bootstrap対応するには
いろいろ修正が必要なようで手間がかかりました。

以下に情報があって助かりました。
http://www.datatables.net/blog/Twitter_Bootstrap_2

まとめと今後の予定

Common Lispでサービスを作るという事を初めて試みて見ましたが、割と快適だったように思います。
SLIMEによるところが大きいですが、書いてその場でどうなるかをすぐ試せるのは良いですね。

やり残している作業としては、インストール方法とか書いてなかったり使われていないコードが残っていたりするのでその整備を行ないたいところ。

emacsのmagitの性能を改善した

emacs + gitの環境

最近gitを使うときコマンドラインから使うのは億劫になってきたのでGUIに逃げようかと
思ったり色々試していたのですが、結局Emacsからコミットコメントや操作も出来たほうが
便利という結論に至ったので、色々elispを物色していました。

Emacsとgit連携を実現するプラグインの中にmagit(https://github.com/magit/magit)
というものがあります。googleしてみると割りと利用者も多そうで良さげで、実際
Macで試してみて使用感も問題なく良い感じだと思っていました。

遅すぎワロタ

しかし、Windowsで動作させてみるとその動作速度に愕然としました...。
環境にもよるのだと思いますが、git statusコマンドを実行するmagit-statusの実行に
10秒程度かかってしまいました。そのあとgit addに相当する操作をしてもまた10秒程度...。
作業に全くならないという状況でした。

他のelispに乗り換えるのも面倒くさいのでmagitを改良してしまったほうが早いと思い
改良しました。結果10秒かかっていた処理が1~2秒程度で完了するようになりました。
これなら実用になります。

何したの?

elp-instrument-packageでプロファイルしてみると、どうもmagit-cmd-output当たりが
遅そうという事がわかってきたので色々変更してみて試してみました。

magitでgitを呼び出しているprocess-fileをshell-command-to-stringに置き換えたら改善しました。どうやらWindowsだとprocess-fileは遅いらしく、shellを通じてコマンド実行するshell-command-to-stringの方が圧倒的に早いようです。

変更点は以下の様な感じ。patchにしても良かったのですが、そのうちmagitをforkしてpull request送ったほうが良いかなと思うので...。

;; フォーマットを変えないと実行失敗するので修正
(defvar magit-git-log-options
  (list
   "--pretty=format:\"* %h %s\""
   (format "--abbrev=%s" magit-sha1-abbrev-length)))

;; magit-cmd-outputとmagit-run*をこんな感じに書き換えた
(defun magit-cmd-output (cmd args)
  (let* ((arg (apply #'concatenate 'string (mapcar #'(lambda (x) (concatenate 'string " " x)) args)))
	 ;; execute git command
	 (cmd-output  (shell-command-to-string (concatenate 'string cmd arg))))
    (replace-regexp-in-string "\e\\[.*?m" "" cmd-output)))

(defun magit-run* (cmd-and-args
                   &optional logline noerase noerror nowait input)
  (if (and magit-process
           (get-buffer magit-process-buffer-name))
      (error "Git is already running"))
  (let ((cmd (car cmd-and-args))
        (args (cdr cmd-and-args))
        (dir default-directory)
        (buf (get-buffer-create magit-process-buffer-name))
        (successp nil))
    (magit-set-mode-line-process
     (magit-process-indicator-from-command cmd-and-args))
    (setq magit-process-client-buffer (current-buffer))
    (with-current-buffer buf
      (view-mode 1)
      (set (make-local-variable 'view-no-disable-on-exit) t)
      (setq view-exit-action
            (lambda (buffer)
              (with-current-buffer buffer
                (bury-buffer))))
      (setq buffer-read-only t)
      (let ((inhibit-read-only t))
        (setq default-directory dir)
        (if noerase
            (goto-char (point-max))
          (erase-buffer))
        (insert "$ " (or logline
                         (mapconcat 'identity cmd-and-args " "))
                "\n")
        (cond (nowait
               (setq magit-process
                     (let ((process-connection-type magit-process-connection-type))
                       (apply 'magit-start-process cmd buf cmd args)))
               (set-process-sentinel magit-process 'magit-process-sentinel)
               (set-process-filter magit-process 'magit-process-filter)
               (when input
                 (with-current-buffer input
                   (process-send-region magit-process
                                        (point-min) (point-max)))
                 (process-send-eof magit-process)
                 (sit-for 0.1 t))
               (cond ((= magit-process-popup-time 0)
                      (pop-to-buffer (process-buffer magit-process)))
                     ((> magit-process-popup-time 0)
                      (run-with-timer
                       magit-process-popup-time nil
                       (function
                        (lambda (buf)
                          (with-current-buffer buf
                            (when magit-process
                              (display-buffer (process-buffer magit-process))
                              (goto-char (point-max))))))
                       (current-buffer))))
               (setq successp t))
              (input
               (with-current-buffer input
                 (setq default-directory dir)
                 (setq magit-process
                       ;; Don't use a pty, because it would set icrnl
                       ;; which would modify the input (issue #20).
                       (let ((process-connection-type nil))
                         (apply 'magit-start-process cmd buf cmd args)))
                 (set-process-filter magit-process 'magit-process-filter)
                 (process-send-region magit-process
                                      (point-min) (point-max))
                 (process-send-eof magit-process)
                 (while (equal (process-status magit-process) 'run)
                   (sit-for 0.1 t))
                 (setq successp
                       (equal (process-exit-status magit-process) 0))
                 (setq magit-process nil))
               (magit-set-mode-line-process nil)
               (magit-need-refresh magit-process-client-buffer))
              (t
	       (let* ((exec-cmd (apply #'concatenate 'string 
				       (mapcar #'(lambda (x) (concatenate 'string " " x)) args)))
		      (exec-cmd-output (shell-command-to-string (concatenate 'string "git " exec-cmd))))
		 (insert exec-cmd-output))
	       (setq successp (equal "0" (shell-command-to-string "echo $?")))
               (magit-set-mode-line-process nil)
               (magit-need-refresh magit-process-client-buffer))))
      (or successp
          noerror
          (error
           "%s ... [Hit %s or see buffer %s for details]"
           (or (with-current-buffer (get-buffer magit-process-buffer-name)
                 (when (re-search-backward
                        (concat "^error: \\(.*\\)" paragraph-separate) nil t)
                   (match-string 1)))
               "Git failed")
           (with-current-buffer magit-process-client-buffer
             (key-description (car (where-is-internal
                                    'magit-display-process))))
           magit-process-buffer-name))
      successp)))

git svnのブランチの扱いについて

自分はバージョン管理システムとしてgitを利用してます。
プロジェクトで採用しているバージョン管理システムSubversionの場合でもgit-svnで連携して生活してます。

今関わっているプロジェクトのsubversion上の構成がgit-svnにとって扱いづらいものが採用されていて、それの対処方法についてまとめておきます。

前提のプロジェクト構成

現在関わっているプロジェクトのバージョン管理システムでは以下のようなブランチ構成になっています。

branches/A/subA1
branches/A/subA2
branches/A/subA3

branches/B/subB1
branches/B/subB2
branches/B/subB3

branches/C/subC1/subsubC1-2
branches/C/subC1/subsubC1-2
branches/C/subC2/subsubC2-1
branches/C/subC2/subsubC2-2

ある機能Bの開発を進めているブランチがあって、その中に更に細かい機能向けのブランチが切ってあるような状況です。また、機能によって深さなども違っていたりします。

何が困るの?

svnをそのまま使って入れば特に困ったりしないのですが、git-svnでは上記構成の場合問題が発生します。具体的には、

  • cloneする時
  • ブランチが新規に追加された時
チェックアウト時

普通に

$ git svn clone -s http://example.com/git-url/

などとすると設定がうまくいかないです。git svn cloneは再帰的にブランチを探しに行きません。この場合ですと、git-url/branches/の直下にあるものをブランチとして認識してしまいます。つまりAとかBとかがブランチになってしまいます。subA1とかをブランチとして認識して欲しいのでこれでは困ります。

これに対する解決策は逐一branchの場所をちゃんと教えてあげれば大丈夫です。

$ git clone http://example.com/git-url/ -T trunk -b branches/A -b branches/B -b branchs/C/subC1/ -b branches/C/subC2/ -t tags/

このようにコマンドを発行する必要があります。ブランチが本当にある場所を指定して実行してあげれば大丈夫です。

ブランチが追加、削除された時

上記の設定をして安心して生活していても、こっそりブランチが追加されたりしたりします。

branches/D/subD/

とかが追加されたりするわけです。

この場合は以下の手順でgitに追加で認識させることができます

  • .git/configに新しくできたブランチの場所を追加する
        branches = branches/D/subD/*:refs/remotes/D/* # この行を追加
  • 以下のコマンドを発行してブランチをチェックアウトさせる
$ rm .git/svn/.metadata packed-refs
$ git svn fetch

これで、gitが新しいbranchを認識するようになるはずです。

それでは、皆さん良いgitライフを。