import java.util.Random; import java.text.DecimalFormat; public class Frenz2 { private static final int INPUTS[] = new int[]{-2, 1, 1}; // (Change in cholesterol level, // Change in weight, // Family history) private static final double X1[][] = new double[][] {{-3.0, -1.0, -3.0}, {-9.0, -1.0, 4.0}, {12.0, 1.0, 15.0}, {17.0, 1.0, 23.0}, {-16.0, -1.0, 2.0}, {26.0, -1.0, 16.0}, {3.0, -1.0, -12.0}, {-15.0, 1.0, -3.0}}; private static final double X2[][] = new double[][] {{-6.0, -1.0, 2.0}, {4.0, -1.0, 17.0}, {12.0, 1.0, -6.0}}; private static final double TARGET_VALUE1[] = new double[] {-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0}; private static final double TARGET_VALUE2[] = new double[] {-1.0, 1.0, 1.0}; private static final double ALPHA = 0.01; // Learning rate (alpha), which limits // the size of a weight/bias adjustment // step in a single training iteration. // The smaller the value of alpha, the // longer a network will take to train. private static final double MU = 0.7; // Momentum value (mu). This enables the // weight to change in response to the // current gradient step and also to the // previous one. It allows the network to // find a reasonable solution in fewer // training iterations. When both the // current step and previous step are in // agreement, it allows for a larger step size. private static final int N = 3; // The number neurons in the input // layer equals the number of variables // inherent to the problem. private static final int M = 3; private static final int NUM_CASES = 8; private static final int TEST_CASES = 3; private static double hbias[] = null; // 'h' refers to the hidden layer. private static double hweight[][] = null; private static double obias = 0.0; // 'o' refers to the output layer. private static double oweight[] = null; private static double hbias2[] = null; // '2' refers to training copies. private static double hweight2[][] = null; private static double obias2 = 0.0; private static double oweight2[] = null; private static double hin[] = null; private static double hout[] = null; private static double oin = 0.0; private static double oout = 0.0; // Since the physicians only want to know // whether or not the individual is at // risk for the disease, you'll only // need a single output neuron. private static int inputNeuron[] = null; private static void initialize(final int n, final int m) { hbias = new double[M]; hweight = new double[N][M]; oweight = new double[M]; hbias2 = new double[M]; hweight2 = new double[N][M]; oweight2 = new double[M]; hin = new double[M]; hout = new double[M]; inputNeuron = new int[N]; // Since there's no way of knowing the appropriate weights and // biases prior to training, randomly initialize each neuron // with a weight between –0.5 and 0.5 for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { hweight[i][j] = new Random().nextDouble() - 0.5; // hidden layer connections. hweight2[i][j] = hweight[i][j]; // copies for training. } // j } // i for(int j = 0; j < m; j++) { hbias[j] = new Random().nextDouble() - 0.5; // hidden layer biases. hbias2[j] = hbias[j]; // copies for training. oweight[j] = new Random().nextDouble() - 0.5; // output layer connections. oweight2[j] = oweight[j]; // copies for training. } // j obias = new Random().nextDouble() - 0.5; // output neuron bias obias2 = obias; // copy for training. return; } private static void trainNetwork() { double trainError = 0.0; int j = 0; int trainingCycles = 0; boolean trainingComplete = false; DecimalFormat dfm = new java.text.DecimalFormat("###0.000"); while(j < 10000) { trainError = 0; for(int i = 0; i < NUM_CASES; i++) { for(int k = 0; k < N; k++) { inputNeuron[k] = (int)Math.round(X1[i][k]); } // k hiddenInput(N, M); outputInput(M); oout = sigmoid(oin); backPropagate(i, M); if(j > 999){ if(j % 1000 == 0){ System.out.println(j + " " + i + " Expected: " + TARGET_VALUE1[i] + " Actual: " + dfm.format(oout)); } } trainError += Math.pow((TARGET_VALUE1[i] - oout), 2); } // i trainError = Math.sqrt(trainError / NUM_CASES); // Display training error level every thousand epochs. if(j > 999){ if(j % 1000 == 0){ System.out.println("Training Error: " + dfm.format(trainError) + "\n"); } } // The net trains through 10000 epochs anyway, but it's interesting to see where the // training error became satisfactory. if(trainError < 0.01){ if(trainingComplete == false){ trainingCycles = j; trainingComplete = true; } } j++; } System.out.println("Training iterations needed: " + trainingCycles); return; } private static void hiddenInput(final int n, final int m) { // This carries out the summation process for each neuron in the // hidden layer of the neural network. // The input value to a node is: // the bias for that node // added to the sum of each input interconnection, // where the value of an interconnection is the input neuron's value // multiplied by the weight of the connection. double sum = 0.0; for(int j = 0; j < m; j++) { sum = 0; for(int i = 0; i < n; i++) { sum += (inputNeuron[i] * hweight[i][j]); } // i hin[j] = hbias[j] + sum; } // j // Send each scaled input of the hidden layer through the bipolar sigmoid (trans) function. for(int j = 0; j < m; j++) { hout[j] = sigmoid(hin[j]); } // j return; } private static void outputInput(final int m) { // Pass hidden layer values through the weighted connections to the output layer. double sum = 0.0; for(int j = 0; j < m; j++) { sum += (hout[j] * oweight[j]); } // j oin = obias + sum; return; } private static void backPropagate(final int i, final int m) { // Back-propagates the error to the interconnections between the output and hidden layer neurons. double odelta = sigmoidDerivative(oin) * (TARGET_VALUE1[i] - oout); double dobias = 0.0; double doweight[] = new double[m]; for(int j = 0; j < m; j++) { doweight[j] = (ALPHA * odelta * hout[j]) + (MU * (oweight[j] - oweight2[j])); oweight2[j] = oweight[j]; oweight[j] += doweight[j]; } // j dobias = (ALPHA * odelta) + (MU * (obias - obias2)); obias2 = obias; obias += dobias; updateHidden(N, m, odelta); return; } private static void updateHidden(final int n, final int m, final double d) { // Since there can be multiple hidden layer neurons as well as multiple // input neurons, some additional loops are utilized to ensure that every // weight and bias is updated appropriately. double hdelta = 0.0; double dhbias[] = new double[m]; double dhweight[][] = new double[n][m]; for(int j = 0; j < m; j++) { hdelta = (d * oweight[j]) * sigmoidDerivative(hin[j]); for(int i = 0; i < n; i++) { dhweight[i][j] = (ALPHA * hdelta * inputNeuron[i]) + (MU * (hweight[i][j] - hweight2[i][j])); hweight2[i][j] = hweight[i][j]; hweight[i][j] += dhweight[i][j]; } // i dhbias[j] = (ALPHA * hdelta) + (MU * (hbias[j] - hbias2[j])); hbias2[j] = hbias[j]; hbias[j] += dhbias[j]; } // j return; } private static void examinePatient() { inputNeuron[0] = INPUTS[0]; // Change in cholesterol. inputNeuron[1] = INPUTS[1]; // Change in weight. inputNeuron[2] = INPUTS[2]; // Family members with heart disease. hiddenInput(N, M); outputInput(M); oout = sigmoid(oin); if(oout < 0){ System.out.println("\nThe patient is at a reduced risk for disease\n"); }else{ System.out.println("\nThe patient is at an increased risk for disease\n"); } return; } private static void validateNet() { int correct = 0; DecimalFormat dfm = new java.text.DecimalFormat("###0.000"); System.out.println("Validating network training..."); for(int i = 0; i < TEST_CASES; i++) { for(int k = 0; k < N; k++) { inputNeuron[k] = (int)Math.round(X2[i][k]); } // k hiddenInput(N, M); outputInput(M); oout = sigmoid(oin); if(TARGET_VALUE2[i] == Math.round(oout)){ correct++; } System.out.println("Expected: " + TARGET_VALUE2[i] + " Actual: " + dfm.format(oout)); } // i if(correct >= 2){ System.out.println(correct + " out of " + TEST_CASES + " Match. Validation Successful."); }else{ System.out.println(correct + " out of " + TEST_CASES + " Match. Validation Unsuccessful."); } return; } private static double sigmoid(final double val) { // A.K.A. the activation function, it takes the sum of all the neurons' // weighted inputs and uses the value to calculate the neurons' output. // This output is also thought of as the "excitation" of the neuron. return (2.0 / (1.0 + (Math.exp(-val)))) - 1.0; // Output ranges from -1 to 1. } private static double sigmoidDerivative(final double val) { // Backpropagation: // Calculate delta by multiplying the difference between: // an output computed by the neural network for a given pattern, // and a target value for a known outcome for the given pattern, // by the derivative of the activation function (Sigmoid). return (1.0 / 2.0) * (1.0 + sigmoid(val)) * (1.0 - sigmoid(val)); } public static void main(String[] args) { initialize(N, M); trainNetwork(); examinePatient(); validateNet(); return; } }