【机器学习】支持向量机(7)——手写数字识别案例

前言

之前我们用 k k -近邻算法(kNN)实现了手写数字识别系统,博客链接

从上面的案例中,我们可知使用 kNN k N N 方法的效果不错,但是需要保留所有的训练样本。而对于支持向量机来说,其需要保留的样本少了很多(只需要保留支持向量),但能获得可比的效果。

说明:
这里我们只考虑 SVM S V M 的二分类问题,所以我们只使用了手写数字识别数据集中的1和9数据;
如果为9,则输出-1;为1,则输出+1
此外需要用到上篇博客中的大量代码

手写识别问题

代码:

import random
from numpy import *


# 手写数字识别(SVM)

# 将图像格式转化为向量 32*32 --> 1*1024
def img2vector(filename):
    returnVect = zeros((1, 1024))  # 创建1*1024的0填充向量矩阵
    fr = open(filename)  # 打开文件
    for i in range(32):  # 读取文件的前32行,前32列
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVect  # 返回每个图像的向量


def selectJrand(i, m):
    j = i
    while (j == i):
        j = int(random.uniform(0, m))
    return j


def clipAlpha(aj, H, L):
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj


# 加核函数
def calcEkK(oS, k):
    fXk = float(multiply(oS.alphas, oS.labelMat).T * oS.K[:, k] + oS.b)
    Ek = fXk - float(oS.labelMat[k])
    return Ek


# 内循环中的启发式方法,选择第二个alpha
def selectJK(i, oS, Ei):
    maxK = -1
    maxDeltaE = 0
    Ej = 0
    oS.eCache[i] = [1, Ei]
    validEcacheList = nonzero(oS.eCache[:, 0].A)[0]  # 构建出一个非零表,函数nonzero返回一个列表

    # 在所有值上进行循环并选择其中使得该变量最大的那个值
    if (len(validEcacheList)) > 1:
        for k in validEcacheList:
            if k == i:
                continue
            Ek = calcEkK(oS, k)
            deltaE = abs(Ei - Ek)
            if (deltaE > maxDeltaE):
                maxK = k
                maxDeltaE = deltaE
                Ej = Ek
        return maxK, Ej
    else:
        j = selectJrand(i, oS.m)
        Ej = calcEkK(oS, j)
    return j, Ej


# 计算误差值并存入缓存中,在对alpha值进行优化之后会用到这个值
def updateEkK(oS, k):
    Ek = calcEkK(oS, k)
    oS.eCache[k] = [1, Ek]


# 核转换函数
def kernelTrans(X, A, kTup):  # 2个数值型变量和一个元祖,kTup是一个包含核函数信息的元祖,该元祖的第一个参数描述的是所用核函数的类型的一个字符串
    m, n = shape(X)
    K = mat(zeros((m, 1)))
    if kTup[0] == 'lin':  # 线性核函数,内积计算在“所有数据集”和“数据集中的一行”这两个输入之间展开
        K = X * A.T
    elif kTup[0] == 'rbf':  # 径向核函数,在for循环中对矩阵的每个元素计算高斯函数值
        for j in range(m):
            deltaRow = X[j, :] - A
            K[j] = deltaRow * deltaRow.T
        K = exp(K / (-1 * kTup[1] ** 2))
    else:
        raise NameError('Houston We have a  Problem -- That Kernel is not recognized')  # 遇到无法识别的核函数抛出异常
    return K


# 此版本相比于之前的,只是引入了新变量kTup,这是个包含核函数信息的元祖
# 构建一个数据结构来容纳所有数据,然后对控制函数退出的一些变量进行初始化
class optStruct:
    def __init__(self, dataMatIn, classLabels, C, toler, kTup):
        self.X = dataMatIn
        self.labelMat = classLabels
        self.C = C
        self.tol = toler
        self.m = shape(dataMatIn)[0]
        self.alphas = mat(zeros((self.m, 1)))
        self.b = 0
        self.eCache = mat(zeros((self.m, 2)))
        self.K = mat(zeros((self.m, self.m)))
        for i in range(self.m):
            self.K[:, i] = kernelTrans(self.X, self.X[i, :], kTup)


