機器學習(一):記一次k一近鄰算法的學習與Kaggle實戰

本篇博客是基於以Kaggle中手寫數字識別實戰爲目標,以KNN算法學習爲驅動導向來進行講解。python

  1. <a href="#a">寫這篇博客的緣由</a>
  2. <a href="#b">什麼是KNN</a>
  3. <a href="#d">kaggle實戰</a>
  4. <a href="#e">優缺點及其優化方法</a>
  5. <a href="#f">總結</a>
  6. <a href="#g">參考文獻</a>

##<a name="a">寫這篇博客的緣由</a>git

寫下這篇博客,很大程度上是但願能記錄和督促本身學習機器學習的過程,同時也在之後的學習生活中,能夠將之前的博客翻來看看,從新回顧知識。github

##<a name="b">什麼是KNN?</a>算法

在模式識別和機器學習中,k-近鄰算法(如下簡稱:KNN)是一種經常使用的監督學習中分類方法。KNN能夠說是機器學習算法中最簡單的一個算法,我但願它能帶領你們走進機器學習,瞭解其中最基本的原理,並應用於實際生活中。KNN的工做機制很是簡單,它是一種處理分類和迴歸問題的無參算法,簡而言之就是經過某種距離度量,計算出測試集與訓練集之間的距離,選取前k個最近距離的訓練樣本,從這k箇中選出訓練樣本中出現最多的類型來做爲測試樣本的類型。數組

k-近鄰算法的通常流程 :app

(1)收集數據:可使用任何方法。 (2)準備數據:格式化數據格式。 (3)分析數據:可使用任何方法。 (4)訓練算法:K-近鄰算法不涉及訓練。 (3)測試算法:計算錯誤率。 (3)使用算法:輸入樣本數據,進行分類。機器學習

###名詞解釋與案例分析:函數

####以手寫數字識別爲例進行說明:學習

訓練集:一組有標籤的數字圖像,即每張圖片,咱們都對它進行了標註,代表這張圖片所顯示的數字是多少。在本案例中,全部的圖片都是以矩陣的形式保存在數據集中。測試

測試集:一組沒有標籤的數字圖像,即給出了一組圖片,可是並無對它進行標註,即它的類型是什麼,咱們也不清楚。

分類:好比手寫數字識別中,給出一張圖片,咱們能夠清楚的分辨,上面所寫的數字,可是計算機,並不能有效的識別出來,所以機器學習的一個應用即是讓計算機從已知分類狀況,推斷未知狀況的類別。

迴歸:拿函數來講,一個函數在圖像上是連續,且有必定規律的時候,咱們能夠經過函數去算出未知的狀況。計算機就是經過已知狀況,而後模擬生成一個函數,去擬合這樣一個模型,從而推斷出未知的狀況。

距離度量:歐式距離、曼哈頓距離、切比雪夫距離。

樣本:在本篇博客中,每一個樣本就是一張數字圖片,測試集中的樣本集,即每一張測試樣本都是沒有分類的。而訓練集中的樣本集,都是有明確的分類。

這裏,博主只是使用了最基本的KNN算法進行手寫數字識別,經過計算歐式距離,達到計算機對手寫數字識別和分類。

##<a name="d">kaggle實戰</a> 在Kaggle中,有一場比賽是knowledge類型的。嗯,就決定是你了!

首先從Kaggle中下載訓練集及測試集。點開訓練集,能夠看見訓練集是由42000張數字圖片組成,咱們能夠將它轉換爲一個420001的標籤矩陣和一個42000784的像素矩陣。(注:normaling函數和toInt函數是對返回的數據進行格式化。後面會對函數進行說明。)

# 讀取Train數據
def loadTrainData():
    filename = 'train.csv'
    with open(filename, 'r') as f_obj:
        f = [x for x in csv.reader(f_obj)]
        f.remove(f[0])
        f = array(f)
        labels = f[:,0]
        datas = f[:,1:]

        # print(shape(labels))

        return normaling(toInt(datas)), toInt(labels)

打開測試集。由於測試集並無分類,所以並無標籤。因此能夠將這個測試集轉換爲28000*784的像素矩陣。

#讀取Test數據
def loadTestData():
    filename = 'test.csv'
    with open(filename, 'r') as f_obj:
        f = [x for x in csv.reader(f_obj)]
        f.remove(f[0])
        f = array(f)

        return normaling(toInt(f))

前面提到的normaling函數是爲了將數據集進行歸一化,歸一化的目的是爲了解決數據指標之間的可比性,防止某些數據過大,致使分類結果的誤差較大。

