Competition Add : https://www.kaggle.com/c/digit-recognizer
此题目已经把每个图片向量化了。下载到的data里包含三个文件:
数据文件train.csv和test.csv包含从0到9的手绘数字的灰度图像。
每个图像的高度为28个像素,宽度为28个像素,总共为784个像素。每个像素具有与其相关联的单个像素值,指示该像素的亮度或暗度,较高的数字意味着较暗。该像素值是0到255之间的整数,包括0和255。
训练数据集(train.csv)有785列。第一列称为“标签”,是用户绘制的数字。其余列包含关联图像的像素值。
训练集中的每个像素列都具有像pixelx这样的名称,其中x是0到783之间的整数,包括0和783。为了在图像上定位该像素,假设我们已经将x分解为x = i * 28 + j,其中i和j是0到27之间的整数,包括0和27。然后,pixelx位于28 x 28矩阵的第i行和第j列上(索引为零)。
例如,pixel31表示从左边开始的第四列中的像素,以及从顶部开始的第二行,如下面的ascii图中所示。
在视觉上,如果我们省略“像素”前缀,像素组成图像如下:
000 001 002 003 ... 026 027
028 029 030 031 ... 054 055
056 057 058 059 ... 082 083
| | | | ...... | |
728 729 730 731 ... 754 755
756 757 758 759 ... 782 783
测试数据集(test.csv)与训练集相同,只是它不包含“标签”列。
具体代码如下:
import numpy as np # linear algebra
import csv
import operator
import os
import time # 会用到时间函数
# print(os.listdir("./data")) # 列出此文件夹下的所有文件目录
"""
train_mat 是训练数据组成的矩阵,即train.csv去掉表头 和 每一行的第一个数(label)之后的剩余部分
"""
def digitRecognizer():
train_labels = [] # 存储 训练数据的标签
test_labels = [] # 存储 测试数据的标签
train_list = [] # 存储 去除表头的所有列表
test_list = [] # 存储 去除表头的所有测试数据列表
len_train = 0 # 存储 训练集的个数
with open('./data/train.csv') as f:
content = csv.reader(f)
dot = 0
for one in content:
if dot > 0:
# trans_int = [ int(i) for i in one ] 或者用下面的方法
trans_int = list(map(int, one)) # 把列表中的str全部转化成int类型
train_list.append(trans_int)
dot += 1
len_train = len(train_list)
# 因为list不支持list**2操作(幂乘),所以需要转化成numpy矩阵的形式。
train_mat = np.zeros((len_train, 784)) # numpy.matrix类型,用0填充一个 len_train*784的矩阵
dot = 0
for one in train_list:
train_labels.append(one[0])
train_mat[dot, :] = one[1:]
dot += 1
# print(train_mat.shape) # (42000, 784)
with open('./data/test.csv') as f:
content = csv.reader(f)
dot = 0
for one in content:
if dot > 0:
# trans_int = [ int(i) for i in one ] 或者用下面的方法
trans_int = list(map(int, one)) # 把列表中的str全部转化成int类型
test_list.append(trans_int)
dot += 1
# print(len(test_list)) # 返回 28000
dot = 1
for ele in test_list:
result = classify(ele, train_mat, train_labels, 5) # k=5
test_labels.append(result)
# print(result)
dot += 1
if dot % 1000 == 0: # 每一千次输出一次时间
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
# 写ans文件
with open('./data/ans.csv', 'w', encoding='UTF-8', newline='') as f:
head = ['ImageId', 'Label']
ww = csv.writer(f)
ww.writerow(head)
for i in range(len(test_labels)):
mid = [str(i + 1), str(test_labels[i])]
ww.writerow(mid)
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
def classify(inX, data_set, labels, k): # 分类函数,k近邻判断结果所属类别
data_set_size = data_set.shape[0] # 返回 矩阵行数
diff_mat = np.tile(inX, (data_set_size, 1)) - data_set # 计算此测试向量 与 矩阵中每一个行向量中元素的差值
sq_diff_mat = diff_mat ** 2 # 各个差值求平方
sq_distance = sq_diff_mat.sum(axis=1) # 按行向量求和
distance = sq_distance ** 0.5 # 再开方,此时得到的是此测试向量到各个训练数据的 欧氏距离
sorted_dis = distance.argsort() # 将 距离 升序排序, 返回的是对应的index索引号
class_count = {}
for i in range(k):
vote = labels[sorted_dis[i]]
class_count[vote] = class_count.get(vote, 0) + 1
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
return sorted_class_count[0][0]
"""
# 上面sorted函数的第一个参数是可迭代的对象
# operator.itemgetter(1)是获取可迭代对象中 第一个域的值(从0开始),即对字典的value值排序
# reverse=True 表示排序完成之后(默认从小到大),再逆序。即由大到小排序
# 排序完成之后,最大的值在最前面。并且返回的是list
# 例如:待排序的dist为{'b':111, 'a':666}, 返回的结果是[('a', 666), ('b', 111)],tuple也是可以按下标索引的
"""
if __name__ == '__main__':
digitRecognizer()
可以看到训练集有42000条数据,每条数据有784(28像素*28像素)个数字。测试集有28000条数据,每条也有784个数字。
在进行距离的计算时,需要将测试集里的每一条数据 分别计算到训练集4.2万个‘图’的每一个距离。即算法要为每个测试向量做42000次的距离计算,并且每个距离计算包含784个浮点运算,然后还需要开方,总计要计算28000次(28000个测试数据)。
总计算次数数量级:几乎是千亿级别了。(784*4.2万*2.8万 = 9219.84亿)所以可以看出会非常耗时。
我运行此代码的时候大致输出了一下时间,内存12G, i5CPU 无SSD大概运行了仨小时。-_-#
得到了ans.csv文件
内容形如:
然后满怀期待的进行了提交...:)
额... 可以看到准确率为96.900%
用的k近邻算法,k的值选取为5.
并且我观察了一下,发现向量中大部分为0值,就没进行归一化( normalization )。
不过在两千多组提交中排名一千六百多,这成绩还是很不理想的。
后续会给出别的解法。
有问题请留言,欢迎转发,转发需标明出处。
稍后补充更新...