Jump to content


Photo

Including a neural network

neural artificial intelligence ai

  • Please log in to reply
5 replies to this topic

#1 Nico Wouterse

Nico Wouterse
  • Members
  • 183 posts
  • LocationAustralia

Posted 09 January 2016 - 07:29 AM

If you want to include a neural network into your app, you can use the unit below which basically is a wrapper around the brain.js library

 

The unit exposes 'AddExample', 'Train' and 'Run' as the main procs, which speak for themselves. 

 

As an example the following neural network is defined with 3 inputs (r, g, b and 3 possible outputs (orange, green and purple).

To train the network 4 examples are provided which should be enough to get reasonable results.

Running the network on inputs r,g,b = 1,1,0 gives as outcome 'orange' as the best result (approx 80% certainty)

procedure TForm1.W3Button1Click(Sender: TObject);
var
  TrainingInputs: Array of float;
  RunInputs: Array of float;
  outcome : array of float;
  MinVal, MaxVal : Float;
  HighInt : Integer;
begin
//
//Define inputs and possible outcomes
  Brain1.InputArray  := ['r', 'g', 'b'];
  Brain1.OutputArray := ['orange', 'green', 'purple'];
 
//
//Give some examples
//
//training example 1
  TrainingInputs := [1, 0.65, 0];
  Brain1.AddExample(TrainingInputs,'orange'); // better : Brain1.OutputArray[0]);
 
//training example 2
  TrainingInputs.Clear;
  TrainingInputs := [0, 0.54, 0];
  Brain1.AddExample(TrainingInputs,Brain1.OutputArray[1]); //output : green
 
//training example 3
  TrainingInputs.Clear;
  TrainingInputs := [0.6, 1, 0.5];
  Brain1.AddExample(TrainingInputs,Brain1.OutputArray[1]); //output : green
 
//training example 4
  TrainingInputs.Clear;
  TrainingInputs := [0.67, 0, 1];
  Brain1.AddExample(TrainingInputs,Brain1.OutputArray[2]); //output : purple
 
//Train the network
  Brain1.Train;
 
//Run the network on a specific set of input values
  RunInputs := [1, 1, 0];
 
  outcome := Brain1.Run(RunInputs);
 
//Get the highest score
  minval := outcome[0];
  maxval := outcome[0];
  highint := 0;
  for var i := 1 to outcome.Count-1 do
  begin
  if outcome[i]<minval then
    minval := outcome[i]
  else if outcome[i]>maxval then
    begin
      maxval := outcome[i];
      highint := i;
    end;
  end;
  ShowMessage ('r=1, g=1 and b=0 gives ''' + Brain1.OutputArray[highint] +
               ''' with certainty of ' + IntToStr(Integer(maxval * 100)) + ' %');
 
end;
 
procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
  Brain1 := TBrain.Create;
end;

The brain unit

Note the use of the 'namepair' unit which at present is not part of the latest Smart release, but was posted by Jon Aasenden a while ago. It enables the writing of records / namepairs with a variable nr of fields. Cool.

 
unit Brain;
 
interface
 
uses 
  SmartCL.System, system.types, namepair;
 
type
 
  TBrainHandle = THandle;
 
  EBrain = class(EW3Exception);
  TBrain = class(TObject)
  private
    FHandle:      TBrainHandle;
    exin, exout, example:  Variant;
    NrofTrainingExamples : Integer := 0;
  protected
    Procedure     CreateBrain;
    procedure     ReleaseBrain;
  public
    Property      Handle:TBrainHandle read FHandle;
 
    Procedure     InitialiseAPIData;
    Procedure     AddExample (TrainingInputs: Array of float; TrainingOutput: String);
    Procedure     Train;
    Function      Run(RunInputs : Array of float) : Array of Float;
 
    Constructor   Create;virtual;
    Destructor    Destroy;Override;
    HiddenLayer:  Integer := 4;
    InputArray, OutputArray: Array of String;
  end;
 
Procedure BrainInitialize;overload;
Procedure BrainInitialize(DriverURL:String);overload;
function  BrainReady:Boolean;
 
implementation
 
uses SmartCL.fileutils;
 
 
{$R 'brain-0.6.3.js'}
 
var
  __BrainREADY:  Boolean := False;
 
 
Procedure TBrain.CreateBrain;
begin
  if (FHandle) then
    ReleaseBrain;
 
  asm
    (@self.FHandle) = new brain.NeuralNetwork({
       hiddenLayers: [@HiddenLayer]
    });
  end;
 
  InitialiseAPIData;
 
end;
 
procedure TBrain.ReleaseBrain;
begin
  if FHandle then
  begin
    FHandle:=TVariant.CreateObject;
    FHandle:=NULL;
  end;
end;
 
Constructor TBrain.Create;
begin
  inherited Create;
 
  try
    CreateBrain;
  except
    on e: exception do
    Raise Exception.Create(e.message);
  end;
end;
 
Destructor TBrain.Destroy;
begin
  if (FHandle) then
    ReleaseBrain;
  inherited;
end;
 
Procedure TBrain.AddExample (TrainingInputs: Array of float; TrainingOutput: String);
begin
  var Structure := TW3JsonStructure.Create(null);
  var JSONData:String;
 
  Structure.Clear;
  For var i := 0 to TrainingInputs.Count-1 do begin
    Structure.WriteFloat(InputArray[i], TrainingInputs[i]);
  end;
  Structure.SaveToJSON(JSONData);
  exin[NrofTrainingExamples] := JSon.Parse(JSonData);
 
  Structure.Clear;
  Structure.WriteInt(TrainingOutput,1);
  Structure.SaveToJSON(JSONData);
  exout[NrofTrainingExamples] := JSon.Parse(JSonData);
 
  Inc(NrofTrainingExamples);
end;
 
Procedure TBrain.Train;
var
  ExampleArray: Array of variant;
begin
  if (FHandle) then begin
    For var i := 0 to NrofTrainingExamples-1 do begin
      example[i] := TVariant.CreateObject;
      example[i].input := exin[i];
      example[i].output := exout[i];
    end;
 
    ExampleArray.Clear;
    For var j := 0 to NrofTrainingExamples-1 do begin
      ExampleArray[j] := example[j];
    end;
//    asm alert(JSON.stringify(@ExampleArray)); end;
    FHandle.train(ExampleArray);
 
  end;
end;
 
Function TBrain.Run(RunInputs : Array of float) : Array of Float;
var
  MyOutputArray : Array of float;
  outcome: variant;
  RunValues : Variant;
begin
  if (FHandle) then begin
 
    var Structure := TW3JsonStructure.Create(null);
    var JSONData:String;
    Structure.Clear;
    For var i := 0 to RunInputs.Count-1 do begin
      Structure.WriteFloat(InputArray[i], RunInputs[i]);
    end;
    Structure.SaveToJSON(JSONData);
    RunValues := JSon.Parse(JSonData);
//    asm alert(JSON.stringify(@RunValues)); end;
 
    outcome := FHandle.run(RunValues);
    asm
      var j = 0;
      for (var i in @outcome)
 {
      @MyOutputArray[j] = @outcome[i];
      j=j+1;
 }
    end;
    Result := MyOutputArray;
  end;
end;
 
Procedure TBrain.InitialiseAPIData;
begin
  NrofTrainingExamples := 0;
 
  if not TVariant.IsNull(exin)
  and not exin.IsUnassigned
    then exin.Destroy;
  exin  := TVariant.CreateObject;
 
  if not TVariant.IsNull(exout)
  and not exout.IsUnassigned
    then exout.Destroy;
  exout := TVariant.CreateObject;
 
  if not TVariant.IsNull(example)
  and not example.IsUnassigned
    then example.Destroy;
  example := TVariant.CreateObject;
end;
 
Procedure BrainInitialize(DriverURL:String);overload;
begin
  if not __BrainREADY then
  begin
    try
      writeln("Loading Brain driver");
      TW3Storage.LoadScript(DriverURL,
        procedure ()
        begin
          writeln("Brain Driver loaded and ready");
          w3_setTimeout( procedure ()
            begin
              __BrainREADY := true;
            end, 20);
        end);
    except
      on e: exception do
      Raise EBrain.CreateFmt
      ("Failed to load Brain driver, system threw exception %s with [%s]",
      [e.classname,e.message]);
    end;
  end;
end;
 
Procedure BrainInitialize;
begin
  BrainInitialize("/lib/brain-0.6.3.js");
end;
 
function  BrainReady:Boolean;
begin
  result:=__BrainREADY;
end;
 
initialization
begin
  __BrainReady:=False;
  BrainInitialize;
end;
 
end.
 

The namepair code can be found on http://smartmobilest...author/lennart/ posted by Jon Aasenden on 26 sept 2015

 

 

 

 


  • Jørn E. Angeltveit likes this

Nico Wouterse


#2 Christian-W. Budde

Christian-W. Budde
  • Administrators
  • 334 posts
  • LocationGermany

Posted 09 January 2016 - 03:37 PM

If you want to condense your header for the brain library (and get rid of the asm sections in your code) you could also use the below header translation:
 
 
unit NeuralNetwork;

interface

type
  JTrainReturn = class external
    error: Float;
    iterations: Integer;
  end;
  
  TNeuralNetworkCallback = procedure (Value: JTrainReturn); 

  JTrainOptions = record
    iterations: Integer;
    errorThresh: Float;
    log: Boolean;
    logPeriod: Integer;
    learningRate: Float;
    callback: TNeuralNetworkCallback;
    callbackPeriod: Integer;
  end;
  
  JNeuralNetworkTrainData = record
    input: array of Float;
    output: array of Float;
  end; 

  JNeuralNetworkOptions = record
    learningRate: Float;
    momentum: Float;
    hiddenSizes: Float;
    binaryThresh: Float;
  end;
  
  JTestStats = class external
    error: Float;
    misclasses: Variant;    
  end; 
  
  JTrainStream = class;
  JTrainStreamOptions = class;
  
  TNeuralNetworkFunction = function(Input: array of Float): array of Float;
  
  JNeuralNetwork = class external 'brain.NeuralNetwork'
  protected
    procedure initialize(sizes: array of Integer);

    procedure calculateDeltas(target: array of Float); 
    procedure adjustWeights(learningRate: Float);
  public
    sizes: array of Integer;
    outputLayer: Variant;
    biases: array of Variant;
    weights: array of Variant;
    outputs: array of Variant;

    deltas: array of Variant;
    changes: array of Variant;
    errors: array of Variant;
    
    hiddenSizes: Integer;
    
    constructor Create; overload;  
    constructor Create(Options: JNeuralNetwork); overload;  

    function run(input: array of Float): array of Float;
    function runInput(input: array of Float): array of Float;

    function train(data: array of JNeuralNetworkTrainData): JTrainReturn; overload;
    function train(data: array of JNeuralNetworkTrainData; options: JTrainOptions): JTrainReturn; overload;
    function trainPattern(input, target: array of Float; learningRate: Float): Float;
    
    function formatData(data: Variant): array of Float;
    function test(data: Variant): JTestStats;
    
    function toJSON: Variant; 
    function fromJSON(json: Variant): Variant;
    
    function toFunction: TNeuralNetworkFunction;
    function createTrainStream(opts: JTrainStreamOptions): JTrainStream;
  end;
   
  TTrainStreamFloodCallback = procedure;
  TTrainStreamDoneTrainingCallback = procedure (Value: JTrainReturn);

  JTrainStreamOptions = class external
    neuralNetwork: JNeuralNetwork;
    iterations: Integer;
    errorThresh: Float;
    log: Boolean;
    logPeriod: Integer;
    callback: TNeuralNetworkCallback;
    callbackPeriod: Integer;
    floodCallback: TTrainStreamFloodCallback;
    doneTrainingCallback: TTrainStreamDoneTrainingCallback;
  end; 

  JTrainStream = class external 'brain.TrainStream'
  protected
    function _write(chunk, enc, next: Variant): Variant; 
    procedure trainDatum (datum: Variant); 
    procedure finishStreamIteration; 
  public
    constructor Create; overload;
    constructor Create(Options: JTrainStreamOptions); overload;
  
    neuralNetwork: JNeuralNetwork;
    dataFormatDetermined: Boolean;

    inputKeys: array of Variant;
    outputKeys: array of Variant;
    iterations: Integer;
    errorThresh: Float;
    log: Boolean;
    logPeriod: Integer;
    callback: TNeuralNetworkCallback;
    callbackPeriod: Integer;
    floodCallback: TTrainStreamFloodCallback;
    doneTrainingCallback: TTrainStreamDoneTrainingCallback;

    size: Integer;
    count: Integer;

    sum: Float;  
  end;

function RandomWeight: Float; external 'randomWeight';  
function Zeros(Size: Integer): array of Float; external 'zeros';  
function Randos(Size: Integer): array of Float; external 'randos';  
function MeanSquareError(errors: array of Float): Float; external 'mse';  

{$R 'brain-0.6.3.min.js'}

implementation

end.
 
With this the XOR example can be written as simple as this:
 
uses 
  NeuralNetwork;

var TrainingData: array of JNeuralNetworkTrainData;

// provide some input (XOR function)
for var x := 0 to 1 do
  for var y := 0 to 1 do
  begin
    var data: JNeuralNetworkTrainData;
    data.input := [x, y];
    data.output := [x xor y];
    TrainingData.Add(data);
  end;

// create neural network
var net := JNeuralNetwork.Create;

// train data
net.train(TrainingData);

// run with supposed data
for var x := 0 to 1 do
  for var y := 0 to 1 do
    Console.Log(IntToStr(x) + ' XOR ' + IntToStr(y) + ': ' +
      FloatToStr(net.run([x, y])[0]) + ' - should be ' + IntToStr(x xor y));
Note: The header translation contains internal fields and functions than necessary, but in case you want to extend the original JS code, this might be useful.
  • Jørn E. Angeltveit likes this

#3 Nico Wouterse

Nico Wouterse
  • Members
  • 183 posts
  • LocationAustralia

Posted 10 January 2016 - 09:44 AM

Hi Christian

 

did you use a Typescript converter to derive your unit-code ?

http://forums.smartm...udde#entry15866

 

if yes, when/where would that tool be available

 

if not, can you post the process you used ?

 

cheers

Nico


Nico Wouterse


#4 Christian-W. Budde

Christian-W. Budde
  • Administrators
  • 334 posts
  • LocationGermany

Posted 10 January 2016 - 01:13 PM

As far as I know there is no TypeScript header for this library, so I didn't even tried the converter. In fact the state it is right now is rather a proof of concept than a tool that can be easily used.

Instead I had a look at the libraries specification at GitHub. To make a minimum header translation only a few information are necessary. But let's look at the example first:

var net = new brain.NeuralNetwork();

net.train([{input: [0, 0], output: [0]},
{input: [0, 1], output: [1]},
{input: [1, 0], output: [1]},
{input: [1, 1], output: [0]}]);

var output = net.run([1, 0]); // [0.987]

In this context

  • 'brain.' is something like a scope
  • 'NeuralNetwork' is something like a class.
  • 'net' is something like the instantiated object
  • 'train' is a method to train the neural network
  • 'run' is a method to run the neural network

Based on these information a simple class can be written like this:
 

type
  TNeuralNetwork = class external
    procedure Train(Input: Variant);
    function Run(Input: Variant): Variant;
  end;

At this time it's very basic and limited. It's not possible to construct this object, but calls like this:

TNeuralNetwork(SomeHandle).Train(SomeInput);

is already possible now.

 

From this one has to inspect the code a little bit further. For example the function 'run' seems to receive an array of numbers as parameter and returns a float value. In the context of JavaScript there is no differentiation between a float or an integer, both are numbers. In DWScript there is this differentiation and thus in Smart as well. Thus the function should rather be written as:

function Run(Input: array of Integer): Float; overload;
function Run(Input: array of Float): Float; overload;

Of course one can omit the first (overloaded) definition with the consequence that you need to take care to always pass float values in the array.

Next let's head over to the train function. Of course you can leave it in this broad definition with a variant as input, but we can do better than this. From the example it seems that an array of objects is passed. Thus a first improvement would be to write something like:

    procedure Train(Input: array of Variant);

But we can do even better than this. However, it means that we have to extend the rather short definition a bit further:

   procedure Train(Input: array of TTrainData);

with TTrainData written like:

type
  TTrainData = record
    input: array of Float;
    output: array of Float;
  end;

Side Note: Here, a differentiation between Float and Integer is not really needed as it is implicitly clear by the record.

 

Next we need to ensure that there is a constructor. This is typically always exposed by DWScript, it's just not explicitly clear how this looks like. Luckily we have the 'constructor' keyword to specify a constructor in Pascal. DWScript typically ignores the specified name (in particular if it is 'Create') and uses the class name instead. However, out of convenience we can still use the 'Create' name. Thus a constructor can look something like this:

type
  TNeuralNetwork = class external
    constructor Create;
    [...]
  end;

Now if one calls

TNeuralNetwork.Create

it will be translated to

new TNeuralNetwork();

on the JS side.

 

This already looks promising, except for the fact that the name is still wrong. It can be fixed by either supplying an external name for the class or for the constructor.

 

The example below shows both (only one would be really necessary)

type
  TNeuralNetwork = class external 'brain.NeuralNetwork'
    constructor Create; external 'brain.NeuralNetwork';
    [...]
  end;

There are a few other solutions, but this is the workflow straight out of my head (if I would need to translate it right now on the fly).

 

In order to get a better idea about the specification, it's often better to look at the JS code directly ("the code is the truth"), otherwise you might miss variations in the parameters of functions. For example, often an optional 'options' object is passed that presets some options. With this you can also get an idea about internal fields, which are typically only useful if you want to extend the class (in the means of 'inherit from' the class).

 

With this approach you can write code that often translates to no JS code at all, but still offers type safety in Smart Mobile Studio. An advantage over writing pure JS code (at least if not working with a modern editor).

 

I hope this helps.


  • Jørn E. Angeltveit, Dany and BobLawrence like this

#5 Nico Wouterse

Nico Wouterse
  • Members
  • 183 posts
  • LocationAustralia

Posted 11 January 2016 - 03:11 AM

Hi Christian

I like the way your brain works (pun intended :)

 

What I like about your approach is that it avoids any asm code, that it works through all of the js library code (‘code is truth’), that it encourages type safe operation and that it explains the class external feature.

 

Personally I tend to treat any js library as much as a black box as possible. That suits me fine as I’m usually too impatient to try and grasp all of someone elses js code intricacies. 

 

So far I've approached this by looking at the calling conventions. If for instance the library documentation states that the syntax to put some text into a pdf document is ‘pdfdoc.Text(left, top, ‘text’) then I can be pretty confident that soon as I have established a handle to the js class I can write in pascal ‘PDF1.Text(left, top, ‘text’);

 

The downside of this is that I need a single asm code-line in the wrapper unit. 

 asm (@self.FHandle) = new jsPDF(); end;

I’ve convinced myself that I can live with that though.

 

Thanks for your reply. I’ve been hanging out for a discussion or documentation on how to best wrap external js libraries for a long time. I’ve only done a couple so far but pieces are falling into place.


  • Dany likes this

Nico Wouterse


#6 Dany

Dany
  • Members
  • 127 posts

Posted 12 January 2016 - 03:55 PM

Thanks for your reply. I’ve been hanging out for a discussion or documentation on how to best wrap external js libraries for a long time. I’ve only done a couple so far but pieces are falling into place.

 

I so so agree!

 

Chrstians writeup above is the first on constructing JS objects i've seen hithereto and i've asked too.

 

Best Regards,

 

/Dany







Also tagged with one or more of these keywords: neural, artificial, intelligence, ai

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

IPB Skin By Virteq