3

現在、バックプロパゲーション アルゴリズムに問題があります。私はそれを実装して、顔の方向(左、右、下、まっすぐ)を認識するために使用しようとしています。基本的に、N 個の画像があり、ピクセルを読み取り、その値 (0 から 255) を 0.0 から 1.0 の値に変更します。すべての画像は 32*30 です。960 ニューロンの入力層、3 ニューロンの隠れ層、4 ニューロンの出力層があります。たとえば、出力 <0.1,0.9,0.1,0.1> は、人が右を見ていることを意味します。私は疑似コードに従いました。ただし、正しく機能しません。正しい重みが計算されないため、トレーニングとテストの例を処理できません。コードの一部を次に示します。

    // main function - it runs the algorithm
     private void runBackpropagationAlgorithm() {
        for (int i = 0; i < 900; ++i) {
            for (ImageUnit iu : images) {
                double [] error = calcOutputError(iu.getRatioMatrix(), iu.getClassification());
                changeHiddenUnitsOutWeights(error);
                error = calcHiddenError(error);
                changeHiddenUnitsInWeights(error,iu.getRatioMatrix());
            }
        }
    }

  // it creates the neural network
    private void createNeuroneNetwork() {
            Random generator = new Random();
            for (int i = 0; i < inHiddenUnitsWeights.length; ++i) {
                for (int j = 0; j < hiddenUnits; ++j) {
                    inHiddenUnitsWeights[i][j] = generator.nextDouble();
                }
            }
            for (int i = 0; i < hiddenUnits; ++i) {
                for (int j = 0; j < 4; ++j) {
                    outHddenUnitsWeights[i][j] = generator.nextDouble();
                }
            }
        }
   // Calculates the error in the network. It runs through the whole network.
private double [] calcOutputError(double[][] input, double [] expectedOutput) {
        int currentEdge = 0;
        Arrays.fill(hiddenUnitNodeValue, 0.0);
        for (int i = 0; i < input.length; ++i) {
            for (int j = 0; j < input[0].length; ++j) {
                for (int k = 0; k < hiddenUnits; ++k) {
                    hiddenUnitNodeValue[k] += input[i][j] * inHiddenUnitsWeights[currentEdge][k];
                }
                ++currentEdge;
            }
        }
        double[] out = new double[4];
        for (int j = 0; j < 4; ++j) {
            for (int i = 0; i < hiddenUnits; ++i) {
                out[j] += outHddenUnitsWeights[i][j] * hiddenUnitNodeValue[i];
            }
        }
        double [] error = new double [4];
        Arrays.fill(error, 4);
        for (int i = 0; i < 4; ++i) {
            error[i] = ((expectedOutput[i] - out[i])*(1.0-out[i])*out[i]);
            //System.out.println((expectedOutput[i] - out[i]) + " " + expectedOutput[i] + " " +  out[i]);
        }
        return error;
    }

// Changes the weights of the outgoing edges of the hidden neurons
private void changeHiddenUnitsOutWeights(double [] error) {
        for (int i = 0; i < hiddenUnits; ++i) {
            for (int j = 0; j < 4; ++j) {
                outHddenUnitsWeights[i][j] += learningRate*error[j]*hiddenUnitNodeValue[i];
            }
        }
    }

// goes back to the hidden units to calculate their error.
private double [] calcHiddenError(double [] outputError) {
        double [] error = new double[hiddenUnits];
        for (int i = 0; i < hiddenUnits; ++i) {
            double currentHiddenUnitErrorSum = 0.0;
            for (int j = 0; j < 4; ++j) {
                currentHiddenUnitErrorSum += outputError[j]*outHddenUnitsWeights[i][j];
            }
            error[i] = hiddenUnitNodeValue[i] * (1.0 - hiddenUnitNodeValue[i]) * currentHiddenUnitErrorSum;
        }
        return error;
    }

// changes the weights of the incomming edges to the hidden neurons. input is the matrix of ratios
private void changeHiddenUnitsInWeights(double [] error, double[][] input) {
        int currentEdge = 0;
        for (int i = 0; i < input.length; ++i) {
            for (int j = 0; j < input[0].length; ++j) {
                for (int k = 0; k < hiddenUnits; ++k) {
                    inHiddenUnitsWeights[currentEdge][k] += learningRate*error[k]*input[i][j];
                }
                ++currentEdge;
            }
        }
    }

