cs231n之KNN、SVM

cs231n之KNN、SVM

0.说在前面1.KNN2.SVM3.作者的话

0.说在前面

最近在学习cs231n,觉得有点困难,今天抽了一晚上时间来写这篇文章,作为总结。下面一起来看任务一的题目,由于篇幅长,故分成两部分,下节重点softmax!

1.KNN

np.flatnonzero

该函数输入一个矩阵,返回扁平化后矩阵中非零元素的位置(index)!!!

一个特殊用法:

idxs = np.flatnonzero(y_train == y)

上面这行代码是knn中的可视化图像数据的一行代码,表示从y_train中找到label为y的index,具体的例子实战如下:

In:

import numpy as np
x = np.arange(-3,5)

Out:

array([-3, -2, -1,  0,  1,  2,  3,  4])

In:

np.flatnonzero(x)

Out:

array([0, 1, 2, 4, 5, 6, 7], dtype=int64)

In:

np.flatnonzero(x==-3)

Out:

array([0], dtype=int64)

np.random.choice

原型

numpy.random.choice(a, size=None, replace=True, p=None)

从给定的一维数组中生成一个随机样本

参数

参数意义

a

为一维数组或者int数据;

size

为生成的数组维度;

replace

是否原地替换;

p

为样本出现的概率;

样例

np.random.choice(5,3) # 等价于np.random.randint(0,5,3)

p参数:

np.random.choice(5,3,p=[0,0,0.1,0.6,0.3])

注意p里面数量与a的数量一致,表示每个数随机选择的概率!

replace参数:

In:

a = ['bird','meh','sad','d','123']
np.random.choice(a,5) # replace默认为True
np.random.choice(a,5,replace=False)

Out:

array(['d', 'd', 'meh', '123', 'sad'], dtype='<U4')
array(['d', 'bird', 'meh', '123', 'sad'], dtype='<U4')

我们看到当replace为True的时候,也就是默认值,此时输出会重复,而设置为False,则输出不重复!!!

reshape中-1

In:

x = np.arange(-3,5)
x

Out:

array([-3, -2, -1,  0,  1,  2,  3,  4])

假设我们不知道这个数组多少行,我们想reshape成(8,1),那么如何通过-1来实现?

-1可以自动计算出数组的列数或行数

In:

x.reshape(-1,1)

Out:

array([[-3],
       [-2],
       [-1],
       [ 0],
       [ 1],
       [ 2],
       [ 3],
       [ 4]])

In

x.reshape(1,-1)

out:

array([[-3, -2, -1,  0,  1,  2,  3,  4]])

两层循环

首先看一下测试集与训练集的维度:

(5000, 3072) (500, 3072)

这里通过两层循环比较训练集和测试集的每一张图片的间距,最终得到(500,5000)的矩阵!

距离计算方法采用欧式距离计算!

# 传进来x_test shape(500,3072)
# self.X_train shape(5000,3072)
def compute_distances_two_loops(self, X):
    # 500
    num_test = X.shape[0]
    # 5000
    num_train = self.X_train.shape[0]
    # 生成一个shape(500,5000)的全为0矩阵
    dists = np.zeros((num_test, num_train))
    # 两重for循环
    for i in range(num_test):
        for j in range(num_train):
            # 利用欧式距离计算
            dists[i,j]=np.sqrt(np.sum(np.square(X[i,:]-self.X_train[j,:])))
    return dists

修改predict_labels

这里提示用argsort,先来学习一下这个argsort。

示例:

x = np.array([5,3,0,-1,9])
np.argsort(x)

输出:

array([3, 2, 1, 0, 4], dtype=int64)

上述可以得出,通过argsort可以返回数组当中的从小到大的索引数组!

# y_train shape(5000,)
def predict_labels(self, dists, k=1):
    num_test = dists.shape[0]
    y_pred = np.zeros(num_test)
    # 上面两层循环的得出dists shape(500,5000)
    # 下面通过循环来得出测试图片500张与训练图片5000张距离最近的前k个
    for i in range(num_test):
        closest_y = []
        # 目的是得出测试图片500张与训练图片5000张距离最近的前k个
        closest_y = self.y_train[np.argsort(dists[i,:])[:k]]
        # 投票得出预测值
        y_pred[i] = np.argmax(np.bincount(closest_y))
       return y_pred

在对k个图片投票时候,得票数最多的就是预测值,那么这里运用了,numpy的argmax与bincount,首先通过bincount计算出数组中索引出现的次数,为什么要这么取?

因为我们在预测后,会有很多个label,而当我们取出距离前k个label,只需要统计在这k个label中计算每个label的次数,然后直接再通过argmax取出出现次数最多的就是最后的预测结果!

实例:

假设最后的label只有三类,分别是0,1,2,我们用一个array模拟

x = np.array([0,1,2,2,1,1])

通过bincount计算出每个label的次数!

np.bincount(x)

输出:

array([1, 3, 2], dtype=int64)

