2019-05-20 00:41:12 qq_37367702 阅读数 210
  • 数据挖掘模型篇之R语言实践

    理论与实践结合的方式,通过通俗易懂的教学方式培养学生运用R语言完成常用挖掘模型算法建立及评估,学习完课程可以掌握:线性回归模型、聚类分析、关联规则算法、KNN近邻算法和主成分分析等常用的模型算法实现。针对具体的数据挖掘应用需求,能熟练抽象出可合适的数据挖掘模型,并整理出其技术实现路线。

    3694 人正在学习 去看看 谢佳标

目录

 

一、K邻近分类法(KNN)

1.1 knn算法概述

1.2 knn算法基本思想

1.3 knn算法方法

1.4 knn算法步骤

二、实现代码及结果分析 

2.1 knn

2.2 二维示例

2.3 图像特征-稠密SIFT 

 2.4 图像分类:手势识别


一、K邻近分类法(KNN)

1.1 knn算法概述

  K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。

1.2 knn算法基本思想

  KNN算法的基本思想是:把待分类文本表示成文本向量,与训练样本组成的样本空间中的向量计算相似度,得到k篇与该文本距离最近(最相似)的文本,根据这k篇文本所属的类别判定新文本所属的类别,在新文本的k个邻居中依次计算每类的权重,将文本分到权重最大的类中。

1.3 knn算法方法

  • 目标:分类未知类别案例。
  • 输入:待分类未知类别案例项目。已知类别案例集合D ,其中包含 j个已知类别的案例。
  • 输出:项目可能的类别。

1.4 knn算法步骤

  1. 依公式计算 Item 与 D1、D2 … …、Dj 之相似度。得到Sim(Item, D1)、Sim(Item, D2)… …、Sim(Item, Dj)。
  2. 将Sim(Item, D1)、Sim(Item, D2)… …、Sim(Item, Dj)排序,若是超过相似度门槛t则放入邻居案例集合NN。
  3. 自邻居案例集合NN中取出前k名,依多数决,得到Item可能类别。

二、实现代码及结果分析 

2.1 knn

给定训练样本集和对应的标记列表,定义一个类并用训练数据初始化

from numpy import * 
class KnnClassifier(object):
    def __init__(self,labels,samples):
        """ Initialize classifier with training data. """
        
        self.labels = labels
        self.samples = samples
    def classify(self,point,k=3):
        """ Classify a point against k nearest 
            in the training data, return label. """
        
        # compute distance to all training points
        dist = array([L2dist(point,s) for s in self.samples])
        
        # sort them
        ndx = dist.argsort()
        
        # use dictionary to store the k nearest
        votes = {}
        for i in range(k):
            label = self.labels[ndx[i]]
            votes.setdefault(label,0)
            votes[label] += 1
            
        return max(votes, key=lambda x: votes.get(x))
def L2dist(p1,p2):
    return sqrt( sum( (p1-p2)**2) )

2.2 二维示例

先创建两个不同的二维点集

