【问题标题】:Help me with my backprop implementation in Python帮助我在 Python 中实现反向传播
【发布时间】:2011-04-28 15:27:02
【问题描述】:

EDIT2:

新的训练集...

输入:

[
 [0.0, 0.0], 
 [0.0, 1.0], 
 [0.0, 2.0], 
 [0.0, 3.0], 
 [0.0, 4.0], 
 [1.0, 0.0], 
 [1.0, 1.0], 
 [1.0, 2.0], 
 [1.0, 3.0], 
 [1.0, 4.0], 
 [2.0, 0.0], 
 [2.0, 1.0], 
 [2.0, 2.0], 
 [2.0, 3.0], 
 [2.0, 4.0], 
 [3.0, 0.0], 
 [3.0, 1.0], 
 [3.0, 2.0], 
 [3.0, 3.0], 
 [3.0, 4.0],
 [4.0, 0.0], 
 [4.0, 1.0], 
 [4.0, 2.0], 
 [4.0, 3.0], 
 [4.0, 4.0]
]

输出:

[
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [1.0], 
 [1.0], 
 [0.0], 
 [0.0], 
 [0.0], 
 [1.0], 
 [1.0]
]

编辑1:

我已经用我的最新代码更新了这个问题。我修复了一些小问题,但在网络学习后,所有输入组合的输出仍然相同。

这里是反向传播算法的解释:Backprop algorithm


是的,这是一个家庭作业,一开始就说明这一点。

我应该在一个简单的神经网络上实现一个简单的反向传播算法。

我选择 Python 作为这项任务的首选语言,并且我选择了这样的神经网络:

3 层:1 个输入层、1 个隐藏层、1 个输出层:

O         O

                    O

O         O

输入神经元上都有一个整数,输出神经元上有 1 或 0。

这是我的整个实现(有点长)。下面我将选择较短的相关 sn-ps,我认为错误可能位于:

import os
import math
import Image
import random
from random import sample

#------------------------------ class definitions

class Weight:
    def __init__(self, fromNeuron, toNeuron):
        self.value = random.uniform(-0.5, 0.5)
        self.fromNeuron = fromNeuron
        self.toNeuron = toNeuron
        fromNeuron.outputWeights.append(self)
        toNeuron.inputWeights.append(self)
        self.delta = 0.0 # delta value, this will accumulate and after each training cycle used to adjust the weight value

    def calculateDelta(self, network):
        self.delta += self.fromNeuron.value * self.toNeuron.error

class Neuron:
    def __init__(self):
        self.value = 0.0        # the output
        self.idealValue = 0.0   # the ideal output
        self.error = 0.0        # error between output and ideal output
        self.inputWeights = []
        self.outputWeights = []

    def activate(self, network):
        x = 0.0;
        for weight in self.inputWeights:
            x += weight.value * weight.fromNeuron.value
        # sigmoid function
        if x < -320:
            self.value = 0
        elif x > 320:
            self.value = 1
        else:
            self.value = 1 / (1 + math.exp(-x))

class Layer:
    def __init__(self, neurons):
        self.neurons = neurons

    def activate(self, network):
        for neuron in self.neurons:
            neuron.activate(network)

