用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进

选自线性代数经典教材
Introduction to Applied Linear Algebra Vectors, Matrices, and Least Squares
而且,这本书免费下载!讲解最小二乘法非常透彻,比如包括它的回归和分类(包括多分类)以及非最小二乘和它们有约束问题的应用。手写字体分类题目在本书的第490页

本书的下载链接:https://www.getfreeebooks.com/introduction-to-applied-linear-algebra-vectors-matrices-and-least-squares/

MNIST数据集分类

  • 一、题目介绍
  • 二、题目分析
    • 2.1 训练集和测试集
    • 2.2 最小二乘法的标准形式
    • 2.3 A矩阵的构造
    • 2.4 矩阵维度分析
    • 2.5 随机特征法
  • 三、结果显示
    • 3.1 训练集和测试集
    • 3.2 增加五千个随机特征的训练集和测试集
    • 3.3 运行时间分析
  • 四、代码与分析
    • 4.1 完整代码
    • 4.1 代码分析
  • 四、GPU版本
    • 4.1、环境介绍
    • 4.2、结果显示
    • 4.3、完整代码
    • 4.4、代码分析

一、题目介绍

对于手写数字集MNIST进行处理,实现0和非0元素的分类。该数据集包括70000 张 28 ×
28的手写数字灰度图像,图像数据已经被转换为28 × 28 = 784
维的向量形式存储,标签对应的为10维向量存储,如数字3对应的标签为[0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
0.0]。(注:如实现多分类问题,可以继续操作,如分离了0后,对1到9的数字进行1和非1处理…最终实现所有数字的分类)

二、题目分析

下载数据:http://yann.lecun.com/exdb/mnist/ 解压后或更改后缀得到以下四个文件
用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第1张图片

2.1 训练集和测试集

70000张图片已经分成了60000个训练集和10000个测试集以及它们的标签
导入方法见完整代码。

2.2 最小二乘法的标准形式

A x = b Ax=b Ax=b
其最小二乘解为:
x = ( A T A ) − 1 A T b x=(A^{T}A)^{-1}A^{T}b x=(ATA)1ATb

  • A t r a i n A_{train} Atrain矩阵应该通过训练集图片来构造
  • b t r a i n b_{train} btrain向量是对应的训练集数字标签

解出 x x x 后,让测试集的 A t e s t A_{test} Atest 矩阵与 x x x 相乘得到了预测的测试集标签 b t e s t , p r e d i c t b_{test,predict} btest,predict

  • 如果 b t e s t , p r e d i c t > 0 b_{test,predict}>0 btest,predict>0说明预测的是正确,否则预测错误(类似于softmax)
  • 根据真实的测试集标签 b t e s t b_{test} btest b t e s t , p r e d i c t b_{test,predict} btest,predict可以算出来TP,TN,FP,FN,因此也就可以求得精确率、召回率、错误率等指标。
  • 在处理标签 b t e s t , p r e d i c t b_{test,predict} btest,predict之前,要进行转换,将实际的标签0非0转换为1和-1

而用这种方法解最小二乘问题效率不高,因此采用QR分解法

采用矩阵分解的最小二乘问题求解算法如下:

步骤 1:计算矩阵 A A A Q R QR QR 分解, A = Q R A=QR A=QR
步骤 2:计算 Q T b Q^{T}b QTb
步骤 3:求解 R x = Q T b Rx=Q^{T}b Rx=QTb,获得 x x x

2.3 A矩阵的构造

对于手写数字的图片来说,数字大多数在图像的中央,而周围有很多冗余的区域,对于图像识别来说意义不大,因此我们采用如下的标准:
用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第2张图片

对于训练集60000张图片来说,取它的1%即为600张,想象以下,如果把60000张图片一层一层堆叠在一起,用一根针垂直扎进去,如果有超过600张图片该区域非0(即图像里不是黑色的,比如0.5,0.6等等),也就是超过59400张该区域是有实际图像的像素值的时,那么就提取这样的一个特征。
一共有28 × 28 = 784个位置,经过这种方法提取后,我们可以算出来有493个特征位置。

