Convergência estranha em rede neural simples

Estou lutando há algum tempo com a criação de um NN simplista em Java. Estou trabalhando neste projeto há alguns meses e quero finalizá-lo. Meu principal problema é que não sei como implementar a retropropagação corretamente (todas as fontes usam Python, jargão matemático ou explicam a idéia muito brevemente). Hoje tentei deduzir a ideologia sozinho e a regra que estou usando é:

a atualização de peso = erro * sigmoidDerivative (erro) * próprio peso;
erro = saída - real; (última camada)
error = sigmoidDerivative (erro da camada anterior) * peso que liga esse neurônio ao neurônio que gera o erro (camada intermediária)

Meus principais problemas são que as saídas convergem para um valor médio e meu problema secundário é que os pesos são atualizados para um valor extremamente estranho. (provavelmente o problema de pesos está causando a convergência)

O que estou tentando treinar: para as entradas 1 a 9, a saída esperada é: (x * 1,2 + 1) / 10. Esta é apenas uma regra que me ocorreu aleatoriamente. Estou usando um NN com a estrutura 1-1-1 (3 camadas, 1 rede / camada). No link abaixo, anexei duas execuções: uma na qual estou usando o conjunto de treinamento que segue a regra (x * 1,2 + 1) / 10 e na outra estou usando (x * 1,2 + 1) / 100. Com a divisão por 10, o primeiro peso vai para o infinito; Com a divisão por 100, o segundo peso tende a 0. Eu continuei tentando depurá-lo, mas não tenho idéia do que devo procurar ou do que está errado. Todas as sugestões são muito apreciadas. Agradecemos antecipadamente e um ótimo dia a todos!

https://wetransfer.com/downloads/55be9e3e10c56ab0d6b3f36ad990ebe120171210162746/1a7b6f

Eu tenho como amostras de treinamento 1-> 9 e suas respectivas saídas, seguindo a regra acima e as executo por 100_000 épocas. Registro o erro a cada 100 épocas, pois é mais fácil plotar com menos pontos de dados, enquanto ainda tenho 1000 pontos de dados para cada saída esperada do 9. Código para retropropagação e atualizações de peso:

    //for each layer in the Dweights array
    for(int k=deltaWeights.length-1; k >= 0; k--)
    {
        for(int i=0; i<deltaWeights[k][0].length; i++)     // for each neuron in the layer
        {
            if(k == network.length-2)      // if we're on the last layer, we calculate the errors directly
            {
                outputErrors[k][i] = outputs[i] - network[k+1][i].result;
                errors[i] = outputErrors[k][i];
            }
            else        // otherwise the error is actually the sum of errors feeding backwards into the neuron currently being processed * their respective weight
            {
                for(int j=0; j<outputErrors[k+1].length; j++)
                {                         // S'(error from previous layer) * weight attached to it
                    outputErrors[k][i] += sigmoidDerivative(outputErrors[k+1][j])[0] * network[k+1][i].emergingWeights[j];
                }
            }
        }

        for (int i=0; i<deltaWeights[k].length; i++)           // for each neuron
        {
            for(int j=0; j<deltaWeights[k][i].length; j++)     // for each weight attached to that respective neuron
            {                        // error                S'(error)                                  weight connected to respective neuron                
                deltaWeights[k][i][j] = outputErrors[k][j] * sigmoidDerivative(outputErrors[k][j])[0] * network[k][i].emergingWeights[j];
            }
        }
    }

    // we use the learning rate as an order of magnitude, to scale how drastic the changes in this iteration are
    for(int k=deltaWeights.length-1; k >= 0; k--)       // for each layer
    {
        for (int i=0; i<deltaWeights[k].length; i++)            // for each neuron
        {
            for(int j=0; j<deltaWeights[k][i].length; j++)     // for each weight attached to that respective neuron
            {
                deltaWeights[k][i][j] *=  1;       // previously was learningRate; MSEAvgSlope

                network[k][i].emergingWeights[j] += deltaWeights[k][i][j];
            }
        }
    }

    return errors;

Edit: uma pergunta rápida que vem à mente: como estou usando sigmoide como minha função de ativação, meus neurônios de entrada e saída devem estar apenas entre 0-1? Minha saída está entre 0-1, mas minhas entradas literalmente são 1-9.

Edit2: normalizou os valores de entrada para 0,1-0,9 e mudou:

    outputErrors[k][i] += sigmoidDerivative(outputErrors[k+1][j])[0] * network[k+1][i].emergingWeights[j];     

para:

    outputErrors[k][i] = sigmoidDerivative(outputErrors[k+1][j])[0] * network[k+1][i].emergingWeights[j]* outputErrors[k+1][j];       

para que eu mantenha o sinal do próprio erro de saída. Isso reparou a tendência do infinito no primeiro peso. Agora, com a corrida / 10, o primeiro peso tende a 0 e, com a corrida / 100, o segundo peso tende a 0. Ainda esperando que alguém apareça para esclarecer as coisas para mim. :(

questionAnswers(1)

yourAnswerToTheQuestion