# 加入核函数
def innerLK(i, oS):
    Ei = calcEkK(oS, i)  # 计算E值
    if ((oS.labelMat[i] * Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or \
            ((oS.labelMat[i] * Ei > oS.tol) and (oS.alphas[i] > 0)):
        j, Ej = selectJK(i, oS, Ei)  # 第二个alpha选择中的启发式方法
        alphaIold = oS.alphas[i].copy()
        alphaJold = oS.alphas[j].copy()
        if (oS.labelMat[i] != oS.labelMat[j]):
            L = max(0, oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
        else:
            L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
            H = min(oS.C, oS.alphas[j] + oS.alphas[i])
        if L == H:
            print("L==H")
            return 0
        eta = 2.0 * oS.K[i, j] - oS.K[i, i] - oS.K[j, j]
        if eta >= 0:
            print("eta>=0")
            return 0
        oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
        oS.alphas[j] = clipAlpha(oS.alphas[j], H, L)
        updateEkK(oS, j)  # 计算误差值,并存入缓存
        if (abs(oS.alphas[j] - alphaJold) < 0.00001):
            print("j not moving enough")
            return 0
        oS.alphas[i] += oS.labelMat[j] * oS.labelMat[i] * (alphaJold - oS.alphas[j])
        updateEkK(oS, i)
        b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, i] - oS.labelMat[j] * \
             (oS.alphas[j] - alphaJold) * oS.K[i, j]
        b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, j] - oS.labelMat[j] * \
             (oS.alphas[j] - alphaJold) * oS.K[j, j]
        if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]):
            oS.b = b1
        elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]):
            oS.b = b2
        else:
            oS.b = (b1 + b2) / 2.0
        return 1
    else:
        return 0


