1. 程式人生 > >【人工智慧】利用C語言實現KNN演算法進行手寫數字識別

【人工智慧】利用C語言實現KNN演算法進行手寫數字識別

KNN演算法稱為鄰近演算法,或者說K最近鄰(kNN,k-NearestNeighbor)分類演算法。所謂K最近鄰,就是k個最近的鄰居的意思,說的是每個樣本都可以用它最接近的k個鄰居來代表。

kNN演算法的核心思想是如果一個樣本在特徵空間中的k個最相鄰的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別,並具有這個類別上樣本的特性。該方法在確定分類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。 kNN方法在類別決策時,只與極少量的相鄰樣本有關。由於kNN方法主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來確定所屬類別的,因此對於類域的交叉或重疊較多的待分樣本集來說,kNN方法較其他方法更為適合。

假設給定一個訓練資料:


在不同的類別上有近乎均等的樣本數量,當對一個實際資料進行歸類的時候,計算該資料與訓練樣本的距離(如下圖):


顯然,這種方法對於訓練資料的要求是在每個種類的樣本數量基本相同,當每個種類的樣本數量差別較大的時候會嚴重影響由於K值導致的錯誤:


即便每個種類資料的樣本數量相同,對資料進行歸類的時候也有可能不準確(天然誤差),這種誤差是無法避免的:


下面簡單介紹KNN演算法的C語言實現(以手寫數字識別為例)。

1.手寫數字的標準影象是MNIST資料集,這裡我對這個資料集進行了改動方便檢視,一個手寫資料的格式為:

/*一個手寫數字的結構體*/
typedef struct
{
    int pixel[1024];
    int label;
}Digit;

/*一個手寫數字的txt格式*/
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
9

2.txt手寫資料的載入

int loadDigit(Digit *digit, FILE *fp, int *labels)
/*讀取digit*/
{
    int index=0;
    for(index = 0; index<1024; index++)
    {
        if(!fscanf(fp, "%d", &(digit->pixel[index])) )
        {
            printf("FILE already read finish.\n");
            return -1;
        }
    }
    fscanf(fp, "%d", &(digit->label));
    *labels = digit->label; 
    
    return 1;
}

    int i;
    FILE *fp;
    /*讀入訓練資料*/
    int trainLabels[ntrain];
    int trainCount[10] = {0};
    Digit *Dtrain = (Digit*)malloc(ntrain*sizeof(Digit));
    fp = fopen(trainingFile,"r");
    printf("..load training digits.\n");
    for(i=0;i<ntrain;i++)
    {
        loadDigit(&Dtrain[i], fp, &trainLabels[i]);
        trainCount[Dtrain[i].label] ++;
    }
    fclose(fp);
    printf("..Done.\n");

同樣,對於測試資料的載入方法相同。

3.距離的計算

/*一個有label的距離結構體*/
typedef struct 
{
    float distance;
    int label;
}Distance;

float calDistance(Digit digit1, Digit digit2)
/*求距離*/
{
    int i, squareSum=0.0;
    for(i=0;i<1024;i++)
    {
        squareSum += pow(digit1.pixel[i]-digit2.pixel[i], 2.0);
    }
    return sqrtf(squareSum);
}

4.採用選擇排序演算法對距離進行從小到大排序

void exchange(Distance *in, int index1, int index2) 
/*交換字串兩項*/   
{  
    Distance tmp = (Distance)in[index1];  
    in[index1] = in[index2];  
    in[index2] = tmp;  
}
 
void selectSort(Distance *in, int length)  
/*選擇排序*/ 
{  
    int i, j, min;  
    int N = length;  
    for(i=0;i<N-1;i++)  
    {  
        min = i;  
        for(j=i+1;j<N;j++)  
        {  
            if(in[j].distance<in[min].distance) min = j;   
        }  
        exchange(in,i,min);  
    }   
}

5.資料的預測

