目录

创建工程

Deeplearning4j

MNIST 数据集

加载数据

创建模型

创建单层回归模型

创建深度信念网络

创建多层卷积网络

完整代码


本节将讨论如何使用Deeplearning4j库实现一些神经网络结构。

创建工程

接着前面的项目,首先导入maven依赖

        <dependency>
            <groupId>org.deeplearning4j</groupId>
            <artifactId>deeplearning4j-core</artifactId>
            <version>0.4-rc3.8</version>
        </dependency>
        <dependency>
            <groupId>org.deeplearning4j</groupId>
            <artifactId>deeplearning4j-nlp</artifactId>
            <version>0.4-rc3.8</version>
        </dependency>
        <dependency>
            <groupId>org.nd4j</groupId>
            <artifactId>nd4j-x86</artifactId>
            <version>0.4-rc3.8</version>
        </dependency>
        <dependency>
            <groupId>org.nd4j</groupId>
            <artifactId>canova-nd4j-image</artifactId>
            <version>0.0.0.14</version>
        </dependency>
        <dependency>
            <groupId>org.nd4j</groupId>
            <artifactId>canova-nd4j-codec</artifactId>
            <version>0.0.0.14</version>
        </dependency>

Deeplearning4j

Deeplearning4j库,它是Java与Scala环境下的开源分布式深度学习项目。 Deeplearning4j依赖Spark与Hadoop使用MapReduce框架,并行训练模型,且反复对中心模型中产 生参数进行平均。

MNIST 数据集

MNIST数据集是最著名的数据集之一,由手写数字组成,如图所示。该数据集包含60 000 个训练与10 000个测试图像。

这个数据集通常用在图像识别问题中,以测试算法性能。最差记录的错误率是12%,测试时 使用单层神经网络中的SVM算法,并且没有做预处理。截止到2016年,最低的错误率只有0.21%, 使用的是DropConnect神经网络;紧随其后的是深度卷积网络,错误率为0.23%;然后是深度前 馈网络,错误率是0.35%。
接下来,让我们看看如何加载数据集。

加载数据

Deeplearning4j 提供 了“开箱 即用”的 MNIST 数 据集 加载器。加 载器被初始 化为 DataSetIterator。先导入DataSetIterator类与所有支持的数据集,这些数据集是impl包 的一部分,包含的数据集有Iris、MNIST及其他。

接着定义一些常量,比如28×28个像素组成的图像,有10个目标类与60 000个样本。新初始 化一个MnistDataSetIterator类,用于下载数据集及其标签。参数分别是迭代批大小、总样 本数,以及是否将数据集二值化:

        // 定义常量
        final int numRows = 28; // 输入图像的高度
        final int numColumns = 28; // 输入图像的宽度
        int outputNum = 10; // 输出的类别数量(0-9)
        int numSamples = 60000; // 训练集的样本数量
        int batchSize = 100; // 每批数据的大小
        int iterations = 10; // 训练时的迭代次数
        int seed = 123; // 随机种子
        int listenerFreq = batchSize / 5; // 监听器的频率,每5批次打印一次分数
        // 输出加载数据的信息
        System.out.println("加载数据···");
        // 创建MNIST数据集迭代器,用于读取训练集
        DataSetIterator iterator = new MnistDataSetIterator(batchSize, numSamples, true);