0出现1次,1出现3次,2出现2次,那么再通过argmax可以求出最多次数的label

np.argmax(np.bincount(x))

输出:

1

可以得到最后的预测label为1。也就是我们进行投票的结果,上述模拟的array就是我们的前k个最大值,类比过去,就好理解了!

一层循环

一层循环,直接使用矩阵切片来完成,

def compute_distances_one_loop(self, X):
        # 500
        num_test = X.shape[0]
        # 5000
        num_train = self.X_train.shape[0]
        # (500,5000)
        dists = np.zeros((num_test, num_train))
        for i in range(num_test):
            dists[i, :] = np.sqrt(np.sum(np.square(self.X_train - X[i, :]), axis=1))
        return dists

X_train为(5000,3072),而X为(500,3072),对比维度

                维度:  0     1
self.X_train          5000  3072
X                     500   3072

根据broadcast机制,其中一个维度必须为1才可以满足broadcast的兼容性,那么将0维变为1即可,怎么变?

每次取X的当前行,也就是shape=(1,3072),这里就用到了numpy的broadcast机制。

就是上述,循环500次,每次取X一行,上述维度对比:

                维度:  0     1
self.X_train          5000  3072
X[i,:]                1     3072

那么此时就可以满足self.X_train与X[i,:]相减,此时得到的shape为(5000,3072),然后axis=1,按照行求和,得到shape(5000,),针对这个我这里用一个小例子验证:

t = np.sum([[0,1,2],[2,1,3]],axis=1)
t.shape

输出:

(2,)

对应的dists的shape为(500,5000),那么将上述每一行赋值给dists每一行即可完成一层循环操作!

零层循环

转化为公式:

def compute_distances_no_loops(self, X):
        num_test = X.shape[0]
        num_train = self.X_train.shape[0]
        dists = np.zeros((num_test, num_train))
        dists = np.sqrt(np.sum(X**2, axis=1, keepdims=True) + (-2 * np.dot(X, self.X_train.T)) + np.sum(self.X_train**2, axis=1))
        return dists

上述keepdims=True解释如下:

保持维度特性!!!

实例:

无keepdims参数加法:

np.sum(a**2,axis=1)

输出:

array([ 5, 25], dtype=int32)

会发现这个shape为(2,),跟原来的数组的维度不一样,那么如何保持原有维度?

这就需要加上keepdims参数!

np.sum(a**2,axis=1,keepdims=True)

输出:

array([[ 5],
       [25]], dtype=int32)        

同理,本题也是如此,保持了500行1列的维度,然后进行矩阵运算,而后面self.X_train没有用到keepdims参数,为什么?

我们想要得到的是(500,5000),而对于x^2与-2xy得到的分别是(500,1)与(500,5000)的shape,根据broadcast机制,直接可以进行加法,而后面那个如果加上keepdims参数,shape变为(5000,1)后,对比维度:

                维度:  0     1
x^2-2xy              500  5000
self.X_train^2       5000   1

此时,第0个维度,无法进行加法,所以不能使用keepdims参数!!!

如果不加keepdims,self,.X_train^2直接是一维的数组,那么直接可以对行进行扩展成500,列保持5000,最终shape为(500,5000),也就完成了零层循环!

最后,np.square**都是对矩阵的每个元素平方,而如果要对矩阵进行平方,可以用a.dot(a)或者np.dot(a,a),则是矩阵相乘!还有一个常用的是np.multiply(a,a),与上述square以及**结果一致!

交叉验证

设置超参数k为5,然后通过交叉验证寻找最佳k!

代码提示用np.array_split分割,下面给出实例:

x = np.arange(6)
np.array_split(x,3,axis=0)

输出:

[array([0, 1]), array([2, 3]), array([4, 5])]

对array进行切分!

将训练集与测试集进行按照5折进行切分!

X_train_folds = np.array_split(X_train, num_folds, axis=0) # list
y_train_folds = np.array_split(y_train, num_folds, axis=0) # list

循环5次,设置将第1个array设置为验证集,后4个设置为训练集,训练集通过np.concatenate进行纵向合并!

这里参考网上大佬的代码,下面来解释一下,主要是里面的swap data,这里截一张k折图,每次将当前的fold作业一个验证集,而在这里通过交换验证集的数据,将验证集的数据交换到fold1位置,这样便于处理!

后面便是通过选择k来进行模型训练,寻找最佳k即可!

