在 Kaggle 上面的 Notebook 给可爱的学弟学妹们用于参考... 代码这个东西一定要自己多写,我一边听着林宥嘉的《想自由》,一边写出了大致的实现。K 近邻算法大概做的是一件什么事情呢?你去商店买衣服的时候,突然忘记了自己要买的衣服多大尺码比较合适(S/M/L/XL 这种)。这个时候你就要找几个身材和你差不多的几个店内顾客问一问了,结果你发现你这样的身材的人大多买的是 XL 的衣服,所以你最后告诉老板你也买 XL 的衣服,果然是机智聪明啊。
下面是解决该问题的思路重点:
- 身材和你相似的人的建议比较有参考价值。(排序取前 K 个最相似的)
- 你是怎么判断其它人身材和你的相似程度的?(距离度量方式)
- 你最终参考的是最多被购买的那一类尺码。(毕竟不同的人建议可能不同)
关于数据集的读入
MNIST 数据集可以在这里获取:THE MNIST DATABASE of handwritten digits . 你一定很好奇——为什么在 Kaggle 里面数据集是 CSV 格式,而在数据集官网提供的是四个压缩文件?这没什么好稀奇的,你只需要根据不同的数据格式采用不同的数据读取套路就好了,只要最后的数据格式的维度一致即可。
数据集已经解压
def load_mnist(path, kind='train'):
"""Load MNIST data from `path`"""
labels_path = os.path.join(path,'%s-labels-idx1-ubyte'% kind)
images_path = os.path.join(path,'%s-images-idx3-ubyte'% kind)
with open(labels_path,'rb') as lbpath:
labels = np.frombuffer(lbpath.read(), dtype=np.uint8,
offset=8)
with open(images_path,'rb') as imgpath:
images = np.frombuffer(imgpath.read(), dtype=np.uint8,
offset=16).reshape(len(labels), 784)
return images, labels
数据集未解压
def load_mnist(path, kind='train'):
"""Load MNIST data from `path`"""
labels_path = os.path.join(path,
'%s-labels-idx1-ubyte.gz'
% kind)
images_path = os.path.join(path,
'%s-images-idx3-ubyte.gz'
% kind)
with gzip.open(labels_path, 'rb') as lbpath:
labels = np.frombuffer(lbpath.read(), dtype=np.uint8,
offset=8)
with gzip.open(images_path, 'rb') as imgpath:
images = np.frombuffer(imgpath.read(), dtype=np.uint8,
offset=16).reshape(len(labels), 784)
return images, labels
数据集已经被转为 CSV 格式且无测试集标签(Kaggle)
# It will takes about 1 ~ 2 minutes (depends on CPU)
train_data = np.genfromtxt('../input/train.csv', delimiter=',',
skip_header=1).astype(np.dtype('uint8'))
X_train = train_data[:,1:]
y_train = train_data[:,:1]
X_test = np.genfromtxt('../input/test.csv', delimiter=',',
skip_header=1).astype(np.dtype('uint8'))
检查数据导入是否顺利
indices
即在训练集索引列表中随机取了 9 个值,用于预览图片的导入效果。这个 list
你也可以自己指定。
np.random.seed(0);
indices = list(np.random.randint(m_train, size=9))
for i in range(9):
plt.subplot(3,3,i + 1)
plt.imshow(X_train[indices[i]].reshape(28,28), cmap='gray', interpolation='none')
plt.title("Index {} Class {}".format(indices[i], y_train[indices[i]]))
plt.tight_layout()
定义距离度量
之前我们说了,你需要找几个身材和你相似的人,那么你就可以根据三维的属性定义一个距离度量公式。而对于图片来说,图片的相似度计算方法有很多,下面只给出了两种常见的距离度量公式。
def euclidean_distance(vector1, vector2):
return np.sqrt(np.sum(np.power(vector1 - vector2, 2)))
def absolute_distance(vector1, vector2):
return np.sum(np.absolute(vector1 - vector2))
找相似邻居(K Neighbours)
和你身材相似的人的尺码参考价值比较高,但是也不可轻信一家之言,所以你需要找和你身材最相似的 K 个人,以避免出现意外。
import operator
def get_neighbours(X_train, test_instance, k):
distances = []
neighbors = []
for i in range(0, X_train.shape[0]):
dist = euclidean_distance(X_train[i], test_instance)
distances.append((i, dist))
distances.sort(key=operator.itemgetter(1))
for x in range(k):
# print(distances[x])
neighbors.append(distances[x][0])
return neighbors
得到投票最多的建议
大家众说纷纭,最好的决策方法自然就是看最多数的人给出的建议了。
def predictkNNClass(output, y_train):
classVotes = {}
for i in range(len(output)):
# print(output[i], y_train[output[i]])
if y_train[output[i]][0] in classVotes:
classVotes[y_train[output[i]][0]] += 1
else:
classVotes[y_train[output[i]][0]] = 1
sortedVotes = sorted(classVotes.items(), key=operator.itemgetter(1), reverse=True)
# print(sortedVotes)
return sortedVotes[0][0]
拿实例来进行测试
从测试集中取出第 667 张图片来测试,找出训练集中和它最相似的 9 (K=9)张图片,其中 6 张图片的类别为 “1” ,那么这张图片分类为 “1” 的概率最大。
instance_num = 666
k = 9
plt.imshow(X_test[instance_num].reshape(28,28), cmap='gray', interpolation='none')
instance_neighbours = get_neighbours(X_train, X_test[instance_num], 9)
indices = instance_neighbours
for i in range(9):
plt.subplot(3,3,i + 1)
plt.imshow(X_train[indices[i]].reshape(28,28), cmap='gray', interpolation='none')
plt.title("Index {} Class {}".format(indices[i], y_train[indices[i]]))
plt.tight_layout()
predictkNNClass(instance_neighbours, y_train)
CSV 的导出(Kaggle)
import csv
submit = pd.DataFrame(columns=('ImageId', 'Label'))
for i in range(5): # change 5 to X_test.shape[0] will takes a long long long ... TIME!
neighbours = get_neighbours(X_train, X_test[i], 20)
label = predictkNNClass(neighbours, y_train)
submit.loc[i]={'ImageId': i + 1,'Label': label}
submit.to_csv('csv_to_submit.csv', index = False)
最后算法给出的预测类别也是 “1” ,但是 KNN 算法的准确率其实并不高,不信你就换换上面的 instance_num
,自行测试一下预测结果。另外一个需要注意的问题是 K
的取值,这个可以算作是一种超参数,具体取什么值就见仁见智了。至于我为什么没有对训练集全部运行 KNN 导出 CSV 文件去得到在 MNIST 的预测准确率,请尝试计算一下上面算法所需的时间复杂度... 这个时候你就能体会到 Scikit-Learn 的优点了,毕竟我们的代码实现只能算是一个 demo,这里有一篇 推荐博文 。