カッパノイド Movie

カッパノイド(http://kappanoid.com)のムービーができましたぁ。

カッパノイドは、人間が気兼ねなくコミュニケーションできるロボットです。インターフェースとして人間のそばに居てくれる、そんな役割をしてもらいたいなぁと思っています。

好きな食べ物をあげると喜ぶ。

アタマをたたくと起きます!

カルピスウォーター効果

カルピスウォーターが発売されたとき、僕は『自分で作ったカルピスのほうが絶対おいしい』と思いました。これ絶対そう思う。

なぜか?きっと、混ざりすぎて、濃さが均一だとおいしくないんじゃないか?濃さにムラがあったほうがおいしいんじゃない?!

これを仮に『カルピスウォーター効果』と名づけますと、他にも以下のようなものが考えられます。

  • コンビニで売ってるカクテルより、自分で適当に混ぜたほうがおいしい。
  • 牛丼に卵をかけたとき、混ぜすぎるとおいしくない。
  • カレーライス混ぜすぎるとおいしくない。(?)

似たような話で、『楽器の音を周波数分解して、再び合成すると味気ない音しか出てこない』というのを聞いたことがあります。

そう思ったことある人いませんか?あるいは、似たような話とかありませんか?

Perlでニューラルネットワーク

今回も大学のレポートネタ。ニューラルネットワークを用いたパターン認識Perlに移植してみた。バックプロパゲーション法って、意外とシンプルなやり方なんだなぁ。

動かし方

Perlのソースを保存して起動すると、学習を始めます。しばらくするとプロンプトが出てくるので、


□□■■■□□
□■□□□■□
■□□□□□■
■□□□□□■
■□□□□□■
■□□□□□■
■□□□□□■
■□□□□□■
■□□□□□■
□■□□□■□
□□■■■□□
の様に、横7×縦11の■□でできた数字のパターンをコピー&ペースト等で入力してあげると、0〜9のどの数字かを判定してくれます。

ソースコード

use strict;
use warnings;
use utf8;
use Data::Dumper;
$Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 1;

binmode STDOUT, ":encoding(shiftjis)";
binmode STDIN,  ":encoding(shiftjis)";

my $WIDTH = 7;                  # 入力データの幅
my $HEIGHT = 11;                # 入力データの高さ
my $INPUT = $WIDTH * $HEIGHT;   # 入力層の数(入力データ数)
my $HIDDEN = 16;                # 隠れ層の数
my $PATTERN = 10;               # パターンの種類
my $OUTPUT = $PATTERN;          # 出力層の数(出力データ数)
my $OUTER_CYCLES = 200;         # 外部サイクル(一連のパターンの繰返し学習)の回数
my $INNER_CYCLES = 200;         # 内部サイクル(同一パターンの繰返し学習)の回数
my $ALPHA = 1.2;                # 学習の加速係数
my $BETA = 1.2;                 # シグモイド曲線の傾斜

my @sample_in = 1 .. $INPUT;    # 学習用入力
my @written_in = 1 .. $INPUT;   # 認識用手書き入力

my @weight_ih;                  # 入力層と隠れ層の間の重み係数 INPUTxHIDDEN
my @thresh_h = 1 .. $HIDDEN;    # 隠れ層の閾値
my @hidden_out = 1 .. $HIDDEN;  # 隠れ層の出力

my @weight_ho;                  # 隠れ層と出力層の間の重み係数 HIDDENxOUTPUT
my @thresh_o = 1 .. $OUTPUT;    # 出力層の閾値
my @recog_out = 1 .. $OUTPUT;   # 認識出力(出力層の出力)

my @teach = 1 .. $PATTERN;      # 教師信号

# 学習用入力データの基となるパターン
my @sample_array = (
                        [0,0,1,1,1,0,0,  # '0'
                         0,1,0,0,0,1,0,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         0,1,0,0,0,1,0,
                         0,0,1,1,1,0,0],
  
                        [0,0,0,1,0,0,0,  # '1'
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0],
 
                        [0,0,1,1,1,0,0,  # '2'
                         0,1,0,0,0,1,0,
                         1,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,1,0,
                         0,0,0,0,1,0,0,
                         0,0,0,1,0,0,0,
                         0,0,1,0,0,0,0,
                         0,1,0,0,0,0,0,
                         1,1,1,1,1,1,1],
  
                        [0,0,1,1,1,0,0,  # '3'
                         0,1,0,0,0,1,0,
                         1,0,0,0,0,0,1,
                         0,0,0,0,0,1,0,
                         0,0,0,0,1,0,0,
                         0,0,0,0,0,1,0,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         0,1,0,0,0,1,0,
                         0,0,1,1,1,0,0],
 
                        [0,0,0,0,1,0,0,  # '4'
                         0,0,0,1,1,0,0,
                         0,0,1,0,1,0,0,
                         0,0,1,0,1,0,0,
                         0,1,0,0,1,0,0,
                         0,1,0,0,1,0,0,
                         1,0,0,0,1,0,0,
                         1,1,1,1,1,1,1,
                         0,0,0,0,1,0,0,
                         0,0,0,0,1,0,0,
                         0,0,0,0,1,0,0],
 
                        [1,1,1,1,1,1,1,  # '5'
                         1,0,0,0,0,0,0,
                         1,0,0,0,0,0,0,
                         1,0,0,0,0,0,0,
                         1,1,1,1,1,0,0,
                         0,0,0,0,0,1,0,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         1,0,0,0,0,1,0,
                         0,1,1,1,1,1,0],
 
                        [0,0,0,0,1,1,0,  # '6'
                         0,0,0,1,0,0,0,
                         0,0,1,0,0,0,0,
                         0,1,0,0,0,0,0,
                         0,1,0,0,0,0,0,
                         1,0,0,0,0,0,0,
                         1,0,1,1,1,0,0,
                         1,1,0,0,0,1,0,
                         1,0,0,0,0,0,1,
                         0,1,0,0,0,1,0,
                         0,0,1,1,1,0,0],
 
                        [1,1,1,1,1,1,1,  # '7'
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,1,0,
                         0,0,0,0,0,1,0,
                         0,0,0,0,1,0,0,
                         0,0,0,0,1,0,0,
                         0,0,0,1,0,0,0,
                         0,0,0,1,0,0,0,
                         0,0,1,0,0,0,0,
                         0,0,1,0,0,0,0],
 
                        [0,0,1,1,1,0,0,  # '8'
                         0,1,0,0,0,1,0,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         0,1,0,0,0,1,0,
                         0,0,1,1,1,0,0,
                         0,1,0,0,0,1,0,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         0,1,1,1,1,1,0],
                 
                        [0,1,1,1,1,1,0,  # '9'
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         0,1,1,1,1,1,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         0,0,0,0,0,0,1,
                         1,0,0,0,0,0,1,
                         0,1,1,1,1,1,0]
                   );

my @teach_array;                # パターンと出力すべき教師信号の比較表 PATTERNxOUTPUT

# 教師信号の設定
for (my $q=0; $q<$PATTERN; $q++) {
    for(my $k=0; $k<$OUTPUT; $k++){
        if($q==$k)  { $teach_array[$q][$k] = 1; }
        else        { $teach_array[$q][$k] = 0; }
    }
}

#-------------------------------------------------------------------
#--------------------------- 学習モード ----------------------------
#-------------------------------------------------------------------

# 閾値と重みの乱数設定
for (my $j=0; $j<$HIDDEN; $j++) {
    $thresh_h[$j] = rand() - 0.5;
    for(my $i=0; $i<$INPUT; $i++) {
        $weight_ih[$i][$j] = rand() - 0.5;
    }
}
for (my $k=0; $k<$OUTPUT; $k++){
    $thresh_o[$k] = rand() - 0.5;
    for (my $j=0; $j<$HIDDEN; $j++) {
        $weight_ho[$j][$k] = rand() - 0.5;
    }
}

#-------------------------- 学習 --------------------------

print "learning...\n";
for (my $p=0; $p<$OUTER_CYCLES; $p++) { # 外部サイクル

    my $outer_error = 0.0;                 # 外部二乗誤差のクリヤー

    for (my $q=0; $q<$PATTERN; $q++) {  # パターンの切り替え

        # パターンに対応した入力と教師信号の設定
        @sample_in = @{$sample_array[$q]};
        @teach = @{$teach_array[$q]};

        for (my $r=0; $r<$INNER_CYCLES; $r++){  # 内部サイクル

            # 順方向演算
            &forwardNeuralNet(\@sample_in, \@recog_out);       

            # 逆方向演算(バックプロパゲーション)
            &backwardNeuralNet();

        }

        # 内部二乗誤差の計算
        my $inner_error = 0.0;   # 内部二乗誤差のクリヤー
        for (my $k=0; $k<$OUTPUT; $k++) {
            $inner_error += ($teach[$k] - $recog_out[$k]) * ($teach[$k] - $recog_out[$k]);
        }

        $outer_error += $inner_error;       # 外部二乗誤差への累加算

    }

    # 外部サイクルの回数と外部二乗誤差の表示
    print "OuterCycles=$p ";
    print "TotalSquaredError=$outer_error\n";
} 

#--------------------- 学習結果の確認 ---------------------

print "              ";
for (my $k=0; $k<$OUTPUT; $k++) {
    print "   [$k]";
}
print "\n";

for (my $q=0; $q<$PATTERN; $q++) {

    # 入力パターンの設定
    @sample_in = @{$sample_array[$q]};

    # 順方向演算
    &forwardNeuralNet(\@sample_in, \@recog_out);

    # 結果の表示
    print "TestPattern[$q]";
    for (my $k=0; $k<$OUTPUT; $k++) {
        if ($recog_out[$k]>0.99) {      # 99% より大
            print "   YES";
        }
        elsif ($recog_out[$k]<0.01) {   # 1% より小
            print "   NO ";
        }
        else{                           # 1% 以上 99% 以下
            print "   ?  ";
        }
    }
    print "\n";
}

# 解析待ち
my %read = (
    "■" => "1",
    "□" => "0",
);
@written_in = ();

my $count = 1;
print "bp[".sprintf("%02d", $count)."]>";

while (my $line = <STDIN>) {
    chomp($line);
    last if $line eq ""; 

    if ($line =~ /[0-9]{1}/) { # 0-9 の数字一つ

        @written_in = @{$sample_array[$line]};

    } elsif ($line =~ /[□■]{7}/) { # □■でデータ入力

        my @tmp = split(//, $line);
        while (my $x = shift(@tmp)) {
            my $data = $read{$x};
            push (@written_in, $data);
        }
        $count++;

    } else {

        print "error! もう一度入力\n"
    
    }

    if (scalar(@written_in) == $INPUT) {
        print "\n";
        &recognizeCharacter();
        $count = 1;
        @written_in = ();
    }

    print "bp[".sprintf("%02d", $count)."]>";

}

# 順方向演算のメソッド
sub forwardNeuralNet {

    my ($input, $output) = @_;
    my @out = 1 .. $OUTPUT;
    my @hidden = 1 .. $HIDDEN;

    # 隠れ層出力の計算
    for (my $j = 0; $j<$HIDDEN; $j++){
        $hidden[$j] = -$thresh_h[$j];
        for (my $i=0; $i<$INPUT; $i++) {
            $hidden[$j] += ${$input}[$i] * $weight_ih[$i][$j];
        }
        $hidden_out[$j] = &sigmoid($hidden[$j]);
    }

    # 出力層出力の計算
    for (my $k=0; $k<$OUTPUT; $k++){
        $out[$k] = -$thresh_o[$k];
        for (my $j=0; $j<$HIDDEN; $j++) {
            $out[$k] += $hidden_out[$j] * $weight_ho[$j][$k];
        }
        $${output}[$k] = &sigmoid($out[$k]);
    }
}

# 逆方向演算のメソッド
sub backwardNeuralNet {

    my @output_error = 1 .. $OUTPUT;        # 出力層の誤差
    my @hidden_error = 1 .. $HIDDEN;        # 隠れ層の誤差

    my $temp_error;

    # 出力層の誤差の計算
    for (my $k=0; $k<$OUTPUT; $k++) {
        $output_error[$k] = ($teach[$k] - $recog_out[$k]) * $recog_out[$k] * (1.0 - $recog_out[$k]);
    }

    # 隠れ層の誤差の計算
    for (my $j=0; $j<$HIDDEN; $j++) {
        $temp_error = 0.0;
        for (my $k=0; $k<$OUTPUT; $k++) {
            $temp_error += $output_error[$k] * $weight_ho[$j][$k];
        }
        $hidden_error[$j] = $hidden_out[$j] * (1.0 - $hidden_out[$j]) * $temp_error;
    }

    # 重みの補正
    for (my $k=0; $k<$OUTPUT; $k++) {
        for (my $j=0; $j<$HIDDEN; $j++) {
            $weight_ho[$j][$k] += $ALPHA * $output_error[$k] * $hidden_out[$j];
        }
    }
    for (my $j=0; $j<$HIDDEN; $j++) {
        for (my $i=0; $i<$INPUT; $i++) {
            $weight_ih[$i][$j] += $ALPHA * $hidden_error[$j] * $sample_in[$i];
        }
    }

    # 閾値の補正
    for (my $k=0; $k<$OUTPUT; $k++) {
        $thresh_o[$k] -= $ALPHA * $output_error[$k];
    }
    for (my $j=0; $j<$HIDDEN; $j++) {
        $thresh_h[$j] -= $ALPHA * $hidden_error[$j];
    }
}

# Sigmoid関数を計算するメソッド
sub sigmoid {
    my $x = shift;
    return 1.0/(1.0 + exp(-$BETA * $x));
}

# 入力文字を認識するメソッド
sub recognizeCharacter {

    # 順方向演算
    &forwardNeuralNet(\@written_in, \@recog_out);

    # 結果の表示
    for (my $k=0; $k<$OUTPUT; $k++) {
        print "$k である $recog_out[$k]\n";
    }

}

CAIWA広場

CAIWA広場 という会話プログラムで遊んでみた。今、人工無脳を作るという仕事をもらっているので、しつこく質問して遊んでみた。気づいた点はこんな感じ。

  1. 会話シナリオがいくつか用意されている。で、話題を指定される。「趣味とか、血液型について質問してね」とか。
  2. 特定のキーワードにマッチすると、それに対応したシナリオのモードに入る。
  3. シナリオのモードに入ると、質問が投げかけられる。その質問に対する回答中のキーワードによって、以下のパターンで反応する。
    • こちらの返事を無視して、言いたいこと話す独り言モードに入る。
    • キーワードを含むコメントを返す。
  4. ランダムな応答はあまりない。「つかれたので、ちょっと寝るね」というくらい。
  5. キーワードを学習したりもしていない。外部のデータで学習もしていないようだ。

デモ用だからかもしれないが、シナリオが少なくてすぐ飽きてしまった。自分の使った言葉を学習してくれると、「おっ」という返事が来ることもあるのだけれど。

あと、サイトを見た第一印象。『あ、なんか中華風デザインだぞ!!』で、会社概要を見たら、やっぱり社長が中国人だった。きっとデザインはオフショアしてるんでしょう。

『恋するプログラム―Rubyでつくる人工無脳』という本も読んだのですが、こちらの人工無脳の方が断然面白いなぁ。
恋するプログラム―Rubyでつくる人工無脳