モンティ・ホール問題をGoで確かめてみました

少し前に参加した続・わかりやすいパターン認識読書会モンティ・ホール問題について知ったので”司会が正解を知っているか否かによる結果の違い”について、ちょうど触ってみたかったGoで確かめてみました。(Go触りたかっただけ)

モンティ・ホール問題って?

(1) 3つのドア (A, B, C) に(景品、ヤギ、ヤギ)がランダムに入っている。
(2) プレイヤーはドアを1つ選ぶ。
(3) モンティは残りのドアのうち1つを必ず開ける。
(4) モンティの開けるドアは、必ずヤギの入っているドアである。
(5) モンティはプレーヤーにドアを選びなおしてよいと必ず言う。

モンティ・ホール問題 - Wikipedia

(5)で選び直した方が良いのかどうかという問題です。

ここでは、プレイヤーが最初に選んだドアをA、司会のモンティがあけるドア(はずれ)をB、残っている開けられていないドアをCとします。

司会が正解を知っている場合、Aが正解の確率は1/3、Cが正解の確率は2/3となり、選び直した方が良い事になります。 確率はベイズの定理で求まります。

f:id:nihma:20141229004918j:plain

ここで司会が正解を知らなかった場合、確率が変わります。 Aが正解の確率は1/2、Cが正解の確率は1/2でどちらを選んでも同じ確率です。 こちらもベイズの定理で求まります。

f:id:nihma:20141229005058j:plain

P(Bが選ばれる|Bが当たり)とP(Bが選ばれる|Cが当たり)が違います。

確かめてみました

とりあえずGoで10000000回の試行による確率を求める計算をそれぞれ5回ずつ行ってみました。 大数の法則を信じて10000000回もやれば大丈夫と信じました。

コードは次の通りです。
問題の(4)の条件から、Bが必ずはずれであるのでAorCが当たりとなり、司会が正解を知らない場合にBを選んで当たってしまったケースは試行に含まれないです。
(tryA関数は無い方が速いですがgoroutineしてみたかったのでしました。)

package main

import (
  "fmt"
  "math/rand"
  "time"
  "sync"
  "runtime"
)

func main() {
  // CPU全コアを使いたい
  runtime.GOMAXPROCS(runtime.NumCPU())

  // ランダムの初期化
  rand.Seed(time.Now().UnixNano())

  // それぞれ5回ずつprintしてみる
  label_know := map[string] bool {
    "司会が正解を知っている場合": true,
    "司会が正解を知らない場合":   false,
  }
  n := 5
  printP := func(p_a float64, p_c float64) {
    fmt.Printf("P(A) = %.3f, P(C) = %.3f\n", p_a, p_c)
  }
  for label, know_answer := range label_know {
    fmt.Println(label)
    tryNtimes(know_answer, n, printP)
  }
}

/* n回やってみる
 * 引数1:know_answer 司会が当たりを知っているか?
 * 引数2:n やってみる回数
 * 引数3:f 結果を渡す関数
 */
func tryNtimes(know_answer bool, n int,
               f func(float64, float64)) {
  var wg sync.WaitGroup
  for i := 0; i < n; i++ {
    wg.Add(1)
    go func() {
      var p_a float64 = probabilityA(know_answer)
      var p_c float64 = 1 - p_a
      f(p_a, p_c)
      wg.Done()
    }()
  }
  wg.Wait()
}

/* ドアAが当たりの確率
 * 引数:know_answer 司会が当たりを知っているか?
 */
func probabilityA(know_answer bool) float64 {
  try_num := 10000000  // 試行回数(このくらいやれば収束する?)
  a_num := 0 // ドアAが当たりの回数

  ch := tryA(know_answer)
  for i := 0; i < try_num; i++ {
// こっちの方が速いですが->    if isA(know_answer) {
    if <- ch {
      a_num++
    }
  }
  return float64(a_num) / float64(try_num)
}

/* ドアAが正解であるかの試行を生成
 * 引数:know_answer 司会が当たりを知っているか?
 */
func tryA(know_answer bool) chan bool {
  ch := make(chan bool)
  go func() {
    for {
      ch <- isA(know_answer)
    }
  }()
  return ch
}

/* ドアAが正解であるか?
 * 引数:know_answer 司会が当たりを知っているか?
 */
func isA(know_answer bool) bool {
  /* [答えの候補]
   * 0: ドアA <- 最初に選択
   * 1: ドアB <- 司会が選択(はずれ)
   * 2: ドアC <- こちらに変更できる
   */
  // 最初に答えを決める
  answer := rand.Intn(3)  // 0〜2

  if ! know_answer { // 答えを知らない場合
                     // ドアBが当たりになる試行は無効、捨てる
    for answer == 1 {
      answer = rand.Intn(3)
    }
  }
  return answer == 0 // ドアAが当たりか?ドアCがはずれか?
}

結果は理論通りになったようです。

$ go build MontyHall.go
$ ./MontyHall
司会が正解を知っている場合
P(A) = 0.333, P(C) = 0.667
P(A) = 0.334, P(C) = 0.666
P(A) = 0.334, P(C) = 0.666
P(A) = 0.333, P(C) = 0.667
P(A) = 0.333, P(C) = 0.667
司会が正解を知らない場合
P(A) = 0.500, P(C) = 0.500
P(A) = 0.500, P(C) = 0.500
P(A) = 0.500, P(C) = 0.500
P(A) = 0.500, P(C) = 0.500
P(A) = 0.500, P(C) = 0.500

感想

Goを知った2009年頃に受けたネイティブならD言語でええやんという印象から食わず嫌いでしたが意外と面白かったのでgoroutineでもう少し遊んでみたいです。

Pythonで画像の簡単なカテゴリ分け

これはPython Advent Calendar 2014の16日目です。

実践 機械学習システムを読んでいて画像のパターン認識が面白そうだったので実装して遊んでみました。主に10章のコンピュータビジョンと3章のクラスタリングを参考にしました。ほとんど、NumPyscikit-learnMahotasに丸投げです。

ざっくりとした流れ

