Стойкая констрасивная дивергенция BB-RBM на данных MNIST - PullRequest
0 голосов
/ 25 апреля 2019

Я сейчас внедряю BB-RBM с нуля, так что я полностью понимаю это.У меня все работает, но я изо всех сил пытаюсь перейти с Constrastive Divergence (CD) на Persistent Constrastive Divergence (PCD).

Я понимаю, что вы, когда вы делаете выборку Гиббса, в PCD вы используете предыдущий термин вцепь.Что меня смущает, так это то, как конкретно реализовать это в учебном цикле.Прямо сейчас я имею в C ++:

// train for number of epochs
for (int epoch = 1; epoch <= nEpochs; epoch++) {

    // randomize the rows
    vector<int> rowNumbers;
    for (int rowNumber = 0; rowNumber < trainData->nRows; rowNumber++)
        rowNumbers.push_back(rowNumber);
    for (int i = 0; i < trainData->nRows; i++) {
        int index = rand() % rowNumbers.size();
        randomRows[i] = rowNumbers.at(index);
        rowNumbers.erase(rowNumbers.begin() + index);
    }
    int randRowIndex = 0;

    // loop through all batches
    cout << "batch loop ";
    for (int batchLoop = 1; batchLoop <= nBatchLoops; batchLoop++) {
        cout << batchLoop << " ";

        // Gibbs sample for a random batch of training vectors 
        for (int batchNumber = 1; batchNumber <= batchSize; batchNumber++) {

            // get a random row from the training data matrix (corresponding to a random training vector
            int row = randomRows[randRowIndex++];

            // input data into the visible layer
            for (int i = 0; i < nVis; i++)
                vis[i] = trainData->data[row * trainData->nCols + i];

            // do one Monte Carlo sampling of hidden layer
            for (int j = 0; j < nHid; j++) {

                // sum a response from bias plus weighted inputs
                double sum = hidBias[j];
                for (int i = 0; i < nVis; i++)
                    sum += weights[j * nVis + i] * vis[i];

                // input sum into the sigmoid function, to get the probability of turning this hidden node on
                double prob = 1.0 / (1.0 + exp(-1.0 * sum));

                // get a uniformly random number between [0,1]
                double ran = ((double)rand() / (RAND_MAX));

                // turn this node on or off, based on random number and probability
                if (prob >= ran)
                    hid[j] = 1.0;
                else
                    hid[j] = 0.0;

                // save probability of turning this hidden node on
                hid0Probs[j] = prob;
            }

            // now reconstruct visible layer and sample another stochastic hidden layer state for a given number of Gibbs sampling iterations
            for (int gibbs = 1; gibbs <= nGibbs; gibbs++) {

                // if using PCD, then input the pevious chain state here
                if(PCD && gibbs == 1) {
                    for (int i = 0; i < nVis; i++)
                        vis[i] = chains[row * trainData->nCols + i];
                } 
                // otherwise if we are using CD, do one Monte Carlo to reconstruct visible layer
                else {
                    for (int i = 0; i < nVis; i++) {

                        // sum a response from bias plus weighted inputs
                        double sum = visBias[i];
                        for (int j = 0; j < nHid; j++)
                            sum += weights[j * nVis + i] * hid[j];

                        // input sum into the sigmoid function, to get the probability of turning this visible node on
                        double prob = 1.0 / (1.0 + exp(-1.0 * sum));

                        // get a uniformly random number between [0,1]
                        double ran = ((double)rand() / (RAND_MAX));

                        // turn this node on or off, based on random number and probability
                        if (prob >= ran)
                            vis[i] = 1.0;
                        else
                            vis[i] = 0.0;

                        // save probability of turning the visible node on during reconstruction
                        if (gibbs == nGibbs)
                            visFProbs[i] = prob;

                        // if using PCD, save the value in the chain
                        if (PCD && gibbs == nGibbs)
                            chains[row * trainData->nCols + i] = vis[i];
                    }
                }

                // do one Monte Carlo sampling of hidden layer
                for (int j = 0; j < nHid; j++) {

                    // sum a response from bias plus weighted inputs
                    double sum = hidBias[j];
                    for (int i = 0; i < nVis; i++)
                        sum += weights[j * nVis + i] * vis[i];

                    // input sum into the sigmoid function, to get the probability of turning this hidden node on
                    double prob = 1.0 / (1.0 + exp(-1.0 * sum));

                    // get a uniformly random number between [0,1]
                    double ran = ((double)rand() / (RAND_MAX));

                    // turn this node on or off, based on random number and probability
                    if (prob >= ran)
                        hid[j] = 1.0;
                    else
                        hid[j] = 0.0;

                    // save probability of turning this hidden node on
                    if (gibbs == nGibbs)
                        hidFProbs[j] = prob;
                }
            }

            // calculate partial derivatives using Contrastive Divergence, comparing the input and initial hidden state to the final reconstruction and hidden state
            for (int i = 0; i < nVis; i++) {
                // there is alot of debate about if you should use the binary state values or probabilities of the hidden layer
                // they both work, but I used the probabilities to reduce the effect of the random on/off states

                // add the partial derivative of the energy term with respect to the visible bias term
                gVisBias[i] += (trainData->data[row * trainData->nCols + i]) - (vis[i]); // <>data - <>model

                for (int j = 0; j < nHid; j++) {
                    // add the partial derivative of the energy term with respect to the weight
                    gWeights[j * nVis + i] += (hid0Probs[j] * trainData->data[row * trainData->nCols + i]) - (hidFProbs[j] * vis[i]); // <>data - <>model 
                }
            }
            for (int j = 0; j < nHid; j++)
                // add the partial derivative of the energy term with respect to the hidden bias term
                gHidBias[j] += (hid0Probs[j]) - (hidFProbs[j]); // <>data - <>model

            // calculate training reconstruction error, to be more accurate usually testing reconstruction is calculated by using the same test data every time, what I did here is quicker but dirtier
            for (int i = 0; i < nVis; i++)
                err += pow(vis[i] - trainData->data[row * trainData->nCols + i], 2);

            // grab another random input vector for this batch, and do another batch iteration...
        }

        // only update weights and bias terms if used batchSize number of vectors in this batch, if you have even batches than this will not be a problem
        if (!unevenBatches || (unevenBatches && batchLoop != nBatchLoops)) {

            // now that Gibbs sampling is done for our batch of training vectors, we need to update weights...
            for (int i = 0; i < nVis; i++) {

                // calculate the change in visible bias term
                dVisBias[i] *= learningMomentum;
                dVisBias[i] += learningRate * gVisBias[i] / ((double)batchSize);
                dVisBias[i] -= learningRate * L1 * (visBias[i] == 0 ? 0.0 : (visBias[i] > 0 ? 1.0 : -1.0));
                dVisBias[i] -= learningRate * L2 * visBias[i];

                // update visible bias term
                visBias[i] += dVisBias[i];

                for (int j = 0; j < nHid; j++) {
                    // calculate the change in weight
                    dWeights[j * nVis + i] *= learningMomentum;
                    dWeights[j * nVis + i] += learningRate * gWeights[j * nVis + i] / ((double)batchSize);
                    dWeights[j * nVis + i] -= learningRate * L1 * (weights[j * nVis + i] == 0 ? 0.0 : (weights[j * nVis + i] > 0 ? 1.0 : -1.0));
                    dWeights[j * nVis + i] -= learningRate * L2 * weights[j * nVis + i];

                    // update weight
                    weights[j * nVis + i] += dWeights[j * nVis + i];
                }
            }
            for (int j = 0; j < nHid; j++) {

                // calculate the change in hidden bias term
                dHidBias[j] *= learningMomentum;
                dHidBias[j] += learningRate * gHidBias[j] / ((double)batchSize);
                dHidBias[j] -= learningRate * L1 * (hidBias[j] == 0 ? 0.0 : (hidBias[j] > 0 ? 1.0 : -1.0));
                dHidBias[j] -= learningRate * L2 * hidBias[j];

                // update hidden bias term
                hidBias[j] += dHidBias[j];
            }
        }

        // reset weights and bias term gradients
        for (int i = 0; i < nVis; i++) {
            gVisBias[i] = 0.0;
            for (int j = 0; j < nHid; j++)
                gWeights[j * nVis + i] = 0.0;
        }
        for (int j = 0; j < nHid; j++)
            gHidBias[j] = 0.0;

        // now grab next batch for this epoch...
    }

    // output time to finish this epoch and training reconstruction error
    cout << endl << "epoch #" << epoch << " finished in " << stopWatch.lap() << " milliseconds with a training reconstruction error of " << err << endl;

    // reset vars
    err = 0.0;

    // now go to next epoch, repeating the training process...
}

