機械学習 cont

前回の枕で、塀の中での読書通になった人を取り上げた。 その中に、検閲の話が出てたんで、ちょいと調べてみた。

日本における検閲

なるほど、お上に都合が悪いものは、流通させないとな。そして、現実問題 刑務所の差し入れ「本・雑誌」について(1)・・・検閲で不許可になる本は? なんてのが詳しい。

このページの本タイトルが、犯罪予備軍のための刑務所マニュアルってなってるぞ。

そうか、老後に困ったら、塀の中にご厄介になればいいんだな。完全看護(例の人は、ここで 介護士の懲役をしてたそうな)、3食付、勿論医療も充実(かな)。

まてまて、そんな堕落した事を考えたらいかんぜよ。 女房が、『老後の資金がありません』なんて言う身につまされるような題名の本を借りてきた。

庶民の味方ファイナンス・アドバイザー荻原博子さんが推薦されてた。この分野は銀行もこぞって 参入してるけど、やつらの甘言に乘ると、身ぐるみ剝がされるぞ。自分の儲けしか考えて いないから、手数料がガッポガッポ入ってくる危ない運用しか勧めてこない。全ては、お客様の 自己責任ですってね。ああ、この本は、小説なんで、読書のキューに取り合えず登録しとく。 きっと、知恵が満載されてる事でしょう。

今、読書キューのヘッドにあるやつは、『松陰の本棚』って本。吉田松陰って幕末の思想家ぐらいの 認識しかなかったけど、彼は大変な読書家だったそうな。

幕府が治める鎖国の国じゃだめだ。外から強い国が攻めてきて、植民地にされちゃうぞ。 それを防ぐには、敵を知り、己をしれば、戦いに勝てるって寸法で、まずは敵を知ろう。 そのためには、敵地を視察するに限る。丁度今来てる黒船が居るんで、ちょっくら乗せて もらって、外地を見て来るわ。

で、実行が失敗。鎖国の時代にあっては、どんな申し開きをしようとも、密航ですな。 死罪は免れたものも、獄に繋がれる。あーーー、毎日暇だなあ。この機会に本でも読んでみるか。やはりそうなるよな。志ある人はポジティブに考えるんですな。塀の中に2年半入った人も 同じ心境か。

幕末は出版業界も充実していなかった。そこで、友人らから本を借りて読む。友人の友人にも 本の貸し出しを懇願いする。あらゆる機会を捉えて、本を集め読書に耽る。どんな本を読んだ っていう、読書記録が残っている。それによると、4年半で、1500冊近くに達したとか。 コンスタントに、1冊/日のペースですな。

本棚本では、この読書記録から、彼の思想の変遷を辿っている。こういう分析は今まで無かった そうな。で、本のネットワークを通じて、危ない禁制本とかも入ってくる。こうして、同じ志を 持つ人が集まり、世を改革しようって話が自然に生まれ、革命みたいな計画が進行。

問題意識が、世を変えていくんですなあ。老後資金がどうのとか、言ってる場合じゃないよ。

機械学習の資料

stanford U

By python

synaptic for Javascript

JavaScriptでニューラルネットワーク : synaptic.jsですぐに作れる手書き数字分類器

synaptic じゃないけど、有名な画像認識のデモが、こちらに有った。 ConvNetJS MNIST demo

例題は自己分析

前回、デブ/普通の人って例をなぞったんで、今度はリアルな例題をやってみるか。 それとも、先へ進んで ソムリエもどきのワインをやるか。

ワインもいいけど、オイラーなら日本酒だな。口当たりのよい酒、すっきりした酒、辛口と 色々な表現があるけど、日本酒の辛さを10段階で表示したのが有ったはず。それはどんな 成分、製法、価格等で決まるか。そんなデータ無いかな。

色々探したけど無かった。オイラーが仙台にいる頃、好んで飲んでいたのは、一ノ蔵 無鑑査二級酒。税務署の監査を受けて、特級とか一級に判定されると、酒税が上がる ため、販売価格を上げざるを得ない。それじゃ、酒飲みの皆さんに申し訳ない。