今回は用意した複数のjpg画像を教師無しでカテゴライズしてみました。

  1. まず、全ての画像を局所特徴量リストにしました。局所特徴量にはSURFを使用しました。SURFはMahotasのmahotas.features.surf.surfで簡単に取得できます。
  2. そして、すべてのSURFをk-means法でグループ化して基本特徴量(visual word)を求めます。k-means法の計算はsklearn.cluster.KMeans.fitを呼ぶだけでできます。
  3. これを使って画像の局所特徴量リストをbag-of-wordsリスト(実践 機械学習システムではこうなっていたけどbag-of-keypoints,bag-of-visual-words,bag-of-visual-featuresのほうが一般的なのかも)にします。
    それから、(実践 機械学習システムではやってませんが)bag-of-wordsと言えばTF-IDFかなと思ったので今回はbag-of-wordsリストをTF-IDFリストに変換してみました。TF-IDFはsklearn.feature_extraction.text.TfidfTransformerで算出できます。
  4. 最後に、TF-IDFリストをk-means法でカテゴリ分けしておしまいです。

図にするとこんな感じです。

f:id:nihma:20141214221038j:plain

実装

流れの通りに実装しました。
カテゴリ分けしたいjpgと同じディレクトリで実行するとカテゴリ分けの結果が標準出力されます。変数のpicture_category_numを書き換えるとカテゴリの数が変わります。

import mahotas as mh
import numpy as np
from glob import glob
from mahotas.features import surf
from sklearn.cluster import KMeans
from sklearn.feature_extraction.text import TfidfTransformer

picture_category_num = 9
feature_category_num = 512

# image surf
images = glob('./*.jpg')
alldescriptors = []
for im in images:
  im = mh.imread(im, as_grey=True)
  im = im.astype(np.uint8)
  alldescriptors.append(surf.surf(im, descriptor_only=True))

# image surf -> basic feature
concatenated = np.concatenate(alldescriptors)
km = KMeans(feature_category_num)
km.fit(concatenated)

# image surf and basic feature -> features
features = []
for d in alldescriptors:
  c = km.predict(d)
  features.append(np.array([np.sum(c == ci) for ci in range(feature_category_num)]))
features = np.array(features)

# features -> tfidf
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(features)
tfidf.toarray() 
# not use tfidf
# tfidf = features

# categorization
km = KMeans(n_clusters=picture_category_num, init='random', n_init=1, verbose=1)
km.fit(tfidf)

# print result
images = np.array(images)
print('images')
print(images)
for i in range(picture_category_num):
  print('image category{0}'.format(i))
  print(images[km.labels_ == i])

とりあえず動かしてみました

写真素材 足成【フリーフォト、無料写真素材サイト】から適当に選んだフリー画像をカテゴリ分けしてみました。動物、花、乗り物からそれぞれ3分類ずつ、1分類から10画像ずつの計90画像使い9カテゴリに分けてみました。基本特徴量の数は512です。
今回やってみたTF-IDFの処理は余計だったかもしれないので、TF-IDFなしの場合も実行してみました。

使用した画像の内訳は次の通りです。

大分類 小分類 枚数
動物 10枚
動物 10枚
動物 10枚
あじさい 10枚
10枚
ひまわり 10枚
乗り物 飛行機 10枚
乗り物 バイク 10枚
乗り物 10枚

TF-IDFありの結果

ぱっと見だと検討はずれではないように思いますが何とも言えないです。。

カテゴリ0

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215062009j:plain

乗り物 バイク 3

f:id:nihma:20141215062104j:plain

乗り物 バイク 4

f:id:nihma:20141215062435j:plain

乗り物 バイク 8

f:id:nihma:20141215062532j:plain

乗り物 バイク 9

カテゴリ1

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215003314j:plain

動物 6

f:id:nihma:20141215003413j:plain

動物 7

f:id:nihma:20141215003512j:plain

動物 8

f:id:nihma:20141215013124j:plain

あじさい 0

f:id:nihma:20141215013447j:plain

あじさい 4

f:id:nihma:20141215013637j:plain

あじさい 6

f:id:nihma:20141215013817j:plain

あじさい 8

f:id:nihma:20141215014358j:plain

2

f:id:nihma:20141215014446j:plain

3

f:id:nihma:20141215014530j:plain

4

f:id:nihma:20141215014725j:plain

6

f:id:nihma:20141215014943j:plain

9

f:id:nihma:20141215055210j:plain

ひまわり 0

f:id:nihma:20141215055719j:plain

ひまわり 4

f:id:nihma:20141215055829j:plain

ひまわり 5

カテゴリ2

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215060742j:plain

乗り物 飛行機 3

f:id:nihma:20141215061216j:plain

乗り物 飛行機 5

f:id:nihma:20141215061311j:plain

乗り物 飛行機 6

f:id:nihma:20141215061536j:plain

乗り物 飛行機 9

f:id:nihma:20141215062651j:plain

乗り物 0

f:id:nihma:20141215063218j:plain

乗り物 6

f:id:nihma:20141215063312j:plain

乗り物 7

f:id:nihma:20141215063420j:plain

乗り物 8

カテゴリ3

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215002718j:plain

動物 0

f:id:nihma:20141215002911j:plain

動物 2

f:id:nihma:20141215003616j:plain

動物 9

f:id:nihma:20141215055315j:plain

ひまわり 1

f:id:nihma:20141215060523j:plain

乗り物 飛行機 0

f:id:nihma:20141215060623j:plain

乗り物 飛行機 1

f:id:nihma:20141215061000j:plain

乗り物 飛行機 2

f:id:nihma:20141215061122j:plain

乗り物 飛行機 4

カテゴリ4

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215003058j:plain

動物 4

f:id:nihma:20141215003857j:plain

動物 0

f:id:nihma:20141215004029j:plain

動物 1

f:id:nihma:20141215004132j:plain

動物 2

f:id:nihma:20141215004235j:plain

動物 3

f:id:nihma:20141215004329j:plain

動物 4

f:id:nihma:20141215004436j:plain

動物 5

f:id:nihma:20141215004529j:plain

動物 6

f:id:nihma:20141215004624j:plain

動物 7

f:id:nihma:20141215004706j:plain

動物 8

カテゴリ5

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215001119j:plain

動物 0

f:id:nihma:20141215055436j:plain

ひまわり 2

f:id:nihma:20141215055544j:plain

ひまわり 3

f:id:nihma:20141215061721j:plain

乗り物 バイク 0

f:id:nihma:20141215061806j:plain

