C/C++實現(xiàn)手寫數(shù)字識別的示例詳解
絕大多數(shù)人都是用python寫,俺就喜歡用C/C++一句一句寫。代碼還未認真整理,寫的有點亂,應(yīng)該還有優(yōu)化的余地。
程序主要目的:對測試數(shù)據(jù)集的樣本進行預(yù)測,計算預(yù)測準確率。
數(shù)據(jù)集:分別使用32*32數(shù)據(jù)集和28*28數(shù)據(jù)集(mnist數(shù)據(jù)集)。前者是txt文本,后者是png圖片。
算法:使用最簡單的KNN算法。
加快程序執(zhí)行的主要思路:
1.使用多線程(10個線程)讀取0到9數(shù)字對應(yīng)的樣本文件;
2.使用多線程(10個線程)對0到9數(shù)字對應(yīng)的樣本進行預(yù)測;
3.求距離時,使用歐式距離,但是不做開平方運算(開平方運算是多余的,浪費時間);
4.求K個最近距離鄰居(類似于求TOP K),采用C++的push_heap和pop_heap(小根堆)。
對于32*32的數(shù)據(jù)集,由于樣本是txt文件,直接讀取txt文件中的0和1,很容易求出樣本之間的距離。
對于mnist數(shù)據(jù)集,因為原始數(shù)據(jù)是png圖片,所以需要做一些預(yù)處理:可以使用opencv讀取像素數(shù)據(jù),轉(zhuǎn)成類似于32*32的txt數(shù)據(jù)集,方便求距離。另外,方便程序處理,重命名了文件名,文件名分別是0_0.txt,0_1.txt……,轉(zhuǎn)換成txt文件的mnist數(shù)據(jù)集如下:
簡便起見,對樣本個數(shù)做了一些簡化,32*32數(shù)據(jù)集的訓練數(shù)據(jù)是180*10=1800個,測試數(shù)據(jù)是80*10=800個,mnist數(shù)據(jù)集的訓練數(shù)據(jù)是5000*10=5萬個,測試數(shù)據(jù)是800*10=8千個(原始測試樣本是1萬個,每個數(shù)字的測試樣本個數(shù)不同)。從數(shù)據(jù)量對比來看,后者是前者的280倍(訓練數(shù)據(jù)集是28倍,測試數(shù)據(jù)集是10倍),對應(yīng)的程序運行時間(計算測試樣本的預(yù)測準確率)后者也是前者的200多倍(排序算法很重要,如果不使用小根堆求TOP K,而是使用傳統(tǒng)的整體數(shù)據(jù)排序,估計運行速度會差很多)。
部分代碼如下(代碼還未整理,比較亂,暫時只貼主要代碼,表明整體思路),具體代碼回頭整理好了再貼。
#define TrainingDataNum 50000 #define TestDataNum 8000 #define FeatureNum 784 //28*28 #define ClassNum 10 #define FileMaxCol 40 //大于28 #define K 3 //…………………… struct DisAndLabel { int distance; int classLabel; }; bool Cmp(const DisAndLabel& a, const DisAndLabel& b) { return a.distance < b.distance; } int FileToTrainingDataArray(int** pTrainingDataFeture, int* pTrainingDataClass, int tid); int FileToTestDataArray(int** pTestDataFeture, int* pTestDataClass, int tid); void ClassifyTest(int** pTestDataFeture, int** pTrainingDataFeture, int* pTestDataClass, int* pTrainingDataClass, int tid); //…………………… int main() { clock_t t1, t2, t3, t4, t5; t1 = clock(); int** pTrainingDataFeture = new int* [TrainingDataNum]; if (pTrainingDataFeture == NULL) { exit(-1); } for (int i = 0; i < TrainingDataNum; i++) { pTrainingDataFeture[i] = new int[FeatureNum + 1]; if (pTrainingDataFeture[i] == NULL) { exit(-1); } } //…………………… t2 = clock(); double duration = (double(t2) - double(t1)) / CLOCKS_PER_SEC; cout << "分配內(nèi)存:" << duration << "秒" << endl; //…………………… thread td[10]; for (int tid = 0; tid < 10; tid++) { td[tid] = thread(&FileToArrayThread, pTrainingDataFeture, pTrainingDataClass, pTestDataFeture, pTestDataClass, tid); } for (int tid = 0; tid < 10; tid++) { td[tid].join(); } //…………………… int errorCountTotal = 0; for (int i = 0; i < 10; i++) { errorCountTotal += errorCount[i]; } cout << "*********************************************************" << endl; cout << "測試集測試結(jié)果:" << endl; cout << "k=" << K << endl; cout << "測試集樣本個數(shù):" << TestDataNum << endl; cout << "預(yù)測錯誤個數(shù):" << errorCountTotal << endl; cout << "錯誤率:" << double(errorCountTotal * 100.0 / TestDataNum) << "%" << endl; cout << "正確率:" << 100.0 - double(errorCountTotal * 100.0 / TestDataNum) << "%" << endl; //cout << "*********************************************************" << endl; t4 = clock(); duration = (double(t4) - double(t3)) / CLOCKS_PER_SEC; cout << "*********************************************************" << endl; cout << "8000個樣本預(yù)測準確率分析:" << duration << "秒" << endl; duration = (double(t4) - double(t1)) / CLOCKS_PER_SEC; cout << "*********************************************************" << endl; cout << "總共用時:" << duration << "秒" << endl; //…………………… }
運行結(jié)果如下:
K=3時,8000個測試樣本,預(yù)測錯誤了78個(其實算法沒有問題,很多情況是因為測試樣本里的數(shù)字寫的太奇葩了,數(shù)字X實在不像數(shù)字X),下面把K改為5,準確率提高了一些。
從運行時間看,8000個樣本的預(yù)測用時是5秒多,如果只預(yù)測一個樣本,用時大概是5.4秒/8000=0.7毫秒,主要是訓練數(shù)據(jù)集數(shù)據(jù)有點多(5萬個樣本)。
下面是32*32數(shù)據(jù)集的運行結(jié)果,由于訓練樣本和測試樣本的數(shù)據(jù)量比mnist數(shù)據(jù)集少很多,所以程序運行速度快了200倍,平均每個樣本的預(yù)測時間大概是0.03秒/800=0.04毫秒。
程序總共用時不到50毫秒(用python寫,貌似要10秒左右),比python快了200倍。C語言直接操作內(nèi)存+高速緩存+宇宙第一IDE的代碼優(yōu)化+多線程,運行速度比python快2個數(shù)據(jù)級很正常。但是代碼量也比python多很多,一般2到3倍,如果python是直接調(diào)算法庫,比python多10倍以上 -_-。
樣本預(yù)測錯誤的具體原因是什么,可以分析K個最近樣本的距離值,以數(shù)字8(預(yù)測錯誤的重災(zāi)區(qū))為例:
到此這篇關(guān)于C/C++實現(xiàn)手寫數(shù)字識別的示例詳解的文章就介紹到這了,更多相關(guān)C++數(shù)字識別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)選擇排序(selectionSort)
這篇文章主要為大家詳細介紹了C++實現(xiàn)選擇排序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-04-04C++ 標準庫中的 <algorithm> 頭文件算法操作總結(jié)
C++ 標準庫中的 <algorithm> 頭文件提供了大量有用的算法,主要用于操作容器(如 vector, list, array 等),這些算法通常通過迭代器來操作容器元素,本文給大家介紹C++ 標準庫中的 <algorithm> 頭文件算法總結(jié),感興趣的朋友一起看看吧2025-04-04