class Network:
    def __init__(self, layers, learningRate):
        self.layers = layers
        self.learningRate = learningRate # the rate at which the network learns
        self.weights = []
        for hiddenNeuron in self.layers[1].neurons:
            for inputNeuron in self.layers[0].neurons:
                self.weights.append(Weight(inputNeuron, hiddenNeuron))
            for outputNeuron in self.layers[2].neurons:
                self.weights.append(Weight(hiddenNeuron, outputNeuron))

    def setInputs(self, inputs):
        self.layers[0].neurons[0].value = float(inputs[0])
        self.layers[0].neurons[1].value = float(inputs[1])

    def setExpectedOutputs(self, expectedOutputs):
        self.layers[2].neurons[0].idealValue = expectedOutputs[0]

    def calculateOutputs(self, expectedOutputs):
        self.setExpectedOutputs(expectedOutputs)
        self.layers[1].activate(self) # activation function for hidden layer
        self.layers[2].activate(self) # activation function for output layer        

    def calculateOutputErrors(self):
        for neuron in self.layers[2].neurons:
            neuron.error = (neuron.idealValue - neuron.value) * neuron.value * (1 - neuron.value)

    def calculateHiddenErrors(self):
        for neuron in self.layers[1].neurons:
            error = 0.0
            for weight in neuron.outputWeights:
                error += weight.toNeuron.error * weight.value
            neuron.error = error * neuron.value * (1 - neuron.value)

    def calculateDeltas(self):
        for weight in self.weights:
            weight.calculateDelta(self)

    def train(self, inputs, expectedOutputs):
        self.setInputs(inputs)
        self.calculateOutputs(expectedOutputs)
        self.calculateOutputErrors()
        self.calculateHiddenErrors()
        self.calculateDeltas()

    def learn(self):
        for weight in self.weights:
            weight.value += self.learningRate * weight.delta

    def calculateSingleOutput(self, inputs):
        self.setInputs(inputs)
        self.layers[1].activate(self)
        self.layers[2].activate(self)
        #return round(self.layers[2].neurons[0].value, 0)
        return self.layers[2].neurons[0].value


#------------------------------ initialize objects etc


inputLayer = Layer([Neuron() for n in range(2)])
hiddenLayer = Layer([Neuron() for n in range(100)])
outputLayer = Layer([Neuron() for n in range(1)])

learningRate = 0.5

network = Network([inputLayer, hiddenLayer, outputLayer], learningRate)

# just for debugging, the real training set is much larger
trainingInputs = [
    [0.0, 0.0],
    [1.0, 0.0],
    [2.0, 0.0],
    [0.0, 1.0],
    [1.0, 1.0],
    [2.0, 1.0],
    [0.0, 2.0],
    [1.0, 2.0],
    [2.0, 2.0]
]
trainingOutputs = [
    [0.0],
    [1.0],
    [1.0],
    [0.0],
    [1.0],
    [0.0],
    [0.0],
    [0.0],
    [1.0]
]

#------------------------------ let's train

for i in range(500):
    for j in range(len(trainingOutputs)):
        network.train(trainingInputs[j], trainingOutputs[j])
        network.learn()

#------------------------------ let's check


for pattern in trainingInputs:
    print network.calculateSingleOutput(pattern)

现在的问题是,在学习网络后,对于所有输入组合,即使是那些应该接近 1.0 的组合,似乎都返回了一个非常接近 0.0 的浮点数。

我在 100 个循环中训练网络,在每个循环中我都这样做:

对于训练集中的每一组输入:

  • 设置网络输入
  • 使用 sigmoid 函数计算输出
  • 计算输出层中的错误
  • 计算隐藏层中的错误
  • 计算权重的增量

然后我根据学习率和累积的增量调整权重。

这是我的神经元激活函数:

def activationFunction(self, network):
    """
    Calculate an activation function of a neuron which is a sum of all input weights * neurons where those weights start
    """
    x = 0.0;
    for weight in self.inputWeights:
        x += weight.value * weight.getFromNeuron(network).value
    # sigmoid function
    self.value = 1 / (1 + math.exp(-x))

这就是我计算增量的方式:

def calculateDelta(self, network):
    self.delta += self.getFromNeuron(network).value * self.getToNeuron(network).error

这是我算法的一般流程:

for i in range(numberOfIterations):
    for k,expectedOutput in trainingSet.iteritems():
        coordinates = k.split(",")
        network.setInputs((float(coordinates[0]), float(coordinates[1])))
        network.calculateOutputs([float(expectedOutput)])
        network.calculateOutputErrors()
        network.calculateHiddenErrors()
        network.calculateDeltas()
    oldWeights = network.weights
    network.adjustWeights()
    network.resetDeltas()
    print "Iteration ", i
    j = 0
    for weight in network.weights:
        print "Weight W", weight.i, weight.j, ": ", oldWeights[j].value, " ............ Adjusted value : ", weight.value
        j += j

输出的最后两行是:

0.552785449458 # this should be close to 1
0.552785449458 # this should be close to 0

它实际上返回所有输入组合的输出编号。

我错过了什么吗?

