Code snippets from Machine Learning project
My original intention was to make the code in this project as open-ended and expandable as I could, to that end I decided to put the ML code behind an interface so that different ML variants could be swapped, changed and even ran against each other simultaneously without needing to change the trainer code.
As the project has progressed I am discovering that different ML variants benefit from, or even need, specific modifications in the trainer code. As a result this interface is no longer 'universal', but is still used by the Neural Networks.
public interface NACPlayer {
public int NACNextMove(char[] curGrid, char playAs);
public double[][] getArrayReference(int arrayid);
public String getName();
public void incStat(int stat);
public int getStat(int stat);
public void resetStats();
}
A Neural Net with 1 hidden layer that can also accept 'space' positioning on the game grids as an explicit input.
public class NeuralNetv2 implements NACPlayer {
private double[][] weightsH1; // hidden layer weights
private double[][] weightsO1; // output layer weights
private int numInNodes;
private int numHiddenNodes;
private int numOutNodes;
private String name;
private int numMatchesPlayed,numMatchesDrawn,numMatchesLost,numMatchesWon;
public NeuralNetv2 (int inputNodes, int hiddenNodes, String name) {
// version2 accepts 18 or 27
if ((inputNodes == 18)||(inputNodes == 27))
{this.numInNodes = inputNodes;}
else {System.out.println("Invalid NNv2 setup: "+inputNodes+" inputNodes");
System.exit(0);}
this.numHiddenNodes = hiddenNodes; // 30?
this.numOutNodes = 9; // always 9 for this project
this.name = name; // used on reports
resetStats();
weightsH1 = new double[numHiddenNodes + 1][numInNodes + 1];
weightsO1 = new double[numOutNodes + 1][numHiddenNodes + 1];
// Setup hidden node layer input weightings
for (int curNode = 1; curNode <= numHiddenNodes; curNode++) {
for (int curInput = 1; curInput <= numInNodes; curInput++) {
weightsH1[curNode][curInput] = (Math.random() * 2) - 1;
}
}
// Setup final 'output' node layer input weightings
for (int curNode = 1; curNode <= numOutNodes; curNode++) {
for (int curInput = 1; curInput <= numHiddenNodes; curInput++) {
weightsO1[curNode][curInput] = (Math.random() * 2) - 1;
}
}
} // end constructor
public void resetStats() {
numMatchesPlayed = 0;
numMatchesDrawn = 0;
numMatchesLost = 0;
numMatchesWon = 0;
}
public String getName() {
return(name);
}
public void incStat(int stat) {
switch(stat) {
case(GlobalData.VAL_PLAYED):
numMatchesPlayed++; break;
case(GlobalData.VAL_DRAWN):
numMatchesDrawn++; break;
case(GlobalData.VAL_LOST):
numMatchesLost++; break;
case(GlobalData.VAL_WON):
numMatchesWon++; break;
default:
System.out.println("Unrecognised incStat request: "+stat);
System.exit(0);
}
}
public int getStat(int stat) {
switch(stat) {
case(GlobalData.VAL_PLAYED):
return numMatchesPlayed;
case(GlobalData.VAL_DRAWN):
return numMatchesDrawn;
case(GlobalData.VAL_LOST):
return numMatchesLost;
case(GlobalData.VAL_WON):
return numMatchesWon;
default:
System.out.println("Unrecognised getStat request: "+stat);
System.exit(0);
}
return 1; // compiler insists on this
}
public double[][] getArrayReference(int arrayid) {
switch(arrayid) {
case(GlobalData.VAL_OUTPUTARRAY):
return weightsO1;
case(GlobalData.VAL_HIDDENARRAY):
return weightsH1;
default:
System.out.println("Unrecognised getArrayReference: "+arrayid);
System.exit(0);
}
return null; // compiler insists on this
}
public int NACNextMove(char[] curGrid, char playAs) {
double[] outResults = new double[10];
double[] input = new double[28];
// clear NN input array
for (int i = 1; i <= 27; i++) { input[i] = 0; }
// convert input grid to array for NN use
// array 1-9:own pieces, 10-18:opponent, 19-27:spaces
for (int i = 1; i <= 9; i++) {
if (curGrid[i] == playAs)
{ input[i] = 1;} // own piece
else if (curGrid[i] != ' ')
{ input[i+9] = 1;} // opponent piece
// only process spaces for '27' input nodes setup
if ((curGrid[i] == ' ')&&(numInNodes==27))
{ input[i+18] = 1;}
}
// NN: get 9 values for next move
outResults = getNNetOutput(input);
double curHighest = -1;
int curNextMove = 0;
// pick any valid output as a default move
for (int i = 1; i <= 9; i++) {
if (curGrid[i]==' ') {
curHighest = outResults[i];
curNextMove = i;
break;
}
}
if (curNextMove == 0) // grid processing has failed somewhere
{System.out.println("zero move from NNet"); System.exit(0);}
// search for highest output value
for (int i = 1; i <= 9; i++) {
if ((curHighest < outResults[i])&&(curGrid[i]==' ')) {
curHighest = outResults[i];
curNextMove = i;
}
}
// returns value 1-9 for valid move with highest NN output
return curNextMove;
}
private double[] getNNetOutput(double[] input) {
double[] tempResults = new double[numOutNodes + 1];
double[] tempWorking = new double[numHiddenNodes + 1];
// Calculate values for hidden nodes
for (int curHiddenNode = 1; curHiddenNode <= numHiddenNodes; curHiddenNode++) {
double sum = 0;
for (int curInNode = 1; curInNode <= numInNodes; curInNode++) {
sum = sum + (input[curInNode] * weightsH1[curHiddenNode][curInNode]);
}
tempWorking[curHiddenNode] = activationFunction(sum);
}
// Calculate values from output nodes
for (int curOutNode = 1; curOutNode <= numOutNodes; curOutNode++) {
double sum = 0;
for (int curHiddenNode = 1; curHiddenNode <= numHiddenNodes; curHiddenNode++) {
sum = sum + (tempWorking[curHiddenNode] * weightsO1[curOutNode][curHiddenNode]);
}
tempResults[curOutNode] = sum;
}
return tempResults;
} // end getNNetOutput