<jupyter_start><jupyter_text>Densely Connected Networks (DenseNet)ResNet significantly changed the view of how to parametrize the functions in deep networks. DenseNet is to some extent the logical extension of this. To understand how to arrive at it, let us take a small detour to theory. Recall the Taylor expansion for functions. For scalars it can be written as$$f(x) = f(0) + f'(x) x + \frac{1}{2} f''(x) x^2 + \frac{1}{6} f'''(x) x^3 + o(x^3).$$ Function DecompositionThe key point is that it decomposes the function into increasingly higher order terms. In a similar vein, ResNet decomposes functions into$$f(\mathbf{x}) = \mathbf{x} + g(\mathbf{x}).$$That is, ResNet decomposes $f$ into a simple linear term and a more complexnonlinear one. What if we want to go beyond two terms? A solution was proposedby :cite:`Huang.Liu.Van-Der-Maaten.ea.2017` in the form ofDenseNet, an architecture that reported record performance on the ImageNetdataset.![The main difference between ResNet (left) and DenseNet (right) in cross-layer connections: use of addition and use of concatenation. ](https://raw.githubusercontent.com/d2l-ai/d2l-en/master/img/densenet-block.svg):label:`fig_densenet_block`As shown in :numref:`fig_densenet_block`, the key difference between ResNet and DenseNet is that in the latter case outputs are *concatenated* rather than added. As a result we perform a mapping from $\mathbf{x}$ to its values after applying an increasingly complex sequence of functions.$$\mathbf{x} \to \left[\mathbf{x}, f_1(\mathbf{x}), f_2(\mathbf{x}, f_1(\mathbf{x})), f_3(\mathbf{x}, f_1(\mathbf{x}), f_2(\mathbf{x}, f_1(\mathbf{x})), \ldots\right].$$In the end, all these functions are combined in an MLP to reduce the number of features again. In terms of implementation this is quite simple---rather than adding terms, we concatenate them. The name DenseNet arises from the fact that the dependency graph between variables becomes quite dense. The last layer of such a chain is densely connected to all previous layers. The main components that compose a DenseNet are dense blocks and transition layers. The former defines how the inputs and outputs are concatenated, while the latter controls the number of channels so that it is not too large. The dense connections are shown in :numref:`fig_densenet`.![Dense connections in DenseNet](https://raw.githubusercontent.com/d2l-ai/d2l-en/master/img/densenet.svg):label:`fig_densenet` Dense BlocksDenseNet uses the modified "batch normalization, activation, and convolution"architecture of ResNet (see the exercise in :numref:`sec_resnet`).First, we implement this architecture in the`conv_block` function.<jupyter_code>%mavenRepo snapshots https://oss.sonatype.org/content/repositories/snapshots/

%maven ai.djl:api:0.7.0-SNAPSHOT
%maven ai.djl:model-zoo:0.7.0-SNAPSHOT
%maven ai.djl:basicdataset:0.7.0-SNAPSHOT
%maven org.slf4j:slf4j-api:1.7.26
%maven org.slf4j:slf4j-simple:1.7.26
    
%maven ai.djl.mxnet:mxnet-engine:0.7.0-SNAPSHOT
%maven ai.djl.mxnet:mxnet-native-auto:1.7.0-a
%%loadFromPOM
<dependency>
    <groupId>tech.tablesaw</groupId>
    <artifactId>tablesaw-jsplot</artifactId>
    <version>0.30.4</version>
</dependency>
%load ../utils/plot-utils.ipynb
%load ../utils/Training.java
%load ../utils/Accumulator.java
import ai.djl.Model;
import ai.djl.basicdataset.FashionMnist;
import ai.djl.metric.Metrics;
import ai.djl.modality.cv.transform.Resize;
import ai.djl.modality.cv.transform.ToTensor;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDArrays;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.ndarray.types.DataType;
import ai.djl.ndarray.types.Shape;
import ai.djl.nn.*;
import ai.djl.nn.convolutional.Conv2d;
import ai.djl.nn.core.Linear;
import ai.djl.nn.norm.BatchNorm;
import ai.djl.nn.pooling.Pool;
import ai.djl.training.DefaultTrainingConfig;
import ai.djl.training.EasyTrain;
import ai.djl.training.ParameterStore;
import ai.djl.training.Trainer;
import ai.djl.training.dataset.ArrayDataset;
import ai.djl.training.dataset.Dataset;
import ai.djl.training.evaluator.Accuracy;
import ai.djl.training.initializer.XavierInitializer;
import ai.djl.training.listener.TrainingListener;
import ai.djl.training.loss.Loss;
import ai.djl.training.optimizer.Optimizer;
import ai.djl.training.tracker.Tracker;
import ai.djl.translate.Pipeline;
import ai.djl.util.PairList;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import tech.tablesaw.api.*;
import tech.tablesaw.plotly.api.*;
import tech.tablesaw.plotly.components.*;
import tech.tablesaw.plotly.Plot;
import tech.tablesaw.plotly.components.Figure;
import org.apache.commons.lang3.ArrayUtils;
public SequentialBlock convBlock(int numChannels) {

        SequentialBlock block = new SequentialBlock()
                .add(BatchNorm.builder().build())
                .add(Activation::relu)
                .add(Conv2d.builder()
                        .setFilters(numChannels)
                        .setKernelShape(new Shape(3, 3))
                        .optPadding(new Shape(1, 1))
                        .optStride(new Shape(1, 1))
                        .build()
                );

        return block;
}<jupyter_output><empty_output><jupyter_text>A dense block consists of multiple `convBlock` units, each using the same number of output channels. In the forward computation, however, we concatenate the input and output of each block on the channel dimension.<jupyter_code>class DenseBlock extends AbstractBlock {

    private static final byte VERSION = 2;
    public SequentialBlock net = new SequentialBlock();

    public DenseBlock(int numConvs, int numChannels) {
        super(VERSION);
        for (int i = 0; i < numConvs; i++) {
            this.net.add(
                    addChildBlock("denseBlock" + i, convBlock(numChannels))
            );
        }
    }

    @Override
    public String toString() {
        return "DenseBlock()";
    }

    @Override
    public NDList forward(ParameterStore parameterStore, NDList X, boolean training, PairList<String, Object> pairList) {

        NDArray Y;
        for (Block block : this.net.getChildren().values()) {
            Y = block.forward(parameterStore, X, training).singletonOrThrow();
            X = new NDList(NDArrays.concat(new NDList(X.singletonOrThrow(), Y), 1));
        }
        return X;
    }

    @Override
    public Shape[] getOutputShapes(NDManager ndManager, Shape[] inputs) {
        Shape[] shapesX = inputs;
        for (Block block : this.net.getChildren().values()) {
            Shape[] shapesY = block.getOutputShapes(ndManager, shapesX);
            shapesX[0] = new Shape(
                    shapesX[0].get(0),
                    shapesY[0].get(1) + shapesX[0].get(1),
                    shapesX[0].get(2),
                    shapesX[0].get(3)
            );
        }
        return shapesX;
    }

    @Override
    public void initializeChildBlocks(NDManager manager, DataType dataType, Shape... inputShapes) {
        Shape shapesX = inputShapes[0];

        for (Block block : this.net.getChildren().values()) {
            Shape[] shapesY = block.initialize(manager, DataType.FLOAT32, shapesX);
            shapesX = new Shape(
                    shapesX.get(0),
                    shapesY[0].get(1) + shapesX.get(1),
                    shapesX.get(2),
                    shapesX.get(3)
            );
        }
    }
}<jupyter_output><empty_output><jupyter_text>In the following example, we define a convolution block (`DenseBlock`) with two blocks of 10 output channels. When using an input with 3 channels, we will get an output with the $3+2\times 10=23$ channels. The number of convolution block channels controls the increase in the number of output channels relative to the number of input channels. This is also referred to as the growth rate.<jupyter_code>NDManager manager = NDManager.newBaseManager();
SequentialBlock block = new SequentialBlock()
    .add(new DenseBlock(2, 10));

NDArray X = manager.randomUniform(0f, 1.0f, new Shape(4, 3, 8, 8));

block.setInitializer(new XavierInitializer());
block.initialize(manager, DataType.FLOAT32, X.getShape());

ParameterStore parameterStore = new ParameterStore(manager, true);

Shape currentShape = X.getShape();

for (int i = 0; i < block.getChildren().size(); i++) {

        Shape[] newShape = block.getChildren().get(i).getValue().getOutputShapes(manager, new Shape[]{X.getShape()});
        currentShape = newShape[0];    
}

currentShape<jupyter_output><empty_output><jupyter_text>Transition LayersSince each dense block will increase the number of channels, adding too many of them will lead to an excessively complex model. A transition layer is used to control the complexity of the model. It reduces the number of channels by using the $1\times 1$ convolutional layer and halves the height and width of the average pooling layer with a stride of 2, further reducing the complexity of the model.<jupyter_code>public SequentialBlock transitionBlock(int numChannels) {
        SequentialBlock blk = new SequentialBlock()
                .add(BatchNorm.builder().build())
                .add(Activation::relu)
                .add(
                        Conv2d.builder()
                                .setFilters(numChannels)
                                .setKernelShape(new Shape(1, 1))
                                .optStride(new Shape(1, 1))
                                .build()
                )
                .add(Pool.avgPool2dBlock(new Shape(2, 2), new Shape(2, 2)));

        return blk;
}<jupyter_output><empty_output><jupyter_text>Apply a transition layer with 10 channels to the output of the dense block in the previous example.  This reduces the number of output channels to 10, and halves the height and width.<jupyter_code>block = transitionBlock(10);

block.setInitializer(new XavierInitializer());
block.initialize(manager, DataType.FLOAT32, currentShape);

for (int i = 0; i < block.getChildren().size(); i++) {

    Shape[] newShape = block.getChildren().get(i).getValue().getOutputShapes(manager, new Shape[]{currentShape});
    currentShape = newShape[0];
}

currentShape<jupyter_output><empty_output><jupyter_text>DenseNet ModelNext, we will construct a DenseNet model. DenseNet first uses the same single convolutional layer and maximum pooling layer as ResNet.<jupyter_code>SequentialBlock net = new SequentialBlock()
    .add(Conv2d.builder()
            .setFilters(64)
            .setKernelShape(new Shape(7, 7))
            .optStride(new Shape(2, 2))
            .optPadding(new Shape(3, 3))
            .build())
    .add(BatchNorm.builder().build())
    .add(Activation::relu)
    .add(Pool.maxPool2dBlock(new Shape(3, 3), new Shape(2, 2), new Shape(1, 1)));<jupyter_output><empty_output><jupyter_text>Then, similar to the four residual blocks that ResNet uses, DenseNet uses four dense blocks. Similar to ResNet, we can set the number of convolutional layers used in each dense block. Here, we set it to 4, consistent with the ResNet-18 in the previous section. Furthermore, we set the number of channels (i.e., growth rate) for the convolutional layers in the dense block to 32, so 128 channels will be added to each dense block.In ResNet, the height and width are reduced between each module by a residual block with a stride of 2. Here, we use the transition layer to halve the height and width and halve the number of channels.<jupyter_code>int numChannels = 64;
int growthRate = 32;

int[] numConvsInDenseBlocks = new int[]{4, 4, 4, 4};

for (int index = 0; index < numConvsInDenseBlocks.length; index++) {

    int numConvs = numConvsInDenseBlocks[index];
    net.add(new DenseBlock(numConvs, growthRate));

    numChannels += (numConvs * growthRate);

    if (index != (numConvsInDenseBlocks.length - 1)) {
        numChannels = (numChannels / 2);
        net.add(transitionBlock(numChannels));
    }
}<jupyter_output><empty_output><jupyter_text>Similar to ResNet, a global pooling layer and fully connected layer are connected at the end to produce the output.<jupyter_code>net
    .add(BatchNorm.builder().build())
    .add(Activation::relu)
    .add(Pool.globalAvgPool2dBlock())
    .add(Linear.builder().setUnits(10).build());<jupyter_output><empty_output><jupyter_text>Data Acquisition and TrainingSince we are using a deeper network here, in this section, we will reduce the input height and width from 224 to 96 to simplify the computation.<jupyter_code>int batchSize = 256;
float lr = 0.1f;
int numEpochs = 10;

double[] trainLoss;
double[] testAccuracy;
double[] epochCount;
double[] trainAccuracy;

epochCount = new double[numEpochs];

for (int i = 0; i < epochCount.length; i++) {
    epochCount[i] = (i + 1);
}

FashionMnist trainIter =
                FashionMnist.builder()
                        .optPipeline(new Pipeline().add(new Resize(96)).add(new ToTensor()))
                        .optUsage(Dataset.Usage.TRAIN)
                        .setSampling(batchSize, true)
                        .build();

FashionMnist testIter =
                FashionMnist.builder()
                        .optPipeline(new Pipeline().add(new Resize(96)).add(new ToTensor()))
                        .optUsage(Dataset.Usage.TEST)
                        .setSampling(batchSize, true)
                        .build();

trainIter.prepare();
testIter.prepare();

Model model = Model.newInstance("cnn");
model.setBlock(net);

Loss loss = Loss.softmaxCrossEntropyLoss();

Tracker lrt = Tracker.fixed(lr);
Optimizer sgd = Optimizer.sgd().setLearningRateTracker(lrt).build();

DefaultTrainingConfig config = new DefaultTrainingConfig(loss).optOptimizer(sgd) // Optimizer (loss function)
                .addEvaluator(new Accuracy()) // Model Accuracy
                .addTrainingListeners(TrainingListener.Defaults.logging()); // Logging

Trainer trainer = model.newTrainer(config);
trainer.initialize(new Shape(1, 1, 96, 96));

Map<String, double[]> evaluatorMetrics = new HashMap<>();
double avgTrainTimePerEpoch = 0;
Training.trainingChapter6(trainIter, testIter, numEpochs, trainer, evaluatorMetrics, avgTrainTimePerEpoch);
trainLoss = evaluatorMetrics.get("train_epoch_SoftmaxCrossEntropyLoss");
trainAccuracy = evaluatorMetrics.get("train_epoch_Accuracy");
testAccuracy = evaluatorMetrics.get("validate_epoch_Accuracy");

System.out.printf("loss %.3f,", trainLoss[numEpochs - 1]);
System.out.printf(" train acc %.3f,", trainAccuracy[numEpochs - 1]);
System.out.printf(" test acc %.3f\n", testAccuracy[numEpochs - 1]);
System.out.printf("%.1f examples/sec", trainIter.size() / (avgTrainTimePerEpoch / Math.pow(10, 9)));
System.out.println();<jupyter_output><empty_output><jupyter_text>![Contour Gradient Descent.](https://d2l-java-resources.s3.amazonaws.com/img/chapter_convolution-modern-cnn-denseNet.png)<jupyter_code>String[] lossLabel = new String[trainLoss.length + testAccuracy.length + trainAccuracy.length];

Arrays.fill(lossLabel, 0, trainLoss.length, "train loss");
Arrays.fill(lossLabel, trainAccuracy.length, trainLoss.length + trainAccuracy.length, "train acc");
Arrays.fill(lossLabel, trainLoss.length + trainAccuracy.length,
                trainLoss.length + testAccuracy.length + trainAccuracy.length, "test acc");

Table data = Table.create("Data").addColumns(
            DoubleColumn.create("epoch", ArrayUtils.addAll(epochCount, ArrayUtils.addAll(epochCount, epochCount))),
            DoubleColumn.create("metrics", ArrayUtils.addAll(trainLoss, ArrayUtils.addAll(trainAccuracy, testAccuracy))),
            StringColumn.create("lossLabel", lossLabel)
);

render(LinePlot.create("", data, "epoch", "metrics", "lossLabel"),"text/html");<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11279054998094754, 'fraction_numerical': 0.013336720436936365, 'mean_word_length': 3.5976642335766424, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 52, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '154983', 'license': 'MIT-0', 'n_tokens_mistral': 4991, 'n_tokens_neox': 4682, 'n_words': 1308, 'path': 'chapter_convolutional-modern/densenet.ipynb', 'repo_name': 'dltech-xyz/d2l-java', 'size': '15746'}