注:提取非0个数可以采用零范数,如使用numpy写法如下:

non_zero = np.linalg.norm(train_images_2DT[i,:], ord=0) 

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第3张图片

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第4张图片

2.4 矩阵维度分析

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第5张图片

2.5 随机特征法

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第6张图片
因此针对于这道题目,这样构造随机特征矩阵
随机矩阵 R ∈ R 5000 × 494 , R i j R\in R_{5000\times494} , R_{ij} RR5000×494,Rij 随机取值 ±1 ,并且取如下方法计算作为新特征,那么与原来的特征结合后一共5494个特征。
m a x ( 0 , ( R x ) j ) , j = 1 , 2 , 3...5000 max(0,(Rx)_{j}),j=1,2,3...5000 max(0,(Rx)j),j=1,2,3...5000
结合本题,添加的5000个特征的实际意义是,通过 m a x max max函数随机的保留特征,如果该特征比0要大,就保留原来的特征,如果随机的是-1比0小, m a x max max取0,因此不保留这个特征。

因此A矩阵与R矩阵的转置相乘,得到了60000x5000的矩阵,拼接到A(60000x494)的后面变得到了新的A矩阵,剩下的步骤和上述相同。

三、结果显示

3.1 训练集和测试集

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第7张图片

3.2 增加五千个随机特征的训练集和测试集

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第8张图片
可以看出效果明显提高,错误率仅不到0.3%。

3.3 运行时间分析

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第9张图片
增加随机特征后,将A矩阵扩充,实验结果如右图所示,错误率显著降低。但是运行时间很慢,原因是生成的随机矩阵过大,约60000*5000=3亿个数据,使用基于CPU的numpy并不能有效处理这个问题,因此需要一些改进

四、代码与分析

4.1 完整代码

# -*- coding: utf-8 -*-
"""
Created on Mon Nov 16 07:41:38 2020

@author: 孙昊一
"""

import numpy as np
import struct
import prettytable as pt
import time
# 训练集文件
train_images_idx3_ubyte_file = r'D:\2pic\train-images.idx3-ubyte'
# 训练集标签文件
train_labels_idx1_ubyte_file = r'D:\2pic\train-labels.idx1-ubyte'

# 测试集文件
test_images_idx3_ubyte_file = r'D:\2pic\t10k-images.idx3-ubyte'
# 测试集标签文件
test_labels_idx1_ubyte_file = r'D:\2pic\t10k-labels.idx1-ubyte'


def decode_idx3_ubyte(idx3_ubyte_file):
    """
    解析idx3文件的通用函数
    :param idx3_ubyte_file: idx3文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx3_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
    offset = 0
    fmt_header = '>iiii' #因为数据结构中前4行的数据类型都是32位整型,所以采用i格式,但我们需要读取前4行数据,所以需要4个i。我们后面会看到标签集中,只使用2个ii。
    magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols))

    # 解析数据集
    image_size = num_rows * num_cols
    offset += struct.calcsize(fmt_header)  #获得数据在缓存中的指针位置,从前面介绍的数据结构可以看出,读取了前4行之后,指针位置(即偏移位置offset)指向0016。
    print(offset)
    fmt_image = '>' + str(image_size) + 'B'  #图像数据像素值的类型为unsigned char型,对应的format格式为B。这里还有加上图像大小784,是为了读取784个B格式数据,如果没有则只会读取一个值(即一副图像中的一个像素值)
    print(fmt_image,offset,struct.calcsize(fmt_image))
    images = np.empty((num_images, num_rows, num_cols))
    #plt.figure()
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print('已解析 %d' % (i + 1) + '张')
            print(offset)
        images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
        #print(images[i])
        offset += struct.calcsize(fmt_image)
#        plt.imshow(images[i],'gray')
#        plt.pause(0.00001)
#        plt.show()
    #plt.show()

    return images


def decode_idx1_ubyte(idx1_ubyte_file):
    """
    解析idx1文件的通用函数
    :param idx1_ubyte_file: idx1文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx1_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数和标签数
    offset = 0
    fmt_header = '>ii'
    magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张' % (magic_number, num_images))

    # 解析数据集
    offset += struct.calcsize(fmt_header)
    fmt_image = '>B'
    labels = np.empty(num_images)
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print ('已解析 %d' % (i + 1) + '张')
        labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
        offset += struct.calcsize(fmt_image)
    return labels


