あれこれ備忘録@はてなブログ

勉強したことやニュースや出来事を備忘録として書いていきます

(追記あり)ニューラルネットワーク(人工知能)の入門編をやってみたらいきなりつまづいた・・・

www.youtube.com

The Nature of Code

ニューラルネットワークの入門編があるというので、ちょっと挑戦してみることにしたのだが、実践編の最初の段階でつまづいた。

この動画の1つ前の入門編ではJavascriptを使うと言っていたのだが、この動画はJavaベースのスクリプト言語のProcessingで作っていた。

p5.js | home

Processing互換のJavascriptライブラリにp5.jsというものがあるのでそれを使って、見よう見まねで同じものを作ってみた。

しかし、結果はうまくいかなかった。

数式は動画の通りなので間違いないはずなのだが…。

テンプレートから書き足しただけだ。

index.htmlは

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0>
    <style> body {padding: 0; margin: 0;} </style>
    <script src="../p5.min.js"></script>
    <script src="../addons/p5.dom.min.js"></script>
    <script src="../addons/p5.sound.min.js"></script>
    <script src="perceptron.js"></script>
    <script src="point.js"></script>
    <script src="sketch.js"></script>
  </head>
<body>


perceptron.js

function sign(n) {
    if (n > 0) {
        return 1;
    }
    else {
        return -1;
    }
}

class Perceptron {
    constructor() {
        this.weights = new Array(2);
        for (let i = 0; i < this.weights.length; i++) {
            this.weights[i] = random(-1, 1);
        }

        this.lr = 0.00001;
    }

    guess(inputs) {
        let sum = 0.0;
        for (let i = 0; i < this.weights.length; i++) {
            sum += this.weights[i] * inputs[i];
        }

        let output = sign(sum);
        return output;
    }

    train(inputs, desire) {
        for(let i = 0; i < this.weights.length; i++) {
            let error = desire - this.guess(inputs[i]);
            this.weights[i] += this.lr * error * inputs[i];
        }
    }
}

point.js

class Point {
    constructor(width, height) {
        this.x = random(width);
        this.y = random(height);
        if ( this.x > this.y) {
            this.label = 1;
        }
        else {
            this.label = -1;
        }
    }

    show() {
        stroke(0);
        if (this.label == 1) {
            fill(255);
        }
        else {
            fill(0);
        }
        ellipse(this.x, this.y, 12, 12);
    }
}

sketch.js

var points = new Array(200);
var perceptron = new Perceptron();
var width;
var height;
var trainingIndex;

function setup() {
  width = 600;
  height = 600;
  trainingIndex = 0;
  createCanvas(width, height);

  points.forEach(function(pt) {
      pt = new Point(width, height);
  });
}

function draw() {
  background(255);
  stroke(0);
  line(0, 0, width, height);
  points.forEach(function(pt) {
      pt.show();
  });

  points.forEach(function(pt) {
      let inputs = [pt.x, pt.y];
      let guess = perceptron.guess(inputs);
      if(guess == pt.label) {
          fill(0, 255, 0);
      }
      else {
        fill(255, 0, 0);
      }
      ellipse(pt.x, pt.y, 8, 8);
  });

  let training = points[trainingIndex];
  let inputs = [training.x, training.y];
  perceptron.train(inputs, training.label);
  trainingIndex++;
  if(trainingIndex == points.length) {
      trainingIndex = 0;
  }
}

ところどころ、インデントがおかしくなっているが、こんな感じ。

中身はほぼ動画で紹介されている通り。

しかし、実行結果は以下のスクリーンショットの通り。

f:id:t_massann:20171109184305j:plain

本当はこれが全て緑色になるはず。

やっていることは、y = x の線の右側にある点は数値としては1、で白に塗り、左側にあるものは-1として黒に塗っている。

そのあと、その点の座標(x, y)をパーセプトロン(Perceptron)に読み込ませて、guess()で判定させ、合っていたら点の中を緑に、違っていたら赤に塗る。

描画のたびにPerceptronクラスのtrain()で学習させて、重み付けを変更してその都度、判定するという仕組みだ。

Javascriptも、もちろんp5.jsもほとんどやったことがないのでどこが正しいのかどこが間違っているか分からない。

一応、エラーは出ない。

最初の段階では、合ってたり間違っていたりして、赤と緑が入り交じる。

本当は赤になったり緑になったりしながら段々と正しいものが増えていき、最終的に全ての点が緑色になるはずだった。

これが結局、最初の黒と白の色分けと同じようにy = xの直線を境に赤と緑で分かれてしまうのだ。

何がおかしいのかちょっと分からない。

自分の問題解決力の無さにあきれてしまう。

本当は数学も学ばなければいけないのに、それ以前の状況だ。

Javaを思い出しながらProcessingでやったほうが良かっただろうか…。

ブラウザで手軽にできるし、Javascriptの勉強にもなるし良いかと思ったのだが。

うーむ、困った。

公開するのも恥ずかしいが、記録のために残しておく。


追記

Processing.org

Processingをダウンロードしてそれを使って作り直してみた。

結果はうまくいった。

どうやらJavascriptのクセが分かっていないためにどこかの宣言がうまくいっていないとか、設定した数値が引数として渡ったときうまく渡っていないということなのではないかと思う。

数式の入力間違いはなく、動画に対するこちらの理解も間違っていないようだ。

Javascriptのお作法を学びたい。

ちょっとうまくいかなかったが、p5.jsもProcessingもかなり簡単に記述し、実行することが簡単でちょっと感動した。


さらに追記

やっと原因が分かった。

結論から言うとただの書き間違いである…。

Perceptron.jsのtrain()の

let error = desire - this.guess(inputs[i]);

の部分、guess()には配列のinputsを渡さなければいけないのに、inputsの配列の要素を渡している。

しかも、この部分全体はループの外で計算するものだ…。

Javaなどならば引数の型チェックでエラーが出るがJavascriptは型チェックというか引数の型指定がないので何のエラーも出さなかったのである。

Javascriptはこれがあるからハマるんだ…


広告を非表示にする