1. 程式人生 > >《機器學習實戰》——KNN演算法實戰篇

《機器學習實戰》——KNN演算法實戰篇

#-*-coding:utf-8-*-
from numpy import *
import operator
from os import listdir

"""
KNN演算法的原理:
存在一個樣本資料集合,也稱作訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一資料
與所屬分類的對應關係。輸人沒有標籤的新資料後,將新資料的每個特徵與樣本集中資料對應的特徵進行比較,
然後演算法提取樣本集中特徵最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最
相似的資料,這就是 k 近鄰演算法中k的出處 , 通常k是不大於 20 的整數。最後,選擇 k個最相似資料中
出現次數最多的分類,作為新資料的分類。

 構造資料集 -> 歸一化特徵值 -> 分類 -> 測試錯誤率(調整k值) -> 投入使用
"""

#構造資料集
def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group,labels

#-------------------------------------------使用k近鄰演算法改進約會網站的配對效果-----------------------------------------------
"""
------calssify0()------
------inX: 用於分類的輸入向量
------dataSet: 輸入的訓練樣本集
------labels: 標籤向量(標籤向量的元素數目==dataSet的行數)
------k: 用於選擇最近鄰居的數目
"""

#用KNN進行預測
def classify0(inX,dataSet,labels,k):
    # array.shape返回一個元組(行數,列數),shape[0]表示的是行數
    dataSetSize = dataSet.shape[0]

    #計算距離,使用的是歐氏距離公式
    """
    ----------------tile()--------------------
    函式格式tile(A,reps)
    A和reps都是array_like
    A的型別眾多,幾乎所有型別都可以:array, list, tuple, dict, matrix以及基本資料型別int, string, float以及bool型別。
    reps的型別也很多,可以是tuple,list, dict, array, int, bool.但不可以是float, string, matrix型別。
    例如:
    >>> b=[1,3,5]
    >>> tile(b,[2,3])
    array([[1, 3, 5, 1, 3, 5, 1, 3, 5],
          [1, 3, 5, 1, 3, 5, 1, 3, 5]])
    """
    diffMat = tile(inX,(dataSetSize,1)) - dataSet
    sqDiffMat = diffMat ** 2
    """
    ----------------mat.sum()--------------------
    a1=mat([[1,1],[2,3],[4,2]]);
    a2=a1.sum(axis=0);//列和,這裡得到的是1*2的矩陣
    a3=a1.sum(axis=1);//行和,這裡得到的是3*1的矩陣
    a4=sum(a1[1,:]);//計算第一行所有列的和,這裡得到的是一個數值
    """
    sqDistances = sqDiffMat.sum(axis = 1)   #行和,這裡得到的是4*1的矩陣
    distances = sqDistances ** 0.5

    #選擇距離最小的k個點
    """
    ----------------array.argsort()--------------------
    argsort函式返回的是陣列值從小到大的索引值
    >>> x = np.array([3, 1, 2])
    >>> np.argsort(x)
    array([1, 2, 0])
    """
    sorteDistIndicies = distances.argsort()
    #定義一個字典
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sorteDistIndicies[i]]
        """
        ----------------dict.get()--------------------
        Python 字典 get() 方法和 setdefault() 方法類似,返回指定鍵的值,如果鍵不在字典中,返回一個指定值,預設為None。
        key -- 字典中要查詢的鍵。
        default -- 可選引數,如果指定鍵的值不存在時,返回該值,預設為 None。
        """
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1

    #排序
    """
    ----------------sorted()--------------------
    sorted(iterable[, cmp[, key[, reverse]]])
    sorted() 函式對所有可迭代的物件進行排序操作。
    sort 與 sorted 區別:
    sort 是應用在 list 上的方法,sorted 可以對所有可迭代的物件進行排序操作。
    list 的 sort 方法返回的是對已經存在的列表進行操作,而內建函式 sorted 方法返回的是一個
    新的 list,而不是在原來的基礎上進行的操作。
    # sorted()可以利用引數reverse=True進行反向排序
    >>>list=[3,4,2,6,1]
    >>>sorted(list)
    [1, 2, 3, 4, 6]
    >>>sorted(list, reverse=True)
    [6, 4, 3, 2, 1]
    """
    """
    ----------------dict.items()--------------------
    字典(Dictionary) items() 函式以列表返回可遍歷的(鍵, 值) 元組陣列。
    返回可遍歷的(鍵, 值) 元組陣列。
    """
    """
    ----------------operator.itemgetter()--------------------
    operator模組提供的itemgetter函式用於獲取物件的哪些維的資料,引數為一些序號(即需要獲取的資料在物件中的序號),下面看例子。
    a = [1,2,3] 
    >>> b=operator.itemgetter(1)      //定義函式b,獲取物件的第1個域的值
    >>> b(a) 
    2 
    要注意,operator.itemgetter函式獲取的不是值,而是定義了一個函式,通過該函式作用到物件上才能獲取值。
    """
    sortedClassCount = sorted(classCount.items(),
                              key = operator.itemgetter(1),
                              reverse = True)

    #返回k個最近鄰居出現頻率最高的類別,作為當前inX的預測分類
    return sortedClassCount[0][0]

