2016年7月3日日曜日

はじめての人工知能入門の前段階のはなしと実装例(1)

 教科書のような回答をするならWikipediaを見たほうがいいと最初に言っておきます。
ここではもっと分かりやすく人工知能のしくみとシンプルな実装例を提示していきたいと思います。


必要な予備知識をQ&A形式で書いてみました。



1.人口知能とは
 人間の思考パターンをプログラムで表現したものです。


2.音声認識とか画像認識とかに使われてるけど万能なの?
 ニューロンと呼ばれるモデル自体が汎用性が高いのでもの特定分野に特化することで能力が発揮します。


3.どういう計算してるの?
 ニューロンと呼ばれるものはいわば1つの計算式であって、
 簡単に表現すると

  Y=AX+B

 こんな数式があって、
 Xが入力(実際は複数の入力がある)
 Yが出力
 AとBは{不定係数}

 といった具合になってます。
 人工知能が学習するというのはAとBを確率統計などで最適解を求めていき、
 精度が上がるにつれて係数の変化は減っていく傾向があります。


4.学習ってどうやるの?
 よく行う方法は教師画像という正解を教えてあげ正解率を上げていく方法です。


5.人工知能は人類にとって脅威になる?
 現在の方式だと脅威になりません。
 どうしてかというと、教えて覚えさせる方式だと新しい発想することはなく教えられたルールの中でしか答えを出せないからです。
 あと悪意あるユーザーが使えば危険というのは、包丁なども同じことが言えるのでこれは脅威とは呼びません。
 ターミネーターみたいに指令を与えて自ら目的達成するまでのルートを考え行動できるようになるのは
 あと2,3世代進んだ人工知能モデルだと思われます。(2080年辺り?)


6.つまり一言でまとめると
 人工知能とは計算式の集合体です。



いよいよ実装のお話

言葉だけでは分からないので
難しい部分をすべて削って極小な人工知能(ニューロン)を実装してみます。

実装デザインは、
①多入力1出力の構造で演算する部分(ニューロン)

②計算部分
 上で書いたY=AX+Bでもよいが、今回は内部パラメータをすべて角度で統一します。
 入力層の値からASin変換で角度値にしておくことで、内部の計算は足し算だけになり
 出力層でSin変換で値を求めれば良いことになるのでシンプルさを求めるとこうなります。

 数式で表すと

  Y=Σ(X+A)

 こうなります。

③学習方法
 上記式を見て分かる通り内部パラメータの最適化が学習のポイントとなります。
 例えば1+1=2 の場合、入力が1,1 出力が2、このような結果にするに期待される内部パラメータAは???という具合です。
 このとき入力から出力を求める、出力から入力を求められるような内部計算式である必要がある事もあって、今回は内部パラメータを角度にしています。

  Y=X+A (入力→出力を求める場合)
  A=Y-X (出力→入力を求める場合)

学習するときは前回使っていた内部パラメータもあるので、学習する際はそのパラメータも含めて
本来は学習曲線というものを使いますが今回は単純に平均値

 次の内部パラメータ=(前回値+学習結果値)/2

とします。


これでようやく必要な情報で出尽くしました。
次は実装で必要になるクラスを考えていきましょう。

まずニューロンのインターフェイスを考えると
public interface INeuron
{
float Output { get; }
void Learn(float value);
}

これくらいシンプルにしておきます。


次にニューロンクラス{入力・出力}とシナプス(N:1)の3つを実装する。

class InputNeuron : INeuron
class OutputNeuron : INeuron
class Synapse : INeuron


順番に実装説明していくと
・入力クラス
 シグナルは画像データや音声データなどになり、学習は行わないので、インターフェイス実装だけになります。
public class InputNeuron : INeuron
{
    public float Output { get; set; }
    public void Learn(float result) { }
}

・シナプスクラス
 入力値を計算してシグナルを算出して、学習は行うので、先ほどの計算式をここで実装します。