在排序後的距離陣列中選擇前K個距離最近的預測值中最多的一個預測值,即為資料的預測值。

6.準確度測試

訓練資料為943個手寫數字,測試資料為196個手寫數字,分別對這196個數據進行準確度測試,取K==1,得出的準確度為:

0:  (  19 / 20 ) =  95.00%
1:  (  20 / 20 ) =  100.00%
2:  (  25 / 25 ) =  100.00%
3:  (  17 / 18 ) =  94.44%
4:  (  21 / 25 ) =  84.00%
5:  (  16 / 16 ) =  100.00%
6:  (  16 / 16 ) =  100.00%
7:  (  19 / 19 ) =  100.00%
8:  (  15 / 17 ) =  88.24%
9:  (  20 / 20 ) =  100.00%

可以看出來,準確度不是十分高,但是也還說得過去吧。

然後給出幾個未知的手寫字元進行預測:


預測的結果(K==1)為:5-2-1-8-2-9-9-1-5

可以看出預測的準確度還是可以的。

下面給出C語言原始碼(程式碼和資料鏈接在文末):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>

typedef const int cint;
typedef const char cchar;

/*一個手寫數字的結構體*/
typedef struct
{
    int pixel[1024];
    int label;
}Digit;

/*一個有label的距離結構體*/
typedef struct 
{
    float distance;
    int label;
}Distance;

/*檔案路徑+名稱*/
cchar *trainingFile = "./mydatasets/my-digit-training.txt";
cchar *testingFile  = "./mydatasets/my-digit-testing.txt";
cchar *predictFile  = "./mydatasets/my-digit-predict.txt";

/*每個資料集的數字個數*/
cint   ntrain = 943;
cint    ntest = 196;
cint npredict = 9;

float calDistance(Digit digit1, Digit digit2)
/*求距離*/
{
    int i, squareSum=0.0;
    for(i=0;i<1024;i++)
    {
        squareSum += pow(digit1.pixel[i]-digit2.pixel[i], 2.0);
    }
    return sqrtf(squareSum);
}

int loadDigit(Digit *digit, FILE *fp, int *labels)
/*讀取digit*/
{
    int index=0;
    for(index = 0; index<1024; index++)
    {
        if(!fscanf(fp, "%d", &(digit->pixel[index])) )
        {
            printf("FILE already read finish.\n");
            return -1;
        }
    }
    fscanf(fp, "%d", &(digit->label));
    *labels = digit->label; 
    
    return 1;
}

void showDigit(Digit digit)
/*顯示一個Digit 結構體*/
{
    int i, j, id;
    for(i=0;i<32;i++)
    {
        for(j=0;j<32;j++)
        {
            printf("%d", digit.pixel[i*32+j]);
        }
        printf("\n");
    }
    printf(" %d \n", digit.label);
}

void exchange(Distance *in, int index1, int index2) 
/*交換字串兩項*/   
{  
    Distance tmp = (Distance)in[index1];  
    in[index1] = in[index2];  
    in[index2] = tmp;  
}
 
