本文首發(fā)于公眾號CVPy:前言
在之前的博客中我們已經(jīng)實現(xiàn)了Net類的設(shè)計和前向傳播和反向傳播的過程。可以說神經(jīng)網(wǎng)絡(luò)的核心的部分已經(jīng)完成。接下來就是應(yīng)用層面了。要想利用神經(jīng)網(wǎng)絡(luò)解決實際的問題a6神經(jīng)網(wǎng)絡(luò)權(quán)值直接確定法,比如說進行手寫數(shù)字的識別,需要用神經(jīng)網(wǎng)絡(luò)對樣本進行迭代訓(xùn)練,訓(xùn)練完成之后,訓(xùn)練得到的模型是好是壞,我們需要對之進行測試。這正是我們現(xiàn)在需要實現(xiàn)的部分的內(nèi)容。
完善后的Net類
需要知道的是現(xiàn)在的Net類已經(jīng)相對完善了,為了實現(xiàn)接下來的功能,不論是成員變量還是成員函數(shù)都變得更加的豐富?,F(xiàn)在的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();
};
可以看到已經(jīng)有了訓(xùn)練的函數(shù)train()、測試的函數(shù)test(),還有實際應(yīng)用訓(xùn)練好的模型的()函數(shù),以及保存和加載模型的函數(shù)save()和load()。大部分成員變量和成員函數(shù)應(yīng)該還是能夠通過名字就能夠知道其功能的。
訓(xùn)練函數(shù)train()
本文重點說的是訓(xùn)練函數(shù)train()和測試函數(shù)test()。這兩個函數(shù)接受輸入(input)和標(biāo)簽(或稱為目標(biāo)值)作為輸入?yún)?shù)。其中訓(xùn)練函數(shù)還要接受一個閾值作為迭代終止條件,最后一個函數(shù)可以暫時忽略不計,那是選擇要不要把loss值實時畫出來的標(biāo)識。
訓(xùn)練的過程如下:
接受一個樣本(即一個單列矩陣)作為輸入,也即神經(jīng)網(wǎng)絡(luò)的第一層;進行前向傳播,也即()函數(shù)做的事情。然后計算loss;如果loss值小于設(shè)定的閾值,則進行反向傳播更新閾值;重復(fù)以上過程直到loss小于等于設(shè)定的閾值。
train函數(shù)的實現(xiàn)如下:
//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;
}
}
這里考慮到了用單個樣本和多個樣本迭代訓(xùn)練兩種情況。而且還有另一種不用loss閾值作為迭代終止條件,而是用正確率的train()函數(shù),內(nèi)容大致相同,此處略去不表。
在經(jīng)過train()函數(shù)的訓(xùn)練之后,就可以得到一個模型了。所謂模型,可以簡單的認(rèn)為就是權(quán)值矩陣。簡單的說,可以把神經(jīng)網(wǎng)絡(luò)當(dāng)成一個超級函數(shù)組合,我們姑且認(rèn)為這個超級函數(shù)就是y = f(x) = ax +b。那么權(quán)值就是a和b。反向傳播的過程是把a和b當(dāng)成自變量來處理的,不斷調(diào)整以得到最優(yōu)值或逼近最優(yōu)值。在完成反向傳播之后,訓(xùn)練得到了參數(shù)a和b的最優(yōu)值,是一個固定值了。這時自變量又變回了x。我們希望a、b最優(yōu)值作為已知參數(shù)的情況下,對于我們的輸入樣本x,通過神經(jīng)網(wǎng)絡(luò)計算得到的結(jié)果y,與實際結(jié)果相符合是大概率事件。
測試函數(shù)test()
test()函數(shù)的作用就是用一組訓(xùn)練時沒用到的樣本,對訓(xùn)練得到的模型進行測試,把通過這個模型得到的結(jié)果與實際想要的結(jié)果進行比較,看正確來說到底是多少,我們希望正確率越多越好。
test()的步驟大致如下幾步:
用一組樣本逐個輸入神經(jīng)網(wǎng)絡(luò);通過前向傳播得到一個輸出值;比較實際輸出與理想輸出,計算正確率。
test()函數(shù)的實現(xiàn)如下:
//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;
}
}
這里在進行前向傳播的時候不是直接調(diào)用()函數(shù),而是調(diào)用了()函數(shù)a6神經(jīng)網(wǎng)絡(luò)權(quán)值直接確定法,函數(shù)的作用是給定一個輸入,給出想要的輸出值。其中包含了對()函數(shù)的調(diào)用。還有就是對于神經(jīng)網(wǎng)絡(luò)的輸出進行解析,轉(zhuǎn)換成看起來比較方便的數(shù)值。
這一篇的內(nèi)容已經(jīng)夠多了,我決定把對于部分的解釋放到下一篇。
源碼鏈接
鏈接: