共计 10333 个字符,预计需要花费 26 分钟才能阅读完成。
欢送拜访我的 GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及 Java、Docker、Kubernetes、DevOPS 等;
本篇概览
- 作为《DL4J》实战的第三篇,指标是在 DL4J 框架下创立经典的 LeNet- 5 卷积神经网络模型,对 MNIST 数据集进行训练和测试,本篇由以下内容形成:
- LeNet- 5 简介
- MNIST 简介
- 数据集简介
- 对于版本和环境
- 编码
-
验证
LeNet- 5 简介
- 是 Yann LeCun 于 1998 年设计的卷积神经网络,用于手写数字辨认,例如当年美国很多银行用其辨认支票上的手写数字,LeNet- 5 是晚期卷积神经网络最有代表性的试验零碎之一
- LeNet- 5 网络结构如下图所示,一共七层:C1 -> S2 -> C3 -> S4 -> C5 -> F6 -> OUTPUT
- 这张图更加清晰明了(原图地址:https://cuijiahua.com/blog/20…),可能很好的领导咱们在 DL4J 上的编码:
- 依照上图简略剖析一下,用于领导接下来的开发:
- 每张图片都是 28*28 的单通道,矩阵应该是[1, 28,28]
- C1 是卷积层,所用卷积核尺寸 5 *5,滑动步长 1,卷积核数目 20,所以尺寸变动是:28-5+1=24(设想为宽度为 5 的窗口在宽度为 28 的窗口内滑动,能滑多少次),输入矩阵是[20,24,24]
- S2 是池化层,核尺寸 2 *2,步长 2,类型是 MAX,池化操作后尺寸减半,变成了[20,12,12]
- C3 是卷积层,所用卷积核尺寸 5 *5,滑动步长 1,卷积核数目 50,所以尺寸变动是:12-5+1=8,输入矩阵[50,8,8]
- S4 是池化层,核尺寸 2 *2,步长 2,类型是 MAX,池化操作后尺寸减半,变成了[50,4,4]
- C5 是全连贯层(FC),神经元数目 500,接 relu 激活函数
- 最初是全连贯层 Output,共 10 个节点,代表数字 0 到 9,激活函数是 softmax
MNIST 简介
- MNIST 是经典的计算机视觉数据集,起源是 National Institute of Standards and Technology (NIST,美国国家标准与技术研究所),蕴含各种手写数字图片,其中训练集 60,000 张,测试集 10,000 张,
- MNIST 来源于 250 个不同人的手写, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员.,测试集(test set) 也是同样比例的手写数字数据
- MNIST 官网:http://yann.lecun.com/exdb/mn…
数据集简介
- 从 MNIST 官网下载的原始数据并非图片文件,须要按官网给出的格局阐明做解析解决能力转为一张张图片,这些事件显然不是本篇的主题,因而咱们能够间接应用 DL4J 为咱们筹备好的数据集(下载地址稍后给出),该数据集中是一张张独立的图片,这些图片所在目录的名字就是该图片具体的数字,如下图,目录 <font color=”blue”>0</font> 外面全是数字 0 的图片:
- 上述数据集的下载地址有两个:
- 能够在 CSDN 下载(0 积分):https://download.csdn.net/dow…
- github:https://raw.githubusercontent…
- 下载之后解压开,是个名为 <font color=”blue”>mnist_png</font> 的文件夹,稍后的实战中咱们会用到它
对于 DL4J 版本
- 《DL4J 实战》系列的源码采纳了 maven 的父子工程构造,DL4J 的版本在父工程 <font color=”blue”>dlfj-tutorials</font> 中定义为 <font color=”red”>1.0.0-beta7</font>
- 本篇的代码尽管还是 <font color=”blue”>dlfj-tutorials</font> 的子工程,然而 DL4J 版本却应用了更低的 <font color=”red”>1.0.0-beta6</font>,之所以这么做,是因为下一篇文章,咱们会把本篇的训练和测试工作交给 GPU 来实现,而对应的 CUDA 库只有 <font color=”red”>1.0.0-beta6</font>
- 扯了这么多,能够开始编码了
源码下载
- 本篇实战中的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示(https://github.com/zq2599/blo…):
名称 | 链接 | 备注 |
---|---|---|
我的项目主页 | https://github.com/zq2599/blo… | 该我的项目在 GitHub 上的主页 |
git 仓库地址(https) | https://github.com/zq2599/blo… | 该我的项目源码的仓库地址,https 协定 |
git 仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh 协定 |
- 这个 git 我的项目中有多个文件夹,《DL4J 实战》系列的源码在 <font color=”blue”>dl4j-tutorials</font> 文件夹下,如下图红框所示:
- <font color=”blue”>dl4j-tutorials</font> 文件夹下有多个子工程,本次实战代码在 <font color=”blue”>simple-convolution</font> 目录下,如下图红框:
编码
- 在父工程 <font color=”blue”>dl4j-tutorials</font> 下新建名为 <font color=”red”>simple-convolution</font> 的子工程,其 pom.xml 如下,可见这里的 dl4j 版本被指定为 <font color=”red”>1.0.0-beta6</font>:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dlfj-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-convolution</artifactId>
<properties>
<dl4j-master.version>1.0.0-beta6</dl4j-master.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.deeplearning4j</groupId>
<artifactId>deeplearning4j-core</artifactId>
<version>${dl4j-master.version}</version>
</dependency>
<dependency>
<groupId>org.nd4j</groupId>
<artifactId>${nd4j.backend}</artifactId>
<version>${dl4j-master.version}</version>
</dependency>
</dependencies>
</project>
- 接下来依照后面的剖析实现代码,曾经增加了具体正文,就不再赘述了:
package com.bolingcavalry.convolution;
import lombok.extern.slf4j.Slf4j;
import org.datavec.api.io.labels.ParentPathLabelGenerator;
import org.datavec.api.split.FileSplit;
import org.datavec.image.loader.NativeImageLoader;
import org.datavec.image.recordreader.ImageRecordReader;
import org.deeplearning4j.datasets.datavec.RecordReaderDataSetIterator;
import org.deeplearning4j.nn.conf.MultiLayerConfiguration;
import org.deeplearning4j.nn.conf.NeuralNetConfiguration;
import org.deeplearning4j.nn.conf.inputs.InputType;
import org.deeplearning4j.nn.conf.layers.ConvolutionLayer;
import org.deeplearning4j.nn.conf.layers.DenseLayer;
import org.deeplearning4j.nn.conf.layers.OutputLayer;
import org.deeplearning4j.nn.conf.layers.SubsamplingLayer;
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.deeplearning4j.nn.weights.WeightInit;
import org.deeplearning4j.optimize.listeners.ScoreIterationListener;
import org.deeplearning4j.util.ModelSerializer;
import org.nd4j.evaluation.classification.Evaluation;
import org.nd4j.linalg.activations.Activation;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
import org.nd4j.linalg.dataset.api.preprocessor.DataNormalization;
import org.nd4j.linalg.dataset.api.preprocessor.ImagePreProcessingScaler;
import org.nd4j.linalg.learning.config.Nesterovs;
import org.nd4j.linalg.lossfunctions.LossFunctions;
import org.nd4j.linalg.schedule.MapSchedule;
import org.nd4j.linalg.schedule.ScheduleType;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Slf4j
public class LeNetMNISTReLu {
// 寄存文件的地址,请酌情批改
// private static final String BASE_PATH = System.getProperty("java.io.tmpdir") + "/mnist";
private static final String BASE_PATH = "E:\\temp\\202106\\26";
public static void main(String[] args) throws Exception {
// 图片像素高
int height = 28;
// 图片像素宽
int width = 28;
// 因为是黑白图像,所以色彩通道只有一个
int channels = 1;
// 分类后果,0-9,共十种数字
int outputNum = 10;
// 批大小
int batchSize = 54;
// 循环次数
int nEpochs = 1;
// 初始化伪随机数的种子
int seed = 1234;
// 随机数工具
Random randNumGen = new Random(seed);
log.info("检查数据集文件夹是否存在:{}", BASE_PATH + "/mnist_png");
if (!new File(BASE_PATH + "/mnist_png").exists()) {log.info("数据集文件不存在,请下载压缩包并解压到:{}", BASE_PATH);
return;
}
// 标签生成器,将指定文件的父目录作为标签
ParentPathLabelGenerator labelMaker = new ParentPathLabelGenerator();
// 归一化配置(像素值从 0 -255 变为 0 -1)
DataNormalization imageScaler = new ImagePreProcessingScaler();
// 不管训练集还是测试集,初始化操作都是雷同套路:// 1. 读取图片,数据格式为 NCHW
// 2. 依据批大小创立的迭代器
// 3. 将归一化器作为预处理器
log.info("训练集的矢量化操作...");
// 初始化训练集
File trainData = new File(BASE_PATH + "/mnist_png/training");
FileSplit trainSplit = new FileSplit(trainData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ImageRecordReader trainRR = new ImageRecordReader(height, width, channels, labelMaker);
trainRR.initialize(trainSplit);
DataSetIterator trainIter = new RecordReaderDataSetIterator(trainRR, batchSize, 1, outputNum);
// 拟合数据(实现类中实际上什么也没做)
imageScaler.fit(trainIter);
trainIter.setPreProcessor(imageScaler);
log.info("测试集的矢量化操作...");
// 初始化测试集,与后面的训练集操作相似
File testData = new File(BASE_PATH + "/mnist_png/testing");
FileSplit testSplit = new FileSplit(testData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ImageRecordReader testRR = new ImageRecordReader(height, width, channels, labelMaker);
testRR.initialize(testSplit);
DataSetIterator testIter = new RecordReaderDataSetIterator(testRR, batchSize, 1, outputNum);
testIter.setPreProcessor(imageScaler); // same normalization for better results
log.info("配置神经网络");
// 在训练中,将学习率配置为随着迭代阶梯性降落
Map<Integer, Double> learningRateSchedule = new HashMap<>();
learningRateSchedule.put(0, 0.06);
learningRateSchedule.put(200, 0.05);
learningRateSchedule.put(600, 0.028);
learningRateSchedule.put(800, 0.0060);
learningRateSchedule.put(1000, 0.001);
// 超参数
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(seed)
// L2 正则化系数
.l2(0.0005)
// 梯度降落的学习率设置
.updater(new Nesterovs(new MapSchedule(ScheduleType.ITERATION, learningRateSchedule)))
// 权重初始化
.weightInit(WeightInit.XAVIER)
// 筹备分层
.list()
// 卷积层
.layer(new ConvolutionLayer.Builder(5, 5)
.nIn(channels)
.stride(1, 1)
.nOut(20)
.activation(Activation.IDENTITY)
.build())
// 下采样,即池化
.layer(new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
.kernelSize(2, 2)
.stride(2, 2)
.build())
// 卷积层
.layer(new ConvolutionLayer.Builder(5, 5)
.stride(1, 1) // nIn need not specified in later layers
.nOut(50)
.activation(Activation.IDENTITY)
.build())
// 下采样,即池化
.layer(new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
.kernelSize(2, 2)
.stride(2, 2)
.build())
// 浓密层,即全连贯
.layer(new DenseLayer.Builder().activation(Activation.RELU)
.nOut(500)
.build())
// 输入
.layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.nOut(outputNum)
.activation(Activation.SOFTMAX)
.build())
.setInputType(InputType.convolutionalFlat(height, width, channels)) // InputType.convolutional for normal image
.build();
MultiLayerNetwork net = new MultiLayerNetwork(conf);
net.init();
// 每十个迭代打印一次损失函数值
net.setListeners(new ScoreIterationListener(10));
log.info("神经网络共 [{}] 个参数", net.numParams());
long startTime = System.currentTimeMillis();
// 循环操作
for (int i = 0; i < nEpochs; i++) {log.info("第 [{}] 个循环", i);
net.fit(trainIter);
Evaluation eval = net.evaluate(testIter);
log.info(eval.stats());
trainIter.reset();
testIter.reset();}
log.info("实现训练和测试,耗时 [{}] 毫秒", System.currentTimeMillis()-startTime);
// 保留模型
File ministModelPath = new File(BASE_PATH + "/minist-model.zip");
ModelSerializer.writeModel(net, ministModelPath, true);
log.info("最新的 MINIST 模型保留在[{}]", ministModelPath.getPath());
}
}
- 执行上述代码,日志输入如下,训练和测试都顺利完成,准确率达到 0.9886:
21:19:15.355 [main] INFO org.deeplearning4j.optimize.listeners.ScoreIterationListener - Score at iteration 1110 is 0.18300625613640034
21:19:15.365 [main] DEBUG org.nd4j.linalg.dataset.AsyncDataSetIterator - Manually destroying ADSI workspace
21:19:16.632 [main] DEBUG org.nd4j.linalg.dataset.AsyncDataSetIterator - Manually destroying ADSI workspace
21:19:16.642 [main] INFO com.bolingcavalry.convolution.LeNetMNISTReLu -
========================Evaluation Metrics========================
# of classes: 10
Accuracy: 0.9886
Precision: 0.9885
Recall: 0.9886
F1 Score: 0.9885
Precision, recall & F1: macro-averaged (equally weighted avg. of 10 classes)
=========================Confusion Matrix=========================
0 1 2 3 4 5 6 7 8 9
---------------------------------------------------
972 0 0 0 0 0 2 2 2 2 | 0 = 0
0 1126 0 3 0 2 1 1 2 0 | 1 = 1
1 1 1019 2 0 0 0 6 3 0 | 2 = 2
0 0 1 1002 0 5 0 1 1 0 | 3 = 3
0 0 2 0 971 0 3 2 1 3 | 4 = 4
0 0 0 3 0 886 2 1 0 0 | 5 = 5
6 2 0 1 1 5 942 0 1 0 | 6 = 6
0 1 6 0 0 0 0 1015 1 5 | 7 = 7
1 0 1 1 0 2 0 2 962 5 | 8 = 8
1 2 1 3 5 3 0 2 1 991 | 9 = 9
Confusion matrix format: Actual (rowClass) predicted as (columnClass) N times
==================================================================
21:19:16.643 [main] INFO com.bolingcavalry.convolution.LeNetMNISTReLu - 实现训练和测试,耗时 [27467] 毫秒
21:19:17.019 [main] INFO com.bolingcavalry.convolution.LeNetMNISTReLu - 最新的 MINIST 模型保留在[E:\temp\202106\26\minist-model.zip]
Process finished with exit code 0
对于准确率
- 后面的测试结果显示准确率为 <font color=”blue”>0.9886</font>,这是 <font color=”red”>1.0.0-beta6</font> 版本 DL4J 的训练后果,如果换成 <font color=”red”>1.0.0-beta7</font>,准确率能够达到 <font color=”blue”>0.99</font> 以上,您能够尝试一下;
- 至此,DL4J 框架下的经典卷积实战就实现了,截止目前,咱们的训练和测试工作都是 CPU 实现的,工作中 CPU 使用率的回升非常显著,下一篇文章,咱们把明天的工作交给 GPU 执行试试,看是否借助 CUDA 减速训练和测试工作;
你不孤独,欣宸原创一路相伴
- Java 系列
- Spring 系列
- Docker 系列
- kubernetes 系列
- 数据库 + 中间件系列
- DevOps 系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos
正文完