乗り物 バイク 1

f:id:nihma:20141215061903j:plain

乗り物 バイク 2

f:id:nihma:20141215062156j:plain

乗り物 バイク 5

f:id:nihma:20141215062248j:plain

乗り物 バイク 6

f:id:nihma:20141215062336j:plain

乗り物 バイク 7

f:id:nihma:20141215062741j:plain

乗り物 1

f:id:nihma:20141215062839j:plain

乗り物 2

f:id:nihma:20141215062935j:plain

乗り物 3

f:id:nihma:20141215063044j:plain

乗り物 4

f:id:nihma:20141215063131j:plain

乗り物 5

f:id:nihma:20141215063506j:plain

乗り物 9

カテゴリ6

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215001449j:plain

動物 1

f:id:nihma:20141215001623j:plain

動物 2

f:id:nihma:20141215001734j:plain

動物 3

f:id:nihma:20141215001844j:plain

動物 4

f:id:nihma:20141215002339j:plain

動物 8

f:id:nihma:20141215002537j:plain

動物 9

f:id:nihma:20141215002818j:plain

動物 1

f:id:nihma:20141215003210j:plain

動物 5

f:id:nihma:20141215013227j:plain

あじさい 1

f:id:nihma:20141215013400j:plain

あじさい 3

f:id:nihma:20141215013931j:plain

あじさい 9

f:id:nihma:20141215014904j:plain

8

f:id:nihma:20141215060055j:plain

ひまわり 7

f:id:nihma:20141215060308j:plain

ひまわり 9

f:id:nihma:20141215061453j:plain

乗り物 飛行機 8

カテゴリ7

画像 大分類 小分類 n

f:id:nihma:20141215061409j:plain

乗り物 飛行機 7

カテゴリ8

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215002028j:plain

動物 5

f:id:nihma:20141215002128j:plain

動物 6

f:id:nihma:20141215002243j:plain

動物 7

f:id:nihma:20141215003004j:plain

動物 3

f:id:nihma:20141215004803j:plain

動物 9

f:id:nihma:20141215013318j:plain

あじさい 2

f:id:nihma:20141215013535j:plain

あじさい 5

f:id:nihma:20141215013727j:plain

あじさい 7

f:id:nihma:20141215014220j:plain

0

f:id:nihma:20141215014313j:plain

1

f:id:nihma:20141215014639j:plain

5

f:id:nihma:20141215014818j:plain

7

f:id:nihma:20141215055938j:plain

ひまわり 6

f:id:nihma:20141215060157j:plain

ひまわり 8

TF-IDFなしの結果

TF-IDFありの場合とどちらが良いのかぱっと見では何とも言えないです。。

カテゴリ0

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215002911j:plain

動物 2

f:id:nihma:20141215003616j:plain

動物 9

f:id:nihma:20141215055210j:plain

ひまわり 0

f:id:nihma:20141215055315j:plain

ひまわり 1

f:id:nihma:20141215055436j:plain

ひまわり 2

f:id:nihma:20141215055544j:plain

ひまわり 3

f:id:nihma:20141215060055j:plain

ひまわり 7

f:id:nihma:20141215060308j:plain

ひまわり 9

f:id:nihma:20141215060523j:plain

乗り物 飛行機 0

f:id:nihma:20141215060623j:plain

乗り物 飛行機 1

f:id:nihma:20141215061000j:plain

乗り物 飛行機 2

f:id:nihma:20141215060742j:plain

乗り物 飛行機 3

f:id:nihma:20141215061122j:plain

乗り物 飛行機 4

f:id:nihma:20141215061216j:plain

乗り物 飛行機 5

f:id:nihma:20141215061311j:plain

乗り物 飛行機 6

f:id:nihma:20141215061409j:plain

乗り物 飛行機 7

f:id:nihma:20141215061453j:plain

乗り物 飛行機 8

f:id:nihma:20141215061536j:plain

乗り物 飛行機 9

f:id:nihma:20141215062104j:plain

乗り物 バイク 4

f:id:nihma:20141215062839j:plain

乗り物 2

f:id:nihma:20141215063312j:plain

乗り物 7

f:id:nihma:20141215063420j:plain

乗り物 8

f:id:nihma:20141215063506j:plain

乗り物 9

カテゴリ1

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215061721j:plain

乗り物 バイク 0

f:id:nihma:20141215061806j:plain

乗り物 バイク 1

f:id:nihma:20141215062156j:plain

乗り物 バイク 5

f:id:nihma:20141215062248j:plain

乗り物 バイク 6

f:id:nihma:20141215062336j:plain

乗り物 バイク 7

f:id:nihma:20141215062435j:plain

乗り物 バイク 8

f:id:nihma:20141215062651j:plain

乗り物 0

f:id:nihma:20141215062935j:plain

乗り物 3

f:id:nihma:20141215063044j:plain

乗り物 4

f:id:nihma:20141215063131j:plain

乗り物 5

カテゴリ2

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215001119j:plain

動物 0

f:id:nihma:20141215001449j:plain

動物 1

f:id:nihma:20141215001734j:plain

動物 3

f:id:nihma:20141215001844j:plain

動物 4

f:id:nihma:20141215002028j:plain

動物 5

f:id:nihma:20141215002339j:plain

動物 8

f:id:nihma:20141215002818j:plain

動物 1

f:id:nihma:20141215003413j:plain

動物 7

f:id:nihma:20141215003512j:plain

動物 8

f:id:nihma:20141215013124j:plain

あじさい 0

f:id:nihma:20141215013227j:plain

あじさい 1

f:id:nihma:20141215013400j:plain

あじさい 3

f:id:nihma:20141215013447j:plain

あじさい 4

f:id:nihma:20141215013637j:plain

あじさい 6

f:id:nihma:20141215013931j:plain

あじさい 9

f:id:nihma:20141215014358j:plain

2

f:id:nihma:20141215014639j:plain

5

f:id:nihma:20141215014725j:plain

6

f:id:nihma:20141215014943j:plain

9

f:id:nihma:20141215055829j:plain

ひまわり 5

カテゴリ3

画像 大分類 小分類 n

f:id:nihma:20141215063218j:plain

乗り物 6

カテゴリ4

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215002718j:plain

動物 0

f:id:nihma:20141215061903j:plain

乗り物 バイク 2