監査を受けなければ、二級酒とみなされ、酒税が安い。二級酒ってちょっとカッコ悪い けど、気にせずにたっぷり呑んでください。旨さは蔵の名誉にかけて保障しますよと。

仙台では浦霞が有名だったな。贈答品として使われてたようだ。オイラーの口には合わなかった けどね。山形の酒で、樽平ってのがあったな。杉樽の香りが強烈なやつ。病みつきだったよ。

いかんいかん、のん兵衛は酒の話となると、長くなるのが欠点。軌道修正。

身近にあるデータとして、今まで何度も登場した、血圧データがある。これを使って、 機械学習向けの設題を無理して捻り出した。

16121321,100,59,63
16121405,119,72,57
16121421,126,73,65
16121504,142,72,55

もう、何年も朝晩、血圧と脈拍を測っている。第1項は、年月日時。 第2項以降の、最高血圧、最低血圧、脈拍を与えた時、そのデータが、朝のデータか 夜のデータか、機械学習で判別出来るだろうか?

質問に隔たりがなければ、確率50%の丁半博打になるはず。国会ではIR法案が可決したんで、 堂々とこういう博打を取り上げる事が出来る。カジノ法で一番喜ぶのは、地下経済を 牛耳る、やーさん組織、次に喜ぶは、さつのみなさん。利権が出来て、天下り先が増えた。 国家は、枯れる事が無い財布が出来たと更に喜ぶ構図。 ああ、また逸れた。

ちなみに、これらのデータは、最近2ケ月分をまとめると

c:\mine\blood>newlisp ana.lsp
at Wakeup
Min:   116.0  68.0  51.0
Mean:  129.5  74.8  56.1
Max:   144.0  81.0  64.0
Sdev:    6.8   3.1   2.4
at Night
Min:    96.0  54.0  54.0
Mean:  118.7  66.3  62.1
Max:   142.0  81.0  70.0
Sdev:   10.9   5.5   3.1

こんな様子です。過去にRを使って分析したら、最高血圧と最低血圧は正の相関が、血圧と 脈拍は負の相関が確認された。朝と夜との相関は無しという結論でしたよ。

コードを書く

ああ、コードを一から書くのは脳に負担が掛かるので、変更ですかね。実は、Javascriptなんて初めてなのさ。初めてなので、 JavaScriptの配列の使い方まとめなんてのを参照しつつ。。。

まずは、CSVな生データをjsonに変換する所から。

const WINE_FILE = "bld.csv";
const JSON_FILE = "bld.json";
const fs = require('fs');

// ファイルを読み込む
let csv = readCSV(WINE_FILE);
// JSONに変換
const data = [];
for (var i = 0; i < csv.length; i++) {
  let row = csv[i];
  let ampm = row.shift();
  let a = {
    "input": row.slice(0),
    "output": (ampm % 100) < 12 ? [1, 0] : [0, 1]
  };
  console.log(a);
  data.push(a);
}
fs.writeFileSync(JSON_FILE, JSON.stringify(data), "utf8");
console.log("ok");

// CSVファイルを読み込む関数
function readCSV(filename, delimiter) {
  if (delimiter == undefined) delimiter = ',';
  text = fs.readFileSync(filename, "utf8"); // CSVファイルを読む
  const lines = text.split("\n"); // 一行ずつに区切る
  const result = [];
  for (var i = 0; i < lines.length; i++) {
    let cells = lines[i].split(delimiter); // 区切り記号で区切る
    if (cells.length <= 1) continue;
    cells = cells.map(parseFloat); // 実数変換
    result.push(cells);
  }
  return result;
}

readCSVって関数は、引数を2個取るんだけど、第二引数は省略してもいいように細工が 施されている。へーな機能だな。

