本文首發于公眾號CVPy:前言
在之前的博客中我們已經實現了Net類的設計和前向傳播和反向傳播的過程??梢哉f神經網絡的核心的部分已經完成。接下來就是應用層面了。要想利用神經網絡解決實際的問題a6神經網絡權值直接確定法,比如說進行手寫數字的識別,需要用神經網絡對樣本進行迭代訓練,訓練完成之后,訓練得到的模型是好是壞,我們需要對之進行測試。這正是我們現在需要實現的部分的內容。
完善后的Net類
需要知道的是現在的Net類已經相對完善了,為了實現接下來的功能,不論是成員變量還是成員函數都變得更加的豐富。現在的Net類看起來是下面的樣子:
class Net
{
public:
//Integer vector specifying the number of neurons in each layer including the input and output layers.
std::vector<int> layer_neuron_num;
std::string activation_function = "sigmoid";
double learning_rate;
double accuracy = 0.;
std::vector<double> loss_vec;
float fine_tune_factor = 1.01;
protected:
std::vector<cv::Mat> layer;
std::vector<cv::Mat> weights;
std::vector<cv::Mat> bias;
std::vector<cv::Mat> delta_err;
cv::Mat output_error;
cv::Mat target;
float loss;
public:
Net() {};
~Net() {};
//Initialize net:genetate weights matrices、layer matrices and bias matrices
// bias default all zero
void initNet(std::vector<int> layer_neuron_num_);
//Initialise the weights matrices.
void initWeights(int type = 0, double a = 0., double b = 0.1);
//Initialise the bias matrices.
void initBias(cv::Scalar& bias);

//Forward
void farward();
//Forward
void backward();
//Train,use loss_threshold
void train(cv::Mat input, cv::Mat target_, float loss_threshold, bool draw_loss_curve = false); //Test
void test(cv::Mat &input, cv::Mat &target_);
//Predict,just one sample
int predict_one(cv::Mat &input);
//Predict,more than one samples
std::vector<int> predict(cv::Mat &input);
//Save model;
void save(std::string filename);
//Load model;
void load(std::string filename);
protected:
//initialise the weight matrix.if type =0,Gaussian.else uniform.
void initWeight(cv::Mat &dst, int type, double a, double b);
//Activation function
cv::Mat activationFunction(cv::Mat &x, std::string func_type);
//Compute delta error
void deltaError();
//Update weights
void updateWeights();
};
可以看到已經有了訓練的函數train()、測試的函數test(),還有實際應用訓練好的模型的()函數,以及保存和加載模型的函數save()和load()。大部分成員變量和成員函數應該還是能夠通過名字就能夠知道其功能的。
訓練函數train()
本文重點說的是訓練函數train()和測試函數test()。這兩個函數接受輸入(input)和標簽(或稱為目標值)作為輸入參數。其中訓練函數還要接受一個閾值作為迭代終止條件,最后一個函數可以暫時忽略不計,那是選擇要不要把loss值實時畫出來的標識。
訓練的過程如下:
接受一個樣本(即一個單列矩陣)作為輸入,也即神經網絡的第一層;進行前向傳播,也即()函數做的事情。然后計算loss;如果loss值小于設定的閾值,則進行反向傳播更新閾值;重復以上過程直到loss小于等于設定的閾值。
train函數的實現如下:
//Train,use loss_threshold
void Net::train(cv::Mat input, cv::Mat target_, float loss_threshold, bool draw_loss_curve)
{
if (input.empty())
{
std::cout << "Input is empty!" << std::endl;
return;
}
std::cout << "Train,begain!" << std::endl;
cv::Mat sample;
if (input.rows == (layer[0].rows) && input.cols == 1)
{
target = target_;
sample = input;
layer[0] = sample;
farward();
//backward();
int num_of_train = 0;
while (loss > loss_threshold)
{
backward();
farward();
num_of_train++;
if (num_of_train % 500 == 0)
{
std::cout << "Train " << num_of_train << " times" << std::endl;
std::cout << "Loss: " << loss << std::endl;
}
}
std::cout << std::endl << "Train " << num_of_train << " times" << std::endl;
std::cout << "Loss: " << loss << std::endl;
std::cout << "Train sucessfully!" << std::endl;

}
else if (input.rows == (layer[0].rows) && input.cols > 1)
{
double batch_loss = loss_threshold + 0.01;
int epoch = 0;
while (batch_loss > loss_threshold)
{
batch_loss = 0.;
for (int i = 0; i < input.cols; ++i)
{
target = target_.col(i);
sample = input.col(i);
layer[0] = sample;
farward();
backward();
batch_loss += loss;
}
loss_vec.push_back(batch_loss);
if (loss_vec.size() >= 2 && draw_loss_curve)
{
draw_curve(board, loss_vec);
}
epoch++;
if (epoch % output_interval == 0)
{
std::cout << "Number of epoch: " << epoch << std::endl;
std::cout << "Loss sum: " << batch_loss << std::endl;
}
if (epoch % 100 == 0)
{
learning_rate *= fine_tune_factor;
}
}
std::cout << std::endl << "Number of epoch: " << epoch << std::endl;

std::cout << "Loss sum: " << batch_loss << std::endl;
std::cout << "Train sucessfully!" << std::endl;
}
else
{
std::cout << "Rows of input don't cv::Match the number of input!" << std::endl;
}
}
這里考慮到了用單個樣本和多個樣本迭代訓練兩種情況。而且還有另一種不用loss閾值作為迭代終止條件,而是用正確率的train()函數,內容大致相同,此處略去不表。
在經過train()函數的訓練之后,就可以得到一個模型了。所謂模型,可以簡單的認為就是權值矩陣。簡單的說,可以把神經網絡當成一個超級函數組合,我們姑且認為這個超級函數就是y = f(x) = ax +b。那么權值就是a和b。反向傳播的過程是把a和b當成自變量來處理的,不斷調整以得到最優值或逼近最優值。在完成反向傳播之后,訓練得到了參數a和b的最優值,是一個固定值了。這時自變量又變回了x。我們希望a、b最優值作為已知參數的情況下,對于我們的輸入樣本x,通過神經網絡計算得到的結果y,與實際結果相符合是大概率事件。
測試函數test()
test()函數的作用就是用一組訓練時沒用到的樣本,對訓練得到的模型進行測試,把通過這個模型得到的結果與實際想要的結果進行比較,看正確來說到底是多少,我們希望正確率越多越好。
test()的步驟大致如下幾步:
用一組樣本逐個輸入神經網絡;通過前向傳播得到一個輸出值;比較實際輸出與理想輸出,計算正確率。
test()函數的實現如下:
//Test
void Net::test(cv::Mat &input, cv::Mat &target_)
{
if (input.empty())
{
std::cout << "Input is empty!" << std::endl;
return;
}
std::cout << std::endl << "Predict,begain!" << std::endl;
if (input.rows == (layer[0].rows) && input.cols == 1)
{
int predict_number = predict_one(input);
cv::Point target_maxLoc;
minMaxLoc(target_, NULL, NULL, NULL, &target_maxLoc, cv::noArray());
int target_number = target_maxLoc.y;
std::cout << "Predict: " << predict_number << std::endl;
std::cout << "Target: " << target_number << std::endl;
std::cout << "Loss: " << loss << std::endl;
}

else if (input.rows == (layer[0].rows) && input.cols > 1)
{
double loss_sum = 0;
int right_num = 0;
cv::Mat sample;
for (int i = 0; i < input.cols; ++i)
{
sample = input.col(i);
int predict_number = predict_one(sample);
loss_sum += loss;
target = target_.col(i);
cv::Point target_maxLoc;
minMaxLoc(target, NULL, NULL, NULL, &target_maxLoc, cv::noArray());
int target_number = target_maxLoc.y;
std::cout << "Test sample: " << i << " " << "Predict: " << predict_number << std::endl;
std::cout << "Test sample: " << i << " " << "Target: " << target_number << std::endl << std::endl;
if (predict_number == target_number)
{
right_num++;
}
}
accuracy = (double)right_num / input.cols;
std::cout << "Loss sum: " << loss_sum << std::endl;
std::cout << "accuracy: " << accuracy << std::endl;
}
else
{
std::cout << "Rows of input don't cv::Match the number of input!" << std::endl;
return;
}
}
這里在進行前向傳播的時候不是直接調用()函數,而是調用了()函數a6神經網絡權值直接確定法,函數的作用是給定一個輸入,給出想要的輸出值。其中包含了對()函數的調用。還有就是對于神經網絡的輸出進行解析,轉換成看起來比較方便的數值。
這一篇的內容已經夠多了,我決定把對于部分的解釋放到下一篇。
源碼鏈接
鏈接: