反向传播实现问题

4

我应该做什么。我有一张黑白图像(100x100像素):

alt text

我需要训练一个反向传播神经网络,使用这张图片。输入是图像的x、y坐标(从0到99),输出要么是1(白色),要么是0(黑色)。
一旦网络学习完毕,我希望它能根据权重再现这张图片,并尽可能接近原始图片。
这是我的反向传播实现代码:
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(10)])
outputLayer = Layer([Neuron() for n in range(1)])

learningRate = 0.4

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


# let's get the training set
os.chdir("D:/stuff")
image = Image.open("backprop-input.gif")
pixels = image.load()
bbox = image.getbbox()
width = 5#bbox[2] # image width
height = 5#bbox[3] # image height

trainingInputs = []
trainingOutputs = []
b = w = 0
for x in range(0, width):
    for y in range(0, height):
        if (0, 0, 0, 255) == pixels[x, y]:
            color = 0
            b += 1
        elif (255, 255, 255, 255) == pixels[x, y]:
            color = 1
            w += 1
        trainingInputs.append([float(x), float(y)])
        trainingOutputs.append([float(color)])

print "\nOriginal image ... Black:"+str(b)+" White:"+str(w)+"\n"

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

for i in range(500):
    for j in range(len(trainingOutputs)):
        network.train(trainingInputs[j], trainingOutputs[j])
        network.learn()
    for w in network.weights:
        w.delta = 0.0

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

b = w = 0
for x in range(0, width):
    for y in range(0, height):
        out = network.calculateSingleOutput([float(x), float(y)])
        if 0.0 == round(out):
            color = (0, 0, 0, 255)
            b += 1
        elif 1.0 == round(out):
            color = (255, 255, 255, 255)
            w += 1
        pixels[x, y] = color
        #print out

print "\nAfter learning the network thinks ... Black:"+str(b)+" White:"+str(w)+"\n"

显然,我的实现出了一些问题。上面的代码返回:
原始图像...黑色:21 白色:4
在学习网络之后,它认为...
黑色:25 白色:0
如果我尝试使用更大的训练集(我只是为了测试目的从上面的图像中测试了25个像素),它会做同样的事情。在学习后,它会返回所有像素都应该是黑色的。
现在,如果我改用手动训练集,像这样:
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()
    for w in network.weights:
        w.delta = 0.0

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

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

输出示例如下:

0.0330125791296   # this should be 0, OK
0.953539182136    # this should be 1, OK
0.971854575477    # this should be 1, OK
0.00046146137467  # this should be 0, OK
0.896699762781    # this should be 1, OK
0.112909223162    # this should be 0, OK
0.00034058462280  # this should be 0, OK
0.0929886299643   # this should be 0, OK
0.940489647869    # this should be 1, OK

换句话说,网络猜对了所有像素(黑色和白色)。如果我使用实际图像中的像素而不是像上面那样硬编码训练集,为什么它会说所有像素都应该是黑色?
我尝试改变隐藏层中的神经元数量(最多100个神经元),但没有成功。
这是一项作业。
这也是我关于反向传播的上一个问题的延续。

你为什么打上MATLAB的标签?看起来你只是在使用Python。 - gnovice
@gnovice 嗯,我认为MATLAB经常用于编程神经网络和其他人工智能方面,所以我想一些MATLAB程序员可能能够发现我的算法中的错误,即使它是用Python编写的。 - Richard Knop
1个回答

5
我已经有一段时间没有接触这方面的内容了,但我确实拥有相关领域的学位,所以希望我还是能够帮到你的。从我的理解来看,你的中间层神经元对输入集合进行了过度的负荷。也就是说,你的输入集合包含了10,000个离散的输入值(100像素x 100像素),而你试图将这10,000个值编码到10个神经元中。这种编码水平很难(我认为可能是可行的,但肯定很难)。至少,你需要大量的训练(超过500次)才能使其合理地复现。即使使用100个神经元作为中间层,你仍然会看到相对密集的压缩水平(100像素对应一个神经元)。
至于如何解决这些问题,那就比较棘手了。你可以显著增加中间层神经元的数量,这样会产生合理的效果,但当然需要很长时间的训练。不过,我认为可能有另一种解决方案;如果可能的话,你可以考虑使用极坐标而不是笛卡尔坐标作为输入。快速检查输入模式表明存在高度对称性,实际上你正在处理一个沿着角度坐标重复可预测变形的线性模式,这似乎可以在少量的中间层神经元中进行很好的编码。
这些东西很棘手;寻求模式编码的通用解决方案(就像你最初的解决方案一样)非常复杂,即使使用大量的中间层神经元,也通常需要许多训练次数。另一方面,一些先进的启发式任务分解和一点问题重新定义(即从笛卡尔坐标转换为极坐标)可以为定义良好的问题集提供良好的解决方案。当然,在这里永远存在一个问题; 通用解决方案很难得到,但稍微更具体的解决方案确实非常好。无论如何,这是有趣的事情!

@Amro:谢谢,对称性非常适合极坐标系。 - Paul Sonier
1
@McWafflestix:解决机器学习问题,最重要的是拥有有用的特征(预处理步骤),算法考虑其次(通常可以使用某种交叉验证方法找到最佳模型参数)。 - Amro
@McWafflestix 感谢您的建议。我会尝试它们,但要等到周末才行。我现在非常忙。 - Richard Knop
@RichardKnop:没问题,很高兴能帮忙。请更新我们事情的进展! - Paul Sonier
@Amro:没错。OP说输入是笛卡尔坐标系,所以我对极坐标系有点不确定。如果问题的限制只是输入必须是双重的(即笛卡尔或极坐标系),那么极坐标系就是最佳选择,因为该线性变换最有效地利用了给定输入的特征。 - Paul Sonier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接