void selectSort(Distance *in, int length)  
/*選擇排序*/ 
{  
    int i, j, min;  
    int N = length;  
    for(i=0;i<N-1;i++)  
    {  
        min = i;  
        for(j=i+1;j<N;j++)  
        {  
            if(in[j].distance<in[min].distance) min = j;   
        }  
        exchange(in,i,min);  
    }   
}
int prediction(int K, Digit in, Digit *train, int nt)
/*利用訓練資料預測一個數據digit*/
{
    int i, it;
    Distance distance[nt];
    /*求取輸入digit與訓練資料的距離*/
    for(it=0; it<nt; it++)
    {
        distance[it].distance = calDistance(in, train[it]);
        distance[it].label = train[it].label;  
    }
    /*給計算的距離排序(選擇排序)*/
    int predict = 0;
    selectSort(distance, nt);
    for(i=0; i<K; i++)
    {
        predict += distance[i].label;
    }
    return (int)(predict/K);
}
void knn_classifiy(int K)
/*用測試資料集進行測試*/
{
    printf(".knn_classifiy.\n");
    
    int i;
    FILE *fp;
    
    /*讀入訓練資料*/
    int trainLabels[ntrain];
    int trainCount[10] = {0};
    Digit *Dtrain = (Digit*)malloc(ntrain*sizeof(Digit));
    fp = fopen(trainingFile,"r");
    printf("..load training digits.\n");
    for(i=0;i<ntrain;i++)
    {
        loadDigit(&Dtrain[i], fp, &trainLabels[i]);
        trainCount[Dtrain[i].label] ++;
    }
    fclose(fp);
    printf("..Done.\n");
    
    /*讀入測試資料*/
    int testLabels[ntest];
    int testCount[10] = {0};
    Digit *Dtest = (Digit*)malloc(ntest*sizeof(Digit));
    fp = fopen(testingFile,"r");
    printf("..load testing digits.\n");
    for(i=0;i<ntest;i++)
    {
        loadDigit(&Dtest[i], fp, &testLabels[i]);
        testCount[Dtest[i].label] ++;
    }
    fclose(fp);
    printf("..Done.\n");
    
    /*求測試資料與訓練資料之間的距離*/
    printf("..Cal Distance begin.\n");
    Distance Distance2Train[ntrain];
    int CorrectCount[10] = {0};
    int itrain, itest, predict;
    for(itest=0; itest<ntest; itest++)
    {
        predict = prediction(K, Dtest[itest], Dtrain, ntrain);
        //printf("%d-%d\n",predict, Dtest[itest].label);
        
        /*給預測準確的進行計數*/
        if(predict == Dtest[itest].label)
        {
            CorrectCount[predict] ++;
        }
    }
    
    /*輸出測試資料的準確率*/
    printf("    Correct radio:   \n\n");
    for(i=0;i<10;i++)
    {
        printf("%d:  (  %2d / %2d ) =  %.2f%%\n", 
                i, 
                CorrectCount[i],
                testCount[i],
                (float)(CorrectCount[i]*1.0/testCount[i]*100));
    }
}
void knn_predict(int K)
/*預測資料*/
{
    int i;
    FILE *fp;
    
    /*讀入訓練資料*/
    int trainLabels[ntrain];
    int trainCount[10] = {0};
    Digit *Dtrain = (Digit*)malloc(ntrain*sizeof(Digit));
    fp = fopen(trainingFile,"r");
    printf("..load training digits.\n");
    for(i=0;i<ntrain;i++)
    {
        loadDigit(&Dtrain[i], fp, &trainLabels[i]);
        trainCount[Dtrain[i].label] ++;
    }
    fclose(fp);
    printf("..Done.\n");
    
    /*讀入需要預測的資料*/
    int predictLabels[npredict];
    int predictCount[10] = {0};
    Digit *Dpredict = (Digit*)malloc(npredict*sizeof(Digit));
    fp = fopen(predictFile,"r");
    printf("..load predict digits.\n");
    for(i=0;i<npredict;i++)
    {
        loadDigit(&Dpredict[i], fp, &predictLabels[i]);
        predictCount[Dpredict[i].label] ++;
    }
    fclose(fp);
    printf("..Done.\n");
    
    /*求輸入資料與訓練資料之間的距離*/
    printf("..Cal Distance begin.\n");
    Distance Distance2Train[ntrain];
    int itrain, ipredict, predict;
    for(ipredict=0; ipredict<npredict; ipredict++)
    {
        predict = prediction(K, Dpredict[ipredict], Dtrain, ntrain);
        printf("%d\n",predict);
    }
}
int main(int argc, char** argv)
{
    int K = 1;
    /*對已知資料進行測試,統計預測的正確率*/
    knn_classifiy(K);
    /*對位置資料進行預測*/
    knn_predict(K);
    
    return 1;
}

