%matplotlib inline

import numpy as np
import pylab as pl
import math 
from sympy import *
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D

from NN import NN

"""
Explanation: 今回のレポートでは、①オートエンコーダの作成、②再帰型ニューラルネットワークの作成を試みた。
①コブダクラス型生産関数を再現できるオートエンコーダの作成が目標である。
End of explanation
"""


def example1(x_1, x_2):
    z = x_1**0.5*x_2*0.5
    return z

fig = pl.figure()
ax = Axes3D(fig)

X = np.arange(0, 1, 0.1)
Y = np.arange(0, 1, 0.1)

X, Y = np.meshgrid(X, Y)
Z = example1(X, Y)

ax.plot_surface(X, Y, Z, rstride=1, cstride=1)

pl.show()

"""
Explanation: 定義域は0≤x≤1である。
<P>コブ・ダグラス型生産関数は以下の通りである。</P>
<P>z = x_1**0.5*x_2*0.5</P>
End of explanation
"""


nn = NN()

"""
Explanation: NNのクラスはすでにNN.pyからimportしてある。
End of explanation
"""


x_1 = Symbol('x_1')
x_2 = Symbol('x_2')
f = x_1**0.5*x_2*0.5

"""
Explanation: 以下に使い方を説明する。
初めに、このコブ・ダグラス型生産関数を用いる。
End of explanation
"""


nn.set_input_layer(2)

nn.set_hidden_layer(2)

nn.set_output_layer(2)

"""
Explanation: 入力層、中間層、出力層を作る関数を実行する。引数には層の数を用いる。
End of explanation
"""


nn.setup()
nn.initialize()

"""
Explanation: <p>nn.set_hidden_layer()は同時にシグモイド関数で変換する前の中間層も作る。</p>
<p>set_output_layer()は同時にシグモイド関数で変換する前の出力層、さらに教師データを入れる配列も作る。</p>

nn.setup()で入力層ー中間層、中間層ー出力層間の重みを入れる配列を作成する。
nn.initialize()で重みを初期化する。重みは-1/√d ≤ w ≤ 1/√d (dは入力層及び中間層の数)の範囲で一様分布から決定される。
End of explanation
"""


idata = [1, 2]

nn.supervised_function(f, idata)

"""
Explanation: nn.supervised_function(f, idata)は教師データを作成する。引数は関数とサンプルデータをとる。
End of explanation
"""


nn.simulate(1, 0.1)

"""
Explanation: nn.simulate(N, eta)は引数に更新回数と学習率をとる。普通はN=1で行うべきかもしれないが、工夫として作成してみた。N回学習した後に出力層を返す。
End of explanation
"""


X = np.arange(0, 1, 0.2)
Y = np.arange(0, 1, 0.2)
print X, Y

"""
Explanation: nn.calculation()は学習せずに入力層から出力層の計算を行う。nn.simulate()内にも用いられている。
次に実際に学習を行う。サンプルデータは、
End of explanation
"""


X = np.arange(0, 1, 0.2)
Y = np.arange(0, 1, 0.2)

a = np.array([])
b = np.array([])
c = np.array([])

nn = NN()
nn.set_network()

for x in X:
    for y in Y:
        a = np.append(a, x)
        b = np.append(b, y)
        
for i in range(100):
        l = np.random.choice([i for i in range(len(a))])
        m = nn.main2(1, f, [a[l], b[l]], 0.5)

for x in X:
    for y in Y:
        idata = [x, y]
        c = np.append(c, nn.realize(f, idata))

a

b

c

"""
Explanation: の組み合わせである。
End of explanation
"""


fig = pl.figure()
ax = Axes3D(fig)

ax.scatter(a, b, c)

pl.show()

"""
Explanation: 例えば(0, 0)を入力すると0.52328635を返している（つまりa[0]とb[0]を入力して、c[0]の値を返している）。
ここでは交差検定は用いていない。
End of explanation
"""


X = np.arange(0, 1, 0.2)
Y = np.arange(0, 1, 0.2)

a = np.array([])
b = np.array([])
c = np.array([])

nn = NN()
nn.set_network()

for x in X:
    for y in Y:
        a = np.append(a, x)
        b = np.append(b, y)
        
for i in range(10000):
        l = np.random.choice([i for i in range(len(a))])
        m = nn.main2(1, f, [a[l], b[l]], 0.5)