Теперь я сомневаюсь, правильно ли я реализовал шаг PCD.В оригинальной статье (https://www.cs.toronto.edu/~tijmen/pcd/pcd.pdf) не совсем ясно, как именно реализовать PCD, и мне не удалось найти ответ по статьям, примерам и видео, с которыми я сталкивался.для меня смысл, поскольку он поддерживает начальную точку цепочки MCMC (исходную точку данных), корректирует новые параметры, изменяя часть скрытой вершины положительной фазы, и позволяет выборке Гиббса идти дальше, чем обычный CD, запускаяВыборка Гиббса по предыдущим значениям цепочки для расчета отрицательной фазы. Однако моя реализация может быть абсолютно неверной, поскольку я просто изучаю этот материал без учителя или сверстника.

Я также сомневаюсь в своей реализации по двум другим причинам: 1) мини-пакет, я мог бы видеть, что он может иметь больше достоинств, если цикл эпохи вводится в цикл цикла, поэтому он выполняет полное обновление параметров для nEpochs, прежде чем перейти к другому пакету.2) Вычисление hid0Probs [j], как он у меня есть, теперь перенастраивает его для учета новых параметров каждую эпоху, но я также мог бы увидеть преимущества: a) поддерживая hid0Probs [j] до его первоначального значения во время первогоэпоха, так что положительная фаза одинакова в течение каждой тренировочной эпохи (но это не учитывает скорректированные параметры);или б) использование первого скрытого значения узла из предыдущей цепочки (но тогда оно больше не представляет положительную фазу, которая инициировала цепочку).

Может кто-нибудь пролить свет на это?

...