但是这里有个问题,这里是自动从国外拉数据,开了VPN也会失败,所以我们可以写个自定义的数据加载器加载本地数据,不过容易OOM,所以我这里没跑出来。

        // 定义常量
        final int numRows = 28; // 输入图像的高度
        final int numColumns = 28; // 输入图像的宽度
        int outputNum = 10; // 输出的类别数量(0-9)
        int numSamples = 60000; // 训练集的样本数量
        int batchSize = 100; // 每批数据的大小
        int iterations = 10; // 训练时的迭代次数
        int seed = 123; // 随机种子
        int listenerFreq = batchSize / 5; // 监听器的频率,每5批次打印一次分数
        // 输出加载数据的信息
        System.out.println("加载数据···");
        // 创建MNIST数据集迭代器,用于读取训练集
        DataSetIterator iterator = getMnistDataSetIterator(batchSize, true);

    private static DataSetIterator getMnistDataSetIterator(int batchSize, boolean train) throws IOException {
        String imagesFile = train ? "train-images-idx3-ubyte.gz" : "t10k-images-idx3-ubyte.gz";
        String labelsFile = train ? "train-labels-idx1-ubyte.gz" : "t10k-labels-idx1-ubyte.gz";
        INDArray images = loadImages(new File(BASE_PATH, imagesFile));
        INDArray labels = loadLabels(new File(BASE_PATH, labelsFile));
        DataSet dataSet = new DataSet(images, labels);
        // 手动标准化数据
        INDArray mean = images.mean(0);
        INDArray std = images.std(0);
        images.subiRowVector(mean);
        images.diviRowVector(std);
        List<DataSet> list = new ArrayList<>();
        for (int i = 0; i < dataSet.numExamples(); i++) {
            list.add(dataSet.get(i));
        }
        return new ListDataSetIterator(list, batchSize);
    }

    private static INDArray loadImages(File file) throws IOException {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
            int magicNumber = dis.readInt();
            //if (magicNumber != 2051) {
            //    throw new IOException("Invalid magic number in image file!");
            //}
            int numImages = dis.readInt();
            int numRows = dis.readInt();
            int numColumns = dis.readInt();
            INDArray images = Nd4j.create(numImages, 1, numRows, numColumns);
            for (int i = 0; i < numImages; i++) {
                for (int r = 0; r < numRows; r++) {
                    for (int c = 0; c < numColumns; c++) {
                        int pixel = dis.readUnsignedByte();
                        images.putScalar(new int[]{i, 0, r, c}, pixel);
                    }
                }
            }
            return images;
        }
    }

    private static INDArray loadLabels(File file) throws IOException {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
            int magicNumber = dis.readInt();
            //if (magicNumber != 2049) {
            //    throw new IOException("Invalid magic number in label file!");
            //}
            int numLabels = dis.readInt();
            INDArray labels = Nd4j.zeros(numLabels, 10);
            for (int i = 0; i < numLabels; i++) {
                int label = dis.readUnsignedByte();
                labels.putScalar(new int[]{i, label}, 1.0);
            }
            return labels;
        }
    }

创建模型

本节将讨论如何实际创建一个神经网络模型。先创建一个基本的单层神经网络建立一个基准 标杆,并且学习基本操作。随后,使用DBN与多层卷积网络改进初始结果。

创建单层回归模型

先创建一个单层回归模型,它基于softmax激活函数,如图8-10所示。由于我们只有一个层, 所以神经网络的输入是所有图形像素,即28×28=784个神经元。输出神经元的个数为10,对应于 每一个数字。网络中的层是全连接的,如图

使用一个NeuralNetConfiguration Builder对象定义神经网络。

接着,为梯度搜索定义参数,以便使用共轭梯度最优化算法做迭代。其中,momentum参数 指定优化算法收敛到局部最优的速度,momentum值越高,训练得越快;但速度过快有可能降低 模型准确度

接下来,指定网络有一个层,并且定义错误函数(NEGATIVELOGLIKELIHOOD)、内部 感知器激活函数(softmax),以及输入和输出层的数量,对应于总的图像像素和目标变量数