n = 200
# two normal distributions
class_1 = 0.7 * randn(n,2)
class_2 = 1.6 * randn(n,2) + array([6,1])
labels = hstack((ones(n),-ones(n)))
# save with Pickle
#with open('points_normal.pkl', 'wb') as f:
with open('points_normal_test.pkl', 'wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
# normal distribution and ring around it
class_1 = 0.6 * randn(n,2)
r = 0.8 * randn(n,1) + 5
angle = 2*pi * randn(n,1)
class_2 = hstack((r*cos(angle),r*sin(angle)))
labels = hstack((ones(n),-ones(n)))
# save with Pickle
#with open('points_ring.pkl', 'wb') as f:
with open('points_ring_test.pkl', 'wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
pklist=['points_normal.pkl','points_ring.pkl']

figure()

# load 2D points using Pickle
for i, pklfile in enumerate(pklist):
    with open(pklfile, 'rb') as f:
        class_1 = pickle.load(f)
        class_2 = pickle.load(f)
        labels = pickle.load(f)
    # load test data using Pickle
    with open(pklfile[:-4]+'_test.pkl', 'rb') as f:
        class_1 = pickle.load(f)
        class_2 = pickle.load(f)
        labels = pickle.load(f)
    model = knn.KnnClassifier(labels,vstack((class_1,class_2)))
    # test on the first point
    print (model.classify(class_1[0]))
    #define function for plotting
    def classify(x,y,model=model):
        return array([model.classify([xx,yy]) for (xx,yy) in zip(x,y)])
    # lot the classification boundary
    subplot(1,2,i+1)
    imtools.plot_2D_boundary([-6,6,-6,6],[class_1,class_2],classify,[1,-1])
    titlename=pklfile[:-4]
    title(titlename)
show()

图一中第一幅图中class2数据(红色)较为分散时 ,分类器基本能根据蓝色数据较为密集而划分线,但仍有部分红色数据在划分线内;第二幅图 两个数据较为集中,能够很好的画出分类线。 但也许会存在过拟合状态。

 

2.3 图像特征-稠密SIFT 

用密集采样的SIFT描述子处理一幅图像,并将结果保存在一个文件中

def process_image_dsift(imagename,resultname,size=20,steps=10,force_orientation=False,resize=None):
   im = Image.open(imagename).convert('L')
    if resize!=None:
        im = im.resize(resize)
    m,n = im.size
    
    if imagename[-3:] != 'pgm':
        #create a pgm file
        im.save('tmp.pgm')
        imagename = 'tmp.pgm'

    # create frames and save to temporary file
    scale = size/3.0
    x,y = meshgrid(range(steps,m,steps),range(steps,n,steps))
    xx,yy = x.flatten(),y.flatten()
    frame = array([xx,yy,scale*ones(xx.shape[0]),zeros(xx.shape[0])])
    savetxt('tmp.frame',frame.T,fmt='%03.3f')    
    path = os.path.abspath(os.path.join(os.path.dirname("__file__"),os.path.pardir))
    path = path + "\\ch08\\win32vlfeat\\sift.exe "
    if force_orientation:
        cmmd = str(path+imagename+" --output="+resultname+
                    " --read-frames=tmp.frame --orientations")
    else:
        cmmd = str(path+imagename+" --output="+resultname+
                    " --read-frames=tmp.frame")
    os.system(cmmd)
    print ('processed', imagename, 'to', resultname)

计算图片的dsift描述子,并可视化它们的位置

dsift.process_image_dsift('gesture/test/c.jpg','gesture/test/c.dsift',90,40,True)
l,d = sift.read_features_from_file('gesture/test/c.dsift')
im = array(Image.open('gesture/test/c.jpg'))
sift.plot_features(im,l,True)
title('dense SIFT')
show()

 图为尚大楼,特征大小size为90,位置之间步长steps为20

 2.4 图像分类:手势识别

下面方法用于从文件中读取稠密SIFT描述子

def read_gesture_features_labels(path):
    # create list of all files ending in .dsift
    featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
    # read the features
    features = []
    for featfile in featlist:
        l,d = sift.read_features_from_file(featfile)
        features.append(d.flatten())
    features = array(features)
    # create labels
    labels = [featfile.split('/')[-1][0] for featfile in featlist]
    return features,array(labels)

读取训练集测试集的特征和标记信息(用文件名的第一个字母作为类标记)

filelist_train = get_imagelist('gesture/train')
filelist_test = get_imagelist('gesture/test')
imlist=filelist_train+filelist_test

测试KNN及准确率(用训练数据及其标记作为输入,创建分类器对象;然后在整个测试集上遍历并用classify()方法对每幅图像进行分类,将布尔数组和1相乘并求和,可以算出分类的准确率)

k = 1
knn_classifier = knn.KnnClassifier(labels,features)
res = array([knn_classifier.classify(test_features[i],k) for i in
range(len(test_labels))])
# accuracy
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print ('Accuracy:', acc)

 打印出标记及相应的混淆矩阵

def print_confusion(res,labels,classnames):
    n = len(classnames)
    # confusion matrix
    class_ind = dict([(classnames[i],i) for i in range(n)])
    confuse = zeros((n,n))
    for i in range(len(test_labels)):
        confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
    print ('Confusion matrix for')
    print (classnames)
    print (confuse)

下图为带有dsift描述子的各图象及其表示含义

第一次出来的正确率以及每张图片的数量,矩阵显示每类有多少个样本被分在每一类中,其中可看出P被错配成V的错误率较高,有13张,因为两个手势 相似度较高。其次为B错配为F的有4张。

将训练集和测试集对换出来的正确率及混淆矩阵,正确率比上面一组低,其中 P被错配成V的比率较高,但比上面的错误率小一些;F被错配为B的有七张,B被错配为F的有5张,明显比上面的F和B错配率高,原因是在训练集中FIVE中有几张没有长的很开。

 

 

2019-05-19 23:21:39 qq_42584188 阅读数 22
  • 数据挖掘模型篇之R语言实践

    理论与实践结合的方式,通过通俗易懂的教学方式培养学生运用R语言完成常用挖掘模型算法建立及评估,学习完课程可以掌握:线性回归模型、聚类分析、关联规则算法、KNN近邻算法和主成分分析等常用的模型算法实现。针对具体的数据挖掘应用需求,能熟练抽象出可合适的数据挖掘模型,并整理出其技术实现路线。

    3694 人正在学习 去看看 谢佳标

1.KNN介绍

1.1 KNN思想的基本思想

把要分类的对象(例如)一个特征向量与训练集中已知类标记的所有对象进行对比,并由k近邻对指派到哪个类进行投票。
比如说k=3,我们要找到下图中绿色原点属于哪个类别。k个最近的邻居哪个最多,就将该对象分给该类别。黑圈内是绿点的最近的3个邻居。
在这里插入图片描述

1.2 KNN三要素

1.距离度量
KNN算法的核心在于要找到实例点的距离。要找邻居就要度量相似性。估量不同样本之间的相似性,通常采用的方法就是计算样本间的“距离”,相似性度量方法有:欧式距离、余弦夹角、曼哈顿距离、切比雪夫距离等。
2.k值选择
若k值较小,只有与输入实例较近(相似)的训练实例才会对预测结果起作用,预测结果会对近邻实例点非常敏感。如果近邻实例点恰巧是噪声,预测就会出错。容易发生过拟合。
若k较大,与输入实例较远的(不相似的)训练实例也会对预测起作用,容易使预测出错。k值的增大就意味着整体的模型变简单。
3.分类决策规则
分类决策规则是指找到k个邻居后该如何分类待分类对象。常用的方法有:投票表决(少数服从多数),加权投票法(根据距离的远近,对k个近邻的投票进行加权,距离越近则权重越大)

1.3 KNN实现过程

(1)计算当前待分类对象与训练集中已知类标记的所有对象的距离;
(2)按照距离递增次序排序;
(3)选取与待分类对象距离最小的k个训练实例;
(4)统计这k个实例所属各个类别数;
(5)将统计的类别数最多的类别作为待分类的预测类别

2.稠密SIFT(Dense SIFT)

稠密SIFT省去了传统SIFT特征中尺度变换和采样点的步骤,直接在指定尺寸的采样窗口中对图像进行均匀采样。稠密SIFT特征提取方法不需要进行采样点筛选和特征归一化等繁琐计算,特征提取效率较高,易于实现。此外,通过均匀采样提取到的稠密特征能够更为全面地描述图像不同区域的差异信息,并且一定程度上兼顾到图像空间位置关系等全局信息,更适合图像表示和图像分类。
然而,尽管稠密SIF特征有如上优势,但依然无法替代稀疏SIFT,其中一个原因是图像的特征描述很大程度上依赖于图像的尺度,很多细节结构只存在于一定的尺度范围内,传统的稀疏SIFT通过高斯金字塔空间来实现图像的尺度变换,从而可以捕捉到原始图像中难以发现先的深层次细节信息。相比之下,稠密SIFT缺乏多尺度结构,只能发现先图像在单一尺度下表现出来的表层特征,不利于挖掘图像隐藏在深层次中的细节信息。

3.实验分析

3.1 KNN的实现

在这个实验中用欧氏距离进行度量,分类决策规则采用投票决策。

class KnnClassifier(object):
    
    def __init__(self,labels,samples):
        """ Initialize classifier with training data. """
        #使用训练数据初始化分类器
        
        self.labels = labels
        self.samples = samples
    
    def classify(self,point,k=3):
        """ Classify a point against k nearest 
            in the training data, return label. """
            #在训练数据上采用k近邻分类,并返回标记
        
        # compute distance to all training points
        #计算所有训练数据点的距离
        dist = array([L2dist(point,s) for s in self.samples])
        
        # sort them
        #对他们进行排序
        ndx = dist.argsort()
        
        # use dictionary to store the k nearest
        #用字典存储k近邻
        votes = {}
        for i in range(k):
            label = self.labels[ndx[i]]
            votes.setdefault(label,0)
            votes[label] += 1
            
        return max(votes, key=lambda x: votes.get(x))


def L2dist(p1,p2):
    return sqrt( sum( (p1-p2)**2) )
3.2 简单的二维示例

先建立一些简单的而为示例数据集来说明并可视化分类器的工作原理,下面的脚本将创建两个不同的二维点集,每个点集有两类,用PIckle模块来保存创建的数据。

# -*- coding: utf-8 -*-
from numpy.random import randn
import pickle
from pylab import *

# create sample data of 2D points
n = 200
# two normal distributions
class_1 = 0.6 * randn(n,2)
class_2 = 1.2 * randn(n,2) + array([5,1])
labels = hstack((ones(n),-ones(n)))
# save with Pickle
#with open('points_normal.pkl', 'w') as f:
with open('points_normal_test.pkl', 'wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
# normal distribution and ring around it
print ("save OK!")
class_1 = 0.6 * randn(n,2)
r = 0.8 * randn(n,1) + 5
angle = 2*pi * randn(n,1)
class_2 = hstack((r*cos(angle),r*sin(angle)))
labels = hstack((ones(n),-ones(n)))
# save with Pickle
#with open('points_ring.pkl', 'w') as f:
with open('points_ring_test.pkl', 'wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
    
print ("save OK!")

运行上面的代码,我们将会得到4个二维数据及文件,每个分布都有两个文件,我们可以将一个用来训练,另一个用来做测试。

# -*- coding: utf-8 -*-
import pickle
from pylab import *
from PCV.classifiers import knn
from PCV.tools import imtools

pklist=['points_normal.pkl','points_ring.pkl']

figure()

# 利用Pickle导入二维数据集
for i, pklfile in enumerate(pklist):
    with open(pklfile, 'rb') as f:
        class_1 = pickle.load(f)
        class_2 = pickle.load(f)
        labels = pickle.load(f)
    # load test data using Pickle
    #用Pickle模块载入测试数据
    with open(pklfile[:-4]+'_test.pkl', 'rb') as f:
        class_1 = pickle.load(f)
        class_2 = pickle.load(f)
        labels = pickle.load(f)

    model = knn.KnnClassifier(labels,vstack((class_1,class_2)))
    # test on the first point
    #在测试数据集的第一个数据点上进行测试
    print (model.classify(class_1[0]))

    #define function for plotting
    #定义绘图函数
    def classify(x,y,model=model):
        return array([model.classify([xx,yy]) for (xx,yy) in zip(x,y)])

    # lot the classification boundary
    #绘制分类边界
    subplot(1,2,i+1)
    imtools.plot_2D_boundary([-6,6,-6,6],[class_1,class_2],classify,[1,-1])
    titlename=pklfile[:-4]
    title(titlename)
show()

在上面的代码中我们创建了一个简短的辅助函数以获取x和y二维坐标数组和分类器,并返回一个预测的类标记数组。现在把函数作为参数传递给实际的绘图函数,该绘图函数我们将其添加到imtools中:

def plot_2D_boundary(plot_range,points,decisionfcn,labels,values=[0]):
    """    Plot_range is (xmin,xmax,ymin,ymax), points is a list
        of class points, decisionfcn is a funtion to evaluate, 
        labels is a list of labels that decisionfcn returns for each class, 
        values is a list of decision contours to show. """
        #Plot_range为(xmin,xmax,ymin,ymax),points是类数据点列表,decisionfcn是评估函数。
        #Labels是函数decidionfcn关于每个类返回的标记列表。
        
    #不同的类用不同颜色标记    
    clist = ['b','r','g','k','m','y'] # colors for the classes
    
    # evaluate on a grid and plot contour of decision function
    #在一个网格上进行评估,并画出决策函数的边界
    x = arange(plot_range[0],plot_range[1],.1)
    y = arange(plot_range[2],plot_range[3],.1)
    xx,yy = meshgrid(x,y)
    xxx,yyy = xx.flatten(),yy.flatten() # lists of x,y in grid 网格中的x,y坐标点列表
    zz = array(decisionfcn(xxx,yyy)) 
    zz = zz.reshape(xx.shape)
    # plot contour(s) at values
    #以values画出边界
    contour(xx,yy,zz,values) 
        
    # for each class, plot the points with '*' for correct, 'o' for incorrect
    #对于每类,用*画出分类正确的点,用o画出分类不正确的点
    for i in range(len(points)):
        d = decisionfcn(points[i][:,0],points[i][:,1])
        correct_ndx = labels[i]==d
        incorrect_ndx = labels[i]!=d
        plot(points[i][correct_ndx,0],points[i][correct_ndx,1],'*',color=clist[i])
        plot(points[i][incorrect_ndx,0],points[i][incorrect_ndx,1],'o',color=clist[i])
    
    axis('equal')

这个函数需要一个决策函数,并且用meshgrid()函数在一个网格上进行预测。预测函数的等值线可以显示边界的位置,默认边界为零等值线。
随机数的生成使用的是python的numpy.random的randn函数,此函数生成标准正太分布的随机数。以上述代码中的”0.8 * randn(n,1) + 5“的意思就是方差为0.8,均值为5的正太分布数据。
画出的结果如下所示:
在这里插入图片描述
左图的class_1的方差是0.6,class_1的方差是1.2,他们俩的数据的中心点就分开来了,而之所以class_2的数据画出来感觉比较大是因为class_1的数据比较集中,而class_2=“1.2 * randn(n,2) + array([5,1])”,意思就是class_2的数据x轴跟y轴的正太分布的方差都是1.2,但是x轴的方差是5,y轴的方差是1。方差影响的是数据的离散程度,值越大越分散,而在正太分布中曲线是越扁平,所以大家可以看到class_2的数据分布类似椭圆形,而class_1的数据分布类似圆形(因为x轴和y轴的方差一样都是0)
右图的class_1 = 0.6 * randn(n,2),理解如上。而class_2的设置如下

r = 0.8 * randn(n,1) + 5
angle = 2*pi * randn(n,1)
class_2 = hstack((r*cos(angle),r*sin(angle)))

也就是我们先用randn函数正太分布出圆形的半径r,以及随机的角度,根据得到的半径及角度可算出其对应的坐标。所以也就是说如果我们想要自己测试看的话,为了能够让其能跟class_1的数据集分开,就要保证均值要够大才行,方差要跟class_1的相近。
在右图中,
们可以通过修改class_1及class_2的随机函数来修改数据集。

#左图的数据集
class_1 = 0.6 * randn(n,2)+0.6
class_2 = 1.2 * randn(n,2) + array([5,1])

#右图的数据集
class_1 = 0.6 * randn(n,2)
r = 0.8 * randn(n,1) + 2
angle = 2*pi * randn(n,1)
class_2 = hstack((r*cos(angle),r*sin(angle)))
labels = hstack((ones(n),-ones(n)))

下图是修改class_1和class_2的结果图
在这里插入图片描述
跟上图的数据集相比,我将左图的class_1的均值加0.6,说实话单个看的话感觉没变多少,因为我x轴跟y轴的数据的均值都是0.6,所以相比之前其数据会稍微分散了一些。
将右图的class_2的半径r的均值的值从6改为2。红色数据(class_2)会比较集中,是因为均值比较小,数据比较集中。
k值选择
若k值较小,只有与输入实例较近(相似)的训练实例才会对预测结果起作用,预测结果会对近邻实例点非常敏感。如果近邻实例点恰巧是噪声,预测就会出错。容易发生过拟合。
若k较大,与输入实例较远的(不相似的)训练实例也会对预测起作用,容易使预测出错。k值的增大就意味着整体的模型变简单。

3.3 图像分类:手势识别

因为我们自己拍摄手势的照片,我们希望手势的形状可以都提取到其特征,而用稀疏SIFT,可能不会把我们要的特征都提取出来,可能提取到的特征比较多的是背景,这与我们的需求不符,为了能够保证手势的形状特征都能够提取出来,我们使用稠密SIFT描述子来表示这些手势图像。下面的代码功能是展现生成的带有稠密SIFT子的图像。

# -*- coding: utf-8 -*-
import os
from PCV.localdescriptors import sift, dsift
from pylab import  *
from PIL import Image

imlist=['gesture/train/C-uniform02.ppm','gesture/train/B-uniform01.ppm',
        'gesture/train/A-uniform01.ppm','gesture/train/Five-uniform01.ppm',
        'gesture/train/Point-uniform01.ppm','gesture/train/V-uniform01.ppm']

figure()
for i, im in enumerate(imlist):
    print (im)
    dsift.process_image_dsift(im,im[:-3]+'dsift',11,5,True)
    l,d = sift.read_features_from_file(im[:-3]+'dsift')
    dirpath, filename=os.path.split(im)
    im = array(Image.open(im))
    #显示手势含义title
    titlename=filename[:-14]
    subplot(2,3,i+1)
    sift.plot_features(im,l,True)
    title(titlename)
show()

在这里插入图片描述
稠密SIFT代码(某一文件下的全部图像特征描述子)

import os
from PCV.localdescriptors import sift, dsift
from PCV.tools import imtools
from pylab import  *
from PIL import Image

imlist = imtools.get_imlist('gesture/train')
for filename in imlist:
    featfile = filename[:-3]+'dsift'
    dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50));

这里将图像分辨率调成了固定的大小。如果不调整的话,可能会导致后面这些图像生成的描述子数量不一,从而每幅图像的特征向量长度不一样,会导致在后面比较它们时出错。
正式的手势识别实验
代码

# -*- coding: utf-8 -*-
from PCV.localdescriptors import dsift
import os
from PCV.localdescriptors import sift
from pylab import *
from PCV.classifiers import knn

def get_imagelist(path):
    """    Returns a list of filenames for
        all jpg images in a directory. """

    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]

def read_gesture_features_labels(path):
    # 多所有以.dsift为后缀的文件创建一个列表
    featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
    # 读取特征
    features = []
    for featfile in featlist:
        l,d = sift.read_features_from_file(featfile)
        features.append(d.flatten())
    features = array(features)
    # 创建标记
    labels = [featfile.split('/')[-1][0] for featfile in featlist]
    return features,array(labels)

def print_confusion(res,labels,classnames):
    n = len(classnames)
    # 混淆矩阵
    class_ind = dict([(classnames[i],i) for i in range(n)])
    confuse = zeros((n,n))
    for i in range(len(test_labels)):
        confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
    print ('Confusion matrix for')
    print (classnames)
    print (confuse)

filelist_train = get_imagelist('gesture/train')
filelist_test = get_imagelist('gesture/test')
imlist=filelist_train+filelist_test

# process images at fixed size (50,50)
for filename in imlist:
    featfile = filename[:-3]+'dsift'
    dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))

