Code: Select all
; Neural Network basics tutorial (section 2)
; tutorial by Gio
; interactive display grid by Speedmaster
; topic: https://autohotkey.com//boards/viewtopic.php?f=7&t=42420
SetBatchLines, -1
gui, -dpiscale
gui, font, s12
gui, add, text, cblack x20 y20, (click a cell to change its value)
gui, add, text, cred x20 y60, Training rules
gui, add, text, cgreen x280 yp, Expected output
;create a display grid
rows:=8, cols:=4, cllw:=100, cllh:=30,
clln:="", cmj:=0
gpx:=20, gpy:=90, wsp:=-1, hsp:=-1,
r:=0, c:=0 , opt:="0x200 center BackGroundTrans border gclickcell "
While r++ < rows {
while c++ < cols{
gui 1: add, text, % opt " w"cllw " h"cllh " v" ((cmj) ? (clln c "_" r " Hwnd" clln c "_" r):(clln r "_" c " Hwnd" clln r "_" c)) ((c=1 && r=1) ? " x"gpx " y"gpy " section"
: (c!=1 && r=1) ? " x+"wsp " yp" : (c=1 && r!=1) ? " xs" " y+"hsp : " x+"wsp " yp"),
} c:=0
} r:=0, c:=0
gui, add, text, cblue x20 y+1, Validation case
gui, add, text, cpurple x310 yp, Final solution
gui, font, s12
gui, add, button, h30 x155 gcalculate, ANN calculation
gui, add, text, cblack x20 y+10, The default underlying rule of this table is this:`n If (input1 is 1 and input2 is 0) OR (input1 is 0 and input2 is 1), `n the result is 1. Otherwise it is 0. `n( The AI network don't know anything about this rule ! )
gui, +alwaysontop
gui, show,, Neural Network (SECTION 2)
TRAINING_INPUTS := array([0, 0, 1], [0, 1, 1], [1, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 1], [0, 0, 0]) ; We will also feed the net creator code with the values of the inputs in the training samples (all organized in a matrix too). MATRIX 7 x 3.
EXPECTED_OUTPUTS := Array([0],[1],[1],[1],[1],[0],[0]) ; And we will also provide the net creator with the expected answers to our training samples so that the net creator can properly train the net.
VALIDATION_CASE := Array([1,1,0])
drawTRAINING(TRAINING_INPUTS)
drawEXPECTED(EXPECTED_OUTPUTS)
drawValidation(VALIDATION_CASE)
return
PREPARATION_STEPS:
; The code below does a lot of matricial calculations. This is important mostly as a means of organization. We would need far too many loose variables if we did not used matrices, so we are better off using them.
; We start by initializing random numbers into the weight variables (this simulates a first hipotesis of a solution and allows the beggining of the training).
; Since we are planning to have a first layer with 4 neurons that have 3 inputs each and a second layer with 1 neuron that has 4 inputs, we need a total of 16 initial hipothesis (random weights)
Loop 16
{
Random, Weight_%A_Index%, -1.0, 1.0
}
; And than organize them into a matrix for each layer.
WEIGHTS_1 := Array([Weight_1, Weight_2, Weight_3, Weight_4], [Weight_5, Weight_6, Weight_7, Weight_8], [Weight_9, Weight_10, Weight_11, Weight_12]) ; Initital 12 Weights of layer1. MATRIX 3 x 4.
WEIGHTS_2 := Array([Weight_13], [Weight_14], [Weight_15], [Weight_16]) ; Initial 4 Weights of layer2. MATRIX 1 x 4.
; Below we are declaring a number of objects that we will need to hold our matrices.
OUTPUT_LAYER_1 := Object(), OUTPUT_LAYER_2 := Object(), OUTPUT_LAYER_1_DERIVATIVE := Object(), OUTPUT_LAYER_2_DERIVATIVE := Object(), LAYER_1_DELTA := Object(), LAYER_2_DELTA := Object(), OLD_INDEX := 0
Loop 60000 ; This is the training loop (The network creator code). In this loop we recalculate weights to aproximate desired results based on the samples. We will do 60.000 training cycles.
{
; First, we calculate an output from layer 1. This is done by multiplying the inputs and the weights.
OUTPUT_LAYER_1 := SIGMOID_OF_MATRIX(MULTIPLY_MATRICES(TRAINING_INPUTS, WEIGHTS_1))
; Than we calculate a derivative (rate of change) for the output of layer 1.
OUTPUT_LAYER_1_DERIVATIVE := DERIVATIVE_OF_SIGMOID_OF_MATRIX(OUTPUT_LAYER_1)
; Next, we calculate the outputs of the second layer.
OUTPUT_LAYER_2 := SIGMOID_OF_MATRIX(MULTIPLY_MATRICES(OUTPUT_LAYER_1, WEIGHTS_2))
; And than we also calculate a derivative (rate of change) for the outputs of layer 2.
OUTPUT_LAYER_2_DERIVATIVE := DERIVATIVE_OF_SIGMOID_OF_MATRIX(OUTPUT_LAYER_2)
; Next, we check the errors of layers 2. Since layer 2 is the last, this is just a difference between calculated results and expected results.
LAYER_2_ERROR := DEDUCT_MATRICES(EXPECTED_OUTPUTS, OUTPUT_LAYER_2)
; Now we calculate a delta for layer 2. A delta is a rate of change: how much a change will affect the results.
LAYER_2_DELTA := MULTIPLY_MEMBER_BY_MEMBER(LAYER_2_ERROR, OUTPUT_LAYER_2_DERIVATIVE)
; Than, we transpose the matrix of weights (this is just to allow matricial multiplication, we are just reseting the dimensions of the matrix).
WEIGHTS_2_TRANSPOSED := TRANSPOSE_MATRIX(WEIGHTS_2)
; !! IMPORTANT !!
; So, we multiply (matricial multiplication) the delta (rate of change) of layer 2 and the transposed matrix of weights of layer 2.
; This is what gives us a matrix that represents the error of layer 1 (REMEBER: The error of layer 1 is measured by the rate of change of layer 2).
; It may seem counter-intuitive at first that the error of layer 1 is calculated solely with arguments about layer 2, but you have to interpret this line alongside the line below (just read it).
LAYER_1_ERROR := MULTIPLY_MATRICES(LAYER_2_DELTA, WEIGHTS_2_TRANSPOSED)
;Thus, when we calculate the delta (rate of change) of layer 1, we are finally connecting the layer 2 arguments (by the means of LAYER_1_ERROR) to layer 1 arguments (by the means of layer_1_derivative).
; The rates of change (deltas) are the key to understand multi-layer neural networks. Their calculation answer this: If i change the weights of layer 1 by X, how much will it change layer 2s output?
; This Delta defines the adjustment of the weights of layer 1 a few lines below...
LAYER_1_DELTA := MULTIPLY_MEMBER_BY_MEMBER(LAYER_1_ERROR, OUTPUT_LAYER_1_DERIVATIVE)
; Than, we transpose the matrix of training inputs (this is just to allow matricial multiplication, we are just reseting the dimensions of the matrix to better suit it).
TRAINING_INPUTS_TRANSPOSED := TRANSPOSE_MATRIX(TRAINING_INPUTS)
; Finally, we calculate how much we have to adjust the weights of layer 1. The delta of the Layer 1 versus the inputs we used this time are the key here.
ADJUST_LAYER_1 := MULTIPLY_MATRICES(TRAINING_INPUTS_TRANSPOSED, LAYER_1_DELTA)
; Another matricial transposition to better suit multiplication...
OUTPUT_LAYER_1_TRANSPOSED := TRANSPOSE_MATRIX(OUTPUT_LAYER_1)
; And finally, we also calculate how much we have to adjust the weights of layer 2. The delta of the Layer 2 versus the inputs of layer 2 (which are really the outputs of layer 1) are the key here.
ADJUST_LAYER_2 := MULTIPLY_MATRICES(OUTPUT_LAYER_1_TRANSPOSED,LAYER_2_DELTA)
; And than we adjust the weights to aproximate intended results.
WEIGHTS_1 := ADD_MATRICES(WEIGHTS_1, ADJUST_LAYER_1)
WEIGHTS_2 := ADD_MATRICES(WEIGHTS_2, ADJUST_LAYER_2)
; The conditional below is just to display the current progress in the training loop.
If (A_Index >= OLD_INDEX + 600)
{
TrayTip, Status:, % "TRAINING A NEW NETWORK: " . Round(A_Index / 600, 0) . "`%"
OLD_INDEX := A_Index
}
}
; TESTING OUR OUPUT NETWORK!
; First, we convey our validation case to variables:
Input1 := VALIDATION_CASE.1.1
Input2 := VALIDATION_CASE.1.2
Input3 := VALIDATION_CASE.1.3
; Than, we do the function for the first layer components!
Out_1 := Sigmoid(Input1 * WEIGHTS_1[1,1] + Input2 * WEIGHTS_1[2,1] + Input3 * WEIGHTS_1[3,1])
Out_2 := Sigmoid(Input1 * WEIGHTS_1[1,2] + Input2 * WEIGHTS_1[2,2] + Input3 * WEIGHTS_1[3,2])
Out_3 := Sigmoid(Input1 * WEIGHTS_1[1,3] + Input2 * WEIGHTS_1[2,3] + Input3 * WEIGHTS_1[3,3])
Out_4 := Sigmoid(Input1 * WEIGHTS_1[1,4] + Input2 * WEIGHTS_1[2,4] + Input3 * WEIGHTS_1[3,4])
; Which are inputed into the function of the second layer to form the final function!
Out_Final := Sigmoid(Out_1 * WEIGHTS_2[1,1] + Out_2 * WEIGHTS_2[2,1] + Out_3 * WEIGHTS_2[3,1] + Out_4 * WEIGHTS_2[4,1])
CellFont(rows "_" cols, "s12", "helvetica")
drawchar( rows "_" cols , Out_Final, color:="purple")
;return
; REMEMBER: The sigmoidal result below is to be interpreted like this: A number above 0.5 equals an answer of 1. How close the number is to 1 is how certain the network is of its answer. A number below 0.5 equals an answer of 0. How close the number is of 0 is how certain the network is of its answer.
;msgbox % "The final network thinks the result is: " . Out_Final
; The final weights of the network are displayed next. They are what hold the underlying rule and provide the solution. If these are already calculated, there is nothing else to calculate, just apply the weights and you will get the result: that is why a Neural Network is expensive (in termos of processing power) to be trained but extremely light to be implemented (usually).
tooltip % "WEIGHT 1 OF NEURON 1 OF LAYER 1: " . WEIGHTS_1[1,1]
. "`n" "WEIGHT 2 OF NEURON 1 OF LAYER 1: " . WEIGHTS_1[2,1]
. "`n" "WEIGHT 3 OF NEURON 1 OF LAYER 1: " . WEIGHTS_1[3,1]
. "`n`n" "WEIGHT 1 OF NEURON 2 OF LAYER 1: " . WEIGHTS_1[1,2]
. "`n" "WEIGHT 2 OF NEURON 2 OF LAYER 1: " . WEIGHTS_1[2,2]
. "`n" "WEIGHT 3 OF NEURON 2 OF LAYER 1: " . WEIGHTS_1[3,2]
. "`n`n" "WEIGHT 1 OF NEURON 3 OF LAYER 1: " . WEIGHTS_1[1,3]
. "`n" "WEIGHT 2 OF NEURON 3 OF LAYER 1: " . WEIGHTS_1[2,3]
. "`n" "WEIGHT 3 OF NEURON 3 OF LAYER 1: " . WEIGHTS_1[3,3]
. "`n`n" "WEIGHT 1 OF NEURON 4 OF LAYER 1: " . WEIGHTS_1[1,4]
. "`n" "WEIGHT 2 OF NEURON 4 OF LAYER 1: " . WEIGHTS_1[2,4]
. "`n" "WEIGHT 3 OF NEURON 4 OF LAYER 1: " . WEIGHTS_1[3,4]
. "`n`n" "WEIGHT 1 OF NEURON 1 OF LAYER 2: " . WEIGHTS_2[1,1]
. "`n" "WEIGHT 2 OF NEURON 1 OF LAYER 2: " . WEIGHTS_2[2,1]
. "`n" "WEIGHT 3 OF NEURON 1 OF LAYER 2: " . WEIGHTS_2[3,1]
. "`n" "WEIGHT 4 OF NEURON 1 OF LAYER 2: " . WEIGHTS_2[4,1]
, 450, 10
Return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
calculate:
gosub, PREPARATION_STEPS
return
clickcell:
if (debug)
msgbox, % a_guicontrol
s:=strsplit(a_guicontrol, "_")
if !(s.1=rows) && !(s.2=cols) {
(TRAINING_INPUTS[s.1,s.2]) ? (TRAINING_INPUTS[s.1,s.2]:=0) : (TRAINING_INPUTS[s.1,s.2]:=1)
drawTRAINING(TRAINING_INPUTS)
}
if !(s.1=rows) && (s.2=cols) {
(EXPECTED_OUTPUTS[s.1,1]) ? (EXPECTED_OUTPUTS[s.1,1]:=0) : (EXPECTED_OUTPUTS[s.1,1]:=1)
drawEXPECTED(EXPECTED_OUTPUTS)
}
if (s.1=rows) {
(VALIDATION_CASE[1,s.2]) ? (VALIDATION_CASE[1,s.2]:=0) : (VALIDATION_CASE[1,s.2]:=1)
drawVALIDATION(VALIDATION_CASE)
}
drawchar( rows "_" cols , "", color:="purple")
return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The function below applies a sigmoid function to a single value and returns the results.
Sigmoid(x)
{
return 1 / (1 + exp(-1 * x))
}
Return
; The function below applies the derivative of the sigmoid function to a single value and returns the results.
Derivative(x)
{
Return x * (1 - x)
}
Return
; The function below applies the sigmoid function to all the members of a matrix and returns the results as a new matrix.
SIGMOID_OF_MATRIX(A)
{
RESULT_MATRIX := Object()
Loop % A.MaxIndex()
{
CURRENT_ROW := A_Index
Loop % A[1].MaxIndex()
{
CURRENT_COLUMN := A_Index
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] := 1 / (1 + exp(-1 * A[CURRENT_ROW, CURRENT_COLUMN]))
}
}
Return RESULT_MATRIX
}
Return
; The function below applies the derivative of the sigmoid function to all the members of a matrix and returns the results as a new matrix.
DERIVATIVE_OF_SIGMOID_OF_MATRIX(A)
{
RESULT_MATRIX := Object()
Loop % A.MaxIndex()
{
CURRENT_ROW := A_Index
Loop % A[1].MaxIndex()
{
CURRENT_COLUMN := A_Index
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] := A[CURRENT_ROW, CURRENT_COLUMN] * (1 - A[CURRENT_ROW, CURRENT_COLUMN])
}
}
Return RESULT_MATRIX
}
Return
; The function below multiplies the individual members of two matrices with the same coordinates one by one (This is NOT equivalent to matrix multiplication).
MULTIPLY_MEMBER_BY_MEMBER(A,B)
{
If ((A.MaxIndex() != B.MaxIndex()) OR (A[1].MaxIndex() != B[1].MaxIndex()))
{
msgbox, 0x10, Error, You cannot multiply matrices member by member unless both matrices are of the same size!
Return
}
RESULT_MATRIX := Object()
Loop % A.MaxIndex()
{
CURRENT_ROW := A_Index
Loop % A[1].MaxIndex()
{
CURRENT_COLUMN := A_Index
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] := A[CURRENT_ROW, CURRENT_COLUMN] * B[CURRENT_ROW, CURRENT_COLUMN]
}
}
Return RESULT_MATRIX
}
Return
; The function below transposes a matrix. I.E.: Member[2,1] becomes Member[1,2]. Matrix dimensions ARE affected unless it is a square matrix.
TRANSPOSE_MATRIX(A)
{
TRANSPOSED_MATRIX := Object()
Loop % A.MaxIndex()
{
CURRENT_ROW := A_Index
Loop % A[1].MaxIndex()
{
CURRENT_COLUMN := A_Index
TRANSPOSED_MATRIX[CURRENT_COLUMN, CURRENT_ROW] := A[CURRENT_ROW, CURRENT_COLUMN]
}
}
Return TRANSPOSED_MATRIX
}
Return
; The function below adds a matrix to another.
ADD_MATRICES(A,B)
{
If ((A.MaxIndex() != B.MaxIndex()) OR (A[1].MaxIndex() != B[1].MaxIndex()))
{
msgbox, 0x10, Error, You cannot subtract matrices unless they are of same size! (The number of rows and columns must be equal in both)
Return
}
RESULT_MATRIX := Object()
Loop % A.MaxIndex()
{
CURRENT_ROW := A_Index
Loop % A[1].MaxIndex()
{
CURRENT_COLUMN := A_Index
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] := A[CURRENT_ROW,CURRENT_COLUMN] + B[CURRENT_ROW,CURRENT_COLUMN]
}
}
Return RESULT_MATRIX
}
Return
; The function below deducts a matrix from another.
DEDUCT_MATRICES(A,B)
{
If ((A.MaxIndex() != B.MaxIndex()) OR (A[1].MaxIndex() != B[1].MaxIndex()))
{
msgbox, 0x10, Error, You cannot subtract matrices unless they are of same size! (The number of rows and columns must be equal in both)
Return
}
RESULT_MATRIX := Object()
Loop % A.MaxIndex()
{
CURRENT_ROW := A_Index
Loop % A[1].MaxIndex()
{
CURRENT_COLUMN := A_Index
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] := A[CURRENT_ROW,CURRENT_COLUMN] - B[CURRENT_ROW,CURRENT_COLUMN]
}
}
Return RESULT_MATRIX
}
Return
; The function below multiplies two matrices according to matrix multiplication rules.
MULTIPLY_MATRICES(A,B)
{
If (A[1].MaxIndex() != B.MaxIndex())
{
msgbox, 0x10, Error, Number of Columns in the first matrix must be equal to the number of rows in the second matrix.
Return
}
RESULT_MATRIX := Object()
Loop % A.MaxIndex() ; Rows of A
{
CURRENT_ROW := A_Index
Loop % B[1].MaxIndex() ; Cols of B
{
CURRENT_COLUMN := A_Index
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] := 0
Loop % A[1].MaxIndex()
{
RESULT_MATRIX[CURRENT_ROW, CURRENT_COLUMN] += A[CURRENT_ROW, A_Index] * B[A_Index, CURRENT_COLUMN]
}
}
}
Return RESULT_MATRIX
}
Return
; The function below does a single step in matrix multiplication (THIS IS NOT USED HERE).
MATRIX_ROW_TIMES_COLUMN_MULTIPLY(A,B,RowA)
{
If (A[RowA].MaxIndex() != B.MaxIndex())
{
msgbox, 0x10, Error, Number of Columns in the first matrix must be equal to the number of rows in the second matrix.
Return
}
Result := 0
Loop % A[RowA].MaxIndex()
{
Result += A[RowA, A_index] * B[A_Index, 1]
}
Return Result
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;functions for the display grid
drawTRAINING(array){
for i, r in array
for j, c in r
drawchar( i "_" j , c, color:="red")
}
drawEXPECTED(array){
global cols
for i, r in array
for j, c in r
drawchar( i "_" cols , c, color:="green")
}
drawValidation(array){
global rows
for i, r in array
for j, c in r
drawchar( rows "_" j , c, color:="blue")
}
drawchar(varname, chartodraw:="@", color:=""){
guicontrol,, %varname%, %chartodraw%
if color
colorcell(varname, color)
}
ColorCell(cell_to_paint, color:="red"){
GuiControl, +c%color% , %cell_to_paint%
GuiControl, MoveDraw, % cell_to_paint
}
CellFont(cell, params:="", fontname:="") {
Gui, Font, %params%, %fontname%
GuiControl, font , %cell%
guicontrol, movedraw, %cell%
}
return
~f7::
debug:=!debug
return
guiclose:
esc::
exitapp
return