def load_train_images(idx_ubyte_file=train_images_idx3_ubyte_file):
    """
    TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  60000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
    """
    TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  60000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)


def load_test_images(idx_ubyte_file=test_images_idx3_ubyte_file):
    """
    TEST SET IMAGE FILE (t10k-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  10000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):
    """
    TEST SET LABEL FILE (t10k-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  10000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)

def pretreat(train_labels,test_labels,train_images,test_images):
    
    train_images_column=train_images.reshape(60000,784,1)
    test_images_column=test_images.reshape(10000,784,1)
    train_labels=train_labels.reshape(60000,1)
    test_labels=test_labels.reshape(10000,1)
    
    for i in range(len(train_labels)):
        if train_labels[i] == 0:
            train_labels[i] =1
        elif train_labels[i] != 0:
            train_labels[i] = -1 ## 5923个0 /60000 约1/10 正确
        
    for i in range(len(test_labels)):
        if test_labels[i] == 0:
            test_labels[i] =1
        elif test_labels[i] != 0:
            test_labels[i] = -1 ## 980个0 /10000 约1/10  正确
            
    train_images_2D=train_images_column.reshape(60000,784)
    test_images_2D=test_images_column.reshape(10000,784)
    train_images_2DT=train_images_2D.T
    test_images_2DT=test_images_2D.T   
    
    return train_labels,test_labels,train_images_2DT,test_images_2DT

def show_result(labels,result,dataset):
    TP=0 #正类预测为正类
    FN=0 #正类预测为负类   
    FP=0 #负类预测为正类
    TN=0 #负类预测为负类
    for i in range(len(labels)):
        if labels[i]==1 and result[i]==1:
            TP=TP+1
        elif labels[i]==1 and result[i]==-1:
            FN=FN+1
        elif labels[i]==-1 and result[i]==1:
            FP=FP+1
        elif labels[i]==-1 and result[i]==-1:
            TN=TN+1   
    tb = pt.PrettyTable()
    tb.field_names = [dataset,"Predicted y=1","Prediected y=-1","Total"]
    tb.add_row(["y=+1",TP,FN,TP+FN])
    tb.add_row(["y=-1",FP,TN,FP+TN])
    tb.add_row(["All",TP+FP,FN+TN,TP+FP+FN+TN])
    print(tb)   
    error = (FN+FP)/(TP+FP+FN+TN) * 100
    print('错误率为',"%.3f" % error,'%')
    print('\n')
    
def tran1or0(result): ##这个函数来把大于0的转换为1,小于0的转换为0
    result[result>0]=1
    result[result<0]=-1
    result.reshape(len(result),1)    
    return result