# 加入核函数
# 数据集、类别标签、常数C、容错率、退出前最大的循环次数以及核函数
def smoPK(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)):
    oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler, kTup)  # 调用初始化函数

    # 初始化一些控制变量
    iter = 0
    entireSet = True
    alphaPairsChanged = 0

    # 代码主体
    # 退出循环的条件:
    # 1、迭代次数超过指定的最大值;
    # 2、历整个集合都没有对任意alpha值进行修改(即:alphaPairsChanged=0)
    while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:  # 遍历所有的值
            for i in range(oS.m):
                alphaPairsChanged += innerLK(i, oS)  # 调用innerLK()函数
                print("fullSet, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged))
            iter += 1
        else:  # 遍历非边界值(非边界值指的是那些不等于边界0或C的alpha值)
            nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
            for i in nonBoundIs:
                alphaPairsChanged += innerLK(i, oS)  # 调用innerLK()函数
                print("non-bound, iter: %d i:%d, pairs changed %d" % (iter, i, alphaPairsChanged))
            iter += 1
        if entireSet:
            entireSet = False
        elif (alphaPairsChanged == 0):
            entireSet = True
        print("iteration number: %d" % iter)
    return oS.b, oS.alphas


# 基于SVM的手写识别
# 这里我们只讨论SVM的二分类问题,所以数据集只包含1和9
def loadImages(dirName):
    from os import listdir
    hwLabels = []
    trainingFileList = listdir(dirName)  # 加载训练数据集
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        if classNumStr == 9:  # 如果为9,输出-1
            hwLabels.append(-1)
        else:  # 如果不为9,输出1
            hwLabels.append(1)
        trainingMat[i, :] = img2vector('%s/%s' % (dirName, fileNameStr))  # 调用img2vector()函数
    return trainingMat, hwLabels


def testDigits(kTup=('rbf', 10)):
    dataArr, labelArr = loadImages('trainingDigits')  # 加载训练数据集
    b, alphas = smoPK(dataArr, labelArr, 200, 0.0001, 10000, kTup)  # 调用smoPK()函数

    datMat = mat(dataArr)  # 将数据集用mat函数转换为矩阵
    labelMat = mat(labelArr).transpose()  # 转置类别标签(使得类别标签向量的每行元素都和数据矩阵中的每一行一一对应)

    # 计算支持向量个数
    svInd = nonzero(alphas.A > 0)[0]
    sVs = datMat[svInd] # 构建支持向量矩阵
    labelSV = labelMat[svInd]
    print("there are %d Support Vectors" % shape(sVs)[0])

    m, n = shape(datMat)  # 通过矩阵的shape属性得到常数m,n
    errorCount = 0
    for i in range(m):
        kernelEval = kernelTrans(sVs, datMat[i, :], kTup)  # 调用kernelTrans()函数
        predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b # 与其前面的alpha及类别标签值求积
        if sign(predict) != sign(labelArr[i]):  # 如果不等,错误个数加1
            errorCount += 1
    print("the training error rate is: %f" % (float(errorCount) / m))  # 训练集上的错误率并打印

    # 与上面训练集一样
    dataArr, labelArr = loadImages('testDigits')  # 加载测试数据集
    errorCount = 0
    datMat = mat(dataArr)
    labelMat = mat(labelArr).transpose()
    m, n = shape(datMat)
    for i in range(m):# 与前面一个for循环相比,只是数据集不同,使用的是测试数据集
        kernelEval = kernelTrans(sVs, datMat[i, :], kTup)
        predict = kernelEval.T * multiply(labelSV, alphas[svInd]) + b
        if sign(predict) != sign(labelArr[i]):
            errorCount += 1
    print("the test error rate is: %f" % (float(errorCount) / m))


# 测试(通过不同参数,得到不同的识别性能)
testDigits(('rbf', 10))

运行结果:
这里写图片描述

我们可以尝试不同参数下的手写数字识别性能,如下表:

内核,设置训练错误率(%)测试错误率(%)支持向量数
rbf, 0.1052402
rbf, 5032402
rbf, 1000.5132
rbf,2001.650
rbf, 507.44.845
rbf, 1000.41.034
lin, 102.03.836

由图可以看出:
参数在10左右时,可以得到最小的测试错误率;
最小的训练错误率并不对应着最小的支持向量数目;
线性核函数的效果并不是很糟,可以牺牲线性核函数的错误率来换取分类速度的提高

本篇所涉及的数据在上篇博客的百度云链接中的svmMLiA4.py,博客地址

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python机器学习支持向量机(SVM)是一种常用的分类算法,可以用于数字识别数字识别是一个常见的图像分类问题,可以通过将数字图像转换为灰度点阵图来解决。 在Python中,可以使用scikit-learn库来实现SVM分类器。首先,需要将数字图像转换为灰度点阵图,并将其存储为numpy数组。然后,可以使用scikit-learn中的SVM分类器来训练模型并进行预测。 以下是一个简单的示例代码,用于训练SVM分类器并进行数字识别: ``` import numpy as np from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn import svm # 加载数字数据集 digits = datasets.load_digits() # 将数据集分为训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, test_size=0.3, random_state=42) # 训练SVM分类器 clf = svm.SVC(kernel='linear', C=1) clf.fit(X_train, y_train) # 预测测试集 y_pred = clf.predict(X_test) # 计算准确率 accuracy = np.mean(y_pred == y_test) print("Accuracy:", accuracy) ``` 在上面的代码中,我们首先加载了scikit-learn中自带的数字数据集。然后,将数据集分为训练集和测试集。接着,我们使用SVM分类器来训练模型,并使用测试集进行预测。最后,计算预测准确率并输出结果。 需要注意的是,上述代码中的SVM分类器使用的是线性核函数,可以根据实际情况选择不同的核函数。此外,还可以通过调整C参数来控制模型的复杂度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值