k-近邻算法及识别手写数字的案例

K近邻算法学习笔记:
视频地址:https://www.bilibili.com/video/av35390140


名称:k-近邻算法,英文名是k nearest neighbour algorithm,也就是k个最近邻居的算法,简称knn。


算法

原来有一堆数据,它们已经分好了类别,现在有新的数据加进来,那么,它应该属于哪一类呢?

①首先,我们要算出新数据与原来所有数据的“距离”。

这里的距离有不同的定义。比如一个新生进班,原来班中已经分为差生、中等生、优等生,好了,现在新加入的学生属于哪一类呢?我们就必须要计算新生和原班中每一位学生的“距离”。

比如衡量一个学生有三个维度,上学期的期中成绩,期末成绩,与道德评分,你可以记为(x,y,z),每个学生都是这么一个点,新生和每个学生的距离这时就可以用数学中的距离公式来计算了。

②把所有的距离按递增排序,也就是从小到大,因为我们要找离新生距离近的嘛,也就是与新生特征“相近”的那些点。

③排好序后,你截取前k个点,这就是为什么叫k-近邻算法了,你是谁、你属于哪一类,是由那最近的k个点决定的。

④算出k个点所在类别的频率。比如说,k=10,在这最近的10个学生中,有8个是差生,有1个是中等生,有1个是优等生,这就是我说的找到每个数据所处的类别。

⑤把频率最高的类别作为预测类别。那在这里,我们就可以说:新来的学生是差生。


案例

识别数字。

原来的数字图片,经过黑白化后,就可以用0和1两种数字来表示像素点了。

比如下面这张图,它就是一个“0”。

00000000000001111000000000000000
00000000000011111110000000000000
00000000001111111111000000000000
00000001111111111111100000000000
00000001111111011111100000000000
00000011111110000011110000000000
00000011111110000000111000000000
00000011111110000000111100000000
00000011111110000000011100000000
00000011111110000000011100000000
00000011111100000000011110000000
00000011111100000000001110000000
00000011111100000000001110000000
00000001111110000000000111000000
00000001111110000000000111000000
00000001111110000000000111000000
00000001111110000000000111000000
00000011111110000000001111000000
00000011110110000000001111000000
00000011110000000000011110000000
00000001111000000000001111000000
00000001111000000000011111000000
00000001111000000000111110000000
00000001111000000001111100000000
00000000111000000111111000000000
00000000111100011111110000000000
00000000111111111111110000000000
00000000011111111111110000000000
00000000011111111111100000000000
00000000001111111110000000000000
00000000000111110000000000000000
00000000000011000000000000000000

1是原来图片中的黑色像素,0是白色像素点。这是一个32×32的矩阵。

我现在有两个文件夹:trainingDigits和testDigits,分别是训练数据和测试数据。

链接:https://pan.baidu.com/s/11GmlxwoF_MCm7Ds4JPtBCg
提取码:srxg

训练数据文件夹中有1934个txt文件,文件名都是如”0_0.txt”。第一个“0”表示文件中的数字是0,第二个“0”表示这是第一个文件,我们只需关注第一个“0”,因为等一下我们需要把它截取出来,这样才能与文件中的矩阵描述的数字形成一 一对应。测试数据文件夹中只是文本文件个数少一点,其他都一样。

我们现在要做的就是:编写knn算法,作用在训练数据上,将得到的结果与测试集对比,观察算法的可行性。


代码部分:

我现在这里会混着用pycharm和jupyter notebook展示代码

import os
import pandas as pd
from Levenshtein import hamming

'''
得到标记好的训练集
'''

def get_train():
    path = 'digits/trainingDigits'
    trainingFileList = os.listdir(path)
    train = pd.DataFrame()
    img = []
    labels = []
    for i in range(len(trainingFileList)):
        filename = trainingFileList[i]
        txt = pd.read_csv(f'digits/trainingDigits/{filename}', header=None)
        num = ''
        for j in range(txt.shape[0]):
            # num是个Series
            num += txt.iloc[j,:]
        # 取出num的值加进img中
        img.append(num[0])

        filelabel = filename.split('_')[0]
        labels.append(filelabel)

    train['img'] = img
    train['labels'] = labels

    return train

