java写卷积神经网络---卷积神经网络(CupCnn)的数据结构

前言

我在写CupCnn的时候,一个困扰我很久的问题,就是如何组织卷积神经网络的数据结构。尤其是卷积层和全连接层之间的衔接问题。卷积层至少需要四维的数据结构(batch+channel+height+width),而全连接层则只需一个二维的数据即可(batch+数据)。
CupCnn是我用java实现的一个卷积神经网络,它的源码可以从github下载:
点击下载CupCnn

卷积神经网络模型

这里写图片描述
这张图片是一个很典型的卷积神经网络模型,在CupCnn中识别手写数字的例子中使用的模型和它非常类似。从图中可以看出,卷积层和全连接层有很大的不同,全连接层用一维数组就可以表示(加上batch就是二维),而卷积层的数据则至少需要一个三维的数据结构(加上batch就是四维,batch是指训练的时候,一次送入神经网络的图片的数量。)。同一个神经网络,使用统一的数据接口会使编程更加容易,因此,我们必须使用统一的四维模型来装载一切的数据。

在CupCnn中,这个数据接口叫Blob,它的实现如下:

public class Blob implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private double[] data;
    private int numbers;
    private int channels;
    private int width;
    private int height;
    private int id;

    public Blob(int numbers,int channels,int height,int width){
        this.numbers = numbers;
        this.channels = channels;
        this.height = height;
        this.width = width;
        data = new double[getSize()];
    }


    //获取第n个number的第channels个通道的第height行的第width列的数
    public double getDataByParams(int numbers,int channels,int height,int width){
        return data[numbers*get3DSize()+channels*get2DSize()+height*getWidth()+width];
    }

    public int getIndexByParams(int numbers,int channels,int height,int width){
        return (numbers*get3DSize()+channels*get2DSize()+height*getWidth()+width);
    }

    public int getWidth(){
        return width;
    }

    public int getHeight(){
        return height;
    }

    public int getChannels(){
        return channels;
    }

    public int getNumbers(){
        return numbers;
    }

    public int get2DSize(){
        return width * height;
    }

    public int get3DSize(){
        return channels*width*height;
    }

    public int get4DSize(){
        return numbers*channels*width*height;
    }

    public int getSize(){
        return get4DSize();
    }

    public void setId(int id){
        this.id = id;
    }

    public int getId(){
        return id;
    }

    public double[] getData(){
        return data;
    }

    public void fillValue(double value){
        for(int i=0;i<data.length;i++){
            data[i] = value;
        }
    }

    public void cloneTo(Blob to){
        to.numbers = this.numbers;
        to.channels = this.channels;
        to.height = this.height;
        to.width = this.width;
        double[] toData = to.getData();
        for(int i=0;i<data.length;i++){
            toData[i] = this.data[i];
        }
    }

}

Blob的实现中,所有的数据都存储在一个一维的数组中,通过四个变量batch,channel,height,width来分别记录它各个维度的大小,此外,还导出了get(x)DSize()这样获取维度大小的接口。
卷积神经网络工作的工程中,数据的变化如下:
这里写图片描述
对于一个64*64大小的三维图片,经过一个卷积层+一个池化层后,图片的大小变为一半(卷积方式为same),但是通道却极大的增多了,注意,这里要强调的是通道的增加。在CupCnn的实现过程中,假如指定的batch为10,那么每个层,它的batch都是10,至始自终不会改变,卷积层主要会增加channel,池化层不会增加channel,但会使图像减小。
如果解释的还不清楚,再来看下面这张图:
这里写图片描述
注意图片中的连线,图中,第一个卷积层有4个卷积核,分别对原始图片做卷积,得到了4个28*28的图像,这里显然是使用了valide的方式进行的卷积,如果使用的是same的方式,卷积后大小仍为32*32。池化不会再增加通道,而是将每一个图像都变小了。至于卷积和池化的具体工作流程,这里不再展开。
注意:图中的数据没有添加batch的概念,加上batch后会更加复杂。但是只要高清了这幅图中的工作机制,相信理解加上batch后的卷积神经网络也就不是事了。

卷积层与全连接层的衔接

用一个一维的数组保存所有的数据除了速度上的优势之外,还有个很大的便利就是在卷积层和全连接层进行衔接的时候,由于数据本来就是存储在一维数组上的,我们完全可以忘记它是四维的数据结构,而把它当成一个一维的数据结构。这样就可以轻易的实现卷积到全连接的过度。