# num_folds = 5
for i in range(num_folds):
    # train / validation split (80% 20%)
    X_train_batch = np.concatenate(X_train_folds[1:num_folds])   
    y_train_batch = np.concatenate(y_train_folds[1:num_folds])
    X_valid_batch = X_train_folds[0]   
    y_valid_batch = y_train_folds[0]
    # swap data (for next iteration)
    if i < num_folds - 1:
        tmp = X_train_folds[0]
        X_train_folds[0] = X_train_folds[i+1]
        X_train_folds[i+1] = tmp
        tmp = y_train_folds[0]
        y_train_folds[0] = y_train_folds[i+1]
        y_train_folds[i+1] = tmp
    # train model
    model = KNearestNeighbor()
    model.train(X_train_batch, y_train_batch)
    dists = model.compute_distances_no_loops(X_valid_batch)
    # compute accuracy for each k 
    for k in k_choices:
        y_valid_pred = model.predict_labels(dists, k=k)
        # compute validation accuracy
        num_correct = np.sum(y_valid_pred == y_valid_batch)
        accuracy = float(num_correct) / y_valid_batch.shape[0]
        # accumulate accuracy into dictionary
        if i == 0:
            k_to_accuracies[k] = [] 
        k_to_accuracies[k].append(accuracy)

2.SVM

数据预处理

数据shape,同上!

Training data shape:  (49000, 3072)
Validation data shape:  (1000, 3072)
Test data shape:  (1000, 3072)
dev data shape:  (500, 3072)

下面接着就是让训练集与测试集都减去减去图像的平均值!

np.hstack水平扩展

X_train = np.hstack([X_train, np.ones((X_train.shape[0], 1))])

将训练集,水平扩展(X_train.shape[0], 1),最终shape(49000,3073)

svm native

X是矩阵,W是权重(Wi表示每一类别权重)

X表示如下:

W表示如下:

S表示如下:

S中每一行对应

求梯度过程:

(6)式中wjxi表示第j个类别的得分!

Li表示(3)式中的每一行loss,Li的梯度写成如7所示的列向量,将每一行的loss分成每一个元素的loss,即公式(7)。

将Li的梯度分成最小的求Lij梯度!

对应的额代码,如下:

def svm_loss_naive(W, X, y, reg):
  # C
  num_classes = W.shape[1]
  # N
  num_train = X.shape[0]
  loss = 0.0
  for i in range(num_train):
    # Li 每一行,对应(3)式
    scores = X[i].dot(W)
    # scores[y[i]] WyjXi 真正lable得分
    correct_class_score = scores[y[i]]
    # 每一列
    for j in range(num_classes):
      # j=y[i]不能求和,跳过
      if j == y[i]:
        continue
      # Lij 对应(6)式中的每一个Lij
      margin = scores[j] - correct_class_score + 1 # note delta = 1
      # score>0
      if margin > 0:
        # 对应(6)式求和
        loss += margin
        # 对应(10)式
        dW[:, y[i]] += -X[i, :]
        # 对应(9)式
        dW[:, j] += X[i, :]

有关svm及softmax后面继谈!

本文分享自微信公众号 - 光城(guangcity),作者:lightcity

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SVM梯度求导及实现

    昨晚看了一部电影,叫做我是马布里,非常正能量,推荐给各位,看完这部电影的总结话是:

    公众号guangcity
  • 再谈强大的Numpy

    今天发现cs231n还差一个features.py未更新,特更,并且更新中间穿插的numpy使用!

    公众号guangcity
  • Softmax及两层神经网络

    0.说在前面1.Softmax向量化1.1 Softmax梯度推导1.2 Softmax向量化实现2.两层神经网络2.1 反向传播推导2.2 两层神经网络实现3...

    公众号guangcity
  • 斯坦福CS231n项目实战(三):Softmax线性分类

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    红色石头
  • Zookeeper应用:hadoop的HA简单实现

    hadoop的HDFS集群的分布式存储是靠NameNode节点(namenode负责响应客户端请求)来实现。NameNode对应整个hadoop来说,无非是很重...

    dodo_lihao
  • Occupancy Networks:基于学习函数空间的三维重建表示方法

    随着深度神经网络的到来,基于学习的三维重建方法逐渐变得流行。但是和图像不同的是,在3D中没有规范的表示,既能高效地进行计算,又能有效地存储,同时还能表示任意拓扑...

    3D视觉工坊
  • 【解决方案】TSINGSEE青犀视频云边端架构RTSP视频边缘计算平台EasyNVR搭建“云上透明厨房”远程监控平台方案

    餐饮行业对操作和环境要求都比较高。很多直营的餐饮点由于店面较多,人员层次不齐,时长发现一些不和规范的操作,导致响应口碑下载,这样严重影响餐饮企业的品牌口碑。

    EasyNVR
  • Laravel 5.5 在浏览器中渲染 Mailable 类型

    Laravel 框架中提供了很方便快捷的面向对象风格的电子邮件相关功能,可以通过 Markdown 语法、Blade 模板引擎来制作邮件模板,然后通过扩展 Ma...

    小李刀刀
  • 一文详解JDK13新特性

    核心库/ java.nio中添加了FileSystems.newFileSystem(Path,Map <String,?>)方法

    用户2781897
  • 一篇文章看清楚JDK13的特性!

    提高应用程序类 - 数据共享(AppCDS)的可用性。消除了用户进行试运行以创建每个应用程序的类列表的需要。

    程序员追风

扫码关注云+社区

领取腾讯云代金券