Помогите мне с моей реализацией backprop в Python - PullRequest
7 голосов
/ 21 октября 2010

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]
]

EDIT1:

Я обновил вопрос своим последним кодом. Я исправил несколько мелких проблем, но после того, как сеть узнала, я получаю одинаковый вывод для всех комбинаций ввода.

Вот объясненный алгоритм backprop: Алгоритм backprop


Да, это домашнее задание, чтобы прояснить это с самого начала.

Я должен реализовать простой алгоритм обратного распространения в простой нейронной сети.

Я выбрал Python в качестве языка для этой задачи и выбрал нейронную сеть, подобную этой:

3 слоя: 1 входной, 1 скрытый, 1 выходной слой:

O         O

                    O

O         O

Существует целое число в обоих нейронах inptut и 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(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)

Теперь проблема в том, что после изучения сеть, похоже, возвращает число с плавающей точкой, очень близкое к 0,0 для всех входных комбинаций, даже тех, которые должны быть близки к 1,0.

Я обучаю сеть за 100 циклов, в каждом цикле я делаю:

Для каждого набора входов в обучающем наборе:

  • Настройка сетевых входов
  • Рассчитать выходные данные, используя сигмовидную функцию
  • Расчет ошибок в выходном слое
  • Расчет ошибок в скрытом слое
  • Рассчитать дельты весов

Затем я корректирую веса на основе скорости обучения и накопленных дельт.

Вот моя функция активации для нейронов:

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

Фактически возвращает номер выхода для всех комбинаций ввода.

Я что-то упустил?

1 Ответ

5 голосов
/ 21 октября 2010

Похоже, что вы получаете почти начальное состояние нейрона (почти self.idealValue). Может быть, вам не следует инициализировать этот нейрон перед тем, как предоставить фактические данные?

РЕДАКТИРОВАТЬ : ОК, я посмотрел немного глубже в коде и немного упростил его (опубликую упрощенную версию ниже). В основном в вашем коде есть две незначительные ошибки (похоже, что вы только что пропустили), но это приводит к сети, которая определенно не будет работать.

  • Вы забыли установить значение ожидаемого выхода в выходном слое во время фазы обучения. Без этого сеть определенно не сможет чему-то научиться и всегда будет зависеть от первоначального идеала. (Это поведение, которое я заметил при первом чтении). Этого можно было бы даже заметить в вашем описании этапов обучения (и, вероятно, было бы, если бы вы не опубликовали код, это один из тех редких случаев, которые я знаю, когда фактическое размещение кода скрывало ошибку вместо ее совершения). очевидно). Вы исправили это после EDIT1.
  • при активации сети в CalculateSingleOutputs вы забыли активировать скрытый слой.

Очевидно, что любая из этих двух проблем приведет к дисфункциональной сети.

После исправления это работает (ну, в моей упрощенной версии вашего кода).

Ошибки было нелегко обнаружить, поскольку исходный код был слишком сложным. Вы должны дважды подумать, прежде чем вводить новые классы или новые методы. Недостаточное создание методов или классов затруднит чтение и поддержку кода, но создание слишком большого количества может усложнить чтение и поддержку. Вы должны найти правильный баланс. Мой личный метод найти этот баланс - следовать запахам кода и методам рефакторинга, куда бы они меня ни вели. Иногда добавление методов или создание классов, иногда удаление их. Это конечно не идеально, но это то, что работает для меня.

Ниже приведена моя версия кода после некоторого рефакторинга. Я потратил около часа на изменение вашего кода, но всегда поддерживал его функционально эквивалентным. Я воспринял это как хорошее упражнение по рефакторингу, поскольку исходный код было действительно ужасно читать. После рефакторинга потребовалось всего 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 или <-320), <code>math.exp() вызовет исключение. Это произойдет, если вы подадите заявку на обучение, скажем, несколько тысяч. Самый простой способ исправить то, что я вижу, это проверить значение x и, если оно слишком большое или слишком маленькое, установить значение нейрона на 0 или 1 в зависимости от случая, что является предельным значением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...