Я сейчас внедряю 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] до его первоначального значения во время первогоэпоха, так что положительная фаза одинакова в течение каждой тренировочной эпохи (но это не учитывает скорректированные параметры);или б) использование первого скрытого значения узла из предыдущей цепочки (но тогда оно больше не представляет положительную фазу, которая инициировала цепочку).
Может кто-нибудь пролить свет на это?