for x in X:
    for y in Y:
        idata = [x, y]
        c = np.append(c, nn.realize(f, idata))

fig = pl.figure()
ax = Axes3D(fig)

ax.scatter(a, b, c)

pl.show()

"""
Explanation: 確率的勾配降下法を100回繰り返したが見た感じから近づいている。回数を10000回に増やしてみる。
End of explanation
"""


X = np.arange(0, 1, 0.2)
Y = np.arange(0, 1, 0.2)

a = np.array([])
b = np.array([])
c = np.array([])

for x in X:
    for y in Y:
        a = np.append(a, x)
        b = np.append(b, y)

evl = np.array([])

for i in range(len(a)):
    nn = NN()
    nn.set_network()
    for j in range(1):
        l = np.random.choice([i for i in range(len(a))])
        if l != i:
            nn.main2(1, f, [a[l], b[l]], 0.5)
    idata = [a[i], b[i]]
    est = nn.realize(f, idata)
    evl = np.append(evl, math.fabs(est - nn.supervised_data))

np.average(evl)

"""
Explanation: 見た感じ随分近づいているように見える。
最後に交差検定を行う。
初めに学習回数が極めて少ないNNである。
End of explanation
"""


X = np.arange(0, 1, 0.2)
Y = np.arange(0, 1, 0.2)

a = np.array([])
b = np.array([])
c = np.array([])

nn = NN()
nn.set_network(h=7)

for x in X:
    for y in Y:
        a = np.append(a, x)
        b = np.append(b, y)

evl = np.array([])

for i in range(len(a)):
    nn = NN()
    nn.set_network()
    for j in range(100):
        l = np.random.choice([i for i in range(len(a))])
        if l != i:
            nn.main2(1, f, [a[l], b[l]], 0.5)
    idata = [a[i], b[i]]
    evl = np.append(evl, math.fabs(nn.realize(f, idata) - nn.supervised_data))

np.average(evl)

"""
Explanation: 次に十分大きく(100回に)してみる。
End of explanation
"""


nn = NN()
nn.set_network()

X = np.arange(0, 1, 0.05)
Y = np.arange(0, 1, 0.05)

a = np.array([])
b = np.array([])
c = np.array([])

for x in X:
    for y in Y:
        a = np.append(a, x)
        b = np.append(b, y)

evl = np.array([])

s = [i for i in range(len(a))]

for j in range(1000):
    l = np.random.choice(s)
    nn.main2(1, f, [a[l], b[l]], 0.5)

c = np.array([])

for i in range(len(a)):
    idata = [a[i], b[i]]
    c = np.append(c, nn.realize(f, idata))

fig = pl.figure()
ax = Axes3D(fig)

ax.scatter(a, b, c)

pl.show()

"""
Explanation: 誤差の平均であるので小さい方よい。
学習回数を増やした結果、精度が上がった。
最後にオートエンコーダを作成する。回数を増やした方がよいことが分かったため、10000回学習させてみる。
End of explanation
"""


from NN import RNN

"""
Explanation: 十分再現できていることが分かる。
②ゲーム理論で用いられるTit for Tatを再現してみる。二人のプレーヤーが互いにRNNで相手の行動を予測し、相手の行動に対してTit for Tatに基づいた行動を選択する。
End of explanation
"""


nn1 = RNN()
nn1.set_network()
nn2 = RNN()
nn2.set_network()

idata1 = [[1, 0]]
idata2 = [[0, 1]]
sdata1 = [[0]]
sdata2 = [[1]]

for t in range(20):
    
    for i in range(10):
        nn1.main2(idata1, sdata2, 0.9)
        nn2.main2(idata2, sdata1, 0.9)
    
    idata1.append([sdata1[-1][0], sdata2[-1][0]])
    idata2.append([idata1[-1][1], idata1[-1][0]])
                   
    n1r = nn1.realize(idata1)
    n2r = nn2.realize(idata1)
    sdata1.append([np.random.choice([1, 0], p=[n1r, 1-n1r])])
    
    sdata2.append([np.random.choice([1, 0], p=[n2r, 1-n2r])])
        
idata.append([sdata1[-1][0], sdata2[-1][0]])
print nn1.realize(idata1), nn2.realize(idata), idata1

