This article compares the implementation of a classification model for the classic Iris flower dataset using two different approaches: PyTorch, the leading research library in Python, and OpenNN, a high-performance neural networks library written in C++.

While PyTorch provides great flexibility for prototyping, OpenNN offers a streamlined API for deploying neural networks in production environments where C++ performance and integration are critical. We will compare the entire workflow, from data loading to final model deployment.

 

1. Introduction

The Iris flower data set is a multivariate data set that introduces the problem of pattern recognition. The goal is to classify iris flowers into three species (setosa, versicolor, and virginica) based on four physical measurements:

  • Sepal length
  • Sepal width
  • Petal length
  • Petal width

We will build, train, and test a feed-forward neural network to solve this classification task, observing the differences in API design and philosophy between the two libraries.

 

2. Data Set

One of the most critical parts of machine learning is data preparation. In Python, this typically involves a combination of libraries: Pandas for loading, Scikit-Learn for splitting and scaling, and PyTorch for tensor conversion.

PyTorch

You are responsible for every step. This multi-library approach requires managing dependencies between different objects.

# Python: The multi-library approach

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import torch

# 1. Load with Pandas
df = pd.read_csv("irisflowers.csv")
X = df.iloc[:, :-1].values
y = pd.factorize(df.iloc[:, -1])[0]

# 2. Split with Scikit-Learn
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 3. Scale with Scikit-Learn
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)

# 4. Convert to PyTorch Tensors
X_train = torch.tensor(X_train).float()
# ... (repeat for all sets)

 

OpenNN

OpenNN’s Dataset class is designed to handle the entire lifecycle of data management. By simply loading the file, OpenNN automatically handles scaling, splitting, and data structuring internally.

// C++: The unified approach

Dataset dataset("irisflowers.csv", ",", true);

// OpenNN automatically detects inputs and targets,
// calculates statistics, scales variables, and splits the data.

3. Model Construction

To ensure a fair comparison, we will implement the same architecture in both frameworks: a hidden dense layer with 16 neurons and tanh activation, followed by a Softmax output layer.

 

PyTorch

You must manually define each layer and know the technical details of how the final layer and loss function should interact for numerical stability. The standard practice in PyTorch is to have the model output raw logits and use CrossEntropyLoss, which applies Softmax internally.

# Python: Manually defining the architecture to produce logits

model = nn.Sequential(
    nn.Linear(4, 16),
    nn.Tanh(),
    nn.Linear(16, 3)
)

# This loss function expects logits and applies Softmax internally
loss_fn = nn.CrossEntropyLoss()

 

OpenNN

OpenNN’s ClassificationNetwork automatically builds a robust, production-ready architecture. By default, it uses tanh activation and adds the final Softmax layer, encapsulating best practices in a single line of code.

// C++: Professional architecture by default

const Index inputs_number  = dataset.get_variables_number("Input");
const Index targets_number = dataset.get_variables_number("Target");
const Index neurons_number = 16;

// Creates: Scaling -> Perceptron(16, Tanh) -> Probabilistic(Softmax)
ClassificationNetwork classification_network(
    {inputs_number},
    {neurons_number},
    {targets_number}
);

Beyond just loading data, the Dataset class allows for immediate inspection of the data quality. It can automatically calculate descriptive statistics (mean, standard deviation) and basic correlations, giving the engineer instant insight into the problem without writing extra analysis scripts.

 

4. Training Strategy

The optimizer is critical for model performance. For datasets of this size (small to medium), second-order algorithms (Quasi-Newton) are often mathematically superior to standard stochastic gradient descent, offering faster convergence and greater stability.

OpenNN selects this optimal method by default. To ensure a fair comparison based on the best possible algorithm for this problem, we will use its equivalent in PyTorch, L-BFGS, which approximates second-order curvature information and is particularly effective in low-dimensional, full-batch optimization problems like Iris Flowers.

 

PyTorch

Although L-BFGS is not the most commonly used optimizer in PyTorch workflows, it is included here to provide a fair comparison with OpenNN’s default Quasi-Newton training strategy, which is particularly well suited for small, full-batch problems like Iris.

# Python: L-BFGS requires a complex closure structure

