ニューラルネットでXORを遺伝アルゴリズムで学習するpythonサンプル

#はじめに

pythonの遺伝アルゴリズム、ニューラルネットの基本処理を書いてみました。
遺伝アルゴリズムは専門家ではないのでなんちゃって遺伝アルゴリズムです。

#ニューラルネットとは
生物の神経細胞網を模した計算手法です。
[Wikipediaへのリンク](https://ja.wikipedia.org/wiki/%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A9%E3%83%AB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF)

#XORとは
２つの入力が同じだと０異なると１を出力する論理式です。

|入力１|入力２  |出力 | 
|---|---|---|
|0|0 |0| 
|0|1 |1| 
|1|0 |1| 
|1|1 |0| 

#遺伝的アルゴリズムとは
私が遺伝的アルゴリズムで知っているのは、
・良い特徴の個体を選別していく
・良い特徴を持つ個体から特徴を引き継いだ新たな個体を生成する。
・世代交代を繰り返してよりよい特徴の個体を見つけ出し、答えをみつける。
ということです。
様々な手法があるようですが、私は素人なので今回は
・個体群を成績順にソートし
　・下位１０％は新規生成個体に置き換え
　・＋５０％を上位５０％とその他の個体を交差（後述）させ生成
　・これを繰り返し最も適切な回答を導き出す個体を見つけ出す。
という手法をとりました。
#交差とは
２つの個体から特徴を取り出し、新たな個体を作ることだと理解しています。
実際の生物の世界でいうところの子が親の遺伝子を受け継ぐことを模したものです。
実際の遺伝はどちらかの親の遺伝子を１ビット毎にデジタル的に受け継ぐイメージですが、
今回はニューラルネットのシナプスの重み（float型)を配列化し、
２つの個体のシナプス重みを示す２つの配列の各数値の内分をとることで
２つの個体の特徴を遺伝させることを模しました。
例

|  |w1  |w2  |w3 | w4|
|---|---|---|---|---|---|
|個体１のシナプス重み  |1  |1  |3|4|
|個体２のシナプス重み |0  |0  |1|1|
|個体１との類似度  |1|0|0.5|0.1
|個体２との類似度  |0|1|0.5|0.9
|新個体のシナプス重み|1  |0  |2|1.3

ただ、局所解に収束する可能性がありますので２つ重みの外分の値も取りうることとしました。


#サンプルプログラム

```python:sample.py
from numpy.random import *
import numpy as np
import math
import sys

class Body:   #各個体
    #入力層の数
    nn_input=2
    #中間層の数
    nn_hidden=3
    nn_output=1
    #シナプス重み初期値の範囲
    w_range=10
    #シナプス数
    w_length=(nn_input+1)*(nn_hidden+1)+(nn_hidden+1)*(nn_output)
    #シグモイド関数に入力する最小値
    sigmoidinputmin=math.log(sys.float_info.min)
    #シグモイド関数に入力する最大値
    sigmoidinputmax=math.log(sys.float_info.max)

    #交差時の遺伝をどの程度内分、外分にするか
    #0・・・２つの重みの平均
    #1・・・２つの重みの内分のいずれか
    #1以上・・・外分（２つの重みの間の値とならないこと）もありうる
    crossRatio=2
    
    
    def __init__(self):
        self.w=None      #シナプス重み
        self.score=-1    #評価結果
        self.result=None #計算結果

    #新規作成
    def createNewBodyAtInit():
        body=Body()
        body.w=np.random.uniform(-Body.w_range,Body.w_range,Body.w_length)
        body.calc()
        return body

    #結果を表示
    def showResult(self):
        aaa= str(self.score) + ":" + str(self.result)
        print(aaa)

    #交差
    #引数　　交差する個体
    #戻り値　生成した個体
    def cross(self,otherBody):
        w1=self.w
        w2=otherBody.w
        newW=[]
        for i in range(len(w1)):
            a=np.random.random()*Body.crossRatio
            newW.append(w1[i]*a+w2[i]*(1-a))
        newBody=Body()
        newBody.w=newW
        newBody.calc()
        
        return newBody

    #評価計算
    def calc(self):
        result=[]
        for _in in in_sample:
            result.append(self.nn(_in))
        _ = out_sample-result
        error = _*_
        self.result=result
        self.score=sum(error) 


    #シグモイド関数
    def sigmoid(self,x):
        #入力が小さすぎるとオーバーフローするため、１を返す
        if x<Body.sigmoidinputmin :
            return 1
        #大きすぎる場合、オーバーフローはないけど、
        #上と同様の処理（なくても可）
        if Body.sigmoidinputmax<x :
            return 0
        a=math.log(sys.float_info.max)
        return  1/(1+math.e**-x)




    #ニューラルネットの計算    
    def nn(self,inp):
        w=self.w
        w_num=0
        hidden_o=[]
        for i in range (Body.nn_hidden):
            o=0
            for j in range(Body.nn_input):
                o+=w[w_num]*inp[j]
                w_num+=1
            o+=w[w_num]
            w_num+=1

            hidden_o.append(self.sigmoid(o))
        o=0
        for i in range (Body.nn_hidden):
            o+=w[w_num]*hidden_o[i]
            w_num+=1
        o+=w[w_num]
        w_num+=1

        return self.sigmoid(o)



def main():
    #入力値
    global in_sample
    in_sample=np.array([[0,0],
                        [0,1],
                        [1,0],
                        [1,1]])
    #出力の目標値
    global out_sample
    out_sample=np.array([0,1,1,0]);


    #個体数
    body_counts=100

    #世代数
    loop=1000
    

    #個体群
    bodies=[]
    #個体群の初期生成
    for i in range(body_counts):
        body=Body.createNewBodyAtInit()
        bodies.append(body)

    #世代交代しつつ評価
    for i in range(loop):
        print("***")
        bodies=generate(bodies)
        bodies[0].showResult()
        

#個体群再生成
#引数　　世代交代前個体群
#戻り値　世代交代後個体群
def generate(bodies):
    bodies=sort(bodies)
    a1=0
    length=len(bodies)
    newgennum=length-1
    #突然変異個体割合
    mutationRatio=0.1
    stop=newgennum-int(length*mutationRatio)
    #突然変異個体の生成
    while stop<newgennum :
        body = Body.createNewBodyAtInit()
        bodies[newgennum]=body
        newgennum += -1
    #交差個体の生成割合
    crossRatio=0.5
    #交差
    stop=newgennum-int(length*crossRatio)
    while stop<newgennum :
        a2=int(length*np.random.random())
        body = bodies[a1].cross(bodies[a2])

        bodies[newgennum]=body
        a1 += 1
        a2 += -1
        newgennum += -1
    return bodies



#個体群を成績順にソートする。
#引数　　ソート前個体群
#戻り値　ソート後個体群
def sort(bodies):
    di = {}
    for body in bodies:
        di[body.score]=body
    keys = sorted(di.keys())
    li=[]
    for key in sorted(di.keys()):
        li.append(di[key])
    return li


if __name__ =="__main__":
    main()
                                     


```