Levenshtein 这个包我等一下再解释。
其他的也没什么注意的,我在jupyter notebook运行的时候,文件路径带有中文,所以当调用pd.read_csv时,需要在header=None后面加engine=‘python’,将引擎指定为python

1   for j in range(txt.shape[0]):
2       # num是个Series
3       num += txt.iloc[j,:]
4       # 取出num的值加进imgzhong
5       img.append(num[0])

这段代码的含义是把32×32的矩阵变成1×1024的向量,方便之后进行比较。

达成此目的的方法有很多,你当然还可以写两个for循环,遍历矩阵的每一行中的每一个元素。

img中存的是1×1024的向量,代表原来的图片像素,labels中存的是对应的文件名中截取的数字,代表图片所表示的数字。



这就是我们最终得到的train这个DataFrame的样子:


对于测试集我们做同样的操作。

'''
    得到标记好的测试集
    
'''

def get_test():
    path = 'digits/testDigits'
    testFileList = os.listdir(path)
    test = pd.DataFrame()
    img = []
    labels = []
    for i in range(len(testFileList)):
        filename = testFileList[i]
        txt = pd.read_csv(f'digits/testDigits/{filename}',header=None)
        num = ''
        for j in range(txt.shape[0]):
            num += txt.iloc[j,:]
        img.append(num[0])
        filelabel = filename.split('_')[0]
        labels.append(filelabel)

    test['img'] = img
    test['labels'] = labels

    return  test

得到的test大概是这么一个样子:

接下来,我们就拿出测试集中的一个个img中的值,拿出来与训练集中的img的每个值进行比较,测量“距离”。

这时我们用到Levenshtein包。

链接:https://www.lfd.uci.edu/~gohlke/pythonlibs/


我用的是python3.7,系统是win64,所以选择了倒数第三个。

然后打开cmd,输入pip install+刚才下载的文件的绝对路径。

安装好,愚蠢的pycharm也察觉不到,我发现这个包直接是安在了Anaconda下面的,所以在jupyter notebook中可以直接使用


可是我项目环境里却怎么也install不了这个包,所以我干脆把Anaconda下面的Levenshtein包给复制到我项目的site-packages下面,暴力解决了问题。

我们看一下Levenshtein怎么用的:

它是把两个字符串按位置比较,返回值就是不一样的字符个数。

现在我拿test中的第一条数据和train中的每一条数据比较距离,并将这些距离存在一个叫dist的列表中,并且以dist为一个column,以labels为另一个column构建DataFrame。


我们可以这样说:第一条test与train中的第一个0差98,与第二个0差170,与第三个0差159…

下面按dist从小到大排序

这里我的k选为10(最好20以下),现在我要以这k个最近的邻居来决定test中第一个img表示的数字是几。

很显然我们决定它应该是0。并且我们把它放入了result这个list中,以result中的数据构造一个Series加入test中,以便于和原值进行比较,观察算法的优劣。

原来第一个数是0,预测出来是0,nice。


我只解释了循环一次的流程,下面对test中的每一条数据都这么操作。

def handwriting(train,test,k):
    n = train.shape[0]
    m = test.shape[0]
    result = []
    for i in range(m):
        dist = []
        for j in range(n):

            d = str(hamming(train.iloc[j,0],test.iloc[i,0]))
            dist.append(d)
        dist_l = pd.DataFrame({'dist':dist,'labels':train.iloc[:,1]})
        dr = dist_l.sort_values(by='dist')[:k]
        re = dr.loc[:,'labels'].value_counts()
        result.append(re.index[0])
    result = pd.Series(result)
    test['predict'] = result
    # 将预测值与原值一 一比较,acc月接近1,效果越好
    acc = (test.iloc[:,-1]==test.iloc[:,-2]).mean()
    print(f'模型预测准确率为{acc}')
    return test

最后把代码跑起来:

def main_fun():
    train = get_train()
    test = get_train()
    k = 10
    test = handwriting(train,test,k)
    print('带有预测值的测试集:')
    print(test.head())


if __name__=='__main__':
    main_fun()

我得到的结果是:

发布了25 篇原创文章 · 获赞 26 · 访问量 1159

猜你喜欢

转载自blog.csdn.net/weixin_43810802/article/details/101534674