opt = torch.optim.LBFGS(model.parameters(), lr=1.0)

def closure():
    opt.zero_grad()
    loss = loss_fn(model(X_train), y_train)
    loss.backward()
    return loss

for epoch in range(15):
    opt.step(closure)

OpenNN

The TrainingStrategy class automatically selects the best optimizer for the job. For the Iris dataset, it defaults to the Quasi-Newton method. The user code remains minimal and declarative, regardless of the underlying algorithm’s complexity.

// C++: The complexity is abstracted away

TrainingStrategy training_strategy(&classification_network, &dataset);
training_strategy.train();

Furthermore, OpenNN generates comprehensive training reports. While PyTorch requires manual logging and integration with external plotting libraries (like Matplotlib or TensorBoard) to visualize convergence, OpenNN provides built-in tools to monitor the loss history and selection errors automatically.

 

5. Testing Analysis

After training, both models are evaluated on their respective test sets to verify convergence and generalization.

 

PyTorch

The PyTorch model successfully converges, achieving excellent performance with a generic accuracy over 96% on the test set.

PyTorch confusion matrix:
tensor([[10,  0,  0],
        [ 0,  9,  1],
        [ 0,  0, 10]])

OpenNN

The OpenNN model also achieves excellent results, reaching perfect or near-perfect accuracy on the test set. This confirms that OpenNN’s implementation of the Quasi-Newton method is numerically stable and matches the predictive power of the leading research libraries, while handling the data pipeline internally.

OpenNN confusion matrix:
11  0  0  11
 0 10  0  10
 0  0  9   9
11 10  9  30

The evaluation in OpenNN goes beyond a simple confusion matrix. The library is capable of automatically generating advanced engineering metrics such as ROC curves and Lift charts. To replicate this level of reporting in the Python ecosystem, one would need to manually calculate these metrics using Scikit-Learn and plot them, adding yet another layer of dependency.

 

6. Deployment

Finally, we use the trained model to predict the class of a new, unseen flower. Both frameworks expect inputs in a «batch» format, even for a single prediction.

 

PyTorch

For deployment, you must remember to use the scaler object that was fitted on the training data to preprocess any new input.

# Python: Manual scaling is required before prediction

new_sample_raw = [[5.1, 3.5, 1.4, 0.2]]  # Batch of 1
new_sample_scaled = scaler.transform(new_sample_raw)

with torch.no_grad():
    input_tensor = torch.tensor(new_sample_scaled).float()
    predicted_class = model(input_tensor).argmax(1).item()

print("Predicted class:", predicted_class)

OpenNN

In OpenNN, the scaling layer is part of the model itself. The calculate_outputs method handles all necessary preprocessing automatically. The user only needs to provide the raw data in a Tensor object. This encapsulation eliminates the risk of «training-serving skew,» a common production issue where preprocessing logic in deployment subtly differs from training, leading to silent model failures.

// C++: Scaling is handled automatically within the model

Tensor<double, 2> input_tensor(1, 4);  // Batch of 1
input_tensor.setValues({{5.1, 3.5, 1.4, 0.2}});

Tensor<double, 2> output_tensor =
    classification_network.calculate_outputs<2, 2>(input_tensor);

Tensor<double, 1> single_output = output_tensor.chip(0, 0);

// Find the index of the highest probability
Index predicted_class = 0;
for (Index i = 1; i < single_output.size(); ++i)
{
    if (single_output(i) > single_output(predicted_class))
        predicted_class = i;
}

cout << "Predicted class: " << predicted_class << endl;

7. Conclusions

This comparison highlights two different philosophies:

  • PyTorch provides a flexible, low-level toolkit that requires significant expertise to assemble correctly. The user is responsible for data scaling, choosing the right combination of output layers and loss functions, and writing complex training loops for advanced optimizers.
  • OpenNN offers a high-level, integrated solution that encapsulates best practices into simple, powerful classes. It automates data preparation, model architecture, and solver selection, leading to more readable code, fewer errors, and excellent out-of-the-box results.

For engineers and developers who need to build and deploy reliable, high-performance C++ neural networks, OpenNN provides the most direct and robust path from data to deployment.