#歸一化數據
def normaling(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals

    m = dataSet.shape[0]

    denominator = tile(ranges, (m, 1))
    molecular = dataSet - tile(minVals, (m, 1))

    normData = molecular / denominator

    return normData

而toInt函數是由於從csv文件中獲得的數據都是字符串類型,可是測算距離度量是對於數值類型的,所以須要將字符串類型轉換爲數值類型。

#字符串數組轉換整數
def toInt(array):
    array = mat(array)
    m, n =shape(array)
    newArray = zeros((m, n))
    for i in range(m):
        for j in range(n):
            newArray[i,j] = int(array[i,j])
    return newArray

那麼KNN算法的核心就是經過計算測試集中每個測試樣本與訓練集的距離,選取與測試集最近的k個訓練樣本,再從這k個樣本中,選取出現最多的類型做爲訓練樣本的類別。所以計算測試樣本和訓練集之間的距離以下面代碼所示:

# 核心代碼
def k_NN(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistance = sqDiffMat.sum(axis=1)
    distances = sqDistance**0.5

    sortDisn = argsort(distances)

    # print("sortDisn shape: ",sortDisn.shape)
    # print("labels shape:",labels.shape)

    classCount = {}
    for i in range(k):
        # print(sortDisn[i])
        # print(type(sortDisn[i]))

        vote = labels[sortDisn[i]]

        # print("before :",type(vote))
        vote = ''.join(map(str, vote))
        # print("after :", type(vote))

        classCount[vote] = classCount.get(vote, 0) + 1

    sortedD = sorted(classCount.items(), key=operator.itemgetter(1),
                     reverse=True)
    return sortedD[0][0]

將以上的代碼進行整合,便可把測試集的數據進行分類。

#!/user/bin/python3
# -*- coding:utf-8 -*-
#@Date      :2018/6/30 19:35
#@Author    :Syler
import csv
from numpy import *
import operator
# 核心代碼
def k_NN(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistance = sqDiffMat.sum(axis=1)
    distances = sqDistance**0.5

    sortDisn = argsort(distances)

    # print("sortDisn shape: ",sortDisn.shape)
    # print("labels shape:",labels.shape)

    classCount = {}
    for i in range(k):
        # print(sortDisn[i])
        # print(type(sortDisn[i]))

        vote = labels[sortDisn[i]]

        # print("before :",type(vote))
        vote = ''.join(map(str, vote))
        # print("after :", type(vote))

        classCount[vote] = classCount.get(vote, 0) + 1

    sortedD = sorted(classCount.items(), key=operator.itemgetter(1),
                     reverse=True)
    return sortedD[0][0]

#讀取Train數據
def loadTrainData():
    filename = 'train.csv'
    with open(filename, 'r') as f_obj:
        f = [x for x in csv.reader(f_obj)]
        f.remove(f[0])
        f = array(f)
        labels = f[:,0]
        datas = f[:,1:]

        # print(shape(labels))

        return normaling(toInt(datas)), toInt(labels)

#讀取Test數據
def loadTestData():
    filename = 'test.csv'
    with open(filename, 'r') as f_obj:
        f = [x for x in csv.reader(f_obj)]
        f.remove(f[0])
        f = array(f)

        return normaling(toInt(f))

#歸一化數據
def normaling(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals

    m = dataSet.shape[0]

    denominator = tile(ranges, (m, 1))
    molecular = dataSet - tile(minVals, (m, 1))

    normData = molecular / denominator

    return normData

#字符串數組轉換整數
def toInt(array):
    array = mat(array)
    m, n =shape(array)
    newArray = zeros((m, n))
    for i in range(m):
        for j in range(n):
            newArray[i,j] = int(array[i,j])
    return newArray

#保存結果
def saveResult(res):
    with open('res.csv', 'w', newline='') as fw:
        writer = csv.writer(fw)
        writer.writerows(res)

if __name__ == '__main__':
    dataSet, labels = loadTrainData()
    testSet = loadTestData()
    row = testSet.shape[0]

    # print("dataSet Shape:",dataSet.shape)
    # print("labels Shape before",shape(labels))
    labels = labels.reshape(labels.shape[1],1)
    # print("labels Shape after reshape ", shape(labels))
    # print("testSet Shape",testSet.shape)

    resList = []
    for i in range(row):
        res = k_NN(testSet[i], dataSet, labels, 4)
        resList.append(res)
        print(i)
    saveResult(resList)

那麼把這個數據結果提交到Kaggle上,結果如何呢?

總的來講,此次結果仍是很滿意的。畢竟KNN算法算是機器學習算法中比較基礎的一個算法,可以達到97.185%的準確率,且有66%的排名已經算是很不錯的啦~

###<a name="e">優勢:</a> 簡單、易於理解,易於實現,無需訓練。 適合對稀有事件進行分類。 特別使用於多分類問題,KNN比SVM的表現更好。 ###<a name="e">缺點:</a> KNN算法是基於實例的學習或者說是一種「懶惰學習」。使用算法的時候,咱們必須有儘可能接近實際數據的訓練樣本數據,這很大程度是由於它並無訓練模型這樣一個步驟,致使它必須保存全部數據集。一旦數據集很大,將致使大量的存儲空間。並且加上每次對樣本的分類或迴歸,都要對數據集中每一個數據計算距離值,實際使用會很是耗時。其次,它受「噪聲」影響很大,尤爲是樣本不平衡的時候,會致使分類的結果誤差很大。加上它的另外一個缺陷是沒法給出任何數據的基礎結構信息,並不能知道測試集與訓練集之間具備什麼特徵。 ###<a name="e">優化方法</a> 如今KNN算法的改進主要分紅分類效率和分類效果兩方面。 一種流行的增長精準率的方法是使用進化算法去優化特徵範圍。 另外一種則是經過各類啓發式算法,去選取一個適合的K值。 不論是分類仍是迴歸,都是根據距離度量來進行加權,使得鄰近值更加平均。 ##<a name="f">總結 </a> KNN算法對於分類數據是最簡單最有效的算法,它能幫助咱們迅速瞭解監督學習中的分類算法的基本模型。 ##<a name="g">參考</a> <a href="https://baike.baidu.com/item/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E6%88%98/12344225?fr=aladdin">《機器學習實戰》</a> <a href="https://book.douban.com/subject/26708119/">《機器學習》</a> <a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm">維基百科</a>

Github地址:<a href="https://github.com/578534869/machine-learning">https://github.com/578534869/machine-learning</a> (歡迎follow,互相學習,共同進步!:-) )

相關文章
相關標籤/搜索