編譯與執行:

D:\>gcc main.c
D:\>a.exe
.knn_classifiy.
..load training digits.
..Done.
..load testing digits.
..Done.
..Cal Distance begin.
    Correct radio:
0:  (  19 / 20 ) =  95.00%
1:  (  20 / 20 ) =  100.00%
2:  (  25 / 25 ) =  100.00%
3:  (  17 / 18 ) =  94.44%
4:  (  21 / 25 ) =  84.00%
5:  (  16 / 16 ) =  100.00%
6:  (  16 / 16 ) =  100.00%
7:  (  19 / 19 ) =  100.00%
8:  (  15 / 17 ) =  88.24%
9:  (  20 / 20 ) =  100.00%
..load training digits.
..Done.
..load predict digits.
..Done.
..Cal Distance begin.
5
2
1
8
2
9
9
1
5

可以說這個準確度還是可以的。

下面的連結還給出了一個GitHub上另一位作者的專案,使用python寫的,python程式碼一共290行,並且用了很多API介面,我寫的這個用的純C語言,沒用任何的介面,一共237行。我啥也不說。

友情連結:

相關推薦

人工智慧利用C語言實現KNN演算法進行數字識別

KNN演算法稱為鄰近演算法,或者說K最近鄰(kNN,k-NearestNeighbor)分類演算法。所謂K最近鄰,就是k個最近的鄰居的意思,說的是每個樣本都可以用它最接近的k個鄰居來代表。kNN演算法的核心思想是如果一個樣本在特徵空間中的k個最相鄰的樣本中的大多數屬於某一個類

python keras實戰利用VGG卷積神經網路進行字型識別

# encoding: utf-8 import sys reload(sys) sys.setdefaultencoding('utf-8') import numpy as np from keras.datasets import mnist impor

深度學習基於Numpy實現的神經網路進行數字識別

直接先用前面設定的網路進行識別,即進行推理的過程,而先忽視學習的過程。 推理的過程其實就是前向傳播的過程。 深度學習也是分成兩步:學習 + 推理。學習就是訓練模型,更新引數;推理就是用學習到的引數來處理新的資料。 from keras.datasets.mnist impor

AI實戰訓練第一個AI模型:MNIST數字識別模型

在上篇文章中,我們已經把AI的基礎環境搭建好了(見文章:Ubuntu + conda + tensorflow + GPU + pycharm搭建AI基礎環境),接下來將基於tensorflow訓練第一個AI模型:MNIST手寫數字識別模型。 MNIST是一個經典的手寫數字資料集,來自美國國家

Tensorflow - Tutorial (7) : 利用 RNN/LSTM 進行數字識別

ddc htm net sets 手寫 n-2 align csdn global 1. 經常使用類 class tf.contrib.rnn.BasicLSTMCell BasicLSTMCell 是最簡單的一個LSTM類。沒有實現clippi

利用卷積神經網路進行數字識別詳解

import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data ‘’‘可分別用這兩個函式建立卷積核(kernel)與偏置(bias)’’’ #返回一個給定形狀的變數,並自動以截斷正態分佈

機器學習實戰k近鄰演算法(kNN)應用之數字識別程式碼解讀

from numpy import * from os import listdir import operator import time #k-NN簡單實現函式 def classify0(inX,dataSet,labels,k): #求出樣本集的行數,也就是labels標籤的數目

基於k近鄰(KNN)的數字識別

作者:faaronzheng 轉載請註明出處! 最近再看Machine Learning in Action. k近鄰演算法這一章節提供了不少例子,本著Talk is cheap的原則,我們用手寫數字識別來實際測試一下。 簡單的介紹一下k近鄰演算法(KNN):給定測試樣本

C++使用matlab卷積神經網路庫MatConvNet來進行數字識別