Blob的传递

在卷积神经网络中,这一层的输出便是下一层的输入。CupCnn中数据流动的就是Blob这个结构。为了方便下一个层获取上一个层的输出,CupCnn中的每一个层都有一个id,这个id是他在卷积神经网络中的位置,或者序号。比如第一个输入层它的id=0,第二个层它的id=1。此外,每一个层都有一个network的引用,因为所有的数据都由network统一管理,拥有network的引用,可以轻易的通过id索引获取任意一层的数据,包括输出和diff。
一开始就创建所有的需要的数据结构:

    public Network(){
        datas = new ArrayList<Blob>();
        diffs = new ArrayList<Blob>();
        layers = new ArrayList<Layer>();
    }

根据每一个层的配置参数创建层,每一层的输出Blob和残差Blob:

    public void prepare(){
        for(int i=0;i<layers.size();i++){
            BlobParams layerParams = layers.get(i).getLayerParames();
            assert (layerParams.getNumbers()>0 && layerParams.getChannels()>0 && layerParams.getHeight()>0 && layerParams.getWidth() >0):"prapare---layer params error";
            Blob data = new Blob(batch,layerParams.getChannels(),layerParams.getHeight(),layerParams.getWidth());
            datas.add(data);
            Blob diff = new Blob(data.getNumbers(),data.getChannels(),data.getHeight(),data.getWidth());
            diffs.add(diff);
            layers.get(i).setId(i);
            layers.get(i).prepare();
        }
    }

通过id获取指定层的数据:

@Override
    public void forward() {
        // TODO Auto-generated method stub
        Blob input = mNetwork.getDatas().get(id-1);
        Blob output = mNetwork.getDatas().get(id);
        double [] outputData = output.getData();
        double [] zData = z.getData();
        ...

写在最后

写卷积神经网络的时候,建议先写全连接层,因为写完全连接层就可以验证神经网络的正确性。这个时候,大家还是要注意数据结构一开始就用四维的,为以后和卷积层衔接做准备。如果您在写代码的过程中遇到什么困惑或者有什么兴奋的改进,都可以家下面的QQ群互相交流:

机器学习 QQ交流群:704153141

阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
是的,Java 也可以用来实现卷神经网络。在 Java 中,常用的深度学习框架包括 Deeplearning4j、TensorFlow Java 和 Keras Java 等。这里以 Deeplearning4j 为例,演示如何使用 Java 实现基于卷神经网络的图像分类。 首先,我们需要定义一个数据集,这里使用 MNIST 手数字数据集: ``` // 加载 MNIST 手数字数据集 DataSetIterator trainData = new MnistDataSetIterator(batchSize, true, 12345); DataSetIterator testData = new MnistDataSetIterator(batchSize, false, 12345); ``` 接下来,我们定义一个卷神经网络模型: ``` // 定义卷神经网络模型 MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder() .seed(seed) .weightInit(WeightInit.XAVIER) .updater(new Nesterovs(0.005, 0.9)) .list() .layer(new ConvolutionLayer.Builder(5, 5) .nIn(1) .stride(1, 1) .nOut(20) .activation(Activation.IDENTITY) .build()) .layer(new SubsamplingLayer.Builder(PoolingType.MAX) .kernelSize(2, 2) .stride(2, 2) .build()) .layer(new ConvolutionLayer.Builder(5, 5) .stride(1, 1) .nOut(50) .activation(Activation.IDENTITY) .build()) .layer(new SubsamplingLayer.Builder(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(28, 28, 1)) .build(); ``` 这个模型包含了两个卷层、两个池化层和一个全连接层,最终输出 10 个类别的分类结果。 接着,我们使用模型对数据集进行训练和测试: ``` // 定义模型和训练参数 MultiLayerNetwork model = new MultiLayerNetwork(conf); model.init(); model.setListeners(new ScoreIterationListener(10)); // 训练模型 for (int i = 0; i < nEpochs; i++) { model.fit(trainData); trainData.reset(); } // 在测试集上评估模型 Evaluation eval = new Evaluation(outputNum); while (testData.hasNext()) { DataSet ds = testData.next(); INDArray output = model.output(ds.getFeatures()); eval.eval(ds.getLabels(), output); } System.out.println(eval.stats()); ``` 这个代码演示了如何使用 Deeplearning4j 框架在 Java 中实现卷神经网络模型,以及如何使用 MNIST 手数字数据集进行训练和测试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光玻璃杯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值