public class Synapse : INeuron
{
    INeuron InputNeuron { get; set; }
    float Theta { get; set; }
    public Synapse(INeuron model) { InputNeuron = model; }
    public float Output => InputNeuron.Output + Theta;
    public void Learn(float result) { Theta = (result + Theta) / 2.0f; }
}

・出力クラス
 複数のシナプスを参照して出力結果を出して、学習はしないが学習させるトリガーはここで行う
public class OutputNeuron : INeuron
{
    List Synapses { get; set; } = new List();
    public float Output => Synapses.Sum(_ => _.Output);
    public void Subscribe(params INeuron[] inputs) => Observers.AddRange(inputs.Select(_ => new Synapse(_)));
    public void Learn(float value) => foreach(var synapse in Synapses) { synapse.Learn(value); }
}

これが人工知能の原型です。

あとは計算式を変えたり、多段にしたり再帰したりして実装することになります。


ちなみに今回の計算式は適当なので正しく結果はでませんが、学習の様子は分かります。
まずは人工知能ってどういう構造なの?って所から入っていくと1つ1つの理論は難しくてもすんなり入っていくのではと思います。



・サンプルコード
 そのままビルドすると動きます

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var s1 = new InputNeuron();
            var s2 = new InputNeuron();
            var o1 = new OutputNeuron(s1, s2);
            var o2 = new OutputNeuron(s1, s2);

            //学習
            for (var i = 0; i < 10; i++)
            {
                s1.Signal = 1.0f;
                s2.Signal = 1.0f;
                o1.Learn(1);
                o2.Learn(0);

                s1.Signal = 0.0f;
                s2.Signal = 0.0f;
                o1.Learn(0);
                o2.Learn(1);

                s1.Signal = 1.0f;
                s2.Signal = 0.0f;
                o1.Learn(0);
                o2.Learn(1);

                s1.Signal = 0.0f;
                s2.Signal = 1.0f;
                o1.Learn(0);
                o2.Learn(1);
            }

            Console.WriteLine("======");


            s1.Signal = 1.0f;
            s2.Signal = 1.0f;
            Console.WriteLine($"1 == {Math.Sin(o1.Output)}, 0 == {Math.Sin(o2.Output)}");

            s1.Signal = 0.0f;
            s2.Signal = 0.0f;
            Console.WriteLine($"0 == {Math.Sin(o1.Output)}, 1 == {Math.Sin(o2.Output)}");

            s1.Signal = 1.0f;
            s2.Signal = 0.0f;
            Console.WriteLine($"0 == {Math.Sin(o1.Output)}, 1 == {Math.Sin(o2.Output)}");

            s1.Signal = 0.0f;
            s2.Signal = 1.0f;
            Console.WriteLine($"0 == {Math.Sin(o1.Output)}, 1 == {Math.Sin(o2.Output)}");

            Console.ReadKey();
        }

        public class InputNeuron : INeuron
        {
            public float Signal { get; set; }
            public float Output => (float)Math.Asin(Signal);
            public void Learn(float result) { }
        }
        public class Synapse : INeuron
        {
            INeuron neuron { get; set; }
            public float Theta { get; set; }
            public float Output => neuron.Output + Theta;
            public Synapse(INeuron model) { neuron = model; }
            public void Learn(float result) { Theta = (result - neuron.Output + Theta) / 2.0f; }
        }
        public class OutputNeuron : INeuron
        {
            List Synapses { get; set; } = new List();
            public float Output => Synapses.Sum(_ => _.Output);
            public OutputNeuron(params INeuron[] models) { Synapses.AddRange(models.Select(_ => new Synapse(_))); }
            public void Learn(float result) => Synapses.ForEach(_ => _.Learn(result));
        }
        public interface INeuron
        {
            float Output { get; }
            void Learn(float result);
        }
    }
}


ーーーーーーーーーーーーーーーー
続きます

はじめての人工知能入門の前段階のはなしと実装例(2)

0 件のコメント:

Androider