f:id:nihma:20141215062741j:plain

乗り物 1

カテゴリ5

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215002128j:plain

動物 6

f:id:nihma:20141215002537j:plain

動物 9

f:id:nihma:20141215003004j:plain

動物 3

f:id:nihma:20141215003210j:plain

動物 5

f:id:nihma:20141215003314j:plain

動物 6

f:id:nihma:20141215013318j:plain

あじさい 2

f:id:nihma:20141215013535j:plain

あじさい 5

f:id:nihma:20141215013727j:plain

あじさい 7

f:id:nihma:20141215013817j:plain

あじさい 8

f:id:nihma:20141215014904j:plain

8

f:id:nihma:20141215055719j:plain

ひまわり 4

f:id:nihma:20141215055938j:plain

ひまわり 6

f:id:nihma:20141215062009j:plain

乗り物 バイク 3

f:id:nihma:20141215062532j:plain

乗り物 バイク 9

カテゴリ6

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215001623j:plain

動物 2

f:id:nihma:20141215002243j:plain

動物 7

f:id:nihma:20141215003058j:plain

動物 4

f:id:nihma:20141215003857j:plain

動物 0

f:id:nihma:20141215004029j:plain

動物 1

f:id:nihma:20141215004132j:plain

動物 2

f:id:nihma:20141215004329j:plain

動物 4

f:id:nihma:20141215014220j:plain

0

f:id:nihma:20141215014313j:plain

1

f:id:nihma:20141215014818j:plain

7

f:id:nihma:20141215060157j:plain

ひまわり 8

カテゴリ7

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215004235j:plain

動物 3

f:id:nihma:20141215004436j:plain

動物 5

f:id:nihma:20141215004529j:plain

動物 6

f:id:nihma:20141215004624j:plain

動物 7

f:id:nihma:20141215004706j:plain

動物 8

f:id:nihma:20141215004803j:plain

動物 9

カテゴリ8

画像 大分類 小分類 n 画像 大分類 小分類 n

f:id:nihma:20141215014446j:plain

3

f:id:nihma:20141215014530j:plain

4

評価:TF-IDFあり vs TF-IDFなし

実際のところTF-IDFの変換はした方が良かったのか気になったので、TF-IDFありの場合とTF-IDFなしの場合のどちらが良いのかを評価してみました。クラスタの良さの評価に使った尺度はエントロピーと純度です。エントロピーは低いほど良く、純度は高いほど良いようです。

ここからはPythonではなくRです。エントロピーと純度の算出はR言語プログラミング: クラスター分析 - 階層的クラスタリング - hamadakoichi blogの下記関数を使わせていただきました。

#関数:エントロピー(Entropy)算出, ct:クロス集計表
calcEntropy <- function(ct){
    -sum( (apply(ct,1,sum)/sum(ct))
       * apply(ct,1,calcEntropy0))/log(ncol(ct))
}
calcEntropy0<-function(pv){
    p1<-pv/sum(pv)
    p2<-p1[p1 !=0]
    sum(p2*log(p2))
}

#関数:純度 (Purity)算出
calcPurity <-function(ct){
    sum(apply(ct,1,max))/sum(ct)
}

TF-IDFありの算出

各画像ごとのカテゴリは下記の通りです。

id 大分類 小分類 n カテゴリ id 大分類 小分類 n カテゴリ
0 動物 0 5 1 動物 1 6
2 動物 2 6 3 動物 3 6
4 動物 4 6 5 動物 5 8
6 動物 6 8 7 動物 7 8
8 動物 8 6 9 動物 9 6
10 動物 0 3 11 動物 1 6
12 動物 2 3 13 動物 3 8
14 動物 4 4 15 動物 5 6
16 動物 6 1 17 動物 7 1
18 動物 8 1 19 動物 9 3
20 動物 0 4 21 動物 1 4
22 動物 2 4 23 動物 3 4
24 動物 4 4 25 動物 5 4
26 動物 6 4 27 動物 7 4
28 動物 8 4 29 動物 9 8
30 あじさい 0 1 31 あじさい 1 6
32 あじさい 2 8 33 あじさい 3 6
34 あじさい 4 1 35 あじさい 5 8
36 あじさい 6 1 37 あじさい 7 8
38 あじさい 8 1 39 あじさい 9 6
40 0 8 41 1 8
42 2 1 43 3 1
44 4 1 45 5 8
46 6 1 47 7 8
48 8 6 49 9 1
50 ひまわり 0 1 51 ひまわり 1 3
52 ひまわり 2 5 53 ひまわり 3 5
54 ひまわり 4 1 55 ひまわり 5 1
56 ひまわり 6 8 57 ひまわり 7 6
58 ひまわり 8 8 59 ひまわり 9 6
60 乗り物 飛行機 0 3 61 乗り物 飛行機 1 3
62 乗り物 飛行機 2 3 63 乗り物 飛行機 3 2
64 乗り物 飛行機 4 3 65 乗り物 飛行機 5 2
66 乗り物 飛行機 6 2 67 乗り物 飛行機 7 7
68 乗り物 飛行機 8 6 69 乗り物 飛行機 9 2
70 乗り物 バイク 0 5 71 乗り物 バイク 1 5
72 乗り物 バイク 2 5 73 乗り物 バイク 3 0
74 乗り物 バイク 4 0 75 乗り物 バイク 5 5
76 乗り物 バイク 6 5 77 乗り物 バイク 7 5
78 乗り物 バイク 8 0 79 乗り物 バイク 9 0
80 乗り物 0 2 81 乗り物 1 5
82 乗り物 2 5 83 乗り物 3 5
84 乗り物 4 5 85 乗り物 5 5
86 乗り物 6 2 87 乗り物 7 2
88 乗り物 8 2 89 乗り物 9 5

Rスクリプトの内容は下記の通りです。

answer1 <- c(
"動物","動物","動物","動物","動物","動物","動物","動物","動物","動物",
"動物","動物","動物","動物","動物","動物","動物","動物","動物","動物",
"動物","動物","動物","動物","動物","動物","動物","動物","動物","動物",
"花","花","花","花","花","花","花","花","花","花",
"花","花","花","花","花","花","花","花","花","花",
"花","花","花","花","花","花","花","花","花","花",
"乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物",
"乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物",
"乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物","乗り物"
)

