分類模型:
分類模型涉及的一個比較關鍵的問題就是輸出的10維向量是如何與具體的類別掛鉤的。實際上:10維向量的每一位都代表一類,在對於訓練集的表達中,如果輸入數據是0,則10維向量的第一位賦值爲1,其餘均爲0。即0對應[1,0,0,0,0,0,0,0,0,0]。MLP模型訓練完成後,就需要對用戶輸入的數據所屬類別進行判定。這時得到的輸出數據基本不可能是正好的所屬類爲1,其他位置爲0。那具體的分類方法就是判斷這10位中哪一位最大,則這個輸入就屬於哪一類。
test_sample = test_set.row(tsample);
//分類器的輸出
nnetwork.predict(test_sample, classificationResult);
//輸出向量中最大的值即爲樣本所屬的類
// 以下的工作就是找到最大的數是哪個
int maxIndex = 0;
float value=0.0f;
float maxValue=classificationResult.at<float>(0,0);
for(int index=1;index<CLASSES;index++)
{
value = classificationResult.at<float>(0,index);
if(value>maxValue)
{
maxValue = value;
maxIndex=index;
}
}
printf("Testing Sample %i -> class result (digit %d)\n", tsample, maxIndex);
測試集:
測試集是用來測試訓練好的模型是否有良好的泛化性,即是否能識別訓練集以外的數據。所以這裏就要求訓練集與測試集最好不要有相同的圖片。如果測試結果不滿意,則需要增加訓練集重新訓練或者調整MLP的參數。
文章的最後將整個CPP文件分享給大家
#include <opencv2/opencv.hpp>
#include <string.h>
#include <fstream>
#include <stdio.h>
using namespace std;
using namespace cv;
#define ATTRIBUTES 135 //每一個樣本的像素總數.9X15
#define CLASSES 10
#define TRAINING_SAMPLES 460
#define TEST_SAMPLES 200
//將int型轉爲string型
string convertInt(int number)
{
stringstream ss;
ss << number;
return ss.str();
}
//將圖像矩陣轉爲一個向量
void convertToPixelValueArray(Mat &img,int pixelarray[])
{
int i =0;
for(int x=0;x<15;x++)
{
for(int y=0;y<9;y++)
{
pixelarray[i]=(img.at<uchar>(x,y)==255)?1:0;
i++;
}
}
}
//讀取樣本集,並將樣本集按照一個樣本一行的形式寫入一個文件
void readFile(string datasetPath,int samplesPerClass,string outputfile )
{
fstream file(outputfile.c_str(),ios::out);
for(int sample = 1; sample<=samplesPerClass;sample++)
{
for(int digit=0;digit<10;digit++)
{ //構建圖像路徑
string imagePath = datasetPath+convertInt(digit)+"\\"+convertInt(sample)+".bmp";
Mat img = imread(imagePath,0);
Mat output;
int pixelValueArray[135];
//圖像矩陣轉爲向量
convertToPixelValueArray(img,pixelValueArray);
//將這個向量寫入文件
for(int d=0;d<135;d++){
file<<pixelValueArray[d]<<",";
}
//將所屬類別寫入文件(行尾)
file<<digit<<"\n";
}
}
file.close();
}
//從樣本集生成的文件中讀取數據
void read_dataset(char *filename, Mat &data, Mat &classes, int total_samples)
{
int label;
float pixelvalue;
FILE* inputfile = fopen( filename, "r" );
for(int row = 0; row < total_samples; row++)
{
for(int col = 0; col <=ATTRIBUTES; col++)
{
if (col < ATTRIBUTES){
fscanf(inputfile, "%f,", &pixelvalue);
data.at<float>(row,col) = pixelvalue;
}
else if (col == ATTRIBUTES){
//輸出向量的結構是應屬類別的位置賦值爲1,其餘賦值爲0
fscanf(inputfile, "%i", &label);
classes.at<float>(row,label) = 1.0;
}
}
}
fclose(inputfile);
}
int main( int argc, char** argv )
{
readFile("E:\\workdir\\NN\\character_train\\",46,"E:\\workdir\\NN\\trainingset.txt");
readFile("E:\\workdir\\NN\\character_test\\",20,"E:\\workdir\\NN\\testset.txt");
//訓練樣本集構成的矩陣
Mat training_set(TRAINING_SAMPLES,ATTRIBUTES,CV_32F);
//訓練樣本集的標籤(輸出向量)構成的矩陣
Mat training_set_classifications(TRAINING_SAMPLES, CLASSES, CV_32F,Scalar(-1));
//測試樣本集構成的矩陣
Mat test_set(TEST_SAMPLES,ATTRIBUTES,CV_32F);
//測試樣本集的標籤(輸出向量)構成的矩陣
Mat test_set_classifications(TEST_SAMPLES,CLASSES,CV_32F,Scalar(-1));
//
Mat classificationResult(1, CLASSES, CV_32F);
read_dataset("E:\\workdir\\NN\\trainingset.txt", training_set, training_set_classifications, TRAINING_SAMPLES);
read_dataset("E:\\workdir\\NN\\testset.txt", test_set, test_set_classifications, TEST_SAMPLES);
// 定義MLP的結構
// 神經網絡總共有三層
// - 135輸入節點
// - 16 隱藏節點
// - 10 輸出節點.
cv::Mat layers(3,1,CV_32S);
layers.at<int>(0,0) = ATTRIBUTES;//input layer
layers.at<int>(1,0)=16;//hidden layer
layers.at<int>(2,0) =CLASSES;//output layer
//創建神經網絡
//for more details check http://docs.opencv.org/modules/ml/doc/neural_networks.html
CvANN_MLP nnetwork(layers, CvANN_MLP::SIGMOID_SYM,2.0/3.0,1);
CvANN_MLP_TrainParams params(
// 終止訓練在 1000 次迭代之後
// 或者神經網絡的權值某次迭代
// 之後發生了很小的改變
cvTermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 1000, 0.000001),
// 使用BP算法訓練
CvANN_MLP_TrainParams::BACKPROP,
// BP算法的係數
// recommended values taken from http://docs.opencv.org/modules/ml/doc/neural_networks.html#cvann-mlp-trainparams
0.1,
0.1);
// 訓練神經網絡
printf( "\nUsing training dataset\n");
int iterations = nnetwork.train(training_set, training_set_classifications,cv::Mat(),cv::Mat(),params);
printf( "Training iterations: %i\n\n", iterations);
// 保存模型到一個XML文件
CvFileStorage* storage = cvOpenFileStorage( "E:\\workdir\\NN\\param.xml", 0, CV_STORAGE_WRITE );
nnetwork.write(storage,"DigitOCR");
cvReleaseFileStorage(&storage);
// 對生成的模型進行測試.
cv::Mat test_sample;
int correct_class = 0;
int wrong_class = 0;
//分類矩陣記錄某個樣本分到某類的次數.
int classification_matrix[CLASSES][CLASSES]={{}};
for (int tsample = 0; tsample < TEST_SAMPLES; tsample++)
{
test_sample = test_set.row(tsample);
//分類器的輸出
nnetwork.predict(test_sample, classificationResult);
//輸出向量中最大的值即爲樣本所屬的類
// 以下的工作就是找到最大的數是哪個
int maxIndex = 0;
float value=0.0f;
float maxValue=classificationResult.at<float>(0,0);
for(int index=1;index<CLASSES;index++)
{
value = classificationResult.at<float>(0,index);
if(value>maxValue)
{
maxValue = value;
maxIndex=index;
}
}
printf("Testing Sample %i -> class result (digit %d)\n", tsample, maxIndex);
//現在比較神經網絡的預測結果與真實結果. 如果分類正確
//test_set_classifications[tsample][ maxIndex] 應該是 1.
//如果分類錯誤, 記錄下來.
if (test_set_classifications.at<float>(tsample, maxIndex)!=1.0f)
{
wrong_class++;
//標記分類矩陣
for(int class_index=0;class_index<CLASSES;class_index++)
{
if(test_set_classifications.at<float>(tsample, class_index)==1.0f)
{
classification_matrix[class_index][maxIndex]++;// A class_index sample was wrongly classified as maxindex.
break;
}
}
}
else
{
correct_class++;
classification_matrix[maxIndex][maxIndex]++;
}
}
//輸出測試結果
printf( "\nResults on the testing dataset\n"
"\tCorrect classification: %d (%g%%)\n"
"\tWrong classifications: %d (%g%%)\n",
correct_class, (double) correct_class*100/TEST_SAMPLES,
wrong_class, (double) wrong_class*100/TEST_SAMPLES);
cout<<" ";
for (int i = 0; i < CLASSES; i++)
{
cout<< i<<"\t";
}
cout<<"\n";
for(int row=0;row<CLASSES;row++)
{
cout<<row<<" ";
for(int col=0;col<CLASSES;col++)
{
cout<<classification_matrix[row][col]<<"\t";
}
cout<<"\n";
}
return 0;
}
參考文獻: http://www.nithinrajs.in/ocr-using-artificial-neural-network-opencv-part-1/