由于notebook环境的配置较为麻烦,我直接使用pycharm配置本地的python环境完成了cs231n课堂的第一次作业任务。下面是具体的任务要求:
Q1:k-最近邻分类器
Q2:训练一个SVM
Q3:实现Softmax分类器
Q4:实现两层神经网络
Q5:更高层次的表达:图像特征
Q6:加分:做点其他的
接下来一个一个问题的分析并完成代码的编写,在cs231n课堂上给出了大致的代码框架,这里也会沿用框架,但是需要自己去理解和构建整个工程。
所有代码都上传到github中,会不断更新
分为两步:
在最近邻分类器基础上寻找前k个和测试样本相似的图片,然后根据这k个图片的类别确定测试样本,可一定程度上避免噪声的影响。
样本的读取
这里采用和原文一样的数据集,放置的目录为\assiment1\datasets\cifar-10-batches-py
新建文件data_utils.py,在文件中实现读取数据集的操作。
import pickle as p
import numpy as np
import os
读取单个文件中的图片
def load_CIFAR_batch(filename):
with open(filename, 'rb') as f:
datadict = p.load(f, encoding='latin1')
X = datadict['data']
Y = datadict['labels']
X = X.reshape(10000, 3, 32, 32).transpose(0, 2, 3, 1).astype("float")
Y = np.array(Y)
return X, Y
读取所有文件中的图片,注意到数据集中共有5个文件
def load_CIFAR10(ROOT):
xs = []
ys = []
for b in range(1, 6):
f = os.path.join(ROOT, 'data_batch_%d' % (b,))
X , Y = load_CIFAR_batch(f)
xs.append(X) # 将所有batch整合起来
ys.append(Y)
Xtr = np.concatenate(xs) # 使变成行向量,最终Xtr的尺寸为(50000,32,32,3)
Ytr = np.concatenate(ys)
del X, Y
Xte, Yte = load_CIFAR_batch(os.path.join(ROOT, 'test_batch'))
return Xtr, Ytr, Xte, Yte
把所有数据集分类,有的作为训练集有的是测试集,有的则是调参使用的验证集
新建文件,load_Data.py
在这里实现所有有关数据集的操作,从而获得三类数据集。后面也会用在其他的算法验证上,避免重复的工作。
import numpy as np
import matplotlib.pyplot as plt
from data_utils import load_CIFAR10
设置好读取参数然后读取所有的数据集,所有随机在各类里面选择一些图片显示出来,从而验证自己能成功读取到数据集数据。
class load_data(object):
def __init__(self):
self.X_val = 0
self.X_train = 0
self.y_train = 0
self.X_test = 0
self.y_test = 0
self.y_val = 0
plt.rcParams['figure.figsize'] = (10.0, 8.0)
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
# 载入CIFAR-10数据集
cifar10_dir = 'datasets/cifar-10-batches-py'
self.X_train, self.y_train, self.X_test, self.y_test = load_CIFAR10(cifar10_dir)
# 看看数据集中的一些样本
print('Training data shape: ', self.X_train.shape)
print('Training labels shape: ', self.y_train.shape)
print('Test data shape: ', self.X_test.shape)
print('Test labels shape: ', self.y_test.shape)
# 在各类中选择7个样本测试数据集是否读取成功
classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
num_classes = len(classes)
samples_per_class = 7
for y, cls in enumerate(classes):
idxs = np.flatnonzero(self.y_train == y)
idxs = np.random.choice(idxs, samples_per_class, replace=False)
for i, idx in enumerate(idxs):
plt_idx = i * num_classes + y + 1
plt.subplot(samples_per_class, num_classes, plt_idx)
plt.imshow(self.X_train[idx].astype('uint8'))
plt.axis('off')
if i == 0:
plt.title(cls)
plt.show()
pass
def data_generate(self, train_num, num_validation, num_test):
# 随机产生numValidation个样本作为验证数据
mask = range(train_num, train_num + num_validation)
self.X_val = self.X_train[mask]
self.y_val = self.y_train[mask]
# 随机产生trainNum个样本作为训练数据
mask = range(train_num)
self.X_train = self.X_train[mask]
self.y_train = self.y_train[mask]
# 随机产生numTest个样本作为测试数据
mask = range(num_test)
self.X_test = self.X_test[mask]
self.y_test = self.y_test[mask]
# 把图像信息变成一维的向量
self.X_train = np.reshape(self.X_train, (self.X_train.shape[0], -1))
self.X_test = np.reshape(self.X_test, (self.X_test.shape[0], -1))
self.X_val = np.reshape(self.X_val, (self.X_val.shape[0], -1))
print(self.X_train.shape, self.X_test.shape, self.X_val.shape)
return self.X_train, self.y_train, self.X_val, self.y_val, self.X_test, self.y_test
调用函数时只需选择训练集,测试集等的数量即可
from load_data import load_data
test = load_data()
X_train, y_train, X_val, y_val, X_test, y_test = test.data_generate(4900, 100, 1000)
实现分类器
新建文件nearest_neighbor.py,实现分类器的算法
首先是训练部分,当然很简单只需要让代码记住所有的数据就好了,这里在构造函数直接就把数据存下来
def __init__(self, x_train, y_train):
self.x_train = x_train
self.y_train = y_train
pass
然后就是计算距离,这里采用矩阵的开根号等numpy的内部实现
def compute_distances(self, test):
num_test = test.shape[0]
num_train = self.x_train.shape[0]
dists = np.zeros((num_test, num_train))
dists = np.sqrt(-2 * np.dot(test, self.x_train.T) + np.sum(np.square(self.x_train), axis=1) + np.transpose(
[np.sum(np.square(test), axis=1)]))
return dists
可以看到基本上就是把下式用矩阵的方式实现了
∥ t e s t − t r a i n ∥ 2 = t e s t 2 + t r a i n 2 − 2 × t e s t T ⋅ t r a i n \left\|test-train\right\|_2=\sqrt{test^{2}+train^{2}-2\times test^{T}\cdot train} ∥test−train∥2=test2+train2−2×testT⋅train
最后一步就是实现预测部分了,原理上倒是很简单。找到距离最近的图片,然后找到那个图片对应的属性并预测两个属性一样就好。
def predict_labels(self, test, k=1):
dists = self.compute_distances(test)
num_test = dists.shape[0]
y_prediction = np.zeros(num_test)
for i in range(num_test):
closest_y = self.y_train[np.argsort(dists[i])[:1]]
y_prediction[i] = closest_y
return y_prediction
但是可以发现代码并不是很容易看懂,主要是应用了几个numpy的函数,下面可以仔细分析一下
>>> x = np.array([3, 1, 2])
>>> np.argsort(x)
array([1, 2, 0])
可以看出argsort函数首先把[3,1,2]从小到大排序,然后返回对应元素的序号。
排序后[1,2,3],则1的序号是1,2的序号是2,3的序号是0,则返回[1,2,0]
所以closest_y = self.y_train[np.argsort(dists[i])[:1]]
找到最小距离的图片的序号。
最终新建nn.py,运行下面代码可以得到预测的准确率。
import numpy as np
from load_data import load_data
from nearest_neighbor import nearest_neighbor
test = load_data()
X_train, y_train, X_val, y_val, X_test, y_test = test.data_generate(4900, 100, 1000)
nn = nearest_neighbor(X_train, y_train)
pre = nn.predict_labels(X_test)
num_correct = np.sum(pre == y_test)
accuracy = float(num_correct) / 500
print('Got %d / %d correct => accuracy: %f' % (num_correct, 500, accuracy))
运行得到结果
Got 269 / 500 correct => accuracy: 0.538000
由于噪声的影响,不一定最近的就是同一种物品,所以提出k-最近邻分类器。思路就是取前k个较为近的图片,然后统计这k个图片中各类别的数量,认为和最多数量的类别是同一类。
在代码上只用改预测部分将其预测的逻辑修改一下便可。这里新定义一个类,继承原来的最临近分类器,重写预测的函数。
def predict_labels(self, test, k=1):
dists = self.compute_distances(test)
num_test = dists.shape[0]
y_prediction = np.zeros(num_test)
for i in range(num_test):
closest_y = self.y_train[np.argsort(dists[i])[:k]]
y_prediction[i] = np.argmax(np.bincount(closest_y))
return y_prediction
可以说结构和nn中一致,不同的是 closest_y = self.y_train[np.argsort(dists[i])[:k]]
取出前k个数据的标签,然后bincount函数统计各个标签出现的次数,然后argmax函数找到出现次数最多的标签,返回给y_prediction[i]。
新建knn.py,运行如下代码得到结果
import numpy as np
import matplotlib.pyplot as plt
from load_data import load_data
from k_nearest_neighbor import k_nearest_neighbor
test = load_data()
X_train, y_train, X_val, y_val, X_test, y_test = test.data_generate(4900, 100, 1000)
knn = k_nearest_neighbor(X_train, y_train)
accuracy = [11]
for i in range(1, 11):
pre = knn.predict_labels(X_test, i)
num_correct = np.sum(pre == y_test)
print(i)
print(num_correct)
accuracy.append(float(float(num_correct)/500.0))
x = np.linspace(1, 1, 11)
print(accuracy)
[0.538, 0.456, 0.522, 0.536, 0.528, 0.54, 0.544, 0.554, 0.556, 0.554]