Classification: Breast cancer diagnosis
This example trains a binary classification model to distinguish benign and malignant breast-cell samples from nine numeric cytology measurements.
It is an educational machine-learning example based on the Wisconsin Breast Cancer data set. It is not a clinically validated diagnostic system.
Contents:
1. Identify the application type
The target diagnose has two values: 0 for benign and 1 for malignant. The task is therefore binary classification.
The current OpenNN example receives measurements already extracted from breast-cell samples; it does not process the original microscopy images.
2. Load and inspect the data
The maintained breast_cancer.csv contains 683 complete samples. It uses semicolon-separated fields, a header row, nine inputs and one binary target.
TabularDataset dataset(
"../data/breast_cancer/breast_cancer.csv",
";",
true,
false);
The constructor infers the final column as the target and creates random training, validation and testing subsets using the default 60%, 20% and 20% proportions.
clump_thicknesscell_size_uniformitycell_shape_uniformitymarginal_adhesionsingle_epithelial_cell_sizebare_nucleibland_chromatinnormal_nucleolimitoses
These input values use the 1–10 scale from the original data set. The OpenNN CSV converts the original class labels into the binary target used by the model.
3. Build the classification network
ClassificationNetwork creates a scaling layer, one hidden dense layer and a dense sigmoid output for a single binary target:
const Index neurons_number = 3;
ClassificationNetwork classification_network(
dataset.get_input_shape(),
{neurons_number},
dataset.get_target_shape());
The sigmoid output produces a score between zero and one. Values near one represent the malignant class encoded as 1 in this prepared data set.
4. Configure and train the model
The current example uses weighted squared error for the binary target, L1 regularization and stochastic gradient descent:
TrainingStrategy training_strategy(
&classification_network,
&dataset);
training_strategy.set_loss("WeightedSquaredError");
training_strategy.get_loss()->set_regularization("L1");
training_strategy.get_loss()->set_regularization_weight(0.001f);
training_strategy.set_optimization_algorithm(
"StochasticGradientDescent");
auto* sgd = dynamic_cast<StochasticGradientDescent*>(
training_strategy.get_optimization_algorithm());
if(!sgd)
throw runtime_error("SGD configuration failed.");
sgd->set_maximum_epochs(1000);
training_strategy.train();
Weighted squared error accounts for class imbalance when estimating the binary model.
5. Evaluate classification performance
Use the testing subset to calculate binary classification metrics, the confusion matrix and the receiver operating characteristic analysis:
TestingAnalysis testing_analysis(
&classification_network,
&dataset);
testing_analysis.print_binary_classification_tests();
const TestingAnalysis::RocAnalysis roc =
testing_analysis.perform_roc_analysis();
For this type of problem, inspect sensitivity, specificity, false negatives and the ROC curve rather than relying on accuracy alone.
6. Classify new samples and export the model
A new sample must contain all nine measurements in the original column order:
MatrixR inputs(1, 9);
inputs << 4.0f, 3.0f, 3.0f, 2.0f, 3.0f,
4.0f, 3.0f, 2.0f, 1.0f;
const MatrixR outputs =
classification_network.calculate_outputs(inputs);
const float malignant_score = outputs(0, 0);
const bool predicted_malignant = malignant_score >= 0.5f;
The threshold should be selected from validation data and the costs of false positives and false negatives; 0.5 is only a simple demonstration value.
classification_network.save("breast_cancer_model.json");
ModelExpression expression(&classification_network);
expression.save(
"breast_cancer_model.py",
ModelExpression::ProgrammingLanguage::Python);
7. Complete example
#include <iostream>
#include <stdexcept>
#include "opennn/core/configuration.h"
#include "opennn/dataset/tabular_dataset.h"
#include "opennn/models/models.h"
#include "opennn/neural_network/model_expression.h"
#include "opennn/testing_analysis/testing_analysis.h"
#include "opennn/training_strategy/stochastic_gradient_descent.h"
#include "opennn/training_strategy/training_strategy.h"
using namespace opennn;
int main()
{
try
{
Configuration::instance().set(Device::CPU, Type::FP32);
TabularDataset dataset(
"../data/breast_cancer/breast_cancer.csv",
";", true, false);
ClassificationNetwork classification_network(
dataset.get_input_shape(),
{3},
dataset.get_target_shape());
TrainingStrategy training_strategy(
&classification_network,
&dataset);
training_strategy.set_loss("WeightedSquaredError");
training_strategy.get_loss()->set_regularization("L1");
training_strategy.get_loss()->set_regularization_weight(0.001f);
training_strategy.set_optimization_algorithm(
"StochasticGradientDescent");
auto* sgd = dynamic_cast<StochasticGradientDescent*>(
training_strategy.get_optimization_algorithm());
if(!sgd)
throw runtime_error("SGD configuration failed.");
sgd->set_maximum_epochs(1000);
training_strategy.train();
TestingAnalysis testing_analysis(
&classification_network,
&dataset);
testing_analysis.print_binary_classification_tests();
const TestingAnalysis::RocAnalysis roc =
testing_analysis.perform_roc_analysis();
(void)roc;
MatrixR inputs(1, 9);
inputs << 4.0f, 3.0f, 3.0f, 2.0f, 3.0f,
4.0f, 3.0f, 2.0f, 1.0f;
const MatrixR outputs =
classification_network.calculate_outputs(inputs);
cout << "Malignant score: " << outputs(0, 0) << '\n';
classification_network.save("breast_cancer_model.json");
ModelExpression expression(&classification_network);
expression.save(
"breast_cancer_model.py",
ModelExpression::ProgrammingLanguage::Python);
return 0;
}
catch(const exception& error)
{
cerr << error.what() << '\n';
return 1;
}
}