在纯Java中实现人工神经网络(无外部依赖)
使用JavaFx可视化训练损失
深度学习框架过度简化了实现神经网络的过程,有时很容易陷入抽象学习过程的陷阱,相信你可以简单地将任意层叠在一起,它会自动处理所有事情。通过从头开始实施核心概念(如反向传播算法(用于NN,CNN和RNN)),在机器学习(ML)方面具有坚实的基础非常重要。花点时间去理解它的推导过程,试着自己从代码中从头开始推导,并从头开始实现它,看看你能不能让它工作。在本文中,我将介绍我在纯java中实现的一个两层NN的简单实现。
网络架构
下面表示的是我们将在java中实现的两层前馈神经网络。我们将使用以下网络架构,但所有概念都可以针对任意数量的层和节点进行扩展。
双层神经网络
我们将教导我们的神经网络识别的模式是XOR操作。对于操作y = x1 XOR x2,XOR运算符真值表如下所示
一些背景数学
以下是方程式是上述神经网络结构的正向方程。上部索引表示层,下部索引表示节点索引。
第1部分:正向传播方程
矢量
我们都使用for循环来完成大多数需要迭代一长串元素的任务。我确信几乎每个正在阅读本文的人都会在高中或大学时使用for-loop编写他们的第一个矩阵或向量乘法代码。然而,在处理大型数据集(大数据时代的数百万条记录)时执行速度通常较慢。
所以让我们对我们的方程进行矢量化。组合隐藏层的节点计算。
值得注意的是
1.增加节点数会增加权重矩阵的行数。
2.增加特征数量将增加矩阵列数。
例如,假设我们在隐藏层中添加了另一个节点。矩阵方程向下增长。
激活函数
我们需要激活函数来学习数据的输入和目标输出之间的非线性复杂函数映射。从上一节开始,我只是忽略了激活函数方程以便于证明。我们将使用sigmoid激活函数。
第2部分:反向传播方程
我将使用下图来说明梯度计算,然后梯度下降将使用梯度下降来执行可学习参数w和b的更新。为简单起见,我将使用单层神经网络(逻辑回归)。这个想法可以扩展到N层神经网络。
我们将使用交叉熵损失来计算成本
将损失的(dw)导数计算为权重。这可以通过使用如下所示的链式法则来完成。
将损失的(db)导数计算为偏差
更新方程式
我们将使用梯度下降来为每个层执行参数更新,如下所示。
完整的Java代码
理解上述概念是理解此代码如何工作的关键部分。
/**
*
* @author Deus Jeraldy
* @Email: [email protected]
* BSD License
*/
// np.java -> https://gist.github.com/Jeraldy/7d4262db0536d27906b1e397662512bc
import java.util.Arrays;
public class NN {
public static void main(String[] args) {
double[][] X = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
double[][] Y = {{0}, {1}, {1}, {0}};
int m = 4;
int nodes = 400;
X = np.T(X);
Y = np.T(Y);
double[][] W1 = np.random(nodes, 2);
double[][] b1 = new double[nodes][m];
double[][] W2 = np.random(1, nodes);
double[][] b2 = new double[1][m];
for (int i = 0; i < 4000; i++) {
// Foward Prop
// LAYER 1
double[][] Z1 = np.add(np.dot(W1, X), b1);
double[][] A1 = np.sigmoid(Z1);
//LAYER 2
double[][] Z2 = np.add(np.dot(W2, A1), b2);
double[][] A2 = np.sigmoid(Z2);
double cost = np.cross_entropy(m, Y, A2);
costs.getData().add(new XYChart.Data(i, cost));
// Back Prop
//LAYER 2
double[][] dZ2 = np.subtract(A2, Y);
double[][] dW2 = np.divide(np.dot(dZ2, np.T(A1)), m);
double[][] db2 = np.divide(dZ2, m);
//LAYER 1
double[][] dZ1 = np.multiply(np.dot(np.T(W2), dZ2), np.subtract(1.0, np.power(A1, 2)));
double[][] dW1 = np.divide(np.dot(dZ1, np.T(X)), m);
double[][] db1 = np.divide(dZ1, m);
// G.D
W1 = np.subtract(W1, np.multiply(0.01, dW1));
b1 = np.subtract(b1, np.multiply(0.01, db1));
W2 = np.subtract(W2, np.multiply(0.01, dW2));
b2 = np.subtract(b2, np.multiply(0.01, db2));
if (i % 400 == 0) {
print("==============");
print("Cost = " + cost);
print("Predictions = " + Arrays.deepToString(A2));
}
}
}
}
训练结果
以下是训练NN进行4000次迭代后的结果。我们可以清楚地看到(Prediction = [[0.01212,0.9864,0.986300,0.01569]])我们的网络在尝试模拟XOR操作方面做得很好。我们可以看到内部值被推到1而外部值被推到零。
==============
Cost = 0.1257569282040295
Prediction = [[0.15935, 0.8900528, 0.88589, 0.0877284694]]
.
.
.
Cost = 0.015787269324306925
Prediction = [[0.013838, 0.984561, 0.9844246, 0.0177971]]
==============
Cost = 0.013869971354598404
Prediction = [[0.01212, 0.9864, 0.986300, 0.01569]]
结论
这不是神经网络的有效实现,但我的目的是传达对机器学习概念的直观理解,并能够将它们传达给代码。