features,labels = read_gesture_features_labels('gesture/train/')
test_features,test_labels = read_gesture_features_labels('gesture/test/')
classnames = unique(labels)

# 测试 kNN
k = 1
knn_classifier = knn.KnnClassifier(labels,features)
res = array([knn_classifier.classify(test_features[i],k) for i in
range(len(test_labels))])
# 准确率
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print ('Accuracy:', acc)

print_confusion(res,test_labels,classnames)

实验结果
在这里插入图片描述
k值对分类的影响
1.该结果是在k=1,以及稠密SIFT图像描述子的大小为10,位置之间的步长为5的情况下的结果,正确率为94.12%,Four的有张手势照片出错。
在这里插入图片描述
左边是“A”,右边是“Four”。
2.下图结果是在k=3,以及稠密SIFT图像描述子的大小为10,位置之间的步长为5的情况下的结果,正确率为88.23%。
在这里插入图片描述
3.下图结果是在k=6,以及稠密SIFT图像描述子的大小为10,位置之间的步长为5的情况下的结果,正确率为76.47%。
在这里插入图片描述
结果表明:k较大,与输入实例较远的(不相似的)训练实例也会对预测起作用,容易使预测出错。
稠密SIFT图像描述子的大小对分类的影响
1.下图结果是在稠密SIFT图像描述子的大小为10,位置之间的步长为5,以及k=1的情况下的结果,正确率为94.12%,
在这里插入图片描述
2.下图结果是在稠密SIFT图像描述子的大小为5,位置之间的步长为5,以及k=1的情况下的结果,正确率为94.12%,
在这里插入图片描述
3.下图结果是在稠密SIFT图像描述子的大小为2,位置之间的步长为5,以及k=1的情况下的结果,正确率为94.12%,
在这里插入图片描述
4.下图结果是在稠密SIFT图像描述子的大小为15,位置之间的步长为5,以及k=1的情况下的结果,正确率为94.12%,
在这里插入图片描述
5.下图结果是在稠密SIFT图像描述子的大小为25,位置之间的步长为5,以及k=1的情况下的结果,正确率为94.12%,
在这里插入图片描述
1) 1,2,3的实验是描述子的大小逐渐减小,正确率没有改变。4到5是描述子的增大,正确率没有改变。
5.下图结果是在稠密SIFT图像描述子的大小为25,位置之间的步长为5,以及k=6的情况下的结果,正确率为82.36%.我们在前面的实验曾经测试过在“k=6,以及稠密SIFT图像描述子的大小为10,位置之间的步长为5的情况下的结果,正确率为76.47%”。
在这里插入图片描述
6.下图结果是在稠密SIFT图像描述子的大小为5,位置之间的步长为5,以及k=6的情况下的结果,正确率为82.36%.我们在前面的实验曾经测试过在“k=6,以及稠密SIFT图像描述子的大小为10,位置之间的步长为5的情况下的结果,正确率为76.47%”。
在这里插入图片描述
2) 在相同步长的情况下,描述子小的话,每个特征所包含的信息会少,相反大的话,每个特征所包含的信息多。但在实验中我们需要的最好的描述子的大小,是能够确保每个描述子能够将我们所需要的信息都包含进去。综上实验表明,特征描述子的大小在分类器的k值非常适合时,可能不会再影响到实验的结果,不会让实验结果变得更好,也不会更糟糕,但在k值不是那么合适的时候,特征描述子的大小会影响到实验结果,从上面的5跟6可以看出特征描述子不管变小还是变大,在k值不合适的时候都会增加分类的正确性。