answer2 <- c(
"猫","猫","猫","猫","猫","猫","猫","猫","猫","猫",
"犬","犬","犬","犬","犬","犬","犬","犬","犬","犬",
"猿","猿","猿","猿","猿","猿","猿","猿","猿","猿",
"あじさい","あじさい","あじさい","あじさい","あじさい","あじさい","あじさい","あじさい","あじさい","あじさい",
"桜","桜","桜","桜","桜","桜","桜","桜","桜","桜",
"ひまわり","ひまわり","ひまわり","ひまわり","ひまわり","ひまわり","ひまわり","ひまわり","ひまわり","ひまわり",
"飛行機","飛行機","飛行機","飛行機","飛行機","飛行機","飛行機","飛行機","飛行機","飛行機",
"バイク","バイク","バイク","バイク","バイク","バイク","バイク","バイク","バイク","バイク",
"車","車","車","車","車","車","車","車","車","車"
)

result1 <- c(
5,6,6,6,6,8,8,8,6,6,
3,6,3,8,4,6,1,1,1,3,
4,4,4,4,4,4,4,4,4,8,
1,6,8,6,1,8,1,8,1,6,
8,8,1,1,1,8,1,8,6,1,
1,3,5,5,1,1,8,6,8,6,
3,3,3,2,3,2,2,7,6,2,
5,5,5,0,0,5,5,5,0,0,
2,5,5,5,5,5,2,2,2,5
)

ctbl <- table(answer1, result1)
ctbl

calcEntropy(ctbl) #エントロピー(Entropy)の算出
calcPurity(ctbl)  #純度 (Purity)の算出

ctbl <- table(answer2, result1)
ctbl

calcEntropy(ctbl) #エントロピー(Entropy)の算出
calcPurity(ctbl)  #純度 (Purity)の算出

実行するとこうなります。

> ctbl <- table(answer1, result1)
> ctbl
        result1
answer1   0  1  2  3  4  5  6  7  8
  花      0 12  0  1  0  2  6  0  9
  乗り物  4  0  8  4  0 12  1  1  0
  動物    0  3  0  3 10  1  8  0  5
> calcEntropy(ctbl) #エントロピー(Entropy)の算出
[1] 0.670199
> calcPurity(ctbl)  #純度 (Purity)の算出
[1] 0.3777778

> ctbl
          result1
answer2    0 1 2 3 4 5 6 7 8
  あじさい 0 4 0 0 0 0 3 0 3
  バイク   4 0 0 0 0 6 0 0 0
  ひまわり 0 3 0 1 0 2 2 0 2
  猿       0 0 0 0 9 0 0 0 1
  犬       0 3 0 3 1 0 2 0 1
  桜       0 5 0 0 0 0 1 0 4
  車       0 0 4 0 0 6 0 0 0
  猫       0 0 0 0 0 1 6 0 3
  飛行機   0 0 4 4 0 0 1 1 0
> 
> calcEntropy(ctbl) #エントロピー(Entropy)の算出
[1] 0.4478757
> calcPurity(ctbl)  #純度 (Purity)の算出
[1] 0.5111111
> 

TF-IDFなしの算出

各画像ごとのカテゴリは下記の通りです。

id 大分類 小分類 n カテゴリ id 大分類 小分類 n カテゴリ
0 動物 0 2 1 動物 1 2
2 動物 2 6 3 動物 3 2
4 動物 4 2 5 動物 5 2
6 動物 6 5 7 動物 7 6
8 動物 8 2 9 動物 9 5
10 動物 0 4 11 動物 1 2
12 動物 2 0 13 動物 3 5
14 動物 4 6 15 動物 5 5
16 動物 6 5 17 動物 7 2
18 動物 8 2 19 動物 9 0
20 動物 0 6 21 動物 1 6
22 動物 2 6 23 動物 3 7
24 動物 4 6 25 動物 5 7
26 動物 6 7 27 動物 7 7
28 動物 8 7 29 動物 9 7
30 あじさい 0 2 31 あじさい 1 2
32 あじさい 2 5 33 あじさい 3 2
34 あじさい 4 2 35 あじさい 5 5
36 あじさい 6 2 37 あじさい 7 5
38 あじさい 8 5 39 あじさい 9 2
40 0 6 41 1 6
42 2 2 43 3 8
44 4 8 45 5 2
46 6 2 47 7 6
48 8 5 49 9 2
50 ひまわり 0 0 51 ひまわり 1 0
52 ひまわり 2 0 53 ひまわり 3 0
54 ひまわり 4 5 55 ひまわり 5 2
56 ひまわり 6 5 57 ひまわり 7 0
58 ひまわり 8 6 59 ひまわり 9 0
60 乗り物 飛行機 0 0 61 乗り物 飛行機 1 0
62 乗り物 飛行機 2 0 63 乗り物 飛行機 3 0
64 乗り物 飛行機 4 0 65 乗り物 飛行機 5 0
66 乗り物 飛行機 6 0 67 乗り物 飛行機 7 0
68 乗り物 飛行機 8 0 69 乗り物 飛行機 9 0
70 乗り物 バイク 0 1 71 乗り物 バイク 1 1
72 乗り物 バイク 2 4 73 乗り物 バイク 3 5
74 乗り物 バイク 4 0 75 乗り物 バイク 5 1
76 乗り物 バイク 6 1 77 乗り物 バイク 7 1
78 乗り物 バイク 8 1 79 乗り物 バイク 9 5
80 乗り物 0 1 81 乗り物 1 4
82 乗り物 2 0 83 乗り物 3 1
84 乗り物 4 1 85 乗り物 5 1
86 乗り物 6 3 87 乗り物 7 0
88 乗り物 8 0 89 乗り物 9 0

Rスクリプトの内容は下記の通りです。

result2 <- c(
2,2,6,2,2,2,5,6,2,5,
4,2,0,5,6,5,5,2,2,0,
6,6,6,7,6,7,7,7,7,7,
2,2,5,2,2,5,2,5,5,2,
6,6,2,8,8,2,2,6,5,2,
0,0,0,0,5,2,5,0,6,0,
0,0,0,0,0,0,0,0,0,0,
1,1,4,5,0,1,1,1,1,5,
1,4,0,1,1,1,3,0,0,0
)

