多层神经网络,从零开始——(一)、Fortran读取MNIST数据集

前言

目前,深度学习有TensorFlow、Torch 、Caffe、Theano、Deeplearning4j等诸多开源框架,我们可以十分方便的用其实现一个深度神经网络,但是某些框架封装得太完善以至于让人忘记几个简单的调用语句背后程序做了多少工作。

因此,本博客的第一个目标是,在不使用库的情况下,使用Fortran语言完成一个多层神经网络,并用一些经典算例对其进行测试。鉴于并行计算的复杂性(PS:其实主要是我不会cuda,MPI虽然会,但是对于全连接问题,CPU间传输数据量会比较大,OpenMP可能更合适这种情况,这部分在之后探讨),这里不考虑并行性。另外,我们知道像Tensorflow、Theano等都集成了自动梯度工具,这里我们不会实现自动梯度功能,而是通过推导出公式之后再代码实现。

MNIST是一个手写数字的数据集,包含了60000个训练集以及10000个测试集数据,MNIST数据集是NIST数据集的子集,并且统一了图片的尺寸并做了居中化处理。根据作者的介绍,MNIST测试集的前5000个样本来自于NIST数据集的训练集,后5000个样本来自于NIST数据集的测试集,前者比后者干净、容易识别。

多层神经网络,从零开始——(一)、Fortran读取MNIST数据集_第1张图片
MNIST数据集样例

一、MNIST数据集格式

根据MNIST官网介绍,其数据包解压之后得到的二进制文件格式如下:

训练数据的标签数据集格式:

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.

训练数据的图片数据集格式:

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

测试数据的标签数据集格式:

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.

测试数据的图片数据集格式:

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

训练集和测试集的图片数据格式是类似的,开始都是4个4字节的整数,分别表示:magic number、样本数量、每个样本行数28、每个样本的列数28。接下来就是图片的信息,由于图片是灰度图,使用的是1字节的整型表示其灰度值,范围是0~255。

训练集和测试集的标签数据格式也是类似的,开始是2个4字节的整数,分别表示:magic number、样本数量。接下来就是图片代表的哪个数字,用1字节的整型表示,范围是0~9。

二、Fortran读取

2.1 Fortran读取二进制文件

先来看如何使用Fortran读取二进制文件。使用Fortran读取二进制文件有两种方式:直接访问和流访问。

使用二进制阅读器查看train-images.idx3-ubyte文件可以看到前16字节写成八进制形式为:

00 00 08 03
00 00 EA 60
00 00 00 1C
00 00 00 1C

2.1.1 直接访问:

PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, row, col
    integer :: int_len
    
    inquire(iolength=int_len) magic_number
    write(*, *) "int_len = ", int_len
    
    open(UNIT=30, FILE='train-images.idx3-ubyte', ACCESS='direct', FORM='unformatted', RECL=int_len)
    read(30, rec=1) magic_number
    read(30, rec=2) sample_count
    read(30, rec=3) row
    read(30, rec=4) col
    write(*, *) magic_number, sample_count, row, col
    close(30)
    
END PROGRAM

输出结果:

 int_len =            1
    50855936  1625948160   469762048   469762048

数字转为十六进制为:

03 08 00 00
60 EA 00 00
1C 00 00 00
1C 00 00 00

2.1.2 流访问模式

PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, row, col
    
    open(UNIT=30, FILE='train-images.idx3-ubyte', ACCESS='stream', FORM='unformatted')
    read(30) magic_number, sample_count, row, col
    write(*, *) magic_number, sample_count, row, col
    close(30)
    
END PROGRAM

如果使用的是Intel编译器的话,还可以直接使用binary格式。

PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, row, col
    
    open(UNIT=30, FILE='train-images.idx3-ubyte', FORM='binary')
    read(30) magic_number, sample_count, row, col
    write(*, *) magic_number, sample_count, row, col
    close(30)
    
END PROGRAM

2.1.3 结果的探讨

程序读取得到的结果是:50855936 1625948160 469762048 469762048。而不是:2051 60000 28 28 呢?转换成十六进制就可以看出,Fortran将2051十六进制00 00 08 03读进去之后,变成了03 08 00 00,即50855936。也就是说反向了,因而得到了不正确的结果。

2.2 使用Fortran读取数据集文件

将训练集图片信息读取输出到文本文件。这里需要注意的是这行代码:if (pixel_4int < 0) pixel_4int = pixel_4int + 256,这是因为Fortran从文本中读取1个字节赋给1个字节的整型时,有时候会得到赋值,看起来像溢出一样,不知道什么缘故。

PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, row, col, point_count
    integer(kind=1) :: pixel
    integer(kind=4) :: pixel_4int
    integer :: i
    
    open(UNIT=33, FILE='train.txt', FORM='formatted', STATUS='replace')
    open(UNIT=30, FILE='train-images.idx3-ubyte', ACCESS='stream', FORM='unformatted')
    read(30) magic_number, sample_count, row, col
    
    sample_count = 60000
    row = 28
    col = 28
    point_count = row*col*sample_count
    
    do i=1, point_count
        read(30) pixel
        pixel_4int  = pixel
        if (pixel_4int < 0) pixel_4int = pixel_4int + 256
        if (mod(i,28)== 1 .and. i > 1) write(33, *)
        write(33, '(I5\)') pixel_4int       
    end do
    
    close(30)
    close(33)
    
