Moderators lynkfs 614 Posted January 9, 2016 Moderators Report Share Posted January 9, 2016 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://smartmobilestudio.com/author/lennart/ posted by Jon Aasenden on 26 sept 2015 jorn 1 Quote Link to post Share on other sites
CWBudde 160 Posted January 9, 2016 Report Share Posted January 9, 2016 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. jorn 1 Quote Link to post Share on other sites
Moderators lynkfs 614 Posted January 10, 2016 Author Moderators Report Share Posted January 10, 2016 Hi Christian did you use a Typescript converter to derive your unit-code ? http://forums.smartmobilestudio.com/index.php?/topic/3639-typescript-to-smarter-mobile-studio-wrapper-tool/?hl=cwbudde&do=findComment&comment=15866 if yes, when/where would that tool be available if not, can you post the process you used ? cheers Nico Quote Link to post Share on other sites
CWBudde 160 Posted January 10, 2016 Report Share Posted January 10, 2016 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. Dany, BobLawrence and jorn 3 Quote Link to post Share on other sites
Moderators lynkfs 614 Posted January 11, 2016 Author Moderators Report Share Posted January 11, 2016 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 1 Quote Link to post Share on other sites
Dany 37 Posted January 12, 2016 Report Share Posted January 12, 2016 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 Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.