if __name__ == '__main__':
    start1 = time.time()
    
    train_images = load_train_images()
    train_labels = load_train_labels()
    test_images = load_test_images()
    test_labels = load_test_labels()  
    [train_labels,test_labels,train_images_2DT,test_images_2DT]=pretreat(train_labels,test_labels,train_images,test_images)
    tt=0
    index=[]
    train_image_feature=np.zeros([493,60000])
    for i in range (784):
        non_zero = np.linalg.norm(train_images_2DT[i,:], ord=0) 
        if non_zero >= 600:
            train_image_feature[tt,:]=train_images_2DT[i,:]
            tt=tt+1
            index.append(i)
    test_image_feature=np.zeros([493,10000])
    test_image_feature=test_images_2DT[index,:]
    A=np.hstack([np.ones([60000,1]),train_image_feature.T])
    A_test=np.hstack([np.ones([10000,1]),test_image_feature.T])
    b=train_labels
    b_test=test_labels
    print('进行QR分解中...')
    q,r = np.linalg.qr(A)
    print('已完成QR分解')
    print('\n')
    x=np.linalg.pinv(r).dot(q.T.dot(b))
    result=A.dot(x)
    tran1or0(result)
    show_result(train_labels,result,'Train_dataset')
    result_test=A_test.dot(x)
    tran1or0(result_test)
    show_result(test_labels,result_test,'Test_dataset')##
    end1 = time.time()
    print("运行时间:%.2f秒" % (end1 - start1))
    
    start2 = time.time()
    Random_feature=np.random.choice([-1,1],(5000,494))
    A_random=A.dot(Random_feature.T)
    A_random[A_random<0]=0 
    A_add=np.hstack([np.ones([60000,1]),train_image_feature.T,A_random])
    print('进行QR分解中...')
    q_add,r_add = np.linalg.qr(A_add)
    print('已完成QR分解')
    print('对x进行求解中...')
    x_add=np.linalg.pinv(r_add).dot(q_add.T.dot(b))
    print('已求得x的解')
    result_add=A_add.dot(x_add)
    print('已完成结果预测')
    tran1or0(result_add)       
    show_result(train_labels,result_add,'Train_dataset_ADD')  
    A_random_test=np.dot(A_test,Random_feature.T)##10000*50000
    A_random_test[A_random_test<0]=0 
    A_add_test=np.hstack([np.ones([10000,1]),test_image_feature.T,A_random_test]) ## 10000*5494
    result_add_test=A_add_test.dot(x_add)
    tran1or0(result_add_test)
    show_result(test_labels,result_add_test,'Test_dataset_ADD')
    
    
    end2 = time.time()
    print("运行时间:%.2f秒" % (end2 - start2))
 

4.1 代码分析

代码运行时间比较长,在R7000上跑了约3分钟才处理完毕。
注意的地方如下:

  • 首先将矩阵变成60000x784的维度
  • A矩阵是通过拼接写到一起的
  • 尽量避免for循环增加运算量
    因此,需要对该代码进行优化和改进!!!

四、GPU版本

4.1、环境介绍

采用Ubuntu20.04系统下,安装深度学习框架pytorch的GPU版,并且利用了numba进行加速。

4.2、结果显示

用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第10张图片
运行时间减少到原来的1/3左右,显著的提升了运行速度

4.3、完整代码

完整代码如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import numpy as np
import numba
from numba import jit
import torch
import struct
import prettytable as pt
import time

# 训练集文件
train_images_idx3_ubyte_file = r'/home/haoyi/下载/2pic/train-images.idx3-ubyte'
# 训练集标签文件
train_labels_idx1_ubyte_file = r'/home/haoyi/下载/2pic/train-labels.idx1-ubyte'

# 测试集文件
test_images_idx3_ubyte_file = r'/home/haoyi/下载/2pic/t10k-images.idx3-ubyte'
# 测试集标签文件
test_labels_idx1_ubyte_file = r'/home/haoyi/下载/2pic/t10k-labels.idx1-ubyte'


def decode_idx3_ubyte(idx3_ubyte_file):
    """
    解析idx3文件的通用函数
    :param idx3_ubyte_file: idx3文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx3_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
    offset = 0
    fmt_header = '>iiii'  # 因为数据结构中前4行的数据类型都是32位整型,所以采用i格式,但我们需要读取前4行数据,所以需要4个i。我们后面会看到标签集中,只使用2个ii。
    magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols))

    # 解析数据集
    image_size = num_rows * num_cols
    offset += struct.calcsize(fmt_header)  # 获得数据在缓存中的指针位置,从前面介绍的数据结构可以看出,读取了前4行之后,指针位置(即偏移位置offset)指向0016。
    print(offset)
    fmt_image = '>' + str(
        image_size) + 'B'  # 图像数据像素值的类型为unsigned char型,对应的format格式为B。这里还有加上图像大小784,是为了读取784个B格式数据,如果没有则只会读取一个值(即一副图像中的一个像素值)
    print(fmt_image, offset, struct.calcsize(fmt_image))
    images = np.empty((num_images, num_rows, num_cols))
    # plt.figure()
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print('已解析 %d' % (i + 1) + '张')
            print(offset)
        images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
        # print(images[i])
        offset += struct.calcsize(fmt_image)
    #        plt.imshow(images[i],'gray')
    #        plt.pause(0.00001)
    #        plt.show()
    # plt.show()

    return images


def decode_idx1_ubyte(idx1_ubyte_file):
    """
    解析idx1文件的通用函数
    :param idx1_ubyte_file: idx1文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx1_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数和标签数
    offset = 0
    fmt_header = '>ii'
    magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张' % (magic_number, num_images))

    # 解析数据集
    offset += struct.calcsize(fmt_header)
    fmt_image = '>B'
    labels = np.empty(num_images)
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print('已解析 %d' % (i + 1) + '张')
        labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
        offset += struct.calcsize(fmt_image)
    return labels