2016-06-08 20:59:30 littlethunder 阅读数 20628
  • 数据挖掘模型篇之R语言实践

    理论与实践结合的方式,通过通俗易懂的教学方式培养学生运用R语言完成常用挖掘模型算法建立及评估,学习完课程可以掌握:线性回归模型、聚类分析、关联规则算法、KNN近邻算法和主成分分析等常用的模型算法实现。针对具体的数据挖掘应用需求,能熟练抽象出可合适的数据挖掘模型,并整理出其技术实现路线。

    3694 人正在学习 去看看 谢佳标

OCR应用非常广泛,而且有许多方法,今天用KNN算法实现简单的0-9手写数字识别。本程序使用OpenCV 3.0和Python 3。


KNN算法是K近邻分类算法,属于机器学习中的监督学习,需要一定量的带标签的输入样本数据进行“训练”,然后就可以识别。我给“训练”打引号是因为其实KNN没有明显的前期训练过程,它是要给一个样本x分类,就从数据集中在x附近找离它最近的k各数据点,这k个数据点中包含的y类别最多,那么就把x的标签标记为y,这就完成了分类识别的过程。


首先,利用OpenCV自带的手写数字样本集digits.png来进行初始训练:



def initKnn():
    knn = cv2.ml.KNearest_create()
    img = cv2.imread('digits.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
    train = np.array(cells).reshape(-1,400).astype(np.float32)
    trainLabel = np.repeat(np.arange(10),500)
    return knn, train, trainLabel
这是总共5000个数据,0-9各500个,我们读入图片后整理数据,这样得到的train和trainLabel依次对应,图像数据和标签。


def updateKnn(knn, train, trainLabel, newData=None, newDataLabel=None):
    if newData != None and newDataLabel != None:
        print(train.shape, newData.shape)
        newData = newData.reshape(-1,400).astype(np.float32)
        train = np.vstack((train,newData))
        trainLabel = np.hstack((trainLabel,newDataLabel))
    knn.train(train,cv2.ml.ROW_SAMPLE,trainLabel)
    return knn, train, trainLabel
updateKnn是增加自己的训练数据后更新Knn的操作。


def findRoi(frame, thresValue):
    rois = []
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.dilate(gray,None,iterations=2)
    gray2 = cv2.erode(gray2,None,iterations=2)
    edges = cv2.absdiff(gray,gray2)
    x = cv2.Sobel(edges,cv2.CV_16S,1,0)
    y = cv2.Sobel(edges,cv2.CV_16S,0,1)
    absX = cv2.convertScaleAbs(x)
    absY = cv2.convertScaleAbs(y)
    dst = cv2.addWeighted(absX,0.5,absY,0.5,0)
    ret, ddst = cv2.threshold(dst,thresValue,255,cv2.THRESH_BINARY)
    im, contours, hierarchy = cv2.findContours(ddst,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    for c in contours:
        x, y, w, h = cv2.boundingRect(c)
        if w > 10 and h > 20:
            rois.append((x,y,w,h))
    return rois, edges
findRoi函数是找到每个数字的位置,用包裹其最小矩形的左上顶点的坐标和该矩形长宽表示(x, y, w, h)。这里还用到了Sobel算子。edges是原始图像形态变换之后的灰度图,可以排除一些背景的影响,比如本子边缘、纸面的格子、手、笔以及影子等等,用edges来获取数字图像效果比Sobel获取的边界效果要好。

def findDigit(knn, roi, thresValue):
    ret, th = cv2.threshold(roi, thresValue, 255, cv2.THRESH_BINARY)
    th = cv2.resize(th,(20,20))
    out = th.reshape(-1,400).astype(np.float32)
    ret, result, neighbours, dist = knn.findNearest(out, k=5)
    return int(result[0][0]), th
findDigit函数是用KNN来分类,并将结果返回。th是用来手动输入训练数据时显示的图片。20x20pixel的尺寸是OpenCV自带digits.png中图像尺寸,因为我是在其基础上更新数据,所以沿用这个尺寸。


def concatenate(images):
    n = len(images)
    output = np.zeros(20*20*n).reshape(-1,20)
    for i in range(n):
        output[20*i:20*(i+1),:] = images[i]
    return output
concatenate函数是拼接数字图像并显示的,用来输入训练数据。


while True:
    ret, frame = cap.read()
    frame = frame[:,:426]
    rois, edges = findRoi(frame, 50)
    digits = []
    for r in rois:
        x, y, w, h = r
        digit, th = findDigit(knn, edges[y:y+h,x:x+w], 50)
        digits.append(cv2.resize(th,(20,20)))
        cv2.rectangle(frame, (x,y), (x+w,y+h), (153,153,0), 2)
        cv2.putText(frame, str(digit), (x,y), cv2.FONT_HERSHEY_SIMPLEX, 1, (127,0,255), 2)
    newEdges = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
    newFrame = np.hstack((frame,newEdges))
    cv2.imshow('frame', newFrame)
    videoFrame.write(newFrame)
    key = cv2.waitKey(1) & 0xff
    if key == ord(' '):
        break
    elif key == ord('x'):
        Nd = len(digits)
        output = concatenate(digits)
        showDigits = cv2.resize(output,(60,60*Nd))
        cv2.imshow('digits', showDigits)
        cv2.imwrite(str(count)+'.png', showDigits)
        count += 1
        if cv2.waitKey(0) & 0xff == ord('e'):
            pass
        print('input the digits(separate by space):')
        numbers = input().split(' ')
        Nn = len(numbers)
        if Nd != Nn:
            print('update KNN fail!')
            continue
        try:
            for i in range(Nn):
                numbers[i] = int(numbers[i])
        except:
            continue
        knn, train, trainLabel = updateKnn(knn, train, trainLabel, output, numbers)
        print('update KNN, Done!')
这是主函数循环部分,按“x”键会暂停屏幕并显示获取的数字图像,按“e”键会提示输入看到的数字,在终端输入数字用空格隔开,按回车如果显示“update KNN, Done!”则完成一次更新。下面是我用20多组0-9数字更新训练后得到的结果:




演示视频:http://www.bilibili.com/video/av4904541/

完整代码:https://github.com/littlethunder/digitsOCR


转载请注明:转自http://blog.csdn.net/littlethunder/article/details/51615237


2015-11-20 17:16:17 yaoqiang2011 阅读数 47306
  • 数据挖掘模型篇之R语言实践

    理论与实践结合的方式,通过通俗易懂的教学方式培养学生运用R语言完成常用挖掘模型算法建立及评估,学习完课程可以掌握:线性回归模型、聚类分析、关联规则算法、KNN近邻算法和主成分分析等常用的模型算法实现。针对具体的数据挖掘应用需求,能熟练抽象出可合适的数据挖掘模型,并整理出其技术实现路线。

    3694 人正在学习 去看看 谢佳标

作者: 寒小阳
时间:2015年11月。
出处:http://blog.csdn.net/han_xiaoyang/article/details/49949535
声明:版权所有,转载请注明出处,谢谢

1. 图像分类问题

这是人每天自然而然会做的事情,普通到大部分时候,我们都感知不到我们在完成一个个这样的任务。早晨起床洗漱,你要看看洗漱台一堆东西中哪个是杯子,哪个是你的牙刷;吃早餐的时候你要分辨食物和碗碟…
抽象一下,对于一张输入的图片,要判定它属于给定的一些标签/类别中的哪一个。看似很简单的一个问题,这么多年却一直是计算机视觉的一个核心问题,应用场景也很多。它的重要性还体现在,其实其他的一些计算机视觉的问题(比如说物体定位和识别、图像内容分割等)都可以基于它去完成。

咱们举个例子从机器学习的角度描述一下这个问题^_^

计算机拿到一张图片(如下图所示),然后需要给出它对应{猫,狗,帽子,杯子}4类的概率。人类是灰常牛逼的生物,我们一瞥就知道这货是猫。然而对计算机而言,他们是没办法像人一样『看』到整张图片的。对它而言,这是一个3维的大矩阵,包含248*400个像素点,每个像素点又有红绿蓝(RGB)3个颜色通道的值,每个值在0(黑)-255(白)之间,计算机就需要根据这248*400*3=297600个数值去判定这货是『猫』

猫图像=>矩阵

1.1 图像识别的难点

图像识别看似很直接。但实际上包含很多挑战,人类可是经过数亿年的进化才获得如此强大的大脑,对于各种物体有着精准的视觉理解力。总体而言,我们想『教』会计算机去认识一类图,会有下面这样一些困难:

  • 视角不同,每个事物旋转或者侧视最后的构图都完全不同
  • 尺寸大小不统一,相同内容的图片也可大可小
  • 变形,很多东西处于特殊的情形下,会有特殊的摆放和形状
  • 光影等干扰/幻象
  • 背景干扰
  • 同类内的差异(比如椅子有靠椅/吧椅/餐椅/躺椅…)

图像面临的挑战

1.2 识别的途径

首先,大家想想就知道,这个算法并不像『对一个数组排序』或者『求有向图的最短路径』,我们没办法提前制定一个流程和规则去解决。定义『猫』这种动物本身就是一件很难的事情了,更不要说去定义一只猫在图像上的固定表现形式。所以我们寄希望于机器学习,使用『Data-driven approach/数据驱动法』来做做尝试。简单说来,就是对于每个类别,我们都找一定量的图片数据,『喂』给计算机,让它自己去『学习和总结』每一类的图片的特点。对了,这个过程和小盆友学习新鲜事物是一样一样的。『喂』给计算机学习的图片数据就和下图的猫/狗/杯子/帽子一样:

Data-driven approach

1.3 机器学习解决图像分类的流程/Pipeline

整体的流程和普通机器学习完全一致,简单说来,也就下面三步:

  • 输入:我们的给定K个类别的N张图片,作为计算机学习的训练集
  • 学习:让计算机逐张图片地『观察』和『学习』
  • 评估:就像我们上学学了东西要考试检测一样,我们也得考考计算机学得如何,于是我们给定一些计算机不知道类别的图片让它判别,然后再比对我们已知的正确答案。

2. 最近邻分类器(Nearest Neighbor Classifier)

先从简单的方法开始说,先提一提最近邻分类器/Nearest Neighbor Classifier,不过事先申明,它和深度学习中的卷积神经网/Convolutional Neural Networks其实一点关系都没有,我们只是从基础到前沿一点一点推进,最近邻是图像识别一个相对简单和基础的实现方式。

2.1 CIFAR-10

CIFAR-10是一个非常常用的图像分类数据集。数据集包含60000张32*32像素的小图片,每张图片都有一个类别标注(总共有10类),分成了50000张的训练集和10000张的测试集。如下是一些图片示例:

CIFAR-10例子

上图中左边是十个类别和对应的一些示例图片,右边是给定一张图片后,根据像素距离计算出来的,最近的10张图片。

2.2 基于最近邻的简单图像类别判定

假如现在用CIFAR-10数据集做训练集,判断一张未知的图片属于CIFAR-10中的哪一类,应该怎么做呢。一个很直观的想法就是,既然我们现在有每个像素点的值,那我们就根据输入图片的这些值,计算和训练集中的图片距离,找最近的图片的类别,作为它的类别,不就行了吗。

恩,想法很直接,这就是『最近邻』的思想。偷偷说一句,这种直接的做法在图像识别中,其实效果并不是特别好。比如上图是按照这个思想找的最近邻,其实只有3个图片的最近邻是正确的类目。

即使这样,作为最基础的方法,还是得掌握,我们来简单实现一下吧。我们需要一个图像距离评定准则,比如最简单的方式就是,比对两个图像像素向量之间的l1距离(也叫曼哈顿距离/cityblock距离),公式如下:

d1(I1,I2)=pIp1Ip2

其实就是计算了所有像素点之间的差值,然后做了加法,直观的理解如下图:

矩阵的l1距离

我们先把数据集读进内存:

#! /usr/bin/env python
#coding=utf-8
import os
import sys
import numpy as np

def load_CIFAR_batch(filename):
    """
    cifar-10数据集是分batch存储的,这是载入单个batch

    @参数 filename: cifar文件名
    @r返回值: X, Y: cifar batch中的 data 和 labels
    """

    with open(filename, 'r') as f:
        datadict=pickle.load(f)

        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


def load_CIFAR10(ROOT):
    """
    读取载入整个 CIFAR-10 数据集

    @参数 ROOT: 根目录名
    @return: X_train, Y_train: 训练集 data 和 labels
             X_test, Y_test: 测试集 data 和 labels
    """

    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)
        ys.append(Y)

    X_train=np.concatenate(xs)
    Y_train=np.concatenate(ys)

    del X, Y

    X_test, Y_test=load_CIFAR_batch(os.path.join(ROOT, "test_batch"))

    return X_train, Y_train, X_test, Y_test

# 载入训练和测试数据集
X_train, Y_train, X_test, Y_test = load_CIFAR10('data/cifar10/') 
# 把32*32*3的多维数组展平
Xtr_rows = X_train.reshape(X_train.shape[0], 32 * 32 * 3) # Xtr_rows : 50000 x 3072
Xte_rows = X_test.reshape(X_test.shape[0], 32 * 32 * 3) # Xte_rows : 10000 x 3072

下面我们实现最近邻的思路:

class NearestNeighbor:
  def __init__(self):
    pass

  def train(self, X, y):
    """ 
    这个地方的训练其实就是把所有的已有图片读取进来 -_-||
    """
    # the nearest neighbor classifier simply remembers all the training data
    self.Xtr = X
    self.ytr = y

  def predict(self, X):
    """ 
    所谓的预测过程其实就是扫描所有训练集中的图片,计算距离,取最小的距离对应图片的类目
    """
    num_test = X.shape[0]
    # 要保证维度一致哦
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # 把训练集扫一遍 -_-||
    for i in xrange(num_test):
      # 计算l1距离,并找到最近的图片
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
      min_index = np.argmin(distances) # 取最近图片的下标
      Ypred[i] = self.ytr[min_index] # 记录下label

    return Ypred

nn = NearestNeighbor() # 初始化一个最近邻对象
nn.train(Xtr_rows, Y_train) # 训练...其实就是读取训练集
Yte_predict = nn.predict(Xte_rows) # 预测
# 比对标准答案,计算准确率
print 'accuracy: %f' % ( np.mean(Yte_predict == Y_test) )

最近邻的思想在CIFAR上得到的准确度为38.6%,我们知道10各类别,我们随机猜测的话准确率差不多是1/10=10%,所以说还是有识别效果的,但是显然这距离人的识别准确率(94%)实在是低太多了,不那么实用。

2.3 关于最近邻的距离准则

我们这里用的距离准则是l1距离,实际上除掉l1距离,我们还有很多其他的距离准则。

  • 比如说l2距离(也就是大家熟知的欧氏距离)的计算准则如下:

d2(I1,I2)=p(Ip1Ip2)2

  • 比如余弦距离计算准则如下:

1I1I2||I1||||I2||

更多的距离准则可以参见scipy相关计算页面.

3. K最近邻分类器(K Nearest Neighbor Classifier)

这是对最近邻的思想的一个调整。其实我们在使用最近邻分类器分类,扫描CIFAR训练集的时候,会发现,有时候不一定距离最近的和当前图片是同类,但是最近的一些里有很多和当前图片是同类。所以我们自然而然想到,把最近邻扩展为最近的N个临近点,然后统计一下这些点的类目分布,取最多的那个类目作为自己的类别。

恩,这就是KNN的思想。

KNN其实是一种特别常用的分类算法。但是有个问题,我们的K值应该取多少呢。换句话说,我们找多少邻居来投票,比较靠谱呢?

3.1 交叉验证与参数选择

在现在的场景下,假如我们确定使用KNN来完成图片类别识别问题。我们发现有一些参数是肯定会影响最后的识别结果的,比如:

  • 距离的选择(l1,l2,cos等等)
  • 近邻个数K的取值。

每组参数下其实都能产生一个新的model,所以这可以视为一个模型选择/model selection问题。而对于模型选择问题,最常用的办法就是在交叉验证集上实验。

数据总量就那么多,如果我们在test data上做模型参数选择,又用它做效果评估,显然不是那么合理(因为我们的模型参数很有可能是在test data上过拟合的,不能很公正地评估结果)。所以我们通常会把训练数据分为两个部分,一大部分作为训练用,另外一部分就是所谓的cross validation数据集,用来进行模型参数选择的。比如说我们有50000训练图片,我们可以把它分为49000的训练集和1000的交叉验证集。

# 假定已经有Xtr_rows, Ytr, Xte_rows, Yte了,其中Xtr_rows为50000*3072 矩阵
Xval_rows = Xtr_rows[:1000, :] # 构建1000的交叉验证集
Yval = Ytr[:1000]
Xtr_rows = Xtr_rows[1000:, :] # 保留49000的训练集
Ytr = Ytr[1000:]

# 设置一些k值,用于试验
validation_accuracies = []
for k in [1, 3, 5, 7, 10, 20, 50, 100]:

  # 初始化对象
  nn = NearestNeighbor()
  nn.train(Xtr_rows, Ytr)
  # 修改一下predict函数,接受 k 作为参数
  Yval_predict = nn.predict(Xval_rows, k = k)
  acc = np.mean(Yval_predict == Yval)
  print 'accuracy: %f' % (acc,)

  # 输出结果
  validation_accuracies.append((k, acc))

这里提一个在很多地方会看到的概念,叫做k-fold cross-validation,意思其实就是把原始数据分成k份,轮流使用其中k-1份作为训练数据,而剩余的1份作为交叉验证数据(因此其实对于k-fold cross-validation我们会得到k个accuracy)。以下是5-fold cross-validation的一个示例:

k-fold 交叉验证

以下是我们使用5-fold cross-validation,取不同的k值时,得到的accuracy曲线(补充一下,因为是5-fold cross-validation,所以在每个k值上有5个取值,我们取其均值作为此时的准确度)

5-fold 交叉验证

可以看出大概在k=7左右有最佳的准确度。

3.2 最近邻方法的优缺点

K最近邻的优点大家都看出来了,思路非常简单清晰,而且完全不需要训练…不过也正因为如此,最后的predict过程非常耗时,因为要和全部训练集中的图片比对一遍。

实际应用中,我们其实更加关心实施predict所消耗的时间,如果有一个图像识别app返回结果要半小时一小时,你一定第一时间把它卸了。我们反倒不那么在乎训练时长,训练时间稍微长一点没关系,只要最后应用的时候识别速度快效果好,就很赞。后面会提到的深度神经网络就是这样,深度神经网络解决图像问题时训练是一个相对耗时间的过程,但是识别的过程非常快。

另外,不得不多说一句的是,优化计算K最近邻时间问题,实际上依旧到现在都是一个非常热门的问题。Approximate Nearest Neighbor (ANN)算法是牺牲掉一小部分的准确度,而提高很大程度的速度,能比较快地找到近似的K最近邻,现在已经有很多这样的库,比如说FLANN.

最后,我们用一张图来说明一下,用图片像素级别的距离来实现图像类别识别,有其不足之处,我们用一个叫做t-SNE的技术把CIFAR-10的所有图片按两个维度平铺出来,靠得越近的图片表示其像素级别的距离越接近。然而我们瞄一眼,发现,其实靠得最近的并不一定是同类别的。

像素级别图像距离排列

其实观察一下,你就会发现,像素级别接近的图片,在整张图的颜色分布上,有很大的共性,然而在图像内容上,有时候也只能无奈地呵呵嗒,毕竟颜色分布相同的不同物体也是非常多的。

参考资料与原文

cs231n 图像分类与KNN

2018-10-15 13:49:58 Sousky 阅读数 112
  • 数据挖掘模型篇之R语言实践

    理论与实践结合的方式,通过通俗易懂的教学方式培养学生运用R语言完成常用挖掘模型算法建立及评估,学习完课程可以掌握:线性回归模型、聚类分析、关联规则算法、KNN近邻算法和主成分分析等常用的模型算法实现。针对具体的数据挖掘应用需求,能熟练抽象出可合适的数据挖掘模型,并整理出其技术实现路线。

    3694 人正在学习 去看看 谢佳标

week 1  10/11-10/14

计算机视觉综述:cs231n_2018_lecture01

观看视频p1和p2热身,了解计算机视觉概述以及历史背景。观看p3了解整门课程的大纲。

 

学习数据驱动的方法和KNN算法和线性分类器[上]:cs231n_2018_lecture02

观看视频p4,p5和p6学习图像分类笔记上(链接:https://zhuanlan.zhihu.com/p/20894041?refer=intelligentunit,图像分类笔记下链接:https://zhuanlan.zhihu.com/p/20900216?refer=intelligentunit )和线性分类笔记上(链接:https://zhuanlan.zhihu.com/p/20918580?refer=intelligentunit

-->为什么要有验证集

在基于数据进行学习的机器学习算法设计中,超参数是很常见的。一般说来,这些超参数具体怎么设置或取值并不是显而易见的。

你可能会建议尝试不同的值,看哪个值表现最好就选哪个。好主意!我们就是这么做的,但这样做的时候要非常细心。特别注意:决不能使用测试集来进行调优。当你在设计机器学习算法的时候,应该把测试集看做非常珍贵的资源,不到最后一步,绝不使用它。如果你使用测试集来调优,而且算法看起来效果不错,那么真正的危险在于:算法实际部署后,性能可能会远低于预期。这种情况,称之为算法对测试集过拟合。从另一个角度来说,如果使用测试集来调优,实际上就是把测试集当做训练集,由测试集训练出来的算法再跑测试集,自然性能看起来会很好。这其实是过于乐观了,实际部署起来效果就会差很多。所以,最终测试的时候再使用测试集,可以很好地近似度量你所设计的分类器的泛化性能

测试数据集只使用一次,即在训练完成后评价最终的模型时使用。

好在我们有不用测试集调优的方法。其思路是:从训练集中取出一部分数据用来调优,我们称之为验证集(validation set。以CIFAR-10为例,我们可以用49000个图像作为训练集,用1000个图像作为验证集。验证集其实就是作为假的测试集来调优。

把训练集分成训练集和验证集。使用验证集来对所有超参数调优。最后只在测试集上跑一次并报告结果。

-->交叉验证

有时候,训练集数量较小(因此验证集的数量更小),人们会使用一种被称为交叉验证的方法,这种方法更加复杂些。还是用刚才的例子,如果是交叉验证集,我们就不是取1000个图像,而是将训练集平均分成5份,其中4份用来训练,1份用来验证。然后我们循环着取其中4份来训练,其中1份来验证,最后取所有5次验证结果的平均值作为算法验证结果。

实际应用。在实际情况下,人们不是很喜欢用交叉验证,主要是因为它会耗费较多的计算资源。一般直接把训练集按照50%-90%的比例分成训练集和验证集。但这也是根据具体情况来定的:如果超参数数量多,你可能就想用更大的验证集,而验证集的数量不够,那么最好还是用交叉验证吧。至于分成几份比较好,一般都是分成3、5和10份。

preview

常用的数据分割模式。给出训练集和测试集后,训练集一般会被均分。这里是分成5份。前面4份用来训练,黄色那份用作验证集调优。如果采取交叉验证,那就各份轮流作为验证集。最后模型训练完毕,超参数都定好了,让模型跑一次(而且只跑一次)测试集,以此测试结果评价算法。

 

线性分类器:在本模型中,我们从最简单的概率函数开始,一个线性映射:

\displaystyle f(x_i,W,b)=Wx_i+b

preview

一个将图像映射到分类分值的例子。为了便于可视化,假设图像只有4个像素(都是黑白像素,这里不考虑RGB通道),有3个分类(红色代表猫,绿色代表狗,蓝色代表船,注意,这里的红、绿和蓝3种颜色仅代表分类,和RGB通道没有关系)。首先将图像像素拉伸为一个列向量,与W进行矩阵乘,然后得到各个分类的分值。需要注意的是,这个W一点也不好:猫分类的分值非常低。从上图来看,算法倒是觉得这个图像是一只狗。

-->即W中一行代表一个类别,有多少类,W就有多少行。每一行点乘像素加上偏置就是每类别的分数。

将图像看做高维度的点:既然图像被伸展成为了一个高维度的列向量,那么我们可以把图像看做这个高维度空间中的一个点(即每张图像是3072维空间中的一个点)。整个数据集就是一个点的集合,每个点都带有1个分类标签。

 

既然定义每个分类类别的分值是权重和图像的矩阵乘,那么每个分类类别的分数就是这个空间中的一个线性函数的函数值。我们没办法可视化3072维空间中的线性函数,但假设把这些维度挤压到二维,那么就可以看看这些分类器在做什么了:

图像空间的示意图。其中每个图像是一个点,有3个分类器。以红色的汽车分类器为例,红线表示空间中汽车分类分数为0的点的集合,红色的箭头表示分值上升的方向。所有红线右边的点的分数值均为正,且线性升高。红线左边的点分值为负,且线性降低。

从上面可以看到,W的每一行都是一个分类类别的分类器。对于这些数字的几何解释是:如果改变其中一行的数字,会看见分类器在空间中对应的直线开始向着不同方向旋转。而偏差b,则允许分类器对应的直线平移。需要注意的是,如果没有偏差,无论权重如何,在x_i=0时分类分值始终为0。这样所有分类器的线都不得不穿过原点。

图像数据预处理:在上面的例子中,所有图像都是使用的原始像素值(从0到255)。在机器学习中,对于输入的特征做归一化(normalization)处理是常见的套路。而在图像分类的例子中,图像上的每个像素可以看做一个特征。在实践中,对每个特征减去平均值来中心化数据是非常重要的。在这些图片的例子中,该步骤意味着根据训练集中所有的图像计算出一个平均图像值,然后每个图像都减去这个平均值,这样图像的像素值就大约分布在[-127, 127]之间了。下一个常见步骤是,让所有数值分布的区间变为[-1, 1]。零均值的中心化是很重要的,等我们理解了梯度下降后再来详细解释。

作业:

1.阅读python和numpy教程(链接:https://zhuanlan.zhihu.com/p/20878530?refer=intelligentunit )和代码(https://github.com/sharedeeply/cs231n-camp/blob/master/tutorial/python_numpy_tutorial.ipynb )写一个矩阵的类,实现矩阵乘法,只能使用 python 的类(class)和列表(list)。

2. 完成assignment1中的knn.ipynb

打卡!

博文 来自: weixin_43384504
没有更多推荐了,返回首页