This walkthrough trains a CNN to distinguish hot-dog images from unrelated images.
Problem: Classify an image as hot_dog or negative.
Dataset: 467 labeled images: 239 hot-dog images and 228 negative images.
| ML task | Binary image classification |
| Input | 64 × 64 × 3 RGB image |
| Target | hot_dog or negative |
| Model | Convolution → Max Pooling → Fully Connected → Sigmoid |
| Preprocessing | Resize to 64 × 64 and scale RGB values while loading |
| Default training | 10 CPU epochs |
| Saved model | models/hotdog-classifier.dnet |
| Related concepts | Machine Learning Basics, Neural Networks, Backpropagation, Deep Netts API, Model Development |
int width = 64, height = 64;
int epochs = Integer.getInteger("deepnetts.epochs", 10);
String dataset = "src/main/resources/datasets/hotdog-dataset";
System.setProperty("java.awt.headless", "true");
DeepNetts.getInstance().setUseCuda(false);
ImageSet images = ImageData.load(dataset, "index.txt", width, height, false, false);
The index assigns each file its hot_dog or negative label. The loader validates files, resizes them, and creates RGB tensors.
System.out.println("Images: " + images.size());
System.out.println("Images by class: " + images.countByClasses());
Output:
Images: 467
Number of images by label/class
negative : 228
hot_dog : 239
Images by class: {negative=228, hot_dog=239}
The output confirms that all 467 indexed images were loaded. The two classes are close to balanced before the split.
images.shuffle(42);
ImageSet[] split = images.split(0.8, 0.2);
ImageSet trainingSet = split[0];
ImageSet testSet = split[1];
Output:
Splitting data set: [0.8, 0.2]
The fixed seed makes the 80/20 split reproducible.
ConvolutionalNetwork network = ConvolutionalNetwork.builder()
.addInputLayer(width, height, 3)
.addConvolutionalLayer(6, Filters.ofSize(3))
.addMaxPoolingLayer(2, 2)
.addFullyConnectedLayer(16)
.addOutputLayer(1, ActivationType.SIGMOID)
.hiddenActivationFunction(ActivationType.LEAKY_RELU)
.lossFunction(LossType.CROSS_ENTROPY)
.randomSeed(42).build();
The convolution and pooling layers learn local image patterns. The Sigmoid
output represents the probability that an image belongs to the hot_dog
class.
network.getTrainer().setStopEpochs(epochs).setLearningRate(0.001f)
.setOptimizer(OptimizerType.ADAM).setBatchMode(true)
.setBatchSize(16).setShuffle(true);
network.train(trainingSet);
Output:
TRAINING NEURAL NETWORK -------------------------------------------------------------- Initial Train Error:0.6913408 Epoch:1, Time:4144ms, TrainError:0.90619606, TrainErrorChange:0.21485525, TrainAccuracy:0.5227882 TRAINING COMPLETED Total Training Time: 4907ms --------------------------------------------------------------
Smoke-test output
This output was generated with -Ddeepnetts.epochs=1 for faster training.
The example uses 10 epochs by default. Pass the override as a JVM system
property through your IDE's run configuration or the Maven command.
This run uses one epoch as a smoke test. The training error increased and training accuracy stayed near chance level, so the model has not converged.
System.out.println(network.test(testSet));
Output:
Class: hot_dog Total items: 93 True positive:50.0 Number of examples correctly classified as positive True negative:3.0 Number of examples correctly classified as negative False positive:40.0 Number of examples incorrectly classified as positive False negative:0.0 Number of examples incorrectly classified as negative Accuracy (ACC): 0.56989247 How often is a classifier correct in total (percent of correct classifications) Precision (PPV): 0.5555556 How often is a classifier correct when it gives positive prediction Recall: 1.0 When it is actually positive class, how often does it give positive prediction F1 Score: 0.71428573 Harmonic average (balance) of precision and recall Specificity (TNR): 0.069767445 When it is actually negative class, how often does it give negative prediction Fall-out (FPR): 0.9302326 How often it gives false positive prediction in total (percent of false positive predictions) False negative rate (FNR): 0.0 How often it gives false negative prediction in total (percent of false negative predictions)
The one-epoch model found every held-out hot-dog image, which gives it perfect recall, but incorrectly labeled 40 negative images as hot dogs. The low specificity and high fall-out show why recall alone is not enough to judge this classifier.
Files.createDirectories(Path.of("models"));
network.save("models/hotdog-classifier.dnet");
The saved model is the reusable result of training.
ExampleImage image = testSet.get(0);
float probability = network.predict(image.getInput()).getValues()[0];
String predicted = probability >= 0.5f ? "hot_dog" : "negative";
System.out.println("Actual: " + image.getLabel());
System.out.println("Predicted: " + predicted);
System.out.printf("Hot-dog probability: %.4f%n", probability);
Output:
Actual: hot_dog Predicted: hot_dog Hot-dog probability: 0.6097
The probability is above the 0.5 threshold, so this held-out image is
classified as hot_dog. This prediction is correct, although the evaluation
above shows that the one-epoch model is not yet reliable.
network.getThreadPool().shutdown();
This safely stops the network's background worker threads.
Pass these options to the Java process through your IDE's application run configuration or the command line:
--add-modules=jdk.incubator.vector -Ddeepnetts.epochs=1
Output:
WARNING: Using incubator modules: jdk.incubator.vector
One epoch is a complete functional smoke test, not a final accuracy run. This run completed in about 12 seconds. Timings, metrics, and probabilities can differ between machines and runs; remove the epoch override to use the default 10 epochs.
The model distinguishes images labeled hot_dog from the negative class.
Only two outcomes are required, so one Sigmoid value can represent the probability of the positive class.
Was this helpful?
Thank you!