変数名にampmなんて何処かのコンビニの名前みたいのを導入した割には、WINE_FILEなんて 名前のが残っていて、コピペばればれの証拠が満載であります。

debug

ここで、ちょっとnodeのdebugについて調べておく。

sakae@debian:~/js/bld$ node debug c2j.js
< Debugger listening on [::]:5858
connecting to 127.0.0.1:5858 ... ok
break in c2j.js:2
  1 // ワインデータを読み込み正規化を行う
> 2 const WINE_FILE = "bld.csv";
  3 const JSON_FILE = "bld.json";
  4 const fs = require('fs');
n
:
break in c2j.js:18
 16   };
 17   console.log(a);
>18   data.push(a);
 19 }
 20 fs.writeFileSync(JSON_FILE, JSON.stringify(data), "utf8");
a
ReferenceError: a is not defined
    at repl:1:1
    at ContextifyScript.Script.runInContext (vm.js:35:29)
    at Object.exports.runInContext (vm.js:67:17)
    at Interface.controlEval (_debugger.js:957:21)
    at REPLServer.eval (_debugger.js:741:41)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:513:10)
    at emitOne (events.js:96:13)
    at REPLServer.emit (events.js:188:7)

aの値を調べようとしたら、怒られた。

debug> repl
Press Ctrl + C to leave debug repl
> a
{ input: [ 132, 85, 55 ], output: [ 1, 0 ] }
debug> n
break in c2j.js:10
  8 // JSONに変換
  9 const data = [];
>10 for (var i = 0; i < csv.length; i++) {
 11   let row = csv[i];
 12   let ap = row.shift();

そういう時は、ちょっと面倒だけど、replに移行してから 変数を参照すれば良い。使えるコマンドは helpで出て来る。

debug> help
Commands: run (r), cont (c), next (n), step (s), out (o), backtrace (bt), setBreakpoint (sb), clearBreakpoint (cb),
watch, unwatch, watchers, repl, exec, restart, kill, list, scripts, breakOnException, breakpoints, version

いざ勝負

次は、出来上がったjsonを読み込んで、学習し、その結果に基づいて、テストデータを 判定するやる。最近は、ひよこの雄雌を機械判定出来るようになってるそうですが、 これも機械学習の成果なのだろうか?

// synaptic.jsの取り込み
const synaptic = require('synaptic');
const Layer = synaptic.Layer;
const Network = synaptic.Network;
const Trainer = synaptic.Trainer;
// 各レイヤーを生成
const inputLayer = new Layer(3);
const hiddenLayer = new Layer(16);
const outputLayer = new Layer(2);
// レイヤーを接続しニューラルネットワークを構築
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
const bld_network = new Network({
  input: inputLayer,
  hidden: [hiddenLayer],
  output: outputLayer
});

// 訓練データを読み込む
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('bld.json'));
// 読み込んだデータを正規化する必要がある
let setInput = function(high, low, pls) {
  return [high / 200, low / 100, pls / 100]
};
for (var i in data) {
  let v = data[i].input;
  data[i].input = setInput(v[0], v[1], v[2]);
}

// データを学習する
const trainer = new Trainer(bld_network);
trainer.train(data, {
  rate: 0.2, iterations: 30, error: 0.1,
  shuffle: true, log: 1,
  cost: Trainer.cost.CROSS_ENTROPY
});

// データをテストする
t(16121305,127,76,56);
t(16121321,100,59,63);
t(16121405,119,72,57);
t(16121421,126,73,65);
t(16121504,142,72,55);

// データをテストする関数
function t(ymdh, high, low, pls) {
  const labels = ["am", "pm"];
  let n = bld_network.activate(setInput(high, low, pls));
  let result = labels[argmax(n)];
  let exp = ymdh % 100 < 12 ? "AM" : "PM";

  console.log(exp, result, n[0], n[1]);
}

