欢迎来到专栏《2小时玩转开源框架系列》,这是我们第九篇,前面已经说过了caffe,tensorflow,pytorch,mxnet,keras,paddlepaddle,cntk,chainer。
今天说Deeplearning4j(DL4J),本文所用到的资料,程式码请参考我们官方git
https://github.com/longpeng2008/yousan.ai
作者&编辑 | 胡郡郡 言有三
1 Deeplearning4j(DL4J)是什么
不同于深度学习广泛应用的语言Python,DL4J是为java和jvm编写的开源深度学习库,支援各种深度学习模型。DL4J最重要的特点是支援分散式,可以在Spark和Hadoop上执行,支援分散式CPU和GPU执行。DL4J是为商业环境,而非研究所设计的,因此更加贴近某些生产环境。
2 DL4J训练准备
2.1 DL4J安装系统要求:
Java:开发者版7或更新版本(仅支援64位版本)Apache Maven:Maven是针对Java的专案管理工具,相容IntelliJ等IDE,可以让我们轻松安装DL4J专案库IntelliJ IDEA (建议)或 EclipseGit官方提供了很多DL4J的示例。可以通过以下命令下载安装:
$ git clone https://github.com/deeplearning4j/dl4j-examples.git
$ cd dl4j-examples/
$ mvn clean install
mvn clean install 目的是为了安装所依赖的相关包。
然后将下载的dl4j-examples汇入到IntelliJ IDEA中,点选自己想要试的例子进行执行。
2.2 资料准备
DL4J有自己的特殊的资料结构DataVec,所有的输入资料在进入神经网络之前要先经过向量化。向量化后的结果就是一个行数不限的单列矩阵。
熟悉Hadoop/MapReduce的朋友肯定知道它的输入用InputFormat来确定具体的InputSplit和RecordReader。DataVec也有自己FileSplit和RecordReader,并且对于不同的资料型别(文字、CSV、音讯、影象、视讯等),有不同的RecordReader,下面是一个影象的例子。
int height = 48; // 输入影象高度
int width = 48; // 输入影象宽度
int channels = 3; // 输入影象通道数
int outputNum = 2; // 2分类
int batchSize = 64;
int nEpochs = 100;
int seed = 1234;
Random randNumGen = new Random(seed);
// 训练资料的向量化
File trainData = new File(inputDataDir + "/train");
FileSplit trainSplit = new FileSplit(trainData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ParentPathLabelGenerator labelMaker = new ParentPathLabelGenerator(); // parent path as the image label
ImageRecordReader trainRR = new ImageRecordReader(height, width, channels, labelMaker);
trainRR.initialize(trainSplit);
DataSetIterator trainIter = new RecordReaderDataSetIterator(trainRR, batchSize, 1, outputNum);
// 将画素从0-255缩放到0-1 (用min-max的方式进行缩放)
DataNormalization scaler = new ImagePreProcessingScaler(0, 1);
scaler.fit(trainIter);
trainIter.setPreProcessor(scaler);
// 测试资料的向量化
File testData = new File(inputDataDir + "/test");
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(scaler); // same normalization for better results
资料准备的过程分成以下几个步骤:
1)通过FileSplit处理输入档案,FileSplit决定了档案的分散式的分发和处理。
2)ParentPathLabelGenerator通过父目录来直接生成标签,这个生成标签的界面非常方便,比如说如果是二分类,我们先将两个父目录设定为0和1,然后再分别在里面放置对应的影象就行。
3)通过ImageRecordReader读入输入影象。RecordReader是DataVec中的一个类,ImageRecordReader是RecordReader中的一个子类,这样就可以将输入影象转成向量化的带有索引的资料。
4)生成DataSetIterator,实现了对输入资料集的迭代。
2.3 网络定义
在Deeplearning4j中,新增一个层的方式是通过NeuralNetConfiguration.Builder()呼叫layer,指定其在所有层中的输入及输出节点数nIn和nOut,启用方式activation,层的型别如ConvolutionLayer等。
// 设定网络层及超引数
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(seed)
.l2(0.0005)
.updater(new Adam(0.0001))
.weightInit(WeightInit.XAVIER)
.list()
.layer(0, new ConvolutionLayer.Builder(3, 3)
.nIn(channels)
.stride(2, 2)
.nOut(12)
.activation(Activation.RELU)
.weightInit(WeightInit.XAVIER)
.build())
.layer(1, new BatchNormalization.Builder()
.nIn(12)
.nOut(12)
.build())
.layer(2, new ConvolutionLayer.Builder(3, 3)
.nIn(12)
.stride(2, 2)
.nOut(24)
.activation(Activation.RELU)
.weightInit(WeightInit.XAVIER)
.build())
.layer(3, new BatchNormalization.Builder()
.nIn(24)
.nOut(24)
.build())
.layer(4, new ConvolutionLayer.Builder(3, 3)
.nIn(24)
.stride(2, 2)
.nOut(48)
.activation(Activation.RELU)
.weightInit(WeightInit.XAVIER)
.build())
.layer(5, new BatchNormalization.Builder()
.nIn(48)
.nOut(48)
.build())
.layer(6, new DenseLayer.Builder().activation(Activation.RELU)
.nOut(128).build())
.layer(7, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.nOut(outputNum)
.activation(Activation.SOFTMAX)
.build())
.setInputType(InputType.convolutionalFlat(48, 48, 3)) // InputType.convolutional for normal image
.backprop(true).pretrain(false).build();
这里的网络结构和之前的caffe、tensorflow、pytorch等框架采用的网络结构是一样的,都是一个3层的神经网络。
3 模型训练
资料准备好了,网络也建好了,接下来就可以训练了。// 新建一个多层网络模型
MultiLayerNetwork net = new MultiLayerNetwork(conf);
net.init();
// 训练的过程中同时进行评估
for (int i = 0; i net.fit(trainIter);
log.info("Completed epoch " + i);
Evaluation trainEval = net.evaluate(trainIter);
Evaluation eval = net.evaluate(testIter);
log.info("train: " + trainEval.precision());
log.info("val: " + eval.precision());
trainIter.reset();
testIter.reset();
}
//储存模型
ModelSerializer.writeModel(net, new File(modelDir + "/mouth-model.zip"), true);
训练的过程非常简单直观,直接通过net.fit()载入trainIter就可以,其中trainIter在资料准备中已经定义好了。
通过net.evaluate(trainIter)和net.evaluate(testIter)的方式来评估训练和测试的表现,这里我们将每个epoch的准确率打印出来。
4 视觉化
DL4J提供的使用者界面可以在浏览器中看到实时的训练过程。第一步:
将使用者界面依赖项新增到pom档案中:
org.deeplearning4j
deeplearning4j-ui_2.10
${dl4j.version}
第二步:
在专案中启动使用者界面
//初始化使用者界面后端,获取一个UI例项
UIServer uiServer = UIServer.getInstance();
//设定网络资讯(随时间变化的梯度、分值等)的储存位置。这里将其储存于内存。
StatsStorage statsStorage = new InMemoryStatsStorage();
//将StatsStorage例项连线至使用者界面,让StatsStorage的内容能够被视觉化
uiServer.attach(statsStorage);
//新增StatsListener来在网络定型时收集这些资讯
net.setListeners(new StatsListener(statsStorage));
首先我们初始化一个使用者界面后端,设定网络资讯的储存位置。
这里将其储存于内存,也可以放入档案中,通过new FileStatsStorage(File)的方式实现。
再将StatsStorage例项连线至使用者界面,让StatsStorage的内容能够被视觉化。
最后新增StatsListener监听,在网络定型时收集这些资讯。
预设的浏览器地址是:http://localhost:9000/train/overview
下面视觉化一下损失函式值随迭代次数的变化曲线

模型页面中可以直观感受我们建立的模型

看一下最后的训练集和测试集的准确率

有一些过拟合,主要原因还是资料太少。
以上就是我们用自己的资料在DL4J框架上实践的内容,完整程式码可以参考官方git。





