#將文字記錄轉換為Numpy的解析程式
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()#將每一行內容作為一個元素,共同組成一個列表
    numberOfLines = len(arrayOLines)#列表長度,即列表成員個數
    returnMat = zeros((numberOfLines,3))#用0填充一個(numberOfLines*3)的矩陣
    classLabelVector = []#用於儲存每條資料所對應的類別
    index = 0
    for line in arrayOLines:
        """
        ----------------str.strip()--------------------
        Python strip() 方法用於移除字串頭尾指定的字元(預設為空格或換行符)。
        語法:str.strip([chars]);
        """
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector

# 歸一化特徵值
def autoNorm(dataSet):
    """
    ----------------mat.min()--------------------
    a = np.array([[1,5,3],[4,2,6]])  
    print(a.min()) #無參,所有中的最小值  
    print(a.min(0)) # axis=0; 每列的最小值 ,得到1*3的矩陣
    print(a.min(1)) # axis=1;每行的最小值 ,得到2*1的矩陣
    """
    minVals = dataSet.min(0)#得到1*3的矩陣
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))#shape(矩陣),返回一個元組(行數,列數)
    m = dataSet.shape[0]#矩陣的行數
    normDataSet = dataSet - tile(minVals,(m,1))
    normDataSet = normDataSet/tile(ranges,(m,1))
    return normDataSet, ranges, minVals

'''
----------------datingClassTest()------------------
用於測試knn預測結果的錯誤率
首先把資料集datingDataMat分為兩部分:測試集(0-numTestVecs)和訓練集(numTestVecs-m)
'''
def datingClassTest():
    # hoRatio 是測試集佔資料集的比例
    hoRatio = 0.1
    datingDataMat,datingLabels = file2matrix('datingData.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio) #測試集的資料項數目
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],\
                                     normMat[numTestVecs:m,:],\
                                     datingLabels[numTestVecs:m],\
                                     3)
        print('the classifier came back with: %d,the real answer is:%d'\
              % (classifierResult, datingLabels[i]))
        if(classifierResult != datingLabels[i]):
            errorCount += 1.0
    print('the total error rate is: %f' % (errorCount/float(numTestVecs)))

#約會網站預測函式
"""
每年獲得的飛行常客里程數     玩視訊遊戲所耗時間百分比   每週消費的冰激凌公升數         分類
         7-10                 7-10                    1-3             極具魅力的人
         4-6                  4-6                     4-6             魅力一般的人
         1-3                  1-3                     7-10            不喜歡
"""
def classifyPerson():
    resultList = ['in large doses','in small doses','not at all']
    """
    -----------------input()----------------------
    python2.x 用的是 raw_input()
    python3.x 用的是 input()
    """
    percentTats = float(input(\
        'percentage of time spent playing video games:'))
    ffMiles = float(input(\
        'frequent flier miles earned per years:'))
    iceCream = float(input(\
        'liters of ice cream consumed per year:'))
    datingDataMat,datingLabels = file2matrix('datingData.txt')
    normMatm,ranges,minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles,percentTats,iceCream])
    classifierResult = classify0((inArr-minVals)/ranges,\
                                 normMatm,datingLabels,3)
    print('You will probably like this person:%s'\
          % resultList[classifierResult - 1])


#----------------------------------------------------使用 k-近鄰演算法 識別手寫數字---------------------------------------------------
"""
--------------------img2vector()-------------------------
該函式建立1×1024的NumPy陣列,然後開啟給定的檔案,迴圈讀出檔案的前32行,
並將每行的頭32個字元值儲存在NumPy陣列中,最後返回陣列
"""
def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

"""
--------------------handwritingClassTest()-------------------------
手寫數字識別系統的測試程式碼
"""
def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('digits//trainingDigits')
    m = len(trainingFileList)
    trainingMat = zeros((m,1024))
    for i in range(m):
        #該目錄下的檔案按照規則命名,如檔案9_45.txt的分類是9,它是數字9的第45個例項
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        trainingMat[i,:] = img2vector('digits//trainingDigits//%s' % fileNameStr)
    testFileList = listdir('digits//testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('digits//testDigits//%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest,\
                                     trainingMat,hwLabels,3)
        print('the classifier came back with: %d,the real answer is:%d' \
              % (classifierResult, classNumStr))
        if (classifierResult != classNumStr):
            errorCount += 1.0
    print('the total number of errors is: %d' % (errorCount))
    print('the total error rate is: %f' % (errorCount / float(mTest)))