環境:WIN10(64 bit)+VS2010(64 bit)+Matlab2015b(64 bit) 我們的目的是將MatConvNet自帶的手寫數字識別DEMO移植到一個簡單的WIN32 DEMO中使用,主要過程有以下幾個步驟: (1)配置MatConvNet

keras實現mnist資料集數字識別

經過幾天的爬坑,“東搞西搞”終於把深度學習的“HELLO,WORLD”做出來了,以下是自己的實戰過程: 關於keras識別手寫數字的入門準備: 2.Mnist資料集的準備 3.匯入資料集測試 4.實現baseline模型: 5.實現簡單的卷積神經網路 一.

吳裕雄 python 神經網絡——TensorFlow實現AlexNet模型處理數字識別MNIST數據集

its iter style 輸出 init 向量 數字 ict sha import tensorflow as tf # 輸入數據 from tensorflow.examples.tutorials.mnist import input_data m

c基礎利用位運算實現數字反轉

#include <stdio.h> #include <stdlib.h> int main() { //通過位運算實現數字的反轉(操作的物件必須是整數) unsigned int original =0x123; unsig

二分查詢C語言實現一個有序陣列的二分查詢

什麼是二分查詢? 首先,二分查詢也叫折半查詢,它是對於一組有序(升序或降序)數列來說的,我們舉例子說明這個思想。 例如:猜數字遊戲 隨機給出1-100內的一個數字,請猜出這個數字 那我們不能隨機沒有規律的去猜,這時考慮二分查詢的思想 例如38 第一次

原始碼剖析tinyhttpd —— C 語言實現最簡單的 HTTP 伺服器

    tinyhttpd 是一個不到 500 行的超輕量型 Http Server,用來學習非常不錯,可以幫助我們真正理解伺服器程式的本質。     看完所有原始碼,真的感覺有很大收穫,無論是 unix 的程式設計,還是 GET/POST 的 Web 處理流程

程式設計之美C語言實現狀態機(實用)

版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。 本文連結:https://www.cnblogs.com/lihuidashen/p/11510532.html https://mp.weixin.qq.com/s/xDAfaEFY

原創利用“程序注入”實現無檔案不死webshell

引子   上週末,一個好兄弟找我說一個很重要的目標shell丟了,這個shell之前是通過一個S2程式碼執行的漏洞拿到的,現在漏洞還在,不過web目錄全部不可寫,問我有沒有辦法搞個webshell繼續做內網。正好我之前一直有個通過“程序注入”來實現記憶體webshell的想法,於是就趁這個機會以Java為例做

Jsp利用Application物件實現訪問人數統計功能

利用Application物件實現訪問人數的統計很常見,但是一些書籍講解這一技術的時候,往往沒有做完整。一旦伺服器重啟,由於Application會被銷燬,重新建立,人數統計則會再一次從零開始。解決方法,個人認為,應該同時利用到資料庫,這樣無論伺服器要重啟或者遭遇到停機什麼

機器學習演算法實現kNN演算法__識別——基於Python和NumPy函式庫

【機器學習演算法實現】系列文章將記錄個人閱讀機器學習論文、書籍過程中所碰到的演算法,每篇文章描述一個具體的演算法、演算法的程式設計實現、演算法的具體應用例項。爭取每個演算法都用多種語言程式設計實現。所

利用C語言實現大數加減法

大數加法: #include <stdio.h> #include <string.h> #define M 100 //定義了數量M是100作為陣列初始化的數量 in

利用C語言實現計算機影象處理的方法

1.影象平移   影象平移只是改變影象在螢幕上的位置,影象本身並不發生變化。   假設原影象區域左上角座標為(x0, y0),右下角座標為(x1, y1),將影象分別沿x和y軸平移dx和dy,則新影象的左上角座標為(x0 + dx, y0 + dy),右下角座標為(x1 + dx, y1 + dy)。座標平