# -*- coding: latin-1 -*-
from numpy import *
from random import *
import pickle
from math import exp

# Claude TOUZET 11 - 2009 Perceptron multicouche et reconnaissance de caracteres manuscrits.
# mettre 100 exemples (de IMAGEDAT.TXT) dans la base apprentissage, un fichier appele : BA.TXT
# mettre les 90 autres exemples dans la base de test, un fichier appele : BT.TXT
# Ces 2 fichiers doivent etre dans le meme repertoire que ce programme Python (rna1.py)
# Recherchez la meilleure performance possible en ajustant (il est possible d'arriver ˆ 100% !) : 
# le pas de modification des poids (LAMBDA) (entre 0 et 1)
# le nombre d'iteration d'apprentissage (en arretant parfois un peu avant, cela aide la generalisation)
# le nombre de neurones sur la couche cachee (entre 1 et 100). La theorie dit que 21 (2 N_SORTIE+1) est bien pour apprendre
# Notre probleme etant la generalisation, il faut une representation plus compacte = moins de neurones cachees !!!
# Question subsidiaire : quelles sont les liens entre ces diffŽrents parametres ? 

SEUIL = 0.5
N_ENTREE = 15
N_SORTIE = 10

poids12=([])
poids23=([])
poids13=([])
poids2seuil=([])
chiffre=([])
x_proto=([])
y_des=([])
y_0=([])
y_1=([])
y_2=([])
x_1=([])
x_2=([])
grad2=([])
grad1=([])
y_sort=([])
iteration = 0

#********************** Procedure de codage des entrees *******************/

def codage (): 
  for i in range (0, nbproto) :
    for j in range (0, N_ENTREE) : 
        x_proto[i][j]=(x_proto[i][j]-21.0)/21.0;


#******************** Determination de la sortie desiree ****************/

def sortie_des (rangpt, rang) : 
    for i in range (0, N_SORTIE) : 
        y_des[rangpt][i]=-1
    y_des[rangpt][rang]=1


#****** Procedure d'initialisation des poids pour l'apprentissage  ******/

def init_poids () : 
    for i  in range (0, N_ENTREE) : 
        poids12.append([])
        for j in range (0, N_CACHEE) : 
            poids12[i].append (random()*2-1)

    for i in range (0, N_CACHEE) :
        poids23.append([])
        for j in range (0, N_SORTIE) : 
            poids23[i].append(random()*2-1)
  
    for i  in range (0, N_ENTREE) : 
        poids13.append([])
        for j in range (0, N_SORTIE) : 
            poids13[i].append (random()*2-1)
  
    for i in range (0, N_CACHEE) :
        poids2seuil.append(random()*2-1)

    for i in range (0, 180) : 
        y_des.append([])
        y_sort.append([])
        for j in range (0, N_SORTIE) : 
            y_des[i].append(0)
            y_sort[i].append(0)

#*************** Procedure de sauvegarde des poids *****************/

def  sauve_poids() :

    nomfic = "poids.txt"
    f = open(nomfic, 'w') # open for 'w'riting
    pickle.dump(N_CACHEE, f)
    pickle.dump(poids2seuil, f)
    pickle.dump(poids12, f)
    pickle.dump(poids23, f)
    pickle.dump(poids13, f)
    f.close()

#********************** Procedure de lecture des poids  ****************/

def lec_poids() : 
  
    nomfic = "poids.txt"
    f = open(nomfic, 'w') # open for 'w'riting
    N_CACHEE = pickle.load(f)
    poids2seuil = pickle.load(f)
    poids12 = pickle.load(f)
    poids23 = pickle.load(f)
    poids13 = pickle.load(f)
    f.close()

#******************** Procedure de classement  **************/

def classe(rangpt) :

    nb1=0;
    sign  = []
    for i in range (0, N_SORTIE) :
        if (y_sort[rangpt][i]>=0.5) : 
            sign.append(1)
        else : 
            sign.append(-1)
    for i in range (0, N_SORTIE) :
        if (sign[i]==1) : 
            clas=i
            nb1=nb1+1
    if (nb1==1) :
        return(clas)
    else :
        return(-1)

