机器学习实战——KNN

一、简介

参考:https://blog.csdn.net/c406495762/article/details/75172850

环境:win 10, notebook,python 3.6

原blog较长,主要实现其算法,尽可能的简洁些,并对一些细节给出实例,

如果熟练,细节处的函数尽可能的可以忽略,写出来主要是未来加强印象。

三、四、五、三个实例,前两个同一种方法,分类函数是同一个,只是数据不一样,最后一个使用SKLerrn实现KNN,十分方便。

 

二、算法简介

1、原理:

输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

2、距离度量

欧式距离

3、步骤:

计算已知类别数据集中的点与当前点之间的距离;
按照距离递增次序排序;
选取与当前点距离最小的k个点;
确定前k个点所在类别的出现频率;
返回前k个点所出现频率最高的类别作为当前点的预测分类。

 

k-近邻算法的一般流程:

收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据。一般来讲,数据放在txt文本文件中,按照一定的格式进行存储,便于解析及处理。
准备数据:使用Python解析、预处理数据。
分析数据:可以使用很多方法对数据进行分析,例如使用Matplotlib将数据可视化。
测试算法:计算错误率。
使用算法:错误率在可接受范围内,就可以运行k-近邻算法进行分类。

 

优缺点:

优点

简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
可用于数值型数据和离散型数据;
训练时间复杂度为O(n);无数据输入假定;
对异常值不敏感。
缺点:

计算复杂性高;空间复杂性高;
样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易发生误分。
最大的缺点是无法给出数据的内在含义。
 

 

三、KNN简单实例

训练集是一个矩阵,测试集也是一个矩阵,要预测测试集中每一个数据的类别

算法步骤:

1、将测试集中的每一个数据扩展成和训练集一样大小的矩阵,用于该数据与训练集的每一个数据进行距离计算

2、扩展矩阵与训练集对应元素相减,

3、相减后的每一个元素进行平方,

4、每一行的元素相加

5、开根号,

6、找出k个最小的的值,这k个中,类别最多的那个即是预测类型

如图:

代码如下:

def classify(inX,dataSet,labels,k):
    # 返回行数
    dataSetSize = dataSet.shape[0]
    # 扩展维度,相减
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 平方
    sqDiffMat = diffMat**2
    # 求和
    sqDistances = sqDiffMat.sum(axis=1)
    # 开根号
    distances = sqDistances**0.5
    
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 对字典按值排序,返回的是一个列表,列表元素是元组
    sortedClassCount = sorted(classCount.items(),key=lambda item:item[1],reverse=True)
    return sortedClassCount[0][0]

完整代码:

import numpy as np

group = np.array([[1,101],[5,89],[108,5],[115,8]])
labels = ['爱情片','爱情片','动作片','动作片']

def classify(inX,dataSet,labels,k):
    # 返回行数
    dataSetSize = dataSet.shape[0]
    # 扩展维度,相减
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 对字典按值排序,返回的是一个列表,列表元素是元组
    sortedClassCount = sorted(classCount.items(),key=lambda item:item[1],reverse=True)
    return sortedClassCount[0][0]


test = [101,20]
test_class = classify(test, group, labels, 3)
print(test_class)

预测结果:

动作片

注:源代码使用了operator模块对字典进行排序

 

python补充:

四个函数,tile(),  np.sum(),  np.argsort(),  sorted(), 

(1)np.tile()

import numpy as np

a = [2,3]
b  = np.tile(a,(4,3))
print(b)
[[2 3 2 3 2 3]
 [2 3 2 3 2 3]
 [2 3 2 3 2 3]
 [2 3 2 3 2 3]]

(2)np.argsort():

返回从小到大排序后的索引值

import numpy as np

a = np.array([1,2,3,4])
b = np.array([4,3,2,1])
print(a.argsort())
print(b.argsort())
[0 1 2 3]
[3 2 1 0]

(3)np.sum():

0:返回列相加

1:返回行相加

import numpy as np

a = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
print(a)
print('---------axis=0-----------')
b = a.sum(axis=0)
print(b)
print('---------axis=1-----------')
c = a.sum(axis=1)
print(c)
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]
---------axis=0-----------
[22 26 30]
---------axis=1-----------
[ 6 15 24 33]

(4)sorted():

第一个参数:可迭代对象

第二个参数,排序规则

第三个参数,是否降序,True为降序,False为升序,

返回值是一个列表,

