C/C++實(shí)現(xiàn)手寫數(shù)字識(shí)別的示例詳解
絕大多數(shù)人都是用python寫,俺就喜歡用C/C++一句一句寫。代碼還未認(rèn)真整理,寫的有點(diǎn)亂,應(yīng)該還有優(yōu)化的余地。
程序主要目的:對(duì)測(cè)試數(shù)據(jù)集的樣本進(jìn)行預(yù)測(cè),計(jì)算預(yù)測(cè)準(zhǔn)確率。
數(shù)據(jù)集:分別使用32*32數(shù)據(jù)集和28*28數(shù)據(jù)集(mnist數(shù)據(jù)集)。前者是txt文本,后者是png圖片。
算法:使用最簡(jiǎn)單的KNN算法。
加快程序執(zhí)行的主要思路:
1.使用多線程(10個(gè)線程)讀取0到9數(shù)字對(duì)應(yīng)的樣本文件;
2.使用多線程(10個(gè)線程)對(duì)0到9數(shù)字對(duì)應(yīng)的樣本進(jìn)行預(yù)測(cè);
3.求距離時(shí),使用歐式距離,但是不做開(kāi)平方運(yùn)算(開(kāi)平方運(yùn)算是多余的,浪費(fèi)時(shí)間);
4.求K個(gè)最近距離鄰居(類似于求TOP K),采用C++的push_heap和pop_heap(小根堆)。
對(duì)于32*32的數(shù)據(jù)集,由于樣本是txt文件,直接讀取txt文件中的0和1,很容易求出樣本之間的距離。
對(duì)于mnist數(shù)據(jù)集,因?yàn)樵紨?shù)據(jù)是png圖片,所以需要做一些預(yù)處理:可以使用opencv讀取像素?cái)?shù)據(jù),轉(zhuǎn)成類似于32*32的txt數(shù)據(jù)集,方便求距離。另外,方便程序處理,重命名了文件名,文件名分別是0_0.txt,0_1.txt……,轉(zhuǎn)換成txt文件的mnist數(shù)據(jù)集如下:
簡(jiǎn)便起見(jiàn),對(duì)樣本個(gè)數(shù)做了一些簡(jiǎn)化,32*32數(shù)據(jù)集的訓(xùn)練數(shù)據(jù)是180*10=1800個(gè),測(cè)試數(shù)據(jù)是80*10=800個(gè),mnist數(shù)據(jù)集的訓(xùn)練數(shù)據(jù)是5000*10=5萬(wàn)個(gè),測(cè)試數(shù)據(jù)是800*10=8千個(gè)(原始測(cè)試樣本是1萬(wàn)個(gè),每個(gè)數(shù)字的測(cè)試樣本個(gè)數(shù)不同)。從數(shù)據(jù)量對(duì)比來(lái)看,后者是前者的280倍(訓(xùn)練數(shù)據(jù)集是28倍,測(cè)試數(shù)據(jù)集是10倍),對(duì)應(yīng)的程序運(yùn)行時(shí)間(計(jì)算測(cè)試樣本的預(yù)測(cè)準(zhǔn)確率)后者也是前者的200多倍(排序算法很重要,如果不使用小根堆求TOP K,而是使用傳統(tǒng)的整體數(shù)據(jù)排序,估計(jì)運(yùn)行速度會(huì)差很多)。
部分代碼如下(代碼還未整理,比較亂,暫時(shí)只貼主要代碼,表明整體思路),具體代碼回頭整理好了再貼。
#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 << "測(cè)試集測(cè)試結(jié)果:" << endl; cout << "k=" << K << endl; cout << "測(cè)試集樣本個(gè)數(shù):" << TestDataNum << endl; cout << "預(yù)測(cè)錯(cuò)誤個(gè)數(shù):" << errorCountTotal << endl; cout << "錯(cuò)誤率:" << 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個(gè)樣本預(yù)測(cè)準(zhǔn)確率分析:" << duration << "秒" << endl; duration = (double(t4) - double(t1)) / CLOCKS_PER_SEC; cout << "*********************************************************" << endl; cout << "總共用時(shí):" << duration << "秒" << endl; //…………………… }
運(yùn)行結(jié)果如下:
K=3時(shí),8000個(gè)測(cè)試樣本,預(yù)測(cè)錯(cuò)誤了78個(gè)(其實(shí)算法沒(méi)有問(wèn)題,很多情況是因?yàn)闇y(cè)試樣本里的數(shù)字寫的太奇葩了,數(shù)字X實(shí)在不像數(shù)字X),下面把K改為5,準(zhǔn)確率提高了一些。
從運(yùn)行時(shí)間看,8000個(gè)樣本的預(yù)測(cè)用時(shí)是5秒多,如果只預(yù)測(cè)一個(gè)樣本,用時(shí)大概是5.4秒/8000=0.7毫秒,主要是訓(xùn)練數(shù)據(jù)集數(shù)據(jù)有點(diǎn)多(5萬(wàn)個(gè)樣本)。
下面是32*32數(shù)據(jù)集的運(yùn)行結(jié)果,由于訓(xùn)練樣本和測(cè)試樣本的數(shù)據(jù)量比mnist數(shù)據(jù)集少很多,所以程序運(yùn)行速度快了200倍,平均每個(gè)樣本的預(yù)測(cè)時(shí)間大概是0.03秒/800=0.04毫秒。
程序總共用時(shí)不到50毫秒(用python寫,貌似要10秒左右),比python快了200倍。C語(yǔ)言直接操作內(nèi)存+高速緩存+宇宙第一IDE的代碼優(yōu)化+多線程,運(yùn)行速度比python快2個(gè)數(shù)據(jù)級(jí)很正常。但是代碼量也比python多很多,一般2到3倍,如果python是直接調(diào)算法庫(kù),比python多10倍以上 -_-。
樣本預(yù)測(cè)錯(cuò)誤的具體原因是什么,可以分析K個(gè)最近樣本的距離值,以數(shù)字8(預(yù)測(cè)錯(cuò)誤的重災(zāi)區(qū))為例:
到此這篇關(guān)于C/C++實(shí)現(xiàn)手寫數(shù)字識(shí)別的示例詳解的文章就介紹到這了,更多相關(guān)C++數(shù)字識(shí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言函數(shù)棧幀的創(chuàng)建和銷毀介紹
大家好,本篇文章主要講的是C語(yǔ)言函數(shù)棧幀的創(chuàng)建和銷毀介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2021-12-12C++實(shí)現(xiàn)選擇排序(selectionSort)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)選擇排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04C++ 標(biāo)準(zhǔn)庫(kù)中的 <algorithm> 頭文件算法操作總結(jié)
C++ 標(biāo)準(zhǔn)庫(kù)中的 <algorithm> 頭文件提供了大量有用的算法,主要用于操作容器(如 vector, list, array 等),這些算法通常通過(guò)迭代器來(lái)操作容器元素,本文給大家介紹C++ 標(biāo)準(zhǔn)庫(kù)中的 <algorithm> 頭文件算法總結(jié),感興趣的朋友一起看看吧2025-04-04模擬實(shí)現(xiàn)C語(yǔ)言中的內(nèi)存管理
這篇文章主要內(nèi)容是模擬C語(yǔ)言中的內(nèi)存管理,需要的朋友可以參考下2015-07-07C++實(shí)踐分?jǐn)?shù)類中運(yùn)算符重載的方法參考
今天小編就為大家分享一篇關(guān)于C++實(shí)踐分?jǐn)?shù)類中運(yùn)算符重載的方法參考,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02