#**********  Procedure de lecture des prototypes en fichier  *************/

def lec_fich(nomfic) : 

    f=open(nomfic,"r")
#  if f == null : 
#      print(" Pas de fichier : ", nomfic, " \n")
    for i in range (0, nbproto) : 
        x_proto.append([])
        line = f.readline()
#    print (line)
        if (len(line))== 0 : 
            break
        k=0
        m = 0
        while (m < N_ENTREE) :
            nombre = 0
            while  (line[k]<'0' or line[k]>'9') : 
                k = k+1
    #		    print k
            if (line[k]>='0' and line[k]<='9') : 
                nombre = int(line[k])
            k = k+1
            if (line[k]>='0' and line[k]<='9') : 
                nombre = nombre*10 + int(line[k])
            k=k+1
            x_proto[i].append(nombre)
            m = m+1
        nombre = 0
        while (line[k]<'0' or line[k]>'9') : 
            k = k+1
        if (line[k]>='0' and line[k]<='9') : 
            nombre = int(line[k])
        k = k+1
        if (line[k]>='0' and line[k]<='9') : 
            nombre = nombre*10 + int(line[k])
            
        chiffre.append(nombre)
#  print (x_proto)
#  print 'chiffre', chiffre

    f.close()

#********************** reseau *************************/

def reseau1 () : 

    print("reseau \n");
    if (phase == True) :
    #* apprentissage */
        iteration=1
        fin_app=0
        while ((fin_app==0) and (iteration < NBIT)) :
            iteration = iteration +1
            fin_app=1
            for nuproto in range (0,nbproto) : 
        #************** calcul de la sortie **********************/
                for i in range (0, N_ENTREE) : 
                    res_inter=exp(x_proto[nuproto][i])
                    y_0[i]=(res_inter-1)/(res_inter+1)
                
                for i in range (0, N_CACHEE) : 
                    x_1[i] = -poids2seuil[i]
                    for j in range (0, N_ENTREE) : 
                        x_1[i]+=poids12[j][i]*y_0[j]
                    res_inter=exp( x_1[i])
                    y_1[i]=(res_inter-1)/(res_inter+1)
                for i in range (0, N_SORTIE) : 
                    x_2[i]=0.0
                    for j in range (0, N_CACHEE) : 
                        x_2[i]+=poids23[j][i]*y_1[j]
                    for j in range (0, N_ENTREE) : 
                        x_2[i]+=poids13[j][i]*y_0[j]
                    res_inter=exp( x_2[i])
                    y_2[i]=(res_inter-1)/(res_inter+1)

        #*************** modification des poids ****************/

            #* calcul du gradient sur la couche de sortie   */
                for i in range (0, N_SORTIE) : 
                    grad2[i]= 2.0*((1-y_2[i]*y_2[i])/2.0)*(y_des[nuproto][i]-y_2[i])
                
                #* calcul du gradient sur la couche cachee     */
                for i in range (0, N_CACHEE) : 
                    som_modif=0.0
                    for j in range (0, N_SORTIE) : 
                        som_modif+=poids23[i][j]*grad2[j]
                    grad1[i]=som_modif*(1-y_1[i]*y_1[i])/2.0

                #* modification des poids synaptiques           */
                for i in range (0, N_ENTREE) : 
                    for j in range (0, N_CACHEE) : 
                        poids12[i][j]+=LAMDA*grad1[j]*y_0[i]
                
                for i in range (0, N_CACHEE) : 
                    for j in range (0, N_SORTIE) : 
                        poids23[i][j]+=LAMDA*grad2[j]*y_1[i]
                  
                for i in range (0, N_ENTREE) : 
                    for j in range (0, N_SORTIE) : 
                        poids13[i][j]+=LAMDA*grad2[j]*y_0[i]

                #* modification des poids du seuil*/
                for j in range (0, N_CACHEE) : 
                    poids2seuil[j]+=LAMDA*grad1[j]
                
                for nuproto in range (0, nbproto) : 
                    for i in range (0, N_ENTREE) : 
                        res_inter=exp( x_proto[nuproto][i])
                        y_0[i]=(res_inter-1)/(res_inter+1)

                    for i in range (0, N_CACHEE) : 
                        x_1[i]=-poids2seuil[i]
                        for j in range (0, N_ENTREE) : 
                            x_1[i]+=poids12[j][i]*y_0[j]

                        res_inter=exp( x_1[i])
                        y_1[i]=(res_inter-1)/(res_inter+1)

                    for i in range (0, N_SORTIE) : 
                        x_2[i]=0.0
                        for j in range (0, N_CACHEE) : 
                            x_2[i]+=poids23[j][i]*y_1[j]
                      
                        for j in range (0, N_ENTREE) : 
                            x_2[i]+=poids13[j][i]*y_0[j]
                      
                        res_inter=exp( x_2[i])
                        y_2[i]=(res_inter-1)/(res_inter+1)

            #*************** calcul de l'erreur *****************/

                crit=0.0
                for i in range (0, N_SORTIE) : 
                    crit+=(y_des[nuproto][i]-y_2[i])*(y_des[nuproto][i]-y_2[i])
                if (SEUIL<crit) :
                    fin_app=0

        print 'iteration : ',iteration,  'crit : ',  crit

    else : 
    #* reconnaissance : calcul de la sortie*/
        for nuproto in range (0, nbproto) :
            fin_app=1
            for i in range (0, N_ENTREE) : 
                res_inter=exp( x_proto[nuproto][i])
                y_0[i]=(res_inter-1)/(res_inter+1)
            for i in range (0, N_CACHEE) : 
                x_1[i]=-poids2seuil[i]
                for j in range (0, N_ENTREE) : 
                    x_1[i]+=poids12[j][i]*y_0[j]
                res_inter=exp( x_1[i])
                y_1[i]=(res_inter-1)/(res_inter+1)
              
            for i in range (0, N_SORTIE) : 
                x_2[i]=0.0
                for j in range (0, N_CACHEE) : 
                    x_2[i]+=poids23[j][i]*y_1[j]
                for j in range (0, N_ENTREE) : 
                    x_2[i]+=poids13[j][i]*y_0[j]
                res_inter=exp( x_2[i])
                y_2[i]=(res_inter-1)/(res_inter+1)

            for i in range (0, N_SORTIE) : 
                y_sort[nuproto][i]=y_2[i]