d = {'mom':23,'linme':34,'ioio':89,'falali':232}
a = sorted(d.items(),key=lambda item:item[1],reverse=True)
print(a)
c = sorted(d.items(),key=lambda item:item[1],reverse=False)
print(c)
[('falali', 232), ('ioio', 89), ('linme', 34), ('mom', 23)]
[('mom', 23), ('linme', 34), ('ioio', 89), ('falali', 232)]

 

(5)数据处理过程:

import numpy as np

group = np.array([[1,2],[6,4],[80,81],[78,83]])
print('----------group------------')
print('group: ')
print(group)

test = [3,4]
mat = np.tile(test,(4,1))
print('----------mat------------')
print('mat: ')
print(mat)

mat_g = mat - group
print('----------mat_g------------')
print('mat_g: ')
print(mat_g)

mat_2 = mat_g**2
print('----------mat_2------------')
print('mat_2: ')
print(mat_2)

mat_sum = mat_2.sum(axis=1)
print('----------mat_sum------------')
print('mat_sum: ')
print(mat_sum)
----------group------------
group: 
[[ 1  2]
 [ 6  4]
 [80 81]
 [78 83]]
----------mat------------
mat: 
[[3 4]
 [3 4]
 [3 4]
 [3 4]]
----------mat_g------------
mat_g: 
[[  2   2]
 [ -3   0]
 [-77 -77]
 [-75 -79]]
----------mat_2------------
mat_2: 
[[   4    4]
 [   9    0]
 [5929 5929]
 [5625 6241]]
----------mat_sum------------
mat_sum: 
[    8     9 11858 11866]

 

四、海伦数据

文件总共四列,最后一列是标签,海伦收集的样本数据主要包含以下3种特征:

每年获得的飞行常客里程数
玩视频游戏所消耗时间百分比
每周消费的冰淇淋公升数

分析数据:数据可视化,数据归一化

 

 

 

数据读取,获取特征矩阵和标签,

在windows上使用open打开utf-8编码的txt文件时开头会有一个多余的字符\ufeff,它叫BOM,是用来声明编码等信息的,但python会把它当作文本解析。对UTF-16, Python将BOM解码为空字串。然而对UTF-8, BOM被解码为一个字符\ufeff。

步骤:

打开文件, 去除BOM, 获取行数, 对于每一行:

去除每行前后空白符, 按tab切分 前三列保存到特征矩阵中, 最后一保存到标签中,

def file2matrix(filename):
    fr = open(filename,'r',encoding = 'utf-8')
    arrayOLines = fr.readlines()
    #针对有BOM的UTF-8文本,应该去掉BOM,否则后面会引发错误。
    arrayOLines[0]=arrayOLines[0].lstrip('\ufeff')
    numberOfLines = len(arrayOLines)
    returnMat = np.zeros((numberOfLines,3))
    
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split('\t')
        #将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
        
    return returnMat, classLabelVector

归一化步骤:

1、找出每一列最大、小值,返回值是一维列表,每个元素代表该列最大、小值

2、最大值列表减去最小值列表得出差列表

3、将最小值列表扩展为数据集大小,

4、将差值列表扩展为数据集大小,

5、原始数据减去最小值扩展,然后除以差值扩展

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    # 原始值减去最小值
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

则全部代码:

import numpy as np

def classify(inX,dataSet,labels,k):
    # 返回行数
    dataSetSize = dataSet.shape[0]
    # 扩展维度,相减
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 对字典按值排序,返回的是一个列表,列表元素是元组
    sortedClassCount = sorted(classCount.items(),key=lambda item:item[1],reverse=True)
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename,'r',encoding = 'utf-8')
    arrayOLines = fr.readlines()
    #针对有BOM的UTF-8文本,应该去掉BOM,否则后面会引发错误。
    arrayOLines[0]=arrayOLines[0].lstrip('\ufeff')
    numberOfLines = len(arrayOLines)
    returnMat = np.zeros((numberOfLines,3))
    
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split('\t')
        #将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
        
    return returnMat, classLabelVector

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    # 原始值减去最小值
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    return normDataSet, ranges, minVals


