Sample Code

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.

Interface

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();
		
}

Neural Net v2

A Neural Net with 1 hidden layer that can also accept 'space' positioning on the game grids as an explicit input.

Class, Constructor & Misc Support Methods

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
	}

Get Next Move

	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;
	}

Neural Net

	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