#**************** main *****************/

#N_CACHEE = 10
N_CACHEE = int(input('Dimension de la couche cachee : '))
max = N_ENTREE
if N_CACHEE > max : max = N_CACHEE
for i in range (0, max) : 
    y_0.append(0)
    y_1.append(0)
    y_2.append(0)
    x_2.append(0)
    x_1.append(0)
    grad2.append(0)
    grad1.append(0)
init_poids ()
phase = int(input('Phase apprentissage (1) ou de reconnaissance (2) ou fin (0) : '))
while (phase != 0) : 
    chiffre=[]
    x_proto=[]
    if (phase == 1) :
        nbproto = 100
        lec_fich("BA.TXT")
    #    LAMDA = .2
    #    NBIT = 200
        LAMDA = float(input('Entrez la valeur de lamda :  '))
        NBIT = int(input('Nombre iterations maximum : '))
        for nuproto in range (0, nbproto) :
            sortie_des(nuproto, chiffre[nuproto])
    else :
        nbproto = 90
        lec_fich("BT.TXT")
    codage()
    reseau1()

    if (phase != 1) : 
        resultat=0
        for nuproto in range (0, nbproto) :
          print("Le prototype est un ",chiffre[nuproto], ", sa classe est ", classe(nuproto))
          if (classe(nuproto)==chiffre[nuproto]) :
            resultat = resultat+1

        pourcent = 100.0* resultat/nbproto
        print 'Taux de reconnaissance : ', pourcent
     
    phase = int(input('Phase apprentissage (1) ou de reconnaissance (2) ou fin (0) : '))