ctbl <- table(answer1, result2)
ctbl

calcEntropy(ctbl) #エントロピー(Entropy)の算出
calcPurity(ctbl)  #純度 (Purity)の算出

ctbl <- table(answer2, result2)
ctbl

calcEntropy(ctbl) #エントロピー(Entropy)の算出
calcPurity(ctbl)  #純度 (Purity)の算出

実行するとこうなります。

> ctbl <- table(answer1, result2)
> ctbl
        result2
answer1   0  1  2  3  4  5  6  7  8
  花      6  0 11  0  0  7  4  0  2
  乗り物 15 10  0  1  2  2  0  0  0
  動物    2  0  9  0  1  5  7  6  0
> 
> calcEntropy(ctbl) #エントロピー(Entropy)の算出
[1] 0.649445
> calcPurity(ctbl)  #純度 (Purity)の算出
[1] 0.3888889
> 
> ctbl <- table(answer2, result2)
> ctbl
          result2
answer2     0  1  2  3  4  5  6  7  8
  あじさい  0  0  6  0  0  4  0  0  0
  バイク    1  6  0  0  1  2  0  0  0
  ひまわり  6  0  1  0  0  2  1  0  0
  猿        0  0  0  0  0  0  4  6  0
  犬        2  0  3  0  1  3  1  0  0
  桜        0  0  4  0  0  1  3  0  2
  車        4  4  0  1  1  0  0  0  0
  猫        0  0  6  0  0  2  2  0  0
  飛行機   10  0  0  0  0  0  0  0  0
> 
> calcEntropy(ctbl) #エントロピー(Entropy)の算出
[1] 0.4274223
> calcPurity(ctbl)  #純度 (Purity)の算出
[1] 0.5666667
> 

評価結果

結果はこうなりました。今回の実施条件と評価尺度だとTF-IDFは使わない方が良いらしいです。

TF-IDF 分類 エントロピー 純度
あり 大分類 0.670199 0.3777778
あり 小分類 0.4478757 0.5111111
なし 大分類 0.649445 0.3888889
なし 小分類 0.4274223 0.5666667

最後に思った事など

  • 何も考えず特徴量をSURFにしましたが、もうちょっと工夫すると精度は上がるかもしれないです。あと、もうすこしちゃんとした評価もやりたかったですが進捗ダメでしたのであきらめました。人員追加、期日延期、機能削減に迫られたらまず機能削減を考えたいタイプです。
  • 実践 機械学習システムは読みやすくて使えそうな内容ですので機械学習のとっかかりにおすすめの本だと思います。
  • 画像のアップと表の作成がしんどかったです。

FRPを調べててelmに入門した話

これはElm Advent Calendar 2014の11日目です。

よく知らなかったFRP(Functional Reactive Programming,リアクティブプログラミング)について調べてて少しだけElmに入門しました。

※先週やった実装なのでElmのバージョンは0.13です。いろいろ変わったらしい0.14では動かない気がします。。

FRPとはストリームの写像

エクセルの例をよく見かけますが、FRPとは要するに入力および出力をストリーム(時間とともに変化していく状態)として、その間を写像(マッピング)していくプログラミング手法だとざっくり理解しました。

エクセル操作を例にストリームを写像するイメージの落書きをしてみました。

f:id:nihma:20141211010606j:plain

A3セルの定義がA1セルとA2セルの和になっていて、A1セル/A2セルを変更するとA3セルが定義に従って更新されていきます。

ここでは下記あたりを参考にしました。

【翻訳】あなたが求めていたリアクティブプログラミング入門

なぜリアクティブプログラミングは重要か。

The Reactive Manifesto 日本語訳

■ やってみる

良くわからないので適当な課題をでっちあげて実装してみました。FRPといえばHaskellライクなaltJSのElmらしい。というわけでElmを使ってみました。

● 課題

入力と出力が下記のようなGUIページを作ります。

入出力 要素 内容
入力a input/text 入力値
入力b input/text 入力値
入力c input/text 入力値
入力d input/text 入力値
入力e select 0〜3
出力a div ((入力ab))
出力b div [[入力dc]]
出力c div 入力eが0なら入力ababdcdc,入力eが1なら入力abdcabdc,入力eが2なら入力dcabdcab,入力eが3なら入力dcdcabab

● 設計

状態を依存グラフにしてみました。

f:id:nihma:20141211233945j:plain

● 実装

依存グラフを素直にコードに落としました。

-- elm test.elm
import Graphics.Input.Field as Field
import Graphics.Input as Input
import String
import Text
import Window

-- Signals and Inputs
in_a = Input.input Field.noContent
in_b = Input.input Field.noContent
in_c = Input.input Field.noContent
in_d = Input.input Field.noContent

in_e : Input.Input (Int)
in_e = Input.input 0

in_f_options : [(String, Int)]
in_f_options = [ ("0. ababcdcd", 0) , ("1. abcdabcd", 1)
               , ("2. cdabcdab", 2) , ("3. cdcdabab", 3)]

-- functions

main : Signal Element
main = printABCD
         <~ Window.dimensions
          ~ (selectABCD
               <~ (combineABCD
                     <~ (wrapAB <~ (addAB <~ in_a.signal
                                           ~ in_b.signal))
                      ~ (wrapCD <~ (addCD <~ in_c.signal
                                           ~ in_d.signal)))
                ~ in_e.signal)

printABCD : (Int, Int)
         -> (String, (String, String, String, String),
             (String, String, Field.Content, Field.Content),
             (String, String, Field.Content, Field.Content))
         -> Element
printABCD _ (select,
             (ababcdcd, abcdabcd, cdabcdab, cdcdabab),
             (w_ab, ab, in_a', in_b'),
             (w_cd, cd, in_c', in_d')) = 
    flow down 
     [ plainText "input a:", Field.field Field.defaultStyle in_a.handle identity "" in_a'
     , plainText "input b:", Field.field Field.defaultStyle in_b.handle identity "" in_b'
     , plainText "cache a: input a ++ input b", plainText ab
     , plainText "output a: (( ++ cache a ++ ))", plainText w_ab
     , plainText "input c:", Field.field Field.defaultStyle in_c.handle identity "" in_c'
     , plainText "input d:", Field.field Field.defaultStyle in_d.handle identity "" in_d'
     , plainText "cache b: input d ++ input c", plainText cd
     , plainText "output b: [[ ++ cache b ++ ]]:", plainText w_cd
     , plainText "input e:", Input.dropDown in_e.handle in_f_options
     , plainText "cache c(0):", plainText ababcdcd
     , plainText "cache c(1):", plainText abcdabcd
     , plainText "cache c(2):", plainText cdabcdab
     , plainText "cache c(3):", plainText cdcdabab
     , plainText "output c: cache c(input e)", plainText select
    ]