END PROGRAM

将得到的文本文件打开可以看到前四个数字分别是:5 0 4 1。

多层神经网络,从零开始——(一)、Fortran读取MNIST数据集_第2张图片

三、转换为Fortran读取的二进制

为了以后更加方便的使用该数据集,使用Fortran将其按照4字节二进制输出到文件。这些文件已上传到Github上,这里是下载地址。

PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, row, col, point_count
    integer(kind=1) :: pixel
    integer(kind=4) :: pixel_4int
    integer :: i
    
    open(UNIT=33, FILE='train-images.fortran', &
        ACCESS='stream', FORM='unformatted', STATUS='replace')
    open(UNIT=30, FILE='train-images.idx3-ubyte', ACCESS='stream', FORM='unformatted')
    read(30) magic_number, sample_count, row, col
    
    magic_number = 2051
    sample_count = 60000
    row = 28
    col = 28
    point_count = row*col*sample_count
    
    write(33) magic_number, sample_count, row, col
    
    do i=1, point_count
        read(30) pixel
        pixel_4int  = pixel
        if (pixel_4int < 0) pixel_4int = pixel_4int + 256
        write(33) pixel_4int       
    end do
    
    close(30)
    close(33)
    
END PROGRAM
PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, row, col, point_count
    integer(kind=1) :: pixel
    integer(kind=4) :: pixel_4int
    integer :: i
    
    open(UNIT=33, FILE='t10k-images.fortran', &
        ACCESS='stream', FORM='unformatted', STATUS='replace')
    open(UNIT=30, FILE='t10k-images.idx3-ubyte', ACCESS='stream', FORM='unformatted')
    read(30) magic_number, sample_count, row, col
    
    magic_number = 2051
    sample_count = 10000
    row = 28
    col = 28
    point_count = row*col*sample_count
    
    write(33) magic_number, sample_count, row, col
    
    do i=1, point_count
        read(30) pixel
        pixel_4int  = pixel
        if (pixel_4int < 0) pixel_4int = pixel_4int + 256
        write(33) pixel_4int       
    end do
    
    close(30)
    close(33)
    
END PROGRAM
PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, point_count
    integer(kind=1) :: label 
    integer(kind=4) :: label_4int
    integer :: i
    
    open(UNIT=33, FILE='train-labels.fortran', &
        ACCESS='stream', FORM='unformatted', STATUS='replace')
    open(UNIT=30, FILE='train-labels.idx1-ubyte', ACCESS='stream', FORM='unformatted')
    read(30) magic_number, sample_count
    
    magic_number = 2049
    sample_count = 60000
    point_count = sample_count
    
    write(33) magic_number, sample_count
    
    do i=1, point_count
        read(30) label
        label_4int  = label
        if (label_4int < 0) label_4int = label_4int + 256
        write(33) label_4int       
    end do
    
    close(30)
    close(33)
    
END PROGRAM
PROGRAM MAIN
implicit none    
    
    integer(kind=4) :: magic_number, sample_count, point_count
    integer(kind=1) :: label 
    integer(kind=4) :: label_4int
    integer :: i
    
    open(UNIT=33, FILE='t10k-labels.fortran', &
        ACCESS='stream', FORM='unformatted', STATUS='replace')
    open(UNIT=30, FILE='t10k-labels.idx1-ubyte', ACCESS='stream', FORM='unformatted')
    read(30) magic_number, sample_count
    
    magic_number = 2049
    sample_count = 10000
    point_count = sample_count
    
    write(33) magic_number, sample_count
    
    do i=1, point_count
        read(30) label
        label_4int  = label
        if (label_4int < 0) label_4int = label_4int + 256
        write(33) label_4int       
    end do
    
    close(30)
    close(33)
    
END PROGRAM

附录

多层神经网络,从零开始——(一)、Fortran读取MNIST数据集
多层神经网络,从零开始——(二)、Fortran随机生成“双月”分类问题数据
多层神经网络,从零开始——(三)、BP神经网络公式的详细推导
多层神经网络,从零开始——(四)、多层BP神经网络的矩阵形式
多层神经网络,从零开始——(五)、定义数据结构
多层神经网络,从零开始——(六)、激活函数
多层神经网络,从零开始——(七)、损失函数
多层神经网络,从零开始——(八)、分类问题中为什么使用交叉熵作为损失函数
多层神经网络,从零开始——(九)、优化函数
多层神经网络,从零开始——(十)、参数初始化
多层神经网络,从零开始——(十一)、实现训练类
多层神经网络,从零开始——(十二)、实现算例类
多层神经网络,从零开始——(十三)、关于并行计算的简单探讨

你可能感兴趣的:(多层神经网络,从零开始——(一)、Fortran读取MNIST数据集)