def load_train_images(idx_ubyte_file=train_images_idx3_ubyte_file):
    """
    TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  60000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
    """
    TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  60000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)


def load_test_images(idx_ubyte_file=test_images_idx3_ubyte_file):
    """
    TEST SET IMAGE FILE (t10k-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  10000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):
    """
    TEST SET LABEL FILE (t10k-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  10000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)

def pretreat(train_labels, test_labels, train_images, test_images):
    train_images_column = train_images.reshape(60000, 784, 1)
    test_images_column = test_images.reshape(10000, 784, 1)
    train_labels = train_labels.reshape(60000, 1)
    test_labels = test_labels.reshape(10000, 1)

    for i in range(len(train_labels)):
        if train_labels[i] == 0:
            train_labels[i] = 1
        elif train_labels[i] != 0:
            train_labels[i] = -1  ## 5923个0 /60000 约1/10 正确

    for i in range(len(test_labels)):
        if test_labels[i] == 0:
            test_labels[i] = 1
        elif test_labels[i] != 0:
            test_labels[i] = -1  ## 980个0 /10000 约1/10  正确

    train_images_2D = train_images_column.reshape(60000, 784)
    test_images_2D = test_images_column.reshape(10000, 784)
    train_images_2DT = train_images_2D.T
    test_images_2DT = test_images_2D.T

    return train_labels, test_labels, train_images_2DT, test_images_2DT

@jit(nopython=True)
def show_result(labels, result):
    TP = 0  # 正类预测为正类
    FN = 0  # 正类预测为负类
    FP = 0  # 负类预测为正类
    TN = 0  # 负类预测为负类
    for i in range(len(labels)):
        if labels[i] == 1 and result[i] == 1:
            TP = TP + 1
        elif labels[i] == 1 and result[i] == -1:
            FN = FN + 1
        elif labels[i] == -1 and result[i] == 1:
            FP = FP + 1
        elif labels[i] == -1 and result[i] == -1:
            TN = TN + 1
    return [TP,FN,FP,TN]

def show_pretty(output,dataset):
    TP = output[0]
    FN = output[1]
    FP = output[2]
    TN = output[3]
    tb = pt.PrettyTable()
    tb.field_names = [dataset, "Predicted y=1", "Prediected y=-1", "Total"]
    tb.add_row(["y=+1", TP, FN, TP + FN])
    tb.add_row(["y=-1", FP, TN, FP + TN])
    tb.add_row(["All", TP + FP, FN + TN, TP + FP + FN + TN])
    print(tb)
    error = (FN + FP) / (TP + FP + FN + TN) * 100
    print('错误率为', "%.3f" % error, '%')
    print('\n')


def tran1or0(result):  ##这个函数来把大于0的转换为1,小于0的转换为0
    result[result > 0] = 1
    result[result < 0] = -1
    result.reshape(len(result), 1)
    return result

def qr_decomposition(A,A_test,b):
    print('进行QR分解中...')
    q, r = torch.qr(A)
    print('已完成QR分解')
    print('\n')
    x = torch.inverse(r).mm(q.t().mm(b))
    result = A.mm(x)
    result_test = A_test.mm(x)
    return result,result_test