// 配列nのうち最も大きな要素番号を返す
function argmax(n) {
  let v = Number.MIN_VALUE;
  let index = -1;
  for (let i = 0; i < n.length; i++) {
    if (v < n[i]) { index = i; v = n[i]; }
  }
  return index;
}

入力のパラメータは3つ。出力は2つ。隠れ層の深さは、googleのアルファ碁に倣って16層と してみた。

jsonデータを読み込んで、正規化する部分は大いなる手抜き。例えば、最高血圧は200なんて 事は無い(超える事は無いだろう)ってんで、200で割って、0以上、1以下の浮動小数点値に してる。

リミッターをかけているんだな。かけかたが手抜きなんで、ダイナミックレンジが無い、 平凡な音(データ)になってます。学習器の性能を十分に引き出すなら、歪まない範囲で ダイナミックレンジを上げる事。

最大値と最小値を求めて、それを1と0に割り当てる。そうすると、通常のデータは、その 範囲内に収まるって寸法。ちょとした改造でパンチの利いたデータになるんで、やってみれ。

学習に使うデータは、2011年7月から2015年末までの約3200件のデータ。朝夜のデータは 均等になってます。

テストに使うデータは、10日程前の数データをコード中に埋め込んでいる。

sakae@debian:~/js/bld$ node lean-data.js
iterations 1 error 2.916334714097006 rate 0.2
iterations 2 error 2.0881002299901796 rate 0.2
iterations 3 error 2.0982367894639773 rate 0.2
iterations 4 error 2.075626457367536 rate 0.2
iterations 5 error 2.099357524481298 rate 0.2
iterations 6 error 2.113179596225633 rate 0.2
iterations 7 error 2.105953509215758 rate 0.2
iterations 8 error 2.1073087712988303 rate 0.2
iterations 9 error 2.1308958253442514 rate 0.2
iterations 10 error 2.083218090872566 rate 0.2
iterations 11 error 2.0665323405691396 rate 0.2
iterations 12 error 2.1185763581430925 rate 0.2
iterations 13 error 2.1177052695419447 rate 0.2
iterations 14 error 1.9447075962548086 rate 0.2
iterations 15 error 1.0253030639073129 rate 0.2
iterations 16 error 0.8620400741554276 rate 0.2
iterations 17 error 0.7824729043219322 rate 0.2
iterations 18 error 0.7628157916122781 rate 0.2
iterations 19 error 0.7349283404836765 rate 0.2
iterations 20 error 0.6970962109321288 rate 0.2
iterations 21 error 0.6618741727013134 rate 0.2
iterations 22 error 0.6617732617975541 rate 0.2
iterations 23 error 0.666289702891811 rate 0.2
iterations 24 error 0.6631206407160805 rate 0.2
iterations 25 error 0.6708956580475358 rate 0.2
iterations 26 error 0.6546981143469509 rate 0.2
iterations 27 error 0.6636908221325317 rate 0.2
iterations 28 error 0.6484544282956706 rate 0.2
iterations 29 error 0.630733159251033 rate 0.2
iterations 30 error 0.6413011331770679 rate 0.2
AM am 0.9864663525121565 0.013533609223568256
PM pm 0.009066347503499007 0.9909332613628772
AM am 0.9815589685457883 0.018440743711093728
PM pm 0.04040820902548932 0.9595925416408291
AM am 0.9844673689860162 0.015532616525119402

結果の見方は、一番左が期待値、小文字の結果は予想した結果、次の数値は起床時と予想 した自信度、最後は就寝と予想した自信度。

5問出題して、全問とも自信を持って、正解を導き出しているよ。素晴らしい結果。 ここに至るまで、30回の試行錯誤してるんだけど、それぞれのエラー率が報告されてる。 やっぱり、機械と言えども、スランプが有って、なかなかエラー率が下がらない 事が有るのね。人間くさいな。

sakae@debian:~/js/bld$ node lean-data.js
iterations 1 error 2.9143970034130255 rate 0.2
iterations 2 error 2.12675854590919 rate 0.2
  :