最后,为网络开启预训练(pretrain),关闭反向传播,实际创建未经训练的网络结构

    /**
     * 创建一个包含单层输出层的神经网络模型,用于Softmax回归分类任务。
     *
     * @param seed       随机种子,用于确保模型初始化的可重复性。
     * @param iterations 训练时的迭代次数。
     * @param numRows    输入数据的行数。
     * @param numColumns 输入数据的列数。
     * @param outputNum  输出的类别数量。
     * @return 一个新的多层神经网络模型。
     */
    private static MultiLayerNetwork softMaxRegression(int seed, int iterations, int numRows, int numColumns, int outputNum) {
        // 配置神经网络的架构和训练参数
        MultiLayerConfiguration configuration = new NeuralNetConfiguration.Builder()
                .seed(seed) // 设置随机种子,确保每次运行的初始化一致
                .gradientNormalization(GradientNormalization.ClipElementWiseAbsoluteValue) // 梯度裁剪,防止梯度爆炸
                .gradientNormalizationThreshold(1.0) // 梯度裁剪的阈值
                .iterations(iterations) // 设置训练时的迭代次数
                .momentum(0.5) // 设置动量(用于加速训练)
                .momentumAfter(Collections.singletonMap(3, 0.9)) // 在第三个迭代后增加动量
                .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT) // 设置优化算法为共轭梯度
                .list(1) // 定义网络的层数为1层
                .layer(0, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) // 定义输出层
                        .activation("softmax") // 设置激活函数为softmax(用于多分类)
                        .nIn(numColumns * numRows) // 输入层的大小为 numRows * numColumns
                        .nOut(outputNum) // 输出层的大小为输出的类别数量
                        .build())
                .pretrain(true) // 启用预训练(无监督学习)
                .backprop(false) // 禁用反向传播(监督学习)
                .build(); // 构建配置对象
        // 根据配置创建多层神经网络模型
        MultiLayerNetwork model = new MultiLayerNetwork(configuration);
        // 返回创建的模型
        return model;
    }

一旦网络结构定义完成后,可以用它初始化一个MultiLayerNetwork对象

接下来,调用setListeners方法,绑定模型与训练数据

此外,调用fit(int)方法触发端对端的网络训练

为了评价模型,新建并初始化一个Evaluation对象,用于存储批结果

然后,在数据集上分批做迭代,以便让内存消耗保持在一个合理的范围内,并且结果保存在 一个eval对象中

最后,调用stats()函数获取结果

    private static String BASE_PATH = ClassUtils.getDefaultClassLoader().getResource("data/test08").getPath();

    public static void main(String[] args) throws Exception {
        // 定义常量
        final int numRows = 28; // 输入图像的高度
        final int numColumns = 28; // 输入图像的宽度
        int outputNum = 10; // 输出的类别数量(0-9)
        int numSamples = 60000; // 训练集的样本数量
        int batchSize = 100; // 每批数据的大小
        int iterations = 10; // 训练时的迭代次数
        int seed = 123; // 随机种子
        int listenerFreq = batchSize / 5; // 监听器的频率,每5批次打印一次分数
        // 输出加载数据的信息
        System.out.println("加载数据···");
        // 创建MNIST数据集迭代器,用于读取训练集
        DataSetIterator iterator = new MnistDataSetIterator(batchSize, numSamples, true);
        //DataSetIterator iterator = getMnistDataSetIterator(batchSize, true);
        // 输出构建模型的信息
        System.out.println("构建模型···");
        // 创建并初始化神经网络模型
        MultiLayerNetwork model = softMaxRegression(seed, iterations, numRows, numColumns, outputNum);
        model.init();
        // 添加训练监听器,每5批次打印一次分数
        model.setListeners(Collections.singletonList((IterationListener) new ScoreIterationListener(listenerFreq)));
        // 输出训练信息
        System.out.println("训练模型···");
        // 训练模型
        model.fit(iterator);
        // 输出评估信息
        System.out.println("评估模型···");
        // 创建评估对象
        Evaluation evaluation = new Evaluation(outputNum);
        // 创建测试集的数据集迭代器
        DataSetIterator test = new MnistDataSetIterator(batchSize, 10000);
        // 遍历测试集,对模型进行评估
        while (test.hasNext()) {
            DataSet next = test.next();
            INDArray data = model.output(next.getFeatures()); // 获取模型的输出
            evaluation.eval(next.getLabels(), data); // 评估模型的预测结果
        }
        // 打印评估结果
        System.out.println(evaluation.stats());
    }

基本的单层模型准确度如下

Accuracy:  0.8945 
Precision: 0.8985 
Recall:    0.8922 
F1 Score:  0.8953

