用MATLAB搭建DNN

用MATLAB搭建DNN

本文简要介绍了如何用MATLAB搭建一个可自定义层数的深度神经网络,并以MNIST为例进行网络训练、验证、误差可视化展示。不仅限制在该手写数字库,所搭建的DNN也可以用于其他数据。读者需要对MATLAB的矩阵操作、反向传播算法有所了解。

1 工具

MATLAB:version>=7.0.0.19920 (R14)。本人所使用的MATLAB版本较低,对于任何不低于该版本的MATLAB,代码都是可以运行的。
MNIST:手写数字库。也可以使用其他数据。

2 数据准备

你需要了解MNIST的存储格式,并将MNIST原始格式转换为mat格式。鉴于网上已有很多博客介绍了如何将MNIST转换为mat格式,本节直接贴上MATLAB代码loadMNIST。该函数参考了互联网上现有代码,包括多个来源,未指明具体作者。如有冒犯,请您见谅。

function [Train, Label] = loadMNIST(train_file, label_file, force)
% MNIST数据读取与保存.
% train_file = 'data/train-images.idx3-ubyte';
% label_file = 'data/train-labels.idx1-ubyte';
% 返回时将矩阵转置,即矩阵的每一列是一个结果.
% 如果第3个参数输入force为true,则强制从原始文件读取数据.
% 该函数参考了互联网上现有代码,包括多个来源,未指明具体作者.

if nargin < 3
    force = false;
end

% MATLAB7.0
VERSION = datenum(version('-date'));
r2013a = datenum('Jan 01, 2013');