【问题讨论】:

  • 我认为您将不得不自己做更多的工作——这比您合理期望人们为您调试的代码要多。在所有重要位置添加logging.log 语句以跟踪边缘的权重,并使用计算器通过数字计算几个步骤以查看它们的不一致之处。
  • 阅读:stackoverflow.com/questions/3704570/…。对于 Bayseian 滤波器,这是一个标准问题,具有标准解决方案。对于非常非常小的浮点数,您似乎也有同样的标准问题。
  • @katrielalex 是的,当然,我也会继续努力。
  • @S.Lott:问题不可能来自那里,因为 OP 已经使用对数作为权重,这就是为什么需要 math.exp。这导致了另一个问题:当 x 变得太小或太大时,python 会引发异常,但这与观察到的虚假行为无关(只是一个普通的旧错误)。
  • 只需在calculateSingleOutput 中添加:self.layers[2].runActivationFunctionForAllNeurons(self) 即可。但是除了错误修正之外,收敛性不如您编辑后的第一个版本,这令人惊讶。我看不出哪个更改会产生这种效果。

标签: python algorithm math neural-network


【解决方案1】:

看起来你得到的几乎是 Neuron 的初始状态(接近 self.idealValue)。也许你不应该在提供实际数据之前初始化这个神经元?

编辑:好的,我对代码进行了更深入的研究并对其进行了一些简化(将在下面发布简化版本)。基本上你的代码有两个小错误(看起来像你刚刚忽略的东西),但这会导致网络肯定无法工作。

  • 您在学习阶段忘记在输出层设置 expectedOutput 的值。没有它,网络肯定无法学习任何东西,并且总是会停留在初始的理想值上。 (这是我在第一次阅读时发现的行为)。这个甚至可能已经在您对培训步骤的描述中被发现(如果您没有发布代码,可能会发现,这是我知道实际发布代码隐藏错误而不是制造错误的罕见情况之一明显的)。您在 EDIT1 之后修复了这个问题。
  • 在calculateSingleOutputs中激活网络时,忘记激活隐藏层。

很明显,这两个问题中的任何一个都会导致网络失调。

一旦更正,它就可以工作(嗯,在我的简化版代码中可以)。

错误不容易发现,因为初始代码太复杂了。在引入新类或新方法之前,您应该三思而后行。没有创建足够多的方法或类会使代码难以阅读和维护,但创建太多可能会使代码更难阅读和维护。你必须找到合适的平衡点。我个人找到这种平衡的方法是遵循code smells 并在他们引导我的任何地方重构技术。有时添加方法或创建类,有时删除它们。这当然不是完美的,但这对我有用。

以下是我应用了一些重构后的代码版本。我花了大约一小时更改您的代码,但始终保持其功能等效。我认为这是一个很好的重构练习,因为最初的代码真的很难读。重构后只用了 5 分钟就发现了问题。

import os
import math

"""
A simple backprop neural network. It has 3 layers:
    Input layer: 2 neurons
    Hidden layer: 2 neurons
    Output layer: 1 neuron
"""

class Weight:
    """
    Class representing a weight between two neurons
    """
    def __init__(self, value, from_neuron, to_neuron):
        self.value = value
        self.from_neuron = from_neuron
        from_neuron.outputWeights.append(self)
        self.to_neuron = to_neuron
        to_neuron.inputWeights.append(self)

        # delta value, this will accumulate and after each training cycle
        # will be used to adjust the weight value
        self.delta = 0.0

class Neuron:
    """
    Class representing a neuron.
    """
    def __init__(self):
        self.value = 0.0        # the output
        self.idealValue = 0.0   # the ideal output
        self.error = 0.0        # error between output and ideal output
        self.inputWeights = []    # weights that end in the neuron
        self.outputWeights = []  # weights that starts in the neuron

    def activate(self):
        """
        Calculate an activation function of a neuron which is 
        a sum of all input weights * neurons where those weights start
        """
        x = 0.0;
        for weight in self.inputWeights:
            x += weight.value * weight.from_neuron.value
        # sigmoid function
        self.value = 1 / (1 + math.exp(-x))