selectABCD : ((String, String, String, String),
              (String, String, Field.Content, Field.Content),
              (String, String, Field.Content, Field.Content))
           -> Int
           -> (String, (String, String, String, String),
               (String, String, Field.Content, Field.Content),
               (String, String, Field.Content, Field.Content))
selectABCD ((ababcdcd, abcdabcd, cdabcdab, cdcdabab), ab, cd) n =
            let s = case n of 0 -> ababcdcd
                              1 -> abcdabcd
                              2 -> cdabcdab
                              _ -> cdcdabab
            in (s, (ababcdcd, abcdabcd, cdabcdab, cdcdabab), ab, cd)

combineABCD : (String, String, Field.Content, Field.Content)
           -> (String, String, Field.Content, Field.Content) 
           -> ((String, String, String, String),
               (String, String, Field.Content, Field.Content),
               (String, String, Field.Content, Field.Content))
combineABCD (w_ab, ab, a, b) (w_cd, cd, c, d) =
       ((ab++ab++cd++cd, ab++cd++ab++cd, cd++ab++cd++ab, cd++cd++ab++ab),
        (w_ab, ab, a, b), (w_cd, cd, c, d))

addAB : Field.Content -> Field.Content
     -> (String, Field.Content, Field.Content)
addAB a b = (a.string ++ b.string, a, b)

wrapAB : (String, Field.Content, Field.Content)
      -> (String, String, Field.Content, Field.Content) 
wrapAB (ab, a, b) = ("((" ++ ab ++ "))", ab, a, b)

addCD : Field.Content -> Field.Content
     -> (String, Field.Content, Field.Content)
addCD c d = (d.string ++ c.string, c, d)

wrapCD : (String, Field.Content, Field.Content)
      -> (String, String, Field.Content, Field.Content) 
wrapCD (cd, c, d) = ("[[" ++ cd ++ "]]", cd, c, d)

動いたっぽいです。

f:id:nihma:20141212001803p:plain

ここら辺を参考にしました。

What is a Signal?

Using Signals

■ 感想

FRP/Elmともにいまいち理解が正しいのか分かりません。もう少し調査が必要な気がします。

ScalaでFizzBuzzやってみた

(4年半ほど仕事にやられてましたが何とか元気です。)

先日ひさしぶりにFizzBuzzを書く機会があったので再学習中のScalaでもやってみました。

■ 無限リストによる実装

まず、1から始まるFizzBuzzの無限リストを100まで評価する方法で実装してみました。

// 1から100までのFizzBuzz
// scalac FizzBuzz.scala
// scala FizzBuzz

object FizzBuzz extends App {
  // 数値をFizzBuzzに変換する関数
  def toFizzBuzz(n: Int): String = n match {
    case _ if n % (3*5) == 0 => "FizzBuzz"
    case _ if n %     3 == 0 => "Fizz"
    case _ if n %     5 == 0 => "Buzz"
    case _                   => n.toString
  }

  // 整数の無限リスト(n, n+1, n+2, …)の各要素に対してfを適用する関数
  //  -> Stream((n,f(n)), (n+1,f(n+1)), (n+2,f(n+2)), ?)となる
  def mapSeqNums[T](f: Int => T, n: Int): Stream[(Int, T)] = { 
    (n, f(n)) #:: mapSeqNums(f, n+1)
  }

  // (1) 1から100までのFizzBuzzを作成
  val fizzbuzz = mapSeqNums(toFizzBuzz, 1) take 100 

  // 表示
  fizzbuzz.print
}

無限リストの実現にはストリームを利用してみました。

ストリーム (Stream) はリストに似ているが、要素は遅延評価される。そのため、ストリームは無限の長さをもつことができる。呼び出された要素のみが計算される。他の点においては、ストリームはリストと同じ性能特性をもつ。 http://docs.scala-lang.org/ja/overviews/collections/concrete-immutable-collection-classes.html

● 実行結果

実行するとこうなります。

$ scala FizzBuzz
(1,1), (2,2), (3,Fizz), (4,4), (5,Buzz), (6,Fizz), (7,7), (8,8), (9,Fizz), (10,Buzz), (11,11), (12,Fizz), (13,13), (14,14), (15,FizzBuzz), (16,16), (17,17), (18,Fizz), (19,19), (20,Buzz), (21,Fizz), (22,22), (23,23), (24,Fizz), (25,Buzz), (26,26), (27,Fizz), (28,28), (29,29), (30,FizzBuzz), (31,31), (32,32), (33,Fizz), (34,34), (35,Buzz), (36,Fizz), (37,37), (38,38), (39,Fizz), (40,Buzz), (41,41), (42,Fizz), (43,43), (44,44), (45,FizzBuzz), (46,46), (47,47), (48,Fizz), (49,49), (50,Buzz), (51,Fizz), (52,52), (53,53), (54,Fizz), (55,Buzz), (56,56), (57,Fizz), (58,58), (59,59), (60,FizzBuzz), (61,61), (62,62), (63,Fizz), (64,64), (65,Buzz), (66,Fizz), (67,67), (68,68), (69,Fizz), (70,Buzz), (71,71), (72,Fizz), (73,73), (74,74), (75,FizzBuzz), (76,76), (77,77), (78,Fizz), (79,79), (80,Buzz), (81,Fizz), (82,82), (83,83), (84,Fizz), (85,Buzz), (86,86), (87,Fizz), (88,88), (89,89), (90,FizzBuzz), (91,91), (92,92), (93,Fizz), (94,94), (95,Buzz), (96,Fizz), (97,97), (98,98), (99,Fizz), (100,Buzz), empty

● 無限ループにならない理由

コメント(1)の処理で無限ループに陥らないのはtake関数が必要とする所までしかmapSeqNums関数が評価されない為です。 式の評価順序は以下のイメージです。

