【发布时间】:2011-02-28 22:56:27
【问题描述】:
我正在尝试制作一个简单的前馈神经网络的 Java 端口。
这显然涉及大量的数值计算,所以我试图尽可能地优化我的中心循环。结果应该在float 数据类型的限制范围内是正确的。
我当前的代码如下(错误处理和初始化已删除):
/**
* Simple implementation of a feedforward neural network. The network supports
* including a bias neuron with a constant output of 1.0 and weighted synapses
* to hidden and output layers.
*
* @author Martin Wiboe
*/
public class FeedForwardNetwork {
private final int outputNeurons; // No of neurons in output layer
private final int inputNeurons; // No of neurons in input layer
private int largestLayerNeurons; // No of neurons in largest layer
private final int numberLayers; // No of layers
private final int[] neuronCounts; // Neuron count in each layer, 0 is input
// layer.
private final float[][][] fWeights; // Weights between neurons.
// fWeight[fromLayer][fromNeuron][toNeuron]
// is the weight from fromNeuron in
// fromLayer to toNeuron in layer
// fromLayer+1.
private float[][] neuronOutput; // Temporary storage of output from previous layer
public float[] compute(float[] input) {
// Copy input values to input layer output
for (int i = 0; i < inputNeurons; i++) {
neuronOutput[0][i] = input[i];
}
// Loop through layers
for (int layer = 1; layer < numberLayers; layer++) {
// Loop over neurons in the layer and determine weighted input sum
for (int neuron = 0; neuron < neuronCounts[layer]; neuron++) {
// Bias neuron is the last neuron in the previous layer
int biasNeuron = neuronCounts[layer - 1];
// Get weighted input from bias neuron - output is always 1.0
float activation = 1.0F * fWeights[layer - 1][biasNeuron][neuron];
// Get weighted inputs from rest of neurons in previous layer
for (int inputNeuron = 0; inputNeuron < biasNeuron; inputNeuron++) {
activation += neuronOutput[layer-1][inputNeuron] * fWeights[layer - 1][inputNeuron][neuron];
}
// Store neuron output for next round of computation
neuronOutput[layer][neuron] = sigmoid(activation);
}
}
// Return output from network = output from last layer
float[] result = new float[outputNeurons];
for (int i = 0; i < outputNeurons; i++)
result[i] = neuronOutput[numberLayers - 1][i];
return result;
}
private final static float sigmoid(final float input) {
return (float) (1.0F / (1.0F + Math.exp(-1.0F * input)));
}
}
我正在使用 -server 选项运行 JVM,到目前为止,我的代码比类似的 C 代码慢 25% 到 50%。我可以做些什么来改善这种情况?
谢谢,
马丁·维博
编辑#1:在看到大量回复后,我可能应该澄清我们场景中的数字。在典型的运行过程中,该方法将使用不同的输入调用大约 50.000 次。一个典型的网络会有 numberLayers = 3 层,分别有 190、2 和 1 个神经元。因此,最里面的循环将有大约2*191+3=385 次迭代(当计算第 0 层和第 1 层中添加的偏置神经元时)
编辑#1:在这个线程中实现了各种建议之后,我们的实现几乎与 C 版本一样快(在 ~2 % 以内)。感谢所有的帮助!所有建议都很有帮助,但由于我只能将一个答案标记为正确答案,因此我会将其提供给 @Durandal 以建议数组优化并作为唯一一个预先计算 for 循环标头的人。
【问题讨论】:
-
你分析过它吗?知道它大部分时间都花在哪里会很有趣。
-
同意分析。不要目不转睛地猜测需要改进的地方。
-
这样的代码容易并行化吗?如果是这样,编写它的多线程版本将大大拥有单线程版本。我去过那里,用 Java 重写了一个正确的多线程快速排序。在 16 核机器上观看是一种乐趣:stackoverflow.com/questions/2210185(它粉碎了默认的 Java API 排序算法 big 次)。除此之外,我看到了一些微优化,但我对神经网络的了解还不够,无法提供太大帮助。 (顺便说一句,最近单 CPU 机器变得很难买到,例如我不知道苹果是否还在销售单 CPU Mac)
-
你确定不只是 JVM 预热?
-
@CurtainDog JVM 在我得到最好的测量值时会预热(比 C 慢 25%-50%)。 @Webinator 好建议(令人印象深刻的算法!)。我可以并行化整个任务并同时运行此方法,所以我不确定我是否看到拆分 compute() 的好处。 @Donnie 和 Brendan Profiling 绝对是要走的路,我只是没有从 jvisualvm 得到任何有意义的结果。明天将尝试另一个分析器。
标签: java performance optimization neural-network micro-optimization