"""
Explanation: 最初の行動はRNNで指定できないので、所与となる。この初期値と裏切りに対する感応度で収束の仕方が決まる。
協調を1、裏切りを0としている。RNNの予測値は整数値でないが、p=（RNNの出力値）で次回に協調を行う。
例１：1期目に、プレーヤー１が協力、プレーヤー２が裏切り。
End of explanation
"""


p1 = []
p2 = []
for i in range(len(idata1)):
    p1.append(idata1[i][0])
for i in range(len(idata2)):
    p2.append(idata2[i][0])
plt.plot(p1, label='player1')
plt.plot(p2, label='player2')

"""
Explanation: 下の図より、最初は交互に相手にしっぺ返しをしているが、やがて両者が裏切り合うこと状態に収束する。
End of explanation
"""


nn1 = RNN()
nn1.set_network()
nn2 = RNN()
nn2.set_network()

idata1 = [[1, 1]]
idata2 = [[1, 1]]
sdata1 = [[1]]
sdata2 = [[1]]

for t in range(20):
    
    for i in range(10):
        nn1.main2(idata1, sdata2, 0.9)
        nn2.main2(idata2, sdata1, 0.9)
    
    idata1.append([sdata1[-1][0], sdata2[-1][0]])
    idata2.append([idata1[-1][1], idata1[-1][0]])
                   
    n1r = nn1.realize(idata1)
    n2r = nn2.realize(idata1)
    
    prob1 = n1r 
    prob2 = n2r - 0.3
    
    if prob2 < 0:
        prob2 = 0
    
    sdata1.append([np.random.choice([1, 0], p=[prob1, 1-prob1])])
    
    sdata2.append([np.random.choice([1, 0], p=[prob2, 1-prob2])])
        
idata.append([sdata1[-1][0], sdata2[-1][0]])
print nn1.realize(idata1), nn2.realize(idata), idata1

p1 = []
p2 = []
for i in range(len(idata1)):
    p1.append(idata1[i][0])
for i in range(len(idata2)):
    p2.append(idata2[i][0])
plt.plot(p1, label='player1')
plt.plot(p2, label='player2')

"""
Explanation: 例２：1期目に、プレーヤー１が協力、プレーヤー２が協力。ただし、プレーヤー２は相手の裏切りをかなり警戒している。
警戒を表すためにp=（RNNの出力値 - 0.2）とする。p<0の場合はp=0に直す。
End of explanation
"""


nn1 = RNN()
nn1.set_network()
nn2 = RNN()
nn2.set_network()

idata1 = [[1, 0]]
idata2 = [[0, 1]]
sdata1 = [[0]]
sdata2 = [[1]]

for t in range(20):
    
    for i in range(10):
        nn1.main2(idata1, sdata2, 0.9)
        nn2.main2(idata2, sdata1, 0.9)
    
    idata1.append([sdata1[-1][0], np.random.choice([sdata2[-1][0], 1-sdata2[-1][0]], p=[0.8, 0.2])])
    idata2.append([sdata2[-1][0], np.random.choice([sdata1[-1][0], 1-sdata1[-1][0]], p=[0.8, 0.2])])
                   
    n1r = nn1.realize(idata1)
    n2r = nn2.realize(idata1)
    
    prob1 = n1r 
    prob2 = n2r 
    
    sdata1.append([np.random.choice([1, 0], p=[prob1, 1-prob1])])
    
    sdata2.append([np.random.choice([1, 0], p=[prob2, 1-prob2])])
        
idata.append([sdata1[-1][0], sdata2[-1][0]])
print nn1.realize(idata1), nn2.realize(idata), idata1

p1 = []
p2 = []
for i in range(len(idata1)):
    p1.append(idata1[i][0])
for i in range(len(idata2)):
    p2.append(idata2[i][0])
plt.plot(p1, label='player1')
plt.plot(p2, label='player2')

"""
Explanation: 例３：次に相手の行動を完全には観測できない場合を考える。t期の相手の行動をt+1期にノイズが加わって知る。例えば、1期目に相手が協調したことを、確率90%で2期目に正しく知れるが、10%で裏切りと誤って伝わる場合である。
ノイズは20%の確率で加わるものとする。その他の条件は例1と同じにした。
End of explanation
"""