if __name__ == '__main__':
    start1 = time.time()
    train_images = load_train_images()
    train_labels = load_train_labels()
    test_images = load_test_images()
    test_labels = load_test_labels()
    [train_labels, test_labels, train_images_2DT, test_images_2DT] = pretreat(train_labels, test_labels, train_images,
                                                                              test_images)

    train_labels = torch.Tensor(train_labels)
    test_labels = torch.Tensor(test_labels)
    train_images_2DT = torch.Tensor(train_images_2DT)
    test_images_2DT = torch.Tensor(test_images_2DT)
    tt = 0
    index = []
    train_image_feature = torch.zeros([493, 60000])
    for i in range(784):
        non_zero = torch.linalg.norm(train_images_2DT[i, :], ord=0)
        if non_zero >= 600:
            train_image_feature[tt, :] = train_images_2DT[i, :]
            tt = tt + 1
            index.append(i)
    test_image_feature = torch.zeros([493, 10000])
    test_image_feature = test_images_2DT[index, :]
    A = torch.hstack([torch.ones([60000, 1]), train_image_feature.t()])
    A_test = torch.hstack([torch.ones([10000, 1]), test_image_feature.t()])
    b = train_labels
    b_test = test_labels


    result,result_test= qr_decomposition(A,A_test,b)

    tran1or0(result)
    tran1or0(result_test)

    train_labels = train_labels.numpy()
    result = result.numpy()
    test_labels = test_labels.numpy()
    result_test = result_test.numpy()

    output = show_result(train_labels, result)
    show_pretty(output,'Train_dataset')
    output_test = show_result(test_labels, result_test)
    show_pretty(output_test, 'Test_dataset')

    end1 = time.time()
    print("运间:%.2f秒" % (end1 - start1))

    ##对这部分用GPU计算

    start2 = time.time()
    # Random_feature = Random_feature.cuda()
    Random_feature =np.random.choice([-1, 1], (5000, 494))
    Random_feature = torch.Tensor(Random_feature)
    A_random = A.mm(Random_feature.T)
    A_random[A_random < 0] = 0
    A_add = torch.hstack([torch.ones([60000, 1]), train_image_feature.t(), A_random])


    A_random_test = torch.mm(A_test, Random_feature.t())  ##10000*50000
    A_random_test[A_random_test < 0] = 0
    A_add_test = torch.hstack([torch.ones([10000, 1]), test_image_feature.t(), A_random_test])  ## 10000*5494

    result_add, result_add_test = qr_decomposition(A_add,A_add_test,b)

    tran1or0(result_add_test)
    tran1or0(result_add)




    result_add = result_add.numpy()
    result_add_test = result_add_test.numpy()

    output_add = show_result(train_labels,result_add)
    show_pretty(output_add,'Train_dataset_ADD')

    output_add_test = show_result(test_labels,result_add_test)
    show_pretty(output_add_test,'Test_dataset_ADD')


    end2 = time.time()
    print("运行时间:%.2f秒" % (end2 - start2))





4.4、代码分析

由于基于CPU的numpy运行很慢,采用如下几种方式进行优化:

  1. 构造一些函数返回部分结果,将变量转换为局部变量,从而节约内存空间
  2. 对于循环问题,采用可以调用GPU的numba进行加速
  3. 将numpy函数改写成pytorch的GPU版本
  4. 对于一些较大的矩阵,可以利用CUDA将数据拷贝到GPU运算后,结果返回到CPU上。
    具体操作为:
    应用.cuda()将CPU的数据传送到GPU上,然后通过.cpu()命令将数据传送回来。在操作时候需要注意数据类型的转换,GPU只能与GPU的数据操作,如果想用GPU的数据是不能直接转换成numpy类型,需要先通过.cpu()转换成CPU的tensor,然后才能转换为numpy。
    用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第11张图片

注意:

  • 修改的还不是很完美,一些数据在其中要进行转换,如tensor类型与numpy类型

  • @jit 可以对代码进行c++编译,经过比较,显著加速
    用最小二乘法解决MNIST手写数字分类问题,并结合随机特征法改进_第12张图片

  • 想用cuda进行处理,但没有GPU空间实现矩阵分解,也可能是写法不正确,具体办法查看这里,将A写成A.cuda()后操作矩阵分解

你可能感兴趣的:(模式识别,python,最小二乘法,MNIST,手写数字分类,python,随机特征法)