从上面结果可以看到,模型准确率是89.22%,错误率为10.88%,这表示模型在MNIST数据 集上表现很差。接下来对模型做进一步改善,将其从简单的单层网络变为带有适度复杂度的深度 信念网络,这种网络使用了受限玻尔兹曼机与多层卷积网络。

创建深度信念网络

本节将创建一个基于受限玻尔兹曼机的深度信念网络,如图8-11所示。深度信念网络由4个 层组成,第一个层将784个输入缩小为500个神经元,然后是250个,再然后是200个,最后是10 个目标值

代码与上一个例子一样,下面看看如何配置这样一个网络

接着定义梯度优化算法

然后,指定网络有4个层

第一个层的输入有784个神经元,输出为500个神经元。使用均方根误差交叉熵——Xavier算 法初始化权重,基于输入与输出神经元的数目自动确定初始化权重的范围

接下来的两个层拥有相同的参数,但是输入与输出神经元的数目有所不同

现在,使用最后一个层将神经元映射到输出。使用softmax激活函数完成这个任务

    /**
     * 构建一个深度信念网络(Deep Belief Network, DBN)模型。
     *
     * @param seed       随机种子,用于初始化模型的权重和其他随机变量,确保结果的可重复性。
     * @param iterations 训练迭代的次数。
     * @param numRows    输入数据的行数。
     * @param numColumns 输入数据的列数。
     * @param outputNum  输出层的神经元数量,通常等于分类的类别数。
     * @return 返回一个配置好的 MultiLayerNetwork 模型。
     */
    private static MultiLayerNetwork deepBeliefNetwork(int seed, int iterations, int numRows, int numColumns, int outputNum) {
        // 配置神经网络的架构和参数
        MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
                // 设置随机种子,确保模型初始化的权重相同
                .seed(seed)
                // 梯度归一化,用于防止梯度爆炸或消失问题
                .gradientNormalization(GradientNormalization.ClipElementWiseAbsoluteValue)
                // 设置梯度归一化的阈值为1.0
                .gradientNormalizationThreshold(1.0)
                // 设置训练迭代的次数
                .iterations(iterations)
                // 设置初始动量,用于加速训练过程
                .momentum(0.5)
                // 设置动量的变化策略,在第3次迭代后动量增加到0.9
                .momentumAfter(Collections.singletonMap(3, 0.9))
                // 设置优化算法为共轭梯度法(Conjugate Gradient)
                .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT)
                // 设置网络的层数为4层
                .list(4)
                // 第一层:RBM,输入层到隐藏层
                .layer(0, new RBM.Builder()
                        // 输入层的神经元数量为 numRows * numColumns
                        .nIn(numRows * numColumns)
                        // 隐藏层的神经元数量为500
                        .nOut(500)
                        // 权重初始化方式为Xavier初始化
                        .weightInit(WeightInit.XAVIER)
                        // 损失函数为均方根误差交叉熵损失函数
                        .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                        // 可见单元类型为二元单元
                        .visibleUnit(RBM.VisibleUnit.BINARY)
                        // 隐藏单元类型为二元单元
                        .hiddenUnit(RBM.HiddenUnit.BINARY)
                        .build())
                // 第二层:RBM,隐藏层到隐藏层
                .layer(1, new RBM.Builder()
                        // 输入层的神经元数量为500
                        .nIn(500)
                        // 隐藏层的神经元数量为250
                        .nOut(250)
                        // 权重初始化方式为Xavier初始化
                        .weightInit(WeightInit.XAVIER)
                        // 损失函数为均方根误差交叉熵损失函数
                        .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                        // 可见单元类型为二元单元
                        .visibleUnit(RBM.VisibleUnit.BINARY)
                        // 隐藏单元类型为二元单元
                        .hiddenUnit(RBM.HiddenUnit.BINARY)
                        .build())
                // 第三层:RBM,隐藏层到隐藏层
                .layer(2, new RBM.Builder()
                        // 输入层的神经元数量为250
                        .nIn(250)
                        // 隐藏层的神经元数量为200
                        .nOut(200)
                        // 权重初始化方式为Xavier初始化
                        .weightInit(WeightInit.XAVIER)
                        // 损失函数为均方根误差交叉熵损失函数
                        .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                        // 可见单元类型为二元单元
                        .visibleUnit(RBM.VisibleUnit.BINARY)
                        // 隐藏单元类型为二元单元
                        .hiddenUnit(RBM.HiddenUnit.BINARY)
                        .build())
                // 第四层:输出层,用于分类任务
                .layer(3, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
                        // 激活函数为softmax,用于多分类问题
                        .activation("softmax")
                        // 输入层的神经元数量为200
                        .nIn(200)
                        // 输出层的神经元数量为outputNum
                        .nOut(outputNum)
                        .build())
                // 设置预训练为真,即使用RBM进行无监督预训练
                .pretrain(true)
                // 设置反向传播为假,即不进行反向传播微调
                .backprop(false)
                .build();
        // 创建并返回配置好的多层神经网络模型
        MultiLayerNetwork model = new MultiLayerNetwork(conf);
        return model;
    }