class Network:
    """
    Class representing a whole neural network. Contains layers.
    """
    def __init__(self, layers, learningRate, weights):
        self.layers = layers
        self.learningRate = learningRate    # the rate at which the network learns
        self.weights = weights

    def training(self, entries, expectedOutput):
        for i in range(len(entries)):
            self.layers[0][i].value = entries[i]
        for i in range(len(expectedOutput)):
            self.layers[2][i].idealValue = expectedOutput[i]
        for layer in self.layers[1:]:
            for n in layer:
                n.activate()
        for n in self.layers[2]:
            error = (n.idealValue - n.value) * n.value * (1 - n.value)
            n.error = error
        for n in self.layers[1]:
            error = 0.0
            for w in n.outputWeights:
                error += w.to_neuron.error * w.value
            n.error = error
        for w in self.weights:
            w.delta += w.from_neuron.value * w.to_neuron.error

    def updateWeights(self):
        for w in self.weights:
            w.value += self.learningRate * w.delta

    def calculateSingleOutput(self, entries):
        """
        Calculate a single output for input values.
        This will be used to debug the already learned network after training.
        """
        for i in range(len(entries)):
            self.layers[0][i].value = entries[i]
        # activation function for output layer
        for layer in self.layers[1:]:
            for n in layer:
                n.activate()
        print self.layers[2][0].value


#------------------------------ initialize objects etc

neurons = [Neuron() for n in range(5)]

w1 = Weight(-0.79, neurons[0], neurons[2])
w2 = Weight( 0.51, neurons[0], neurons[3])
w3 = Weight( 0.27, neurons[1], neurons[2])
w4 = Weight(-0.48, neurons[1], neurons[3])
w5 = Weight(-0.33, neurons[2], neurons[4])
w6 = Weight( 0.09, neurons[3], neurons[4])

weights = [w1, w2, w3, w4, w5, w6]
inputLayer  = [neurons[0], neurons[1]]
hiddenLayer = [neurons[2], neurons[3]]
outputLayer = [neurons[4]]
learningRate = 0.3
network = Network([inputLayer, hiddenLayer, outputLayer], learningRate, weights)

# just for debugging, the real training set is much larger
trainingSet = [([0.0,0.0],[0.0]),
               ([1.0,0.0],[1.0]),
               ([2.0,0.0],[1.0]),
               ([0.0,1.0],[0.0]),
               ([1.0,1.0],[1.0]),
               ([2.0,1.0],[0.0]),
               ([0.0,2.0],[0.0]),
               ([1.0,2.0],[0.0]),
               ([2.0,2.0],[1.0])]

#------------------------------ let's train
for i in range(100): # training iterations
    for entries, expectedOutput in trainingSet:
        network.training(entries, expectedOutput)
    network.updateWeights()

#network has learned, let's check
network.calculateSingleOutput((1, 0)) # this should be close to 1
network.calculateSingleOutput((0, 0)) # this should be close to 0

顺便说一句,还有第三个问题我没有纠正(但很容易纠正)。如果 x 太大或太小(> 320 或 math.exp() 将引发异常。如果您申请训练迭代,例如几千次,就会发生这种情况。我看到的最简单的纠正方法是检查 x 的值,如果它太大或太小,根据情况将 Neuron 的值设置为 0 或 1,这是极限值。

【讨论】:

  • 好吧,我明天试试。
  • 非常感谢。是的,我想我把它复杂化了。我只是想避免过程式编程,并在 OOP 中做所有事情,然后我就得意忘形了。
  • 顺便试试 print network.calculateSingleOutput(2.0, 1.0)。它会打印不正确的输出:)
  • @Richard Knop:你是说network.calculateSingleOutput([2.0, 1.0]) 吗? (entry 参数只需要一个输入,即两个数字的列表,您的版本会给出语法错误)。通过 100 次学习迭代,它产生:0.04,并不像预期的那样完全为零,但也不是我认为不正确的东西,它仍然接近于零。
  • @Richard Knop:好的,我明白了。它适用于我的版本,而不适用于您的版本。我想这是在 EDIT1 中改变的东西,因为我从初始版本重构。问题的原因(再次)对我来说并不明显,您必须自己检查差异。
猜你喜欢
  • 2014-01-10
  • 2015-09-07
  • 1970-01-01
  • 2017-03-13
  • 2017-05-12
  • 2018-10-20
  • 2013-02-28
  • 1970-01-01
相关资源
最近更新 更多