if ~exist('train-images.mat', 'file') || force
    FID = fopen(train_file, 'rb');
    if FID == -1
        Train = [];
        Label = [];
        fprintf('File [%s] does not exist.\n', train_file);
        return
    end
    magic = fread(FID, 1, 'int32', 0, 'ieee-be');
    if VERSION > r2013a
        assert(magic == 2051, ['Bad magic number in ', train_file, '']);
    end

    numImages = fread(FID, 1, 'int32', 0, 'ieee-be');
    numRows = fread(FID, 1, 'int32', 0, 'ieee-be');
    numCols = fread(FID, 1, 'int32', 0, 'ieee-be');

    Train = fread(FID, inf, 'unsigned char');
    Train = reshape(Train, numCols, numRows, numImages);
    Train = permute(Train,[2 1 3]);

    fclose(FID);
    % Reshape to #pixels x #examples
    Train = reshape(Train, size(Train, 1) * size(Train, 2), size(Train, 3));
    % Convert to double and rescale to [0,1]
    % [参看这里](https://blog.csdn.net/weixin_41503009/article/details/83420189)
    Train = double(Train) * (0.999 / 255) + 0.001;
    if ~force
        save('train-images.mat', 'Train');
    end
else
    % 已存在mat格式文件,则直接加载
    load('train-images.mat');
end

if ~exist('train-labels.mat', 'file') || force
    FID = fopen(label_file,'r');
    if FID == -1
        Train = [];
        Label = [];
        fprintf('File [%s] does not exist.\n', label_file);
        return
    end
    magic=readint32(FID);
    NumberofImages=readint32(FID);
    if VERSION > r2013a
        assert(magic == 2049, ['Bad magic number in ', train_file, '']);
        assert(NumberofImages == size(Train, 2));
    end
    Label = zeros(NumberofImages,10);
    for i = 1:NumberofImages
        temp = fread(FID,1);
        Label(i,temp+1) = 1;
    end

    fclose(FID);
    Label = Label';
    Label(Label==0) = 0.001;
    if ~force
        save('train-labels.mat','Label');
    end
else
    % 已存在mat格式文件,则直接加载
    load('train-labels.mat');
end
end

function [getdata]=readint32(FID)

data = [];
for i = 1:4
    f=fread(FID,1);
    data = strcat(data,num2str(dec2base(f,2,8)));
end
getdata = bin2dec(data);

end

3 代码结构

用MATLAB搭建DNN_第1张图片
MATLAB代码所在目录的下面应该包含data目录和test目录,data目录存放的是训练用的数据”train-images-idx3-ubyte.gz”和”train-labels-idx1-ubyte.gz”,test目录存放的是测试用的数据”t10k-images-idx3-ubyte.gz”和”t10k-labels-idx1-ubyte.gz”。运行代码之前请将这4个文件解压到它们所在目录,否则程序会提示你没有解压。

4 代码介绍

编号 文件名 功能 类型
1 s = Accuracy(DNN, Test, Tag) 计算神经网络DNN在测试集Test上的准确率.Tag是测试集的标签. 函数
2 m = Grad(m) 激活层的梯度:由激活函数决定。 函数
3 [p, s] = Identify(file) 对给定的图像文件进行识别.数字为白底黑字. 函数
4 [Train, Label] = loadMNIST(train_file, label_file) MNIST数据读取与保存. 函数
5 DNN = LoadNN(file) 从指定目录加载现有神经网络. 函数
6 m = reLU(m) 激活函数:reLU,S函数或其他,视具体情况而定。 函数
7 Loss = SaveResult(DNN, loss, errs, iter, n) 更新DNN网络权重和残差,并且绘制Loss曲线. 函数
8 DNN = TrainDNN(Train, Label, Test, Tag, hidden, alpha) 在给定的数据集上训练神经网络. 函数
9 [DNN, state] = TrainRecovery(n) 恢复之前的结果,接着进行训练;或者加载现有神经网络. 函数
10 trainMNIST 在MNIST数据集上进行DNN训练、验证、误差可视化。 脚本/main/入口

本项目一共包含10个m文件,其中9个是function类型,第10个文件类型为script,可视为本项目的主函数或项目入口。在这10个文件当中,TrainDNN是核心训练代码,包括注释也仅仅88行。

function DNN = TrainDNN(Train, Label, Test, Tag, hidden, alpha)
% 在给定的数据集上训练神经网络.
% Train: 给定数据集,每一列代表一个input.
% Label: 数据集归类标签,每一列代表一个output.
% Test: 给定测试集,每一列代表一个input.
% Tag: 测试集归类标签,每一列代表一个output.
% hidden: vector,中间各层神经元个数.
% alpha: 初始学习率.
% DNN: cell数组,依次存放A1, A2, A3, ...和 E, Loss.
% 袁沅祥,2019-7

%% 各层神经元数量
sx = size(Train, 1);    %输入层神经元个数
n = hidden;             %隐藏层神经元个数
sy = size(Label, 1);    %输出层神经元个数
q = length(n) + 1;      %权重矩阵的个数

%% 初始值
if nargin < 4
    alpha = 1e-2; % 初始学习率
end
iter = 1000; % 单次最大迭代次数
[DNN, state] = TrainRecovery([sx, n, sy]);% 恢复训练
start = size(DNN{end}, 2); % 上一次迭代次数
if state
    fprintf('DNN:迭代[%g]次,精度%g.\n', start, DNN{end}(3, end));
    return
end
fprintf('从第[%g]步开始迭代.\n', start);
p = alpha * 0.99^start;
lr = p * 0.99.^(0:iter); % 学习率随迭代次数衰减

%profile on;
%profile clear;
%% 开始迭代
num = size(Train, 2);
% 第一行存放误差,第二、三行存放准确率
errs = zeros(3, iter);
count = 0; EarlyStopping = 3; %DNN早停条件
queue = cell(EarlyStopping+1, 1); %存放最近几次DNN网络
for i = 1:iter
    tic;
    alpha = lr(i);
    % 总误差
    total = zeros(sy, num);
    for k = 1 : num % 遍历元素
        % 前向传播
        X = cell(1, q+1);
        X{1} = Train(:, k); % input
        for p = 1:q
            X{p+1} = reLU(DNN{p} * [1; X{p}]);
        end
        err = X{q+1} - Label(:, k); % error
        total(:, k) = err;

        Store = DNN;
        % BP-误差反向传播
        for p = 1:q
            err = (DNN{end-p}(:, 2:end)' * err) .* Grad(X{q+2-p});
            Store{end-1-p} = DNN{end-1-p} - alpha * err * [1; X{q+1-p}]';
        end
        DNN = Store;
    end
    queue = circshift(queue, 1);
    queue{1} = DNN;
    e = mean(sqrt(sum(total.*total)));
    s = Accuracy(DNN, Train, Label);
    t = Accuracy(DNN, Test, Tag);
    best = max(errs(3, 1:i)); % 前i-1次最好的结果
    errs(1, i) = e; errs(2, i) = s; errs(3, i) = t;
    if t <= best
        count = count + 1;
        if count == EarlyStopping
            DNN = queue{end};
            Loss = SaveResult(DNN, DNN{end}, errs, i-EarlyStopping, 1);
            return
        end
    else
        count = 0;
    end
    % 保存权重
    if t >= 0
        Loss = SaveResult(DNN, DNN{end}, errs, i, 10);
    end
    fprintf('%g err=%g lr=%g acc=%g %g use %gs\n',i+start,e,alpha,s,t,toc);
end
%profile viewer;

5 项目亮点

简单:只使用MATLAB的矩阵操作和cell,就实现了可定义层数的DNN。
早停策略:该DNN训练代码加入了早停策略,以防止过拟合。
可视化:每训练一步都会将当前的误差、训练集精度、验证集精度用图形展示。
支持DNN恢复训练:当DNN训练比较漫长的时候,可先训练一定的步数(建议为10的倍数),后续启动trainMNIST时会恢复之前的训练。

6 项目位置

MATLAB-DNN完整的代码在本人的github,如有需要,敬请移步。

你可能感兴趣的:(深度学习,MATLAB,DNN,深度学习,神经网络,反向传播)