训练与评价的其他部分与单层网络的例子是一样的。请注意,训练深度网络耗费的时间可能 明显要比训练单层网络长得多,但准确度应该能够达到93%左右。
接下来,让我们看一看另外一个深度网络。

创建多层卷积网络

本章最后一个例子中,让我们一起学习如何创建卷积网络,如图8-12所示。这个卷积网络由 7个层组成:首先,用max pooling重复两对卷积与子采样层;然后将最后一个子采样层连接到紧 密相连的前馈神经网络,最后三个层中依次含有120个神经元、84个神经元、10个神经元。这样 一种网络实际上组成了完整的图像识别管道,前4个图层对应于特征提取,后3个图层对应于学习 模型

网络配置的初始化方法与前面一样

接着,指定梯度下降算法及其参数

此外,还要指定7个网络层

第一个卷积层的输入是一幅完整图像,而输出是6个特征图。卷积层应用一个5×5的过滤器, 结果存储在1×1单元格中

第二个层是子采样层,它接收一个2×2区域,并把最大结果存成2×2元素

接下来的两个层重复前面两个层

接着,将子采样层的输出连接到稠密前馈网络,先是120个神经元,然后穿过另一个层,变 成84个神经元

最后一个层将84个神经元与10个输出神经元连接在一起

    /**
     * 构建一个深度卷积神经网络(Deep Convolutional Neural Network, CNN)模型。
     *
     * @param seed       随机种子,用于初始化权重和偏差,确保结果可复现。
     * @param iterations 训练时的迭代次数。
     * @param numRows    输入数据的行数(图像高度)。
     * @param numColumns 输入数据的列数(图像宽度)。
     * @param outputNum  输出层的神经元数量,通常对应分类任务的类别数。
     * @return 返回一个配置好的深度卷积神经网络模型。
     */
    private static MultiLayerNetwork deepConvNetwork(int seed, int iterations, int numRows, int numColumns, int outputNum) {
        // 构建神经网络配置
        MultiLayerConfiguration.Builder conf = new NeuralNetConfiguration.Builder()
                .seed(seed)  // 设置随机种子
                .iterations(iterations)  // 设置训练迭代次数
                .activation("sigmoid")  // 设置激活函数为Sigmoid
                .weightInit(WeightInit.DISTRIBUTION)  // 设置权重初始化方式为分布初始化
                .dist(new NormalDistribution(0.0, 0.01))  // 使用均值为0,标准差为0.01的正态分布初始化权重
                .learningRate(1e-3)  // 设置学习率
                .learningRateScoreBasedDecayRate(1e-1)  // 设置基于得分的学习率衰减率
                .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)  // 使用随机梯度下降优化算法
                .list(7)  // 设置网络层数为7层
                // 第0层:卷积层
                .layer(0, new ConvolutionLayer.Builder(new int[]{5, 5}, new int[]{1, 1})
                        .name("cnn1")  // 层名称为cnn1
                        .nIn(numRows * numColumns)  // 输入数据的维度为图像的行数乘以列数
                        .nOut(6)  // 输出特征图的数量为6
                        .build())
                // 第1层:最大池化层
                .layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2}, new int[]{2, 2})
                        .name("maxpool1")  // 层名称为maxpool1
                        .build())
                // 第2层:卷积层
                .layer(2, new ConvolutionLayer.Builder(new int[]{5, 5}, new int[]{1, 1})
                        .name("cnn2")  // 层名称为cnn2
                        .nOut(16)  // 输出特征图的数量为16
                        .biasInit(1)  // 设置偏差初始化为1
                        .build())
                // 第3层:最大池化层
                .layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2}, new int[]{2, 2})
                        .name("maxpool2")  // 层名称为maxpool2
                        .build())
                // 第4层:全连接层
                .layer(4, new DenseLayer.Builder()
                        .name("ffn1")  // 层名称为ffn1
                        .nOut(120)  // 输出神经元数量为120
                        .build())
                // 第5层:全连接层
                .layer(5, new DenseLayer.Builder()
                        .name("ffn2")  // 层名称为ffn2
                        .nOut(84)  // 输出神经元数量为84
                        .build())
                // 第6层:输出层
                .layer(6, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
                        .name("output")  // 层名称为output
                        .nOut(outputNum)  // 输出神经元数量为outputNum
                        .activation("softmax")  // 使用Softmax激活函数
                        .build())
                .backprop(true)  // 启用反向传播
                .pretrain(false)  // 不进行预训练
                .cnnInputSize(numRows, numColumns, 1);  // 设置CNN输入数据的尺寸(高度、宽度、通道数)
        // 构建并返回多层神经网络模型
        MultiLayerNetwork model = new MultiLayerNetwork(conf.build());
        return model;
    }

