机器学习——KNN、SVM、MLP、CNN实现cifar100
本文是为期一周的实验课,要求用KNN、SVM、MLP和CNN分别实现cifar100数据集的分类。该实验的目的在于对机器学习有一个直观的感受,且对前段时间所学python基础语法课、机器学习100天(前8天)做个复习。
在网上找到很多资料都是cifar10的实验。本文主要介绍实验代码和实验结果及一些分析,不讲述基础原理。代码的参考链接写在代码头部 ,本文的代码是在原来代码的基础上,将cifar10数据集加载改为cifar100。其中一些语言注释并没有改正。
实验结果:KNN准确率正常29%。SVM准确率非常低10%不正常,原因在于核函数的设置。MLP准确率也非常低25%不正常,原因在于直接使用了优化器,没有学习率,模型欠拟合。CNN运行5小时没结果。
注意:本文使用的标签是直接将100个子类标签拿出来用,更精确的做法是将10个大类标签和对应的子类标签组合起来,做成新的100个标签。否则子类之间可能有重叠。
1.KNN-cifar100
1.1KNN实验结果
实验结果:准确率:29%
结果截图:
1.2KNN实验代码
KNN 代码:
# https://github.com/luxiaohao/KNN/blob/master/datasets/data_utils.py
# python+cifar10
import os
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from sklearn import svm as s
import time
def unpickle(file):
"""
功能:将CIFAR10中的数据转化为字典形式
(1)加载data_batch_i(i=1,2,3,4,5)和test_batch文件返回的字典格式为:
dict_keys([b'filenames', b'data', b'labels', b'batch_label'])
其中每一个batch中:
dict[b'data']为(10000,3072)的numpy array
dict[b'labels']为长度为10000的list
(2)加载batchs.meta文件返回的字典格式为:
dict_keys([b'num_cases_per_batch', b'num_vis', b'label_names'])
其中dict[b'label_names']为一个list,记录了0-9对应的类别为:
[b'airplane', b'automobile', b'bird', b'cat', b'deer', b'dog', b'frog', b'horse', b'ship', b'truck']
"""
import pickle
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
# file =''r'D:\cifar-100-python\train'
# dict=unpickle(file)
# print(dict.keys())
# 运行文件可知dict_keys([b'filenames', b'batch_label', b'fine_labels', b'coarse_labels', b'data'])
def load_CIFAR10():
"""
功能:从当前路径下读取CIFAR10数据
输出:
-x_train:(numpy array)训练样本数据(N,D)
-y_train:(numpy array)训练样本数标签(N,)
-x_test:(numpy array)测试样本数据(N,D)
-y_test:(numpy array)测试样本数标签(N,)
"""
x_t = []
y_t = []
for i in range(1, 6):
# path_train = os.path.join('cifar-10-batches-py', 'data_batch_%d' % (i))
path_train =''r'D:\cifar-100-python\train'
data_dict = unpickle(path_train)
x = data_dict[b'data'].astype('float32')
y = np.array(data_dict[b'fine_labels'])
x_t.append(x)
y_t.append(y)
# 将数据按列堆叠进行合并,默认按列进行堆叠
x_train = np.concatenate(x_t)
y_train = np.concatenate(y_t)
# path_test = os.path.join('cifar-10-batches-py', 'test_batch')
path_test = ''r'D:\cifar-100-python\test'
data_dict = unpickle(path_test)
x_test = data_dict[b'data'].astype('float32')
y_test = np.array(data_dict[b'fine_labels'])
return x_train, y_train, x_test, y_test
def data_processing():
"""
功能:进行数据预处理
输出:
x_tr:(numpy array)训练集数据
y_tr:(numpy array)训练集标签
x_val:(numpy array)验证集数据
y_val:(numpy array)验证集标签
x_te:(numpy array)测试集数据
y_te:(numpy array)测试集标签
x_check:(numpy array)用于梯度检查的子训练集数据
y_check:(numpy array)用于梯度检查的子训练集标签
"""
# 加载数据
x_train, y_train, x_test, y_test = load_CIFAR10()
num_train = 10000
num_test = 1000
num_val = 1000
num_check = 100
# 创建训练样本
x_tr = x_train[0:num_train]
y_tr = y_train[0:num_train]
# 创建验证样本
x_val = x_train[num_train:(num_train + num_val)]
y_val = y_train[num_train:(num_train + num_val)]
# 创建测试样本
x_te = x_test[0:num_test]
y_te = y_test[0:num_test]
# 从训练样本中取出一个子集作为梯度检查的数据
mask = np.random.choice(num_train, num_check, replace=False)
x_check = x_tr[mask]
y_check = y_tr[mask]
# 计算训练样本中图片的均值
mean_img = np.mean(x_tr, axis=0)
# 所有数据都减去均值做预处理
x_tr += -mean_img
x_val += -mean_img
x_te += -mean_img
x_check += -mean_img
# 加上偏置项变成(N,3073)
# np.hstack((a,b))等价于np.concatenate((a,b),axis=1),在横向合并
# np.vstack((a,b))等价于np.concatenate((a,b),axis=0),在纵向合并
x_tr = np.hstack((x_tr, np.ones((x_tr.shape[0], 1))))
x_val = np.hstack((x_val, np.ones((x_val.shape[0], 1))))
x_te = np.hstack((x_te, np.ones((x_te.shape[0], 1))))
x_check = np.hstack((x_check, np.ones((x_check.shape[0], 1))))
return x_tr, y_tr, x_val, y_val, x_te, y_te, x_check, y_check
from pandas import read_csv
import pandas as pd
import matplotlib.pyplot as plt
class KNearestNeighbor(object):
"""定义KNN分类器,用L1,L2距离计算"""
def __init__(self):
pass
def train(self, X, y):
"""
训练数据,k近邻算法训练过程只是保存训练数据。
:param X: 输入是一个包含训练样本nun_train,和每个样本信息的维度D的二维数组
:param y: 对应标签的一维向量,y[i] 是 X[i]的标签
:return: 无
"""
self.X_train = X
self.y_train = y
def predict(self, X, k=1, num_loops=0):
"""
:param X: 训练数据输入值,一个二维数组
:param k: 确定K值进行预测投票
:param num_loops: 选择哪一种循环方式,计算训练数据和测试数据之间的距离
:return: 返回一个测试数据预测的向量(num_test,),y[i] 是训练数据 X[i]的预测标签。
"""
if num_loops == 0:
dists = self.compute_distances_no_loops(X)
elif num_loops == 1:
dists = self.compute_distances_one_loop(X)
elif num_loops == 2:
dists = self.compute_distances_two_loops(X)
else:
raise ValueError('Invalid value %d for num_loops' % num_loops)
return self.predict_labels(dists, k=k)
def compute_distances_no_loops(self, X):
"""
计算距离没有运用循环。
只使用基本的数组操作来实现这个函数,特别是不使用SCIPY的函数。
提示:尝试使用矩阵乘法和两个广播求和来制定L2距离。
:param X: 输入是一个(num_test, D)的训练数据。
:return: 返回值是一个(num_test, num_train)的二维数组,dists[i, j]对应相应位置测试数据和训练数据的距离。
"""
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
"""
M = np.dot(X, self.X_train.T)
nrow = M.shape[0]
ncol = M.shape[1]
te = np.diag(np.dot(X, X.T))
tr = np.diag(np.dot(self.X_train, self.X_train.T))
te = np.reshape(np.repeat(te, ncol), M.shape)
tr = np.reshape(np.repeat(tr, nrow), M.T.shape)
sq = -2 * M + te + tr.T
dists = np.sqrt(sq)
#这里利用numpy的broadcasting性质,例如A = [1, 2], B = [[3], [4]], A + B = [[3 + 1, 3 + 2], [4 + 1, 4 + 2]]。
#以及(a - b) ^ 2 = a ^ 2 + b ^ 2 - 2ab。
"""
test_sum = np.sum(np.square(X), axis=1, keepdims=True)
train_sum = np.sum(np.square(self.X_train), axis=1)
test_mul_train = np.matmul(X, self.X_train.T)
dists = test_sum + train_sum - 2 * test_mul_train
return dists
def compute_distances_one_loop(self, X):
"""
应用1层循环的计算方式,计算测试数据和每个训练数据之间的距离。
按训练数据的行索引计算,少了一个循环。
:param X: 输入是一个(num_test, D)的训练数据。
:return: 返回值是一个(num_test, num_train)的二维数组,dists[i, j]对应相应位置测试数据和训练数据的距离。
"""
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
# axis = 1按每个行索引计算
distances = np.sqrt(np.sum(np.square(self.X_train - X[i]), axis=1))
# L1距离
# distances = np.sum(np.abs(self.X_train - X[i, :]), axis=1)
dists[i, :] = distances
return dists
def compute_distances_two_loops(self, X):
'''
应用2层循环(嵌套循环)的计算方式,计算测试数据和每个训练数据之间的距离。
:param X: 输入是一个(num_test, D)的训练数据。
:return: 返回值是一个(num_test, num_train)的二维数组,dists[i, j]对应相应位置测试数据和训练数据的距离。
'''
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
for j in range(num_train):
# 应用L2距离求解每个对应测试集数据和训练集数据的距离,并放在dists数组中,两层循环时间复杂度高
distances = np.sqrt(np.sum(np.square(self.X_train[j] - X[i])))
dists[i, j] = distances
return dists
def predict_labels(self, dists, k=1):
'''
输入一个测试数据和训练数据的距离矩阵,预测训练数据的标签。
:param dists: 距离矩阵(num_test, num_train) 对应dists[i, j]
给出第i个测试数据和第j个训练数据的距离。
:param k: KNN算法超参数K
:return:返回(num_test,)向量,y[i]是X[i]对应的预测标签。
'''
num_test = dists.shape[0]
y_pred = np.zeros(num_test)
"""
这一步,要做的是:用距离矩阵找到与测试集最近的K个训练数据的距离,
并且找到他们对应的标签,存放在closest_y中。
函数介绍:
numpy.argsort(a, axis=-1, kind=’quicksort’, order=None)
功能: 将矩阵a按照axis排序,并返回排序后的下标
参数: a:输入矩阵, axis:需要排序的维度
返回值: 输出排序后的下标,从小到大排
"""
for i in range(num_test):
# 创建一个长度为K的列表,用来存放与测试数据最近的K个训练数据的距离。
closest_y = []
distances = dists[i, :]
indexes =