人工知能に関する記事で今回が5回目になり最後にしたいと思います。
過去4回に渡りニューロンとは、シナプスとは、学習方法などニューラルネットワークに欠かせないことについて触れてきました。
今回はその集大成でニューラルネットワークに学習させて結果を得るというところまでやりたいと思います。
今回5回目である未知のパラメータが与えて教えた結果の推定値を求めることができるようになるので入門は卒業ですね。
この次によくやっているのはゲームで人工知能を使うことです。
これは評価関数を作ってスコアの高い手を指していくという手法が用いられています。
自動化を目指すならやはりイベントドリブンな構成ですね。
どれも発展途上で確率された方法がない状態ですので、
実務で人工知能投入を考えている方は、やはり時間的な制約があるので
確立された手法でないと実践投入することは難しいです。
というような背景をお持ちの方はトライ&エラーで仕上げるより確実な方法で自分でデータを解析していったほうが良くて、今回の人工知能入門が済んだら別のを勉強することをおススメしたいです。
ではさっそく本題。
前回のソースコードで良かったのですが隠れ層を多段にできるように変更を加えました。
あと簡単な数字認識するデータも用意してあります。
実際に動かしてみるとわかると思いますが、それなりに学習させるには多大な計算量が必要であることが分かります。
Googleの囲碁AIも最先端のパソコンを1000台投入しているのでまだまだ一般的ではないことが分かりますね。
しかし一度学習を済ませたニューラルネットワークはロジック回路と同様の扱いになるのでマイコンみたいな小さなCPU上でも扱えるレベルの計算量であることは補足しておきます。
最後に、人工知能とはパターン認識の1つの手法である。
・パラメータの最適化の事を機械学習と言っていて、まるで自ら勉強してるかのようにとらえられがち。言葉の持つ意味と現実はとてもかけ離れている。
・ディープラーニングという言葉が有名になったけど、あれはGoogleカーに用いる技術で使われている1実装だと思ってよい。
・人工知能で最善手を打つというのは、あくまでパターン認識の延長線上であって自ら考えているのではない。AとくればBというようなKey-Value型で表せるのでニューラルネットワークを使う必要はない。
・もし人工知能が人間のようなふるまいをさせるなら、脳の機能をプログラムして自ら改良して知識空間を拡張させ連結させていく辞書型を構築したほうが現実的である。
・パターン認識が分かったところで裏の意味を理解できない機械はミスリーディングする。
例えば選挙ポスターやTV画面に映っている人の顔を人がいると認識してしまうなど、ホントまだまだな領域であるので、実践投入したいが時間がない方は触れない方が悩みが少なくなってハッピーになれる。
以上、一か月経て到達した答えです。
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Text;
using System.Collections;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
NeuralNetwork nn;
//xor
var data1 = new List()
{
new TeachingData(new float[] { 0.0f, 0.0f }, new float[] { 0.0f }),
new TeachingData(new float[] { 0.0f, 1.0f }, new float[] { 1.0f }),
new TeachingData(new float[] { 1.0f, 0.0f }, new float[] { 1.0f }),
new TeachingData(new float[] { 1.0f, 1.0f }, new float[] { 0.0f }),
};
//Odd
var data2 = new List()
{
new TeachingData(new float[] { 0.0f, 0.0f, 0.0f }, new float[] { 0.0f }),
new TeachingData(new float[] { 0.0f, 0.0f, 1.0f }, new float[] { 1.0f }),
new TeachingData(new float[] { 0.0f, 1.0f, 0.0f }, new float[] { 1.0f }),
new TeachingData(new float[] { 0.0f, 1.0f, 1.0f }, new float[] { 0.0f }),
new TeachingData(new float[] { 1.0f, 0.0f, 0.0f }, new float[] { 1.0f }),
new TeachingData(new float[] { 1.0f, 0.0f, 1.0f }, new float[] { 0.0f }),
new TeachingData(new float[] { 1.0f, 1.0f, 0.0f }, new float[] { 0.0f }),
new TeachingData(new float[] { 1.0f, 1.0f, 1.0f }, new float[] { 1.0f }),
};
//digit
var data3 = new List()
{
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
}, new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
}, new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
}, new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 0.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }),
new TeachingData(
new float[] {
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
}, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }),
};
if (File.Exists("neuron.json"))
{
nn = Json.Load("neuron.json");
nn.Calculate(new float[]
{
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
});
Console.WriteLine(nn.Dump());
Show(nn, data3);
}
else
{
nn = NeuralNetwork.CreateInstance(2, 3, 1); //ニューロン作成
nn.Teach(data1); //学習
Show(nn, data1); //学習結果
Json.Save(nn, "neuron.json"); //保存
}
}
static void Show(NeuralNetwork nn, List data)
{
foreach (var dat in data)
{
Console.Write("[");
foreach (var output in dat.Outputs)
{
Console.Write(" " + output.ToString("F2"));
}
Console.Write(" ]");
nn.Calculate(dat.Inputs);
Console.WriteLine(nn.Dump());
}
Console.ReadKey();
}
}
#region "neuron"
public class TeachingData
{
public float[] Inputs { get; set; }
public float[] Outputs { get; set; }
public TeachingData(float[] inputs, float[] outputs)
{
Inputs = inputs;
Outputs = outputs;
}
}
[DataContract]
public class NeuralNetwork
{
static Random random = new Random();
static public float GetRandom() => (float)(Math.Sign(random.NextDouble() - 0.5) * random.NextDouble());
static public bool GetBool() => 0.5 <= random.NextDouble();
static ILossFunc LossFunc = new Sigmoid();
public static float Activate(float input) => LossFunc.Activate(input);
public static float DeActivate(float result) => LossFunc.DeActivate(result);
public float Gain { get; set; }
[DataMember] public List neurons { get; set; } = new List();
public static NeuralNetwork CreateInstance(params int[] neuronArrays)
{
var result = new NeuralNetwork();
var prevCount = 0;
foreach (var neurons in neuronArrays)
{
result.neurons.Add(Neurons.Create(neurons, prevCount));
prevCount = neurons;
}
return result;
}
public void Calculate(params float[] signals)
{
for (var i = 0; i < signals.Length; i++) neurons[0][i].Output = signals[i];
for (var i = 1; i < neurons.Count; i++) foreach (var hidden in neurons[i]) (hidden).Calc(neurons[i - 1]);
}
public void Teach(List data)
{
var sum = 0.0f;
var idx = neurons.Count - 1;
Gain = 1.0f;
var count = 0;
do
{
sum = 0.0f;
foreach (var dat in data)
{
Calculate(dat.Inputs);
for (var i = 0; i < dat.Outputs.Length; i++)
{
var diff = dat.Outputs[i] - neurons[idx][i].Output;
if (0.0 < Math.Abs(diff))
{
neurons[idx][i].Teach(this, idx, diff);
}
sum += Math.Abs(diff);
}
}
sum /= data.Count;
Gain = sum;
if (1.0f < Gain)
{
Gain = 0.8f;
}
else if(0.2f > Gain)
{
Gain = 0.2f;
}
count = (count + 1) % 1000;
if(0 == count) Console.WriteLine(sum);
}
while (0.25f < sum);
}
void Normalize()
{
for (var i = 1; i < neurons.Count; i++)
{
neurons[i].Normalize();
}
}
public override string ToString() => Json.ToJson(this);
public string Dump()
{
var result = "";
neurons.ForEach(_ => result += $" => {_.ToString()}\r\n");
return result;
}
}
[DataContract]
public class Neurons : IEnumerable
{
public IEnumerator GetEnumerator() => Data.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
[DataMember]
public List Data { get; set; } = new List();
public static Neurons Create(int neurons, int weights)
{
var result = new Neurons();
for (var i = 0; i < neurons; i++)
{
var hidden = new Neuron();
for (var j = 0; j < weights; j++) hidden.Weights.Add(NeuralNetwork.GetRandom());
result.Data.Add(hidden);
}
return result;
}
public Neuron this[int index] => Data[index];
public override string ToString()
{
var result = "";
Data.ForEach(_ => result += (0 > _.Output) ? $" {_.Output.ToString("F2")}" : $" +{_.Output.ToString("F2")}");
return $"[{result} ]";
}
public void Normalize()
{
var min1 = Data.Select(_ => _.Weights.Min()).Min();
var max1 = Data.Select(_ => _.Weights.Max()).Max();
var min2 = Data.Select(_ => _.Bias).Min();
var max2 = Data.Select(_ => _.Bias).Max();
var min = Math.Min(min1, min2);
var max = Math.Max(max1, max2);
var dif = max - min;
var gain = 2.0f / dif;
if (-1.0f > min || max > +1.0f)
{
foreach (var dat in Data)
{
dat.Bias = (dat.Bias - min) * gain - 1.0f;
for (var i = 0; i < dat.Weights.Count; i++)
{
dat.Weights[i] = (dat.Weights[i] - min) * gain - 1.0f;
}
}
}
}
}
[DataContract]
public class Neuron
{
[DataMember] public float Bias { get; set; } = NeuralNetwork.GetRandom();
[DataMember] public List Weights { get; set; } = new List();
public float Output { get; set; }
public void Teach(NeuralNetwork nn, int layer_no, float value)
{
var _value = value * NeuralNetwork.DeActivate(Output) * nn.Gain;
for (var i = 0; i < Weights.Count; i++)
{
Weights[i] += _value * nn.neurons[layer_no - 1][i].Output;
if (1 < layer_no)
{
if (NeuralNetwork.GetBool())
{
nn.neurons[layer_no - 1][i].Teach(nn, layer_no - 1, _value);
}
}
}
Bias += _value;
}
public float Calc(Neurons prev_neurons)
{
Output = Bias;
for (var i = 0; i < Weights.Count; i++) Output += Weights[i] * prev_neurons[i].Output;
Output = NeuralNetwork.Activate(Output);
return Output;
}
public override string ToString() => Json.ToJson(this);
}
#endregion
#region "lossFunc"
public interface ILossFunc
{
float Activate(float input);
float DeActivate(float result);
}
public class Sigmoid : ILossFunc
{
public float Activate(float input) => 1.0f / (1.0f + (float)Math.Exp(-input));
public float DeActivate(float result) => result * (1.0f - result);
}
public class Tanh : ILossFunc
{
public float Activate(float input) => (float)Math.Tanh(input);
public float DeActivate(float result) => (1.0f - result * result);
}
public class ReLU : ILossFunc
{
public float Activate(float input) => Math.Max(0.0f, input);
public float DeActivate(float result) => (0.0f < result) ? 1.0f : 0.0f;
}
public class Softsign : ILossFunc
{
public float Activate(float input) => input / (1.0f + Math.Abs(input));
public float DeActivate(float result) => (float)Math.Pow(1.0f - Math.Abs(result), 2);
}
#endregion
#region "json"
public static class Json
{
public static T Load(string filename)
{
if (File.Exists(filename))
{
return ToObject(File.ReadAllText(filename));
}
else
{
return (T)Activator.CreateInstance(typeof(T));
}
}
public static void Save(T model, string filename)
{
File.WriteAllText(filename, ToJson(model));
}
static T ToObject(string jsonString)
{
var serializer = new DataContractJsonSerializer(typeof(T));
var jsonBytes = Encoding.UTF8.GetBytes(jsonString);
using (var sr = new MemoryStream(jsonBytes))
{
return (T)serializer.ReadObject(sr);
}
}
public static string ToJson(T model)
{
if (null == model)
{
return "{}";
}
using (var ms = new MemoryStream())
{
new DataContractJsonSerializer(typeof(T)).WriteObject(ms, model);
var bytes = ms.ToArray();
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
}
}
#endregion
}