iterations 30 error 2.066902877179571 rate 0.2
AM pm 0.09214630002203515 0.907843365360481
PM pm 0.0919035758971922 0.9081015438622518
AM pm 0.09209906877748748 0.9078936502450425
PM pm 0.09212682994903254 0.9078638313781917
AM pm 0.09215078794664561 0.9078386272954831

何回か試行すると、スランプを抜けきらず、予想も不貞腐れて、考えを拒否しましたって 答えてくる事がある。

与えている訓練データは同一なんで、同一挙動を示すかと思ったら違っていて、今まで やってきた、関数型プログラミングのパラダイムが崩れてしまい、大ショックです。

更に上の結果をもっと一般的になるようにして実験する。(コードは最後に掲載) 訓練データのうちの70%を実際に訓練に使い、残り30%をテストデータに充てるという ように例題が出てたのでやってみる。なお、データは試行事にシャッフルして、同じ データにならないように考慮してある。下記はその結果。

sakae@debian:~/js/bld$ node test.js
正解率= 0.9099378881987578 879 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.4979296066252588 481 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.9306418219461697 899 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.8933747412008282 863 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.484472049689441 468 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.9057971014492754 875 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.4886128364389234 472 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.855072463768116 826 / 966
sakae@debian:~/js/bld$ node test.js
正解率= 0.9202898550724637 889 / 966

不貞腐れるというか、ツキに見放される事があるな。まあ、鉄火場ですから、そういう事も あらーな。でも、勝つ時は、良い成績を出しているな。一体、どこに目を付けてるんだ。 眼のつけどころが違うと自慢してたあの会社も、超がつくスランプがあって、買われて しまったから、世は無常。

今年の更新は、これで終了です。来年も宜しくお願い致します。

// synaptic.jsの取り込み
const synaptic = require('synaptic');
const Layer = synaptic.Layer;
const Network = synaptic.Network;
const Trainer = synaptic.Trainer;
// 各レイヤーを生成
const inputLayer = new Layer(3);
const hiddenLayer = new Layer(16);
const outputLayer = new Layer(2);
// レイヤーを接続しニューラルネットワークを構築
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
const bld_network = new Network({
  input: inputLayer,
  hidden: [hiddenLayer],
  output: outputLayer
});

// 訓練データを読み込む
const fs = require('fs');
let data = JSON.parse(fs.readFileSync('bld.json'));
data = shuffle(data);// データをシャッフルする
// データを0-1の範囲に正規化する
let setInput = function(high, low, pls) {
  return [high / 200, low / 100, pls / 100]
};
for (var i in data) {
  let v = data[i].input;
  data[i].input = setInput(v[0], v[1], v[2]);
}

// 訓練データとテストデータを分ける
const test_n = Math.floor(data.length * 0.7);
const train_a = data.slice(0, test_n);
const test_a = data.slice(test_n);

// データを学習する
const trainer = new Trainer(bld_network);
trainer.train(train_a, {
  rate: 0.2, iterations: 50, error: 0.1,
  cost: Trainer.cost.CROSS_ENTROPY
});

// データのテスト
let ok = 0, count = 0;
for (var i = 0; i < test_a.length; i++) {
  in_a = test_a[i].input;
  out_v = argmax(test_a[i].output);
  predict = argmax(bld_network.activate(in_a));
  if (out_v == predict) ok++;
  count++;
}
console.log("正解率=", ok / count, ok, "/", count);

// 配列nのうち最も大きな要素番号を返す
function argmax(n) {
  let v = Number.MIN_VALUE;
  let index = -1;
  for (let i = 0; i < n.length; i++) {
    if (v < n[i]) { index = i; v = n[i]; }
  }
  return index;
}

// データをシャッフルする
function shuffle(array) {
  var n = array.length, t, i;
  while (n) {
    i = Math.floor(Math.random() * n--);
    t = array[n];
    array[n] = array[i];
    array[i] = t;
  }
  return array;
}