# -*- 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

for i in range (0, N_ENTREE) : 
	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)

#********************** 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

  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 : '))
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) : '))