f:id:nihma:20141129215526j:plain

■ 素直な実装

遅延評価による無限リストでの実装との比較のために正格評価による素直な実装もしてみました。

● Rangeによる実装

Rangeによる素直な実装はこうなりました。 最もポピュラーなやり方です。

val fizzbuzz = (1 to 100).map(n => (n, toFizzBuzz(n)))

● Listによる実装

Listによる実装にするとこうなりました。

val fizzbuzz = List.range(1, 100).map(n => (n, toFizzBuzz(n)))

● ListBufferによる実装

ListBufferでするとこうなりました。手続き的な書き方で手動による最適化です。

def range2(s: Int, e: Int): scala.collection.mutable.ListBuffer[Int] = { 
  val list = scala.collection.mutable.ListBuffer[Int]();
  var i = s 
  while (i <= e) {
    list += i
    i += 1
  }   
  list
}
val fizzbuzz = range2(1, 100).map(n => (n, toFizzBuzz(n)))

● ListBufferによる実装(fizzbuzzしながら)

ここまでの素直な実装では次の2回ループが走ってしまう問題があります。

  1. 1〜100のリストを作成する
  2. 各数値をFizzBuzzに変換する(map)

1のついでに2をすればループを1回に減らす事ができるのでやってみました。

def rangeWith[T](s: Int, e: Int, f: Int => T): scala.collection.mutable.ListBuffer[T] = {
  val list = scala.collection.mutable.ListBuffer[T]();
  var i = s
  while (i <= e) {
    list += f(i)
    i += 1
  }
  list
}
val fizzbuzz = rangeWith[(Int, String)](1, 100, n => (n, toFizzBuzz(n)))

■ 性能評価

結局どれが速いのか手元の環境で実測してみました。

機器 プロセッサ メモリ OS scala
MacBook Air 1.7 GHz Intel Core i7 8 GB 1600 MHz DDR3 OS X 10.9.4(13E28) 2.10.4

● 測定方法

1から1000000までのfizzbuzzを求めるようにして、処理を必ず評価させるため最後にListへ変換するコードを入れました。

val fizzbuzz = ...
val result = fizzbuzz.toList // force
// fizzbuzz.print

そしてtimeコマンドて実測し5回の平均を出しました。

$ time scala FizzBuzz

● 結果

無限リストによる実装

項目 1回目 2回目 3回目 4回目 5回目 平均
real(s) 3.689 3.290 3.298 3.598 3.641 3.5032
user(s) 12.088 10.745 11.136 12.122 12.504 11.719
sys(s) 0.288 0.290 0.259 0.274 0.243 0.2708

Rangeによる実装

項目 1回目 2回目 3回目 4回目 5回目 平均
real(s) 1.459 1.543 1.440 1.488 1.399 1.4658
user(s) 4.399 4.645 4.282 4.446 4.163 4.387
sys(s) 0.164 0.175 0.168 0.172 0.162 0.1682

Listによる実装

項目 1回目 2回目 3回目 4回目 5回目 平均
real(s) 2.854 2.844 2.929 3.029 2.915 2.9142
user(s) 9.459 9.455 9.780 10.188 9.638 9.704
sys(s) 0.228 0.227 0.226 0.235 0.232 0.2296

ListBufferによる実装

項目 1回目 2回目 3回目 4回目 5回目 平均
real(s) 2.358 2.360 2.313 2.962 2.362 2.471
user(s) 7.387 7.780 7.447 9.938 7.765 8.0634
sys(s) 0.221 0.199 0.206 0.216 0.201 0.2086

ListBufferによる実装(fizzbuzzしながら)

項目 1回目 2回目 3回目 4回目 5回目 平均
real(s) 1.956 1.958 2.027 1.973 1.980 1.9788
user(s) 6.261 6.164 6.493 6.312 6.291 6.3042
sys(s) 0.171 0.170 0.173 0.167 0.166 0.1694

■ 所感

  • 色々な要因(サンクコスト、CPUアーキテクチャコンパイラ最適化状況、toListの処理コスト、データ量etc)を考慮する必要はあると思いますが、今回の測定では無限リストによる実装は想定したほど性能が出ず、Rangeによる実装の速度が一番という結果になりました。とりあえずRangeでやっとけば問題ないような気がします。(関数を適用しながらRangeできたらさらに速くなるのかな。)
  • より低レイヤーに踏み込んだ調査も、とも考えましたがFizzBuzzですし疲れたのでしませんでした。
  • mapSeqNumsという名前はなんか違う気がします。命名センスを磨きたいです。

アプレンティスシップ・パターン

本書は「徒弟制度」をキーワードとして物事に習熟するための方法が記載されている。確かに「無知の受け入れ」と「適切なフィードバック」の繰り返しは大事だと思う。後は終わりが無いことを楽しむこと。
問題は「適切にフィードバックしてくれる環境と人」をどうすれば見つけることができるのか?運と度胸…。自分にはどちらも足りない。

Perl6はまだ様子見する。

そろそろかなとrakudo(#29)を触ってみた感想。もう少し待とう・・・。とりあえず触った事を記録。

試したのは加算器関数。まず、イメージで実装してみる。

sub make_adder($n) { {$n += $^i} }
my $adder30 = make_adder(30);
$adder30.(1).say;       # 31になる
$adder30.(5).say;       # 36になる
$adder30.(11).say;      # 47になる

ブロックがどうのというエラーで動かない。make_adder関数の戻り値ブロックの表記がだめらしい。しかたがないので表記を変えてみる。

続きを読む

PerlでDSL

Rubyによるデザイン・パターン』を読み終えて内部DSLが分かった気になった。調子に乗ってPackRat*1Perlに移植してみた。少しPerl風にアレンジ。

まず、内部DSLから。

# 内部DSL(backup.pr)

backup {
  from      => '/home/hoge/old1',
  to        => '/tmp/backup',
  file_regx => '^.+\.(txt|doc)$',
  interval  => 60,
};

backup {
  from      => '/home/hoge/old2',
  to        => '/tmp/backup',
  file_regx => '^.+\.(mp3|wav)$',
  interval  => 30,
};

バックアップの定義を行っている。もちろん文法はPerlに従う。そして、内部DSLを実行する部分は次のとおり。

*1:16章のファイルバックアップDSL

続きを読む