def main():
    filename = "datingTestSet.txt"
    datingDataMat, datingLabels = file2matrix(filename)
    normMat, ranges, minVals = autoNorm(datingDataMat)
    
    # 行数
    m = normMat.shape[0]
    # 测试比率
    hoRatio = 0.10
    numTestVecs = int(m * hoRatio)

    #分类错误计数
    errorCount = 0.0

    for i in range(numTestVecs):
    
        classifierResult = classify(normMat[i,:], normMat[numTestVecs:m,:], 
            datingLabels[numTestVecs:m], 4)
        print("分类结果:%s\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            print('分类: ',classifierResult)
            print('真实: ',datingLabels[i])
            errorCount += 1.0
    print("错误率:%f%%" %(errorCount/float(numTestVecs)*100))

main()

结果(只输出最后几行):

分类结果:2	真实类别:3
分类:  2
真实:  3
分类结果:1	真实类别:1
分类结果:2	真实类别:2
分类结果:1	真实类别:1
分类结果:3	真实类别:3
分类结果:3	真实类别:3
分类结果:2	真实类别:2
分类结果:2	真实类别:1
分类:  2
真实:  1
分类结果:1	真实类别:1
错误率:4.000000%

 

python函数:min(),max(),    列表切分

(1)min(),max()

0:列最值

1:行最值

空:全局最值

import numpy as np
a = np.array([[1,2,3,10,4,5],[2,3,4,3,5,6],[3,4,5,4,6,7]])
print(a)

print('-------全局最大小------')
print(a.min())
print(a.max())

print('------列最大小-------')
print(a.min(0))
print(a.max(0))

print('------行最大小-------')
print(a.min(1))
print(a.max(1))
[[ 1  2  3 10  4  5]
 [ 2  3  4  3  5  6]
 [ 3  4  5  4  6  7]]
-------全局最大小------
1
10
------列最大小-------
[1 2 3 3 4 5]
[ 3  4  5 10  6  7]
------行最大小-------
[1 2 3]
[10  6  7]

(2)列表切分:

import numpy as np
a = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
print(a)
print('----------------')
print('a[2,:]: ')
print(a[2,:])
print('----------------')
print('a[0:2,:]: ')
print(a[0:2,:])
print('----------------')
print('a[0:2]: ')
print(a[0:2])
[[1 2 3 4 5]
 [2 3 4 5 6]
 [3 4 5 6 7]]
----------------
a[2,:]: 
[3 4 5 6 7]
----------------
a[0:2,:]: 
[[1 2 3 4 5]
 [2 3 4 5 6]]
----------------
a[0:2]: 
[[1 2 3 4 5]
 [2 3 4 5 6]]

 

五、使用SKLearn中的KNN对手写字体进行处理

步骤:

1、获取训练集特征矩阵,标签

 2、使用训练集特征矩阵、标签初始化KNN实例

3、获取测试集特征矩阵,标签 4、预测测试集,记录正确率

 

其实即代码:

实例KNN: neigh = kNN(n_neighbors = 3, algorithm = 'auto') neigh.fit(trainingMat, hwLabels)

预测: classifierResult = neigh.predict(vectorUnderTest)

 

首先将32*32的数据展开成1*1024,然后实例KNN,预测,代码如下:

import numpy as np
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN

# 将32*32展开成1*1024
def img2vector(filename):
    returnVect = np.zeros((1, 1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        #每一行的前32个元素依次添加到returnVect中
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])
    return returnVect

def handwritingClassTest():
    hwLabels = []
    # 训练集列表
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)
    trainingMat = np.zeros((m, 1024))
    
    # 获取训练集特征矩阵及其标签
    for i in range(m):
        # 训练集文件名
        fileNameStr = trainingFileList[i]
        # 数字
        classNumber = int(fileNameStr.split('_')[0])
        # 训练集标签列表
        hwLabels.append(classNumber)
        # 训练集特征矩阵,即 M*1024
        trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr))
    
    # 构建分类器
    neigh = kNN(n_neighbors = 3, algorithm = 'auto')
    neigh.fit(trainingMat, hwLabels)
    
    # 测试集文件名列表
    testFileList = listdir('testDigits')
    errorCount = 0.0
    # 测试集数量
    mTest = len(testFileList)
    
    for i in range(mTest):
        # 文件名
        fileNameStr = testFileList[i]
        # 类别
        classNumber = int(fileNameStr.split('_')[0])
        # 特征矩阵
        vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr))
        # 预测结果
        classifierResult = neigh.predict(vectorUnderTest)
        print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
        if(classifierResult != classNumber):
            errorCount += 1.0
    print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount/mTest * 100))
    

handwritingClassTest()

结果(最后几行输出):

分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
分类返回结果为9	真实结果为9
总共错了12个数据
错误率为1.268499%

我们发现,使用SKLearn确实比自己手写要方便的多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值