Julia神经网络代码速度与PyPy相同
我有一些用Python写的神经网络代码,后来我把它改写成了Julia。直接用Python运行这段代码大约需要7秒,而用Julia和PyPy运行则只需要大约0.75秒。
sigmoid(z::Float64) = 1/(1 + exp(-z))
sigmoidPrime(z::Float64) = sigmoid(z) * (1 - sigmoid(z))
### Types ###
abstract AbstractNode
type Edge
source::AbstractNode
target::AbstractNode
weight::Float64
derivative::Float64
augmented::Bool
Edge(source::AbstractNode, target::AbstractNode) = new(source, target, randn(1,1)[1], 0.0, false)
end
type Node <: AbstractNode
incomingEdges::Vector{Edge}
outgoingEdges::Vector{Edge}
activation::Float64
activationPrime::Float64
Node() = new([], [], -1.0, -1.0)
end
type InputNode <: AbstractNode
index::Int
incomingEdges::Vector{Edge}
outgoingEdges::Vector{Edge}
activation::Float64
InputNode(index::Int) = new(index, [], [], -1.0)
end
type BiasNode <: AbstractNode
incomingEdges::Vector{Edge}
outgoingEdges::Vector{Edge}
activation::Float64
BiasNode() = new([], [], 1.0)
end
type Network
inputNodes::Vector{InputNode}
hiddenNodes::Vector{Node}
outputNodes::Vector{Node}
function Network(sizes::Array, bias::Bool=true)
inputNodes = [InputNode(i) for i in 1:sizes[1]];
hiddenNodes = [Node() for _ in 1:sizes[2]];
outputNodes = [Node() for _ in 1:sizes[3]];
for inputNode in inputNodes
for node in hiddenNodes
edge = Edge(inputNode, node);
push!(inputNode.outgoingEdges, edge)
push!(node.incomingEdges, edge)
end
end
for node in hiddenNodes
for outputNode in outputNodes
edge = Edge(node, outputNode);
push!(node.outgoingEdges, edge)
push!(outputNode.incomingEdges, edge)
end
end
if bias == true
biasNode = BiasNode()
for node in hiddenNodes
edge = Edge(biasNode, node);
push!(biasNode.outgoingEdges, edge)
push!(node.incomingEdges, edge)
end
end
new(inputNodes, hiddenNodes, outputNodes)
end
end
### Methods ###
function evaluate(obj::Node, inputVector::Array)
if obj.activation > -0.5
return obj.activation
else
weightedSum = sum([d.weight * evaluate(d.source, inputVector) for d in obj.incomingEdges])
obj.activation = sigmoid(weightedSum)
obj.activationPrime = sigmoidPrime(weightedSum)
return obj.activation
end
end
function evaluate(obj::InputNode, inputVector::Array)
obj.activation = inputVector[obj.index]
return obj.activation
end
function evaluate(obj::BiasNode, inputVector::Array)
obj.activation = 1.0
return obj.activation
end
function updateWeights(obj::AbstractNode, learningRate::Float64)
for d in obj.incomingEdges
if d.augmented == false
d.augmented = true
d.weight -= learningRate * d.derivative
updateWeights(d.source, learningRate)
d.derivative = 0.0
end
end
end
function compute(obj::Network, inputVector::Array)
output = [evaluate(node, inputVector) for node in obj.outputNodes]
for node in obj.outputNodes
clear(node)
end
return output
end
function clear(obj::AbstractNode)
for d in obj.incomingEdges
obj.activation = -1.0
obj.activationPrime = -1.0
d.augmented = false
clear(d.source)
end
end
function propagateDerivatives(obj::AbstractNode, error::Float64)
for d in obj.incomingEdges
if d.augmented == false
d.augmented = true
d.derivative += error * obj.activationPrime * d.source.activation
propagateDerivatives(d.source, error * d.weight * obj.activationPrime)
end
end
end
function backpropagation(obj::Network, example::Array)
output = [evaluate(node, example[1]) for node in obj.outputNodes]
error = output - example[2]
for (node, err) in zip(obj.outputNodes, error)
propagateDerivatives(node, err)
end
for node in obj.outputNodes
clear(node)
end
end
function train(obj::Network, labeledExamples::Array, learningRate::Float64=0.7, iterations::Int=10000)
for _ in 1:iterations
for ex in labeledExamples
backpropagation(obj, ex)
end
for node in obj.outputNodes
updateWeights(node, learningRate)
end
for node in obj.outputNodes
clear(node)
end
end
end
labeledExamples = Array[Array[[0,0,0], [0]],
Array[[0,0,1], [1]],
Array[[0,1,0], [0]],
Array[[0,1,1], [1]],
Array[[1,0,0], [0]],
Array[[1,0,1], [1]],
Array[[1,1,0], [1]],
Array[[1,1,1], [0]]];
neuralnetwork = Network([3,4,1])
@time train(neuralnetwork, labeledExamples)
我没有提供Python代码,因为我不确定这是否必要(不过如果你真的想要,我可以提供)。我并不指望大家花很多时间去完全理解这段代码,我主要是想找出在Julia实现中明显的或系统性的低效之处(而不是算法本身的问题)。
我这样做的动机是,设计神经网络的方式比用Numpy进行向量化算法要自然得多,但在Python中,所有的循环和类结构之间的跳转都比较慢。
因此,我觉得把它移植到Julia是个不错的选择,看看能否大幅提升速度。虽然用Julia的速度比直接用Python快一个数量级已经很不错,但我真正希望的是能比PyPy快一个数量级(我在网上找到的一些基准测试似乎表明这是一个合理的期待)。
注意: 这段代码必须在Julia 0.3中运行才能正常工作。
1 个回答
这段内容看起来更像是代码审查,而不是一个问题(没有问号),不过我还是来试试解释一下。唯一明显的性能问题是你在 evaluate
、compute
和 backpropagation
这几个地方用列表推导式来分配数组。其实在 evaluate
中计算加权和,用一个 for 循环会更高效。对于另外两个方法,你可能想用预先分配好的数组,而不是列表推导式。你可以使用 Julia 的 内置性能分析工具 来查看你的代码大部分时间花在哪里,这样可能会发现一些不明显的性能瓶颈,进而进行优化。
关于与 PyPy 的比较,Julia 和 PyPy 在这段代码上表现得都很好,性能接近 C 语言,所以你不会期待 Julia 比 PyPy 快很多,因为它们都接近最佳性能。与 C 语言的实现性能比较会很有帮助,因为这能显示出 Julia 和 PyPy 各自还有多少性能可以提升。幸运的是,这段代码看起来很容易就能移植到 C 语言。