为了训练这个结构,可以重用前面两个例子中编写的代码。再次提醒,训练可能要花一些时 间,训练结束后,网络的准确度应该能达到98%左右。

完整代码

public class NeuralNetworks {
    private static String BASE_PATH = ClassUtils.getDefaultClassLoader().getResource("data/test08").getPath();

    public static void main(String[] args) throws Exception {
        // 定义常量
        final int numRows = 28; // 输入图像的高度
        final int numColumns = 28; // 输入图像的宽度
        int outputNum = 10; // 输出的类别数量(0-9)
        int numSamples = 60000; // 训练集的样本数量
        int batchSize = 100; // 每批数据的大小
        int iterations = 10; // 训练时的迭代次数
        int seed = 123; // 随机种子
        int listenerFreq = batchSize / 5; // 监听器的频率,每5批次打印一次分数
        // 输出加载数据的信息
        System.out.println("加载数据···");
        // 创建MNIST数据集迭代器,用于读取训练集
        DataSetIterator iterator = new MnistDataSetIterator(batchSize, numSamples, true);
        //DataSetIterator iterator = getMnistDataSetIterator(batchSize, true);
        // 输出构建模型的信息
        System.out.println("构建模型···");
        // 创建并初始化神经网络模型
        MultiLayerNetwork model = softMaxRegression(seed, iterations, numRows, numColumns, outputNum);
        model.init();
        // 添加训练监听器,每5批次打印一次分数
        model.setListeners(Collections.singletonList((IterationListener) new ScoreIterationListener(listenerFreq)));
        // 输出训练信息
        System.out.println("训练模型···");
        // 训练模型
        model.fit(iterator);
        // 输出评估信息
        System.out.println("评估模型···");
        // 创建评估对象
        Evaluation evaluation = new Evaluation(outputNum);
        // 创建测试集的数据集迭代器
        DataSetIterator test = new MnistDataSetIterator(batchSize, 10000);
        // 遍历测试集,对模型进行评估
        while (test.hasNext()) {
            DataSet next = test.next();
            INDArray data = model.output(next.getFeatures()); // 获取模型的输出
            evaluation.eval(next.getLabels(), data); // 评估模型的预测结果
        }
        // 打印评估结果
        System.out.println(evaluation.stats());
    }

