Utilizar redes neuronales para resolver un captcha

Aquí tienen una unit llamada CaptchaTrainer que se utiliza para resolver un captcha . Para ello utilizamos la biblioteca open source FANN http://leenissen.dk/fann/wp/ que sirve para implementar redes neuronales artificiales multicapa.

FANN ha sido traducido a 15 lenguajes de programación diferentes, entre ellos no podía faltar Delphi (http://leenissen.dk/fann/wp/language-bindings/ )

Una red neuronal se compone de unidades llamadas neuronas y esta formada por tres capas:
1) Capa de entrada (input layer) que recibe las señales del entorno
2) Capa oculta (hidden layer)
3) Capa de salida (ouput layer)
Cada neurona recibe una o varias entradas y tiene una o varias salidas que viene dada por una serie de funciones:
- Función de propagación: (también conocida como función de excitación), que por lo general consiste en la entrada (suma de todas las señales de entrada) multiplicada por el peso de su interconexión.
- Función de activación, que modifica a la anterior. Puede no existir, siendo en este caso 
Función de transferencia, que se aplica al valor devuelto por la función de activación. 
Se utiliza para acotar la salida de la neurona. Algunas de las más utilizadas son la función sigmoide (genera outputs de valores en el intervalo [0,1]) y la tangente hiperbólica (genera outputs de valores en el intervalo [-1,1])

El número de neuronas de entrada se selecciona en función de las variables de entrada del problema que queramos predecir.
Número de capas ocultas: Las capas ocultas proporcionarán a la red la capacidad de generalizar. En la práctica, se suelen usar redes neuronales con una o dos capas ocultas. Sin embargo, no hay un criterio natural acerca de la fórmula de selección óptima del número de neuronas ocultas. Baily y Thompson (1990) sugieren que el número de neuronas ocultas en una red neuronal de tres capas debe ser de 75% del número de neuronas de entrada mientras que Ersoy (1990) propone duplicar el número de neuronas ocultas hasta que el rendimiento de la red comience a deteriorarse.

Bien, después de esta introducción a las redes neuronales pasamos a comentar lo que hace esta unit:
Inicialmente se crea la red neuronal definiendo el número de entradas en las  3 capas:  entrada, salida y oculta. 
Como la entrada de la red es el bitmap del captcha, el número de neuronas de la entrada sería el tamaño del bitmap bitmapWidth * bitmapHeight.
Hay que tener en cuenta que los valores de cada una de las entradas / salidas siempre serán 1 o 0 (  fInputs[i]:= [1/0]     fOutputs[i]:= [1/0]   )  

Después necesitamos entrenar la red y para ello utilizamos la función Learn que tiene 2 parámetros de entrada: el captcha en formato bitmap y el número que representa y cuando terminemos el entrenamiento utilizaremos la función Guess  para probar la precisión de ese reconocimiento.

Otro aspecto a comentar es la línea 
 result := FANN.Train(fInputs, fOutputs);
del procedimiento Learn, y es que la variable result representa el error cuadrático medio del valor obtenido por la red respecto del valor esperado, por lo que debemos intentar que este error sea lo más pequeño posible para hacer que la red sea lo más precisa posible.


unit CaptchaTrainer;

interface

uses
  SysUtils, Graphics, FannNetwork;

type
  TCaptchaTrainer = class
  private
    fInputs, fOutputs: array of single;
  public
    FANN: TFannNetwork;
    constructor Create;
    procedure CreateNN(numInputs, numOutputs: integer); overload;
    procedure CreateNN(bitmapWidth, bitmapHeight, numOutputs: integer); overload;
    procedure LoadNN(fileName: string);
    procedure SaveNN(fileName: string);
    function Learn(bitmap: TBitmap; value: integer): single;
    function Guess(bitmap: TBitmap): integer;
    destructor Destroy; override;
  end;

implementation

{ TCaptchaTrainer }

function MaxValueIndex(const Data: array of Single): integer;
var I: Integer;
    maxvalue: single;
begin
  maxvalue := Data[Low(Data)];
  result := Low(Data);
  for I := Low(Data) + 1 to High(Data) do
    if maxvalue < Data[I] then
    begin
      maxvalue := Data[I];
      result := I;
    end;
end;

constructor TCaptchaTrainer.Create;
begin
  FANN := TFannNetwork.Create(nil);
  with FANN do
  begin
    ActivationFunctionHidden := afFANN_SIGMOID;
    ActivationFunctionOutput := afFANN_SIGMOID;
    TrainingAlgorithm := taFANN_TRAIN_RPROP;
    ConnectionRate := 1;
    LearningRate := 0.1;
  end;
end;

procedure TCaptchaTrainer.CreateNN(numInputs, numOutputs: integer);
begin
  SetLength(fInputs, numInputs);
  SetLength(fOutputs, numOutputs);
  with FANN.Layers do
  begin
    Add(IntToStr(numInputs));
    Add(IntToStr(50));
    Add(IntToStr(numOutputs));
  end;
  FANN.Build;
end;

procedure TCaptchaTrainer.CreateNN(bitmapWidth, bitmapHeight,
  numOutputs: integer);
begin
  SetLength(fInputs, bitmapWidth * bitmapHeight);
  SetLength(fOutputs, numOutputs);
  with FANN.Layers do
  begin
    Add(IntToStr(bitmapWidth * bitmapHeight));
    Add(IntToStr(50));
    Add(IntToStr(numOutputs));
  end;
  FANN.Build;
end;

destructor TCaptchaTrainer.Destroy;
begin
  FANN.UnBuild;
  FANN.Free;
  inherited;
end;

function TCaptchaTrainer.Guess(bitmap: TBitmap): integer;
var x,y,i: integer;
begin
  i:=0;
  for x:=0 to bitmap.Width-1 do
    for y:=0 to bitmap.Height-1 do
    begin
      if bitmap.Canvas.Pixels[x,y]=$00000000 then fInputs[i]:=1 else fInputs[i]:=0;
      inc(i);
    end;
  FANN.Run(fInputs, fOutputs);
  result := MaxValueIndex(fOutputs);
end;

function TCaptchaTrainer.Learn(bitmap: TBitmap; value: integer): single;
var x,y,i: integer;
begin
  i:=0;
  for x:=0 to bitmap.Width-1 do
    for y:=0 to bitmap.Height-1 do
    begin
      if bitmap.Canvas.Pixels[x,y]=$00000000 then fInputs[i]:=1 else fInputs[i]:=0;
      inc(i);
    end;
  for i:=0 to high(fOutputs) do if i=value then fOutputs[i]:=1 else fOutputs[i]:=0;
  result := FANN.Train(fInputs, fOutputs);
end;

procedure TCaptchaTrainer.LoadNN(fileName: string);
begin
  FANN.LoadFromFile(fileName);
end;

procedure TCaptchaTrainer.SaveNN(fileName: string);
begin
  FANN.SaveToFile(fileName);
end;

end.


Te puede interesar:
Red neuronal backpropagation 
Redes neuronales con delphi
Crear un captcha con delphi

Pulsa aquí si quieres un buen libro sobre redes neuronales. 



3 comentarios:

  1. Es muy interesante esto de usar redes neuronales para resolver problemas.
    ¿Podrias poner un ejemplo de uso de esa unit y el ratio de deteccion correcta?
    Por ejemplo, en la funcion Learn le das como parametros el TBitmap y un integer, que por lo que dices solo puedes ser o 0 o 1. Como sabrias si le metes un TBitmap que sea por ejmplo la letra "A" que la ha reconocido correctamente si solo devuelve uno o cero? es decir, si le "enseño" a reconocer el caracter A, a la hora de usar la funcion Guest, este me devolvera un 1 o un 0, como sabre si es A o si es B etc?

    Saludos.

    ResponderEliminar
  2. Porque lo que te devuelve es el índice del array foutputs.

    Si has entrenado a la red para decirle:
    El captcha N1 corresponde a la letra A, tendrá foutputs [1]:=1,foutputs[2]:=0;foutputs[3]:=0;
    El captcha N2 corresponde a la letra B, tendrá foutputs [1]:=0,foutputs[2]:=1;foutputs[3]:=0;
    El captcha N3 corresponde a la letra C, tendrá foutputs [1]:=0,foutputs[2]:=0;foutputs[3]:=1;

    Si posteriormente quieres que te reconozca el captcha N2, lo que te devolverá la red es un "2"

    ResponderEliminar
  3. Puedes ampliar tu ejemplo hasta N6?? Me muerto de curiosidad.

    ResponderEliminar