アルゴリズムが機能するにつれて、重みがどんどん大きくなり、最終的に無限大 (NaN 値) に近づきます。コードを確認しました。残念ながら、私は自分の問題を解決できませんでした。私を助けようとする人には、心から感謝します。

4

5 に答える 5

3

私はあなたのすべてのコードをチェックしませんでした。一般的なアドバイスをしたいだけです。あなたの目標が (1) 顔の方向を学習することなのか、(2) 独自のニューラル ネットワークを実装することなのかはわかりません。

(1) の場合は、これらのライブラリのいずれかを検討する必要があります。それらはそのまま機能し、より柔軟な構成オプションを提供します。たとえば、標準的なバックプロパゲーションは、ニューラル ネットワークにとって最悪の最適化アルゴリズムの 1 つです。収束は学習率に依存します。実装でどの値を選択したかわかりませんが、高すぎる可能性があります。学習率を必要としない、またはトレーニング中に適応させる他の最適化アルゴリズムがあります。さらに、隠れ層の 3 つのニューロンではおそらく十分ではありません。画像に使用されているほとんどのニューラル ネットワークには、数百、場合によっては数千もの隠れユニットがあります。最初に、完全に開発されたライブラリで問題を解決することをお勧めします。それが機能する場合は、独自の ANN を実装してみるか、満足してください。:)

(2) の場合は、まず、より単純な問題を解決してみてください。非常に単純な人工データ セットを使用し、次に標準的なベンチマークを使用して、それを自分のデータで試します。バックプロパゲーションの実装が機能することを確認する良い方法は、数値微分法と比較することです。

于 2012-08-17T09:47:48.130 に答える
2

コードに伝達関数がありません。ソフトマックス出力を持つロジスティック関数が必要なようです。calcOutputError に以下を含める必要があります

// Logistic transfer function for hidden layer. 
for (int k = 0; k < hiddenUnits; ++k) {
    hiddenUnitNodeValue[k] = logistic(hiddenUnitNodeValue[k]);
}

// Softmax transfer function for output layer.
sum = 0;
for (int j = 0; j < 4; ++j) {
    out[j] = logistic(out[j]);
    sum += out[j];
}
for (int j = 0; j < 4; ++j) {
    out[j] = out[j] / sum;
}

ここで、ロジスティック関数は

public double logistic(double x){
    return (1/(1+(Math.exp(-x)));
}

ソフトマックス伝達関数は、合計が 1 になる出力を与えるため、確率として解釈できることに注意してください。

また、出力層の誤差勾配の計算が正しくありません。それは単にあるべきです

for (int i = 0; i < 4; ++i) {
    error[i] = (expectedOutput[i] - out[i]);
} 
于 2012-08-24T00:01:24.063 に答える
0

私はあなたのコードをテストしていませんが、あなたが最初から大きな重みを持っていることはほぼ確実です。主題に関するほとんどの紹介では、「ランダムな値で重みを初期化」し、アルゴリズムが実際にいくつかの開始値に対して発散(Infに移行)することを省略しています。

たとえば-1/5から1/5の間など、小さい開始値を使用して、縮小してみてください。

さらに、行列の乗算の方法を実行します。これを4回使用しただけで、問題があるかどうかを簡単に確認できます。

于 2012-08-17T14:52:25.630 に答える
0

グレースケール画像を処理するニューラルネットワークでも同様の問題が発生しました。0から255の範囲の960個の入力値があります。初期の重みが小さい場合でも、ニューロンへの入力が非常に大きくなり、バックプロパゲーションアルゴリズムがスタックする可能性があります。

ニューラルネットワークに渡す前に、各ピクセル値を255で割ってみてください。それが私のために働いたものです。コメントで取り上げられた浮動小数点の精度の問題のために、非常に小さい初期重みから始めるだけでは十分ではなかったと思います。

別の回答で示唆されているように、アルゴリズムをテストする良い方法は、ネットワークがXORのような単純な関数を学習できるかどうかを確認することです。

そして、その価値のために、隠れ層の3つのニューロンは私の目的には十分でした(顔の画像の性別を識別する)

于 2012-08-17T20:31:15.417 に答える
0

私はまったく新しいニューラル ネットワーク ライブラリを作成しましたが、それは機能します。前回の試みでは、伝達関数とその導関数を使用するというアイデアを見逃していたことは確かです。皆さん、ありがとうございました!

于 2012-09-05T17:11:38.927 に答える