    private static DataSetIterator getMnistDataSetIterator(int batchSize, boolean train) throws IOException {
        String imagesFile = train ? "train-images-idx3-ubyte.gz" : "t10k-images-idx3-ubyte.gz";
        String labelsFile = train ? "train-labels-idx1-ubyte.gz" : "t10k-labels-idx1-ubyte.gz";
        INDArray images = loadImages(new File(BASE_PATH, imagesFile));
        INDArray labels = loadLabels(new File(BASE_PATH, labelsFile));
        DataSet dataSet = new DataSet(images, labels);
        // 手动标准化数据
        INDArray mean = images.mean(0);
        INDArray std = images.std(0);
        images.subiRowVector(mean);
        images.diviRowVector(std);
        List<DataSet> list = new ArrayList<>();
        for (int i = 0; i < dataSet.numExamples(); i++) {
            list.add(dataSet.get(i));
        }
        return new ListDataSetIterator(list, batchSize);
    }

    private static INDArray loadImages(File file) throws IOException {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
            int magicNumber = dis.readInt();
            //if (magicNumber != 2051) {
            //    throw new IOException("Invalid magic number in image file!");
            //}
            int numImages = dis.readInt();
            int numRows = dis.readInt();
            int numColumns = dis.readInt();
            INDArray images = Nd4j.create(numImages, 1, numRows, numColumns);
            for (int i = 0; i < numImages; i++) {
                for (int r = 0; r < numRows; r++) {
                    for (int c = 0; c < numColumns; c++) {
                        int pixel = dis.readUnsignedByte();
                        images.putScalar(new int[]{i, 0, r, c}, pixel);
                    }
                }
            }
            return images;
        }
    }

    private static INDArray loadLabels(File file) throws IOException {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
            int magicNumber = dis.readInt();
            //if (magicNumber != 2049) {
            //    throw new IOException("Invalid magic number in label file!");
            //}
            int numLabels = dis.readInt();
            INDArray labels = Nd4j.zeros(numLabels, 10);
            for (int i = 0; i < numLabels; i++) {
                int label = dis.readUnsignedByte();
                labels.putScalar(new int[]{i, label}, 1.0);
            }
            return labels;
        }
    }

    /**
     * 创建一个包含单层输出层的神经网络模型,用于Softmax回归分类任务。
     *
     * @param seed       随机种子,用于确保模型初始化的可重复性。
     * @param iterations 训练时的迭代次数。
     * @param numRows    输入数据的行数。
     * @param numColumns 输入数据的列数。
     * @param outputNum  输出的类别数量。
     * @return 一个新的多层神经网络模型。
     */
    private static MultiLayerNetwork softMaxRegression(int seed, int iterations, int numRows, int numColumns, int outputNum) {
        // 配置神经网络的架构和训练参数
        MultiLayerConfiguration configuration = new NeuralNetConfiguration.Builder()
                .seed(seed) // 设置随机种子,确保每次运行的初始化一致
                .gradientNormalization(GradientNormalization.ClipElementWiseAbsoluteValue) // 梯度裁剪,防止梯度爆炸
                .gradientNormalizationThreshold(1.0) // 梯度裁剪的阈值
                .iterations(iterations) // 设置训练时的迭代次数
                .momentum(0.5) // 设置动量(用于加速训练)
                .momentumAfter(Collections.singletonMap(3, 0.9)) // 在第三个迭代后增加动量
                .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT) // 设置优化算法为共轭梯度
                .list(1) // 定义网络的层数为1层
                .layer(0, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) // 定义输出层
                        .activation("softmax") // 设置激活函数为softmax(用于多分类)
                        .nIn(numColumns * numRows) // 输入层的大小为 numRows * numColumns
                        .nOut(outputNum) // 输出层的大小为输出的类别数量
                        .build())
                .pretrain(true) // 启用预训练(无监督学习)
                .backprop(false) // 禁用反向传播(监督学习)
                .build(); // 构建配置对象
        // 根据配置创建多层神经网络模型
        MultiLayerNetwork model = new MultiLayerNetwork(configuration);
        // 返回创建的模型
        return model;
    }

    /**
     * 构建一个深度信念网络(Deep Belief Network, DBN)模型。
     *
     * @param seed       随机种子,用于初始化模型的权重和其他随机变量,确保结果的可重复性。
     * @param iterations 训练迭代的次数。
     * @param numRows    输入数据的行数。
     * @param numColumns 输入数据的列数。
     * @param outputNum  输出层的神经元数量,通常等于分类的类别数。
     * @return 返回一个配置好的 MultiLayerNetwork 模型。
     */
    private static MultiLayerNetwork deepBeliefNetwork(int seed, int iterations, int numRows, int numColumns, int outputNum) {
        // 配置神经网络的架构和参数
        MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
                // 设置随机种子,确保模型初始化的权重相同
                .seed(seed)
                // 梯度归一化,用于防止梯度爆炸或消失问题
                .gradientNormalization(GradientNormalization.ClipElementWiseAbsoluteValue)
                // 设置梯度归一化的阈值为1.0
                .gradientNormalizationThreshold(1.0)
                // 设置训练迭代的次数
                .iterations(iterations)
                // 设置初始动量,用于加速训练过程
                .momentum(0.5)
                // 设置动量的变化策略,在第3次迭代后动量增加到0.9
                .momentumAfter(Collections.singletonMap(3, 0.9))
                // 设置优化算法为共轭梯度法(Conjugate Gradient)
                .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT)
                // 设置网络的层数为4层
                .list(4)
                // 第一层:RBM,输入层到隐藏层
                .layer(0, new RBM.Builder()
                        // 输入层的神经元数量为 numRows * numColumns
                        .nIn(numRows * numColumns)
                        // 隐藏层的神经元数量为500
                        .nOut(500)
                        // 权重初始化方式为Xavier初始化
                        .weightInit(WeightInit.XAVIER)
                        // 损失函数为均方根误差交叉熵损失函数
                        .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                        // 可见单元类型为二元单元
                        .visibleUnit(RBM.VisibleUnit.BINARY)
                        // 隐藏单元类型为二元单元
                        .hiddenUnit(RBM.HiddenUnit.BINARY)
                        .build())
                // 第二层:RBM,隐藏层到隐藏层
                .layer(1, new RBM.Builder()
                        // 输入层的神经元数量为500
                        .nIn(500)
                        // 隐藏层的神经元数量为250
                        .nOut(250)
                        // 权重初始化方式为Xavier初始化
                        .weightInit(WeightInit.XAVIER)
                        // 损失函数为均方根误差交叉熵损失函数
                        .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                        // 可见单元类型为二元单元
                        .visibleUnit(RBM.VisibleUnit.BINARY)
                        // 隐藏单元类型为二元单元
                        .hiddenUnit(RBM.HiddenUnit.BINARY)
                        .build())
                // 第三层:RBM,隐藏层到隐藏层
                .layer(2, new RBM.Builder()
                        // 输入层的神经元数量为250
                        .nIn(250)
                        // 隐藏层的神经元数量为200
                        .nOut(200)
                        // 权重初始化方式为Xavier初始化
                        .weightInit(WeightInit.XAVIER)
                        // 损失函数为均方根误差交叉熵损失函数
                        .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                        // 可见单元类型为二元单元
                        .visibleUnit(RBM.VisibleUnit.BINARY)
                        // 隐藏单元类型为二元单元
                        .hiddenUnit(RBM.HiddenUnit.BINARY)
                        .build())
                // 第四层:输出层,用于分类任务
                .layer(3, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
                        // 激活函数为softmax,用于多分类问题
                        .activation("softmax")
                        // 输入层的神经元数量为200
                        .nIn(200)
                        // 输出层的神经元数量为outputNum
                        .nOut(outputNum)
                        .build())
                // 设置预训练为真,即使用RBM进行无监督预训练
                .pretrain(true)
                // 设置反向传播为假,即不进行反向传播微调
                .backprop(false)
                .build();
        //

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