### Instruction:
Write a Java program that implements a simple neural network with backpropagation for solving XOR problem. The program should read training data from a CSV file and train the neural network accordingly.


### Response:

Here's a simple Java program that implements a neural network with backpropagation for solving the XOR problem. The program reads training data from a CSV file and trains the neural network accordingly.

```java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Random;

public class SimpleNeuralNetwork {

    private static final String TRAINING_DATA_FILE = "training_data.csv";

    public static void main(String[] args) {
        double[][] trainingData = readTrainingDataFromFile(TRAINING_DATA_FILE);
        NeuralNetwork nn = new NeuralNetwork(2, 4, 1);
        nn.train(trainingData, 10000);

        System.out.println("XOR results:");
        for (double[] data : trainingData) {
            double result = nn.feedForward(data)[0];
            System.out.printf("Input: %f, %f -> Output: %f\n", data[0], data[1], result);
        }
    }

    private static double[][] readTrainingDataFromFile(String filename) {
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            return br.lines()
                    .map(line -> line.split(","))
                    .map(values -> new double[]{Double.parseDouble(values[0]), Double.parseDouble(values[1]),
                            Double.parseDouble(values[2])})
                    .toArray(double[][]::new);
        } catch (IOException e) {
            throw new RuntimeException("Error reading training data from file", e);
        }
    }
}

class NeuralNetwork {

    private int inputNodes;
    private int hiddenNodes;
    private int outputNodes;
    private double learningRate;
    private double[][] weightsIH;
    private double[][] weightsHO;
    private double[] biasH;
    private double[] biasO;

    public NeuralNetwork(int inputNodes, int hiddenNodes, int outputNodes) {
        this.inputNodes = inputNodes;
        this.hiddenNodes = hiddenNodes;
        this.outputNodes = outputNodes;
        this.learningRate = 0.1;

        Random random = new Random();
        weightsIH = new double[hiddenNodes][inputNodes];
        for (int i = 0; i < hiddenNodes; i++) {
            for (int j = 0; j < inputNodes; j++) {
                weightsIH[i][j] = random.nextDouble() * 2 - 1;
            }
        }

        weightsHO = new double[outputNodes][hiddenNodes];
        for (int i = 0; i < outputNodes; i++) {
            for (int j = 0; j < hiddenNodes; j++) {
                weightsHO[i][j] = random.nextDouble() * 2 - 1;
            }
        }

        biasH = new double[hiddenNodes];
        for (int i = 0; i < hiddenNodes; i++) {
            biasH[i] = random.nextDouble() * 2 - 1;
        }

        biasO = new double[outputNodes];
        for (int i = 0; i < outputNodes; i++) {
            biasO[i] = random.nextDouble() * 2 - 1;
        }
    }

    public void train(double[][] trainingData, int epochs) {
        for (int e = 0; e < epochs; e++) {
            for (double[] data : trainingData) {
                double[] inputs = new double[]{data[0], data[1]};
                double[] targets = new double[]{data[2]};

                // Feed forward
                double[] hiddenOutputs = feedForward(inputs);

                // Calculate output errors
                double[] outputErrors = new double[outputNodes];
                for (int i = 0; i < outputNodes; i++) {
                    outputErrors[i] = targets[i] - hiddenOutputs[i];
                }

                // Calculate hidden errors
                double[] hiddenErrors = new double[hiddenNodes];
                for (int i = 0; i < hiddenNodes; i++) {
                    for (int j = 0; j < outputNodes; j++) {
                        hiddenErrors[i] += weightsHO[j][i] * outputErrors[j];
                    }
                }

                // Update weights and biases between hidden and output layers
                for (int i = 0; i < outputNodes; i++) {
                    for (int j = 0; j < hiddenNodes; j++) {
                        weightsHO[i][j] += learningRate * outputErrors[i] * hiddenOutputs[j] * (1 - hiddenOutputs[j]);
                    }
                    biasO[i] += learningRate * outputErrors[i];
                }

                // Update weights and biases between input and hidden layers
                for (int i = 0; i < hiddenNodes; i++) {
                    for (int j = 0; j < inputNodes; j++) {
                        weightsIH[i][j] += learningRate * hiddenErrors[i] * inputs[j] * (1 - inputs[j]);
                    }
                    biasH[i] += learningRate * hiddenErrors[i];
                }
            }
        }
    }

    public double[] feedForward(double[] inputs) {
        double[] hiddenOutputs = new double[hiddenNodes];
        for (int i = 0; i < hiddenNodes; i++) {
            hiddenOutputs[i] = sigmoid(dotProduct(weightsIH[i], inputs) + biasH[i]);
        }

        double[] finalOutputs = new double[outputNodes];
        for (int i = 0; i < outputNodes; i++) {
            finalOutputs[i] = sigmoid(dotProduct(weightsHO[i], hiddenOutputs) + biasO[i]);
        }

        return finalOutputs;
    }

    private double sigmoid(double x) {
        return 1 / (1 + Math.exp(-x));
    }

    private double dotProduct(double[] a, double[] b) {
        double result = 0;
        for (int i = 0; i < a.length; i++) {
            result += a[i] * b[i];
        }
        return result;
    }
}
```

To use this program, create a CSV file named `training_data.csv` with the following content:

```
0,0,0
0,1,1
1,0,1
1,1,0
```

This file contains the XOR training data. The first two columns represent the input values and the third column represents the expected output value.

After running the program, you should see the trained neural network's predictions for each of the four possible XOR inputs.