PYTHON mnist.py : import os import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader import time # ==================== Configuration CPU ==================== print("Configuration\n") DEVICE = torch.device("cpu") BATCH_SIZE = 256 # Tu peux monter à 512 si tu as assez de RAM EPOCHS = 5 LEARNING_RATE = 1e-4 # Optionnel : activer torch.compile (PyTorch 2.0+) pour booster les perfs CPU USE_COMPILE = True # ==================== Modèle CNN ==================== print("Modèle\n") class Net(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=5) self.conv2 = nn.Conv2d(32, 64, kernel_size=5) self.fc1 = nn.Linear(1024, 1024) self.fc2 = nn.Linear(1024, 10) self.dropout = nn.Dropout(0.5) def forward(self, x): x = x.view(-1, 1, 28, 28) x = torch.relu(self.conv1(x)) x = nn.functional.max_pool2d(x, 2) x = torch.relu(self.conv2(x)) x = nn.functional.max_pool2d(x, 2) x = x.view(-1, 1024) x = torch.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x print("Définitions des fonctions\n") # ==================== Données ==================== def get_mnist_loaders(): transform = transforms.Compose([transforms.ToTensor()]) train_set = torchvision.datasets.MNIST( root='./data', train=True, download=True, transform=transform ) test_set = torchvision.datasets.MNIST( root='./data', train=False, download=True, transform=transform ) train_loader = DataLoader( train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=4, # Ajuste selon ton CPU (0 à 8) pin_memory=False # Désactivé car pas de GPU ) test_loader = DataLoader( test_set, batch_size=512, shuffle=False, num_workers=4, pin_memory=False ) return train_loader, test_loader # ==================== Évaluation ==================== @torch.no_grad() def evaluate(model, test_loader): model.eval() correct = 0 total = 0 for images, labels in test_loader: images, labels = images.to(DEVICE), labels.to(DEVICE) outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() return 100. * correct / total # ==================== Entraînement ==================== def main(): print(f"Device : {DEVICE}") print(f"torch.compile activé : {USE_COMPILE}\n") train_loader, test_loader = get_mnist_loaders() print("Modèle\n"); model = Net().to(DEVICE) if USE_COMPILE and hasattr(torch, 'compile'): model = torch.compile(model, mode="reduce-overhead") # Bon compromis CPU print("Optimiseur\n") optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE) start_time = time.time() print("Boucle d'entrainement\n") for epoch in range(1, EPOCHS + 1): model.train() epoch_start = time.time() for images, labels in train_loader: images, labels = images.to(DEVICE), labels.to(DEVICE) optimizer.zero_grad() outputs = model(images) loss = nn.functional.cross_entropy(outputs, labels) loss.backward() optimizer.step() accuracy = evaluate(model, test_loader) epoch_time = time.time() - epoch_start print(f"Epoch: {epoch:2d} | Accuracy: {accuracy:5.2f}% | Temps: {epoch_time:5.1f}s") total_time = time.time() - start_time print(f"\nEntraînement terminé en {total_time/60:.1f} minutes sur CPU.") if __name__ == "__main__": main() Exécution : root@puppypc8976:~/mnist# source ~/venv/bin/activate (venv) root@puppypc8976:~/mnist# time python mnist.py Configuration Modèle Définitions des fonctions Device : cpu torch.compile activé : True Modèle Optimiseur Boucle d'entrainement Epoch: 1 | Accuracy: 92.58% | Temps: 167.9s Epoch: 2 | Accuracy: 95.58% | Temps: 57.5s Epoch: 3 | Accuracy: 97.01% | Temps: 58.5s Epoch: 4 | Accuracy: 97.67% | Temps: 57.9s Epoch: 5 | Accuracy: 98.18% | Temps: 72.8s Entraînement terminé en 6.9 minutes sur CPU. real 7m40,475s user 10m40,638s sys 0m12,897s RUST Cargo.toml : [package] name = "chapter5_1_mnist" version = "0.1.0" edition = "2024" [dependencies] anyhow = "1.0" tch = "0.12" reqwest = { version = "0.11", features = ["blocking"] } flate2 = "1.0" tokio = { version = "1", features = ["full"] } src/main.rs : use anyhow::{Result, Context}; use flate2::read::GzDecoder; use std::{fs, fs::File, io::copy, path::Path}; use std::time::Instant; use tch::{nn, nn::ModuleT, nn::OptimizerConfig, Device, Tensor, vision}; use reqwest; // URLs for the MNIST dataset files const MNIST_URLS: &[(&str, &str)] = &[ ("https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz", "data/train-images-idx3-ubyte"), ("https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz", "data/train-labels-idx1-ubyte"), ("https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz", "data/t10k-images-idx3-ubyte"), ("https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz", "data/t10k-labels-idx1-ubyte"), ]; // Function to download and extract MNIST dataset files if they don't exist. async fn download_mnist() -> Result<()> { fs::create_dir_all("data").context("Failed to create data directory")?; for &(url, file_path) in MNIST_URLS { if !Path::new(file_path).exists() { println!("Downloading {}...", url); let response = reqwest::get(url).await?; // Check if the response is a valid GZIP file if response.headers().get("content-type").map(|v| v != "application/x-gzip").unwrap_or(true) { return Err(anyhow::anyhow!("Invalid content type for {}: {:?}", url, response.headers().get("content-type"))); } // Attempt to extract the GZIP file let bytes = response.bytes().await?; let mut gz = GzDecoder::new(bytes.as_ref()); let mut out_file = File::create(file_path).context("Failed to create MNIST file")?; copy(&mut gz, &mut out_file).context("Failed to extract MNIST file")?; println!("Downloaded and extracted to {}", file_path); } else { println!("File {} already exists, skipping download.", file_path); } } Ok(()) } // Main function to download MNIST data and run the CNN model. #[tokio::main] async fn main() -> Result<()> { // Ensure the MNIST dataset is downloaded and extracted. download_mnist().await?; // Run the CNN model training run_conv() } // CNN Model - Should reach around 99% accuracy. #[derive(Debug)] struct Net { conv1: nn::Conv2D, conv2: nn::Conv2D, fc1: nn::Linear, fc2: nn::Linear, } impl Net { // Initializes a new CNN model with layers defined in the `Net` structure. fn new(vs: &nn::Path) -> Net { let conv1 = nn::conv2d(vs, 1, 32, 5, Default::default()); let conv2 = nn::conv2d(vs, 32, 64, 5, Default::default()); let fc1 = nn::linear(vs, 1024, 1024, Default::default()); let fc2 = nn::linear(vs, 1024, 10, Default::default()); Net { conv1, conv2, fc1, fc2 } } } // Implementing the forward pass of the CNN model with ReLU and Dropout. impl nn::ModuleT for Net { fn forward_t(&self, xs: &Tensor, train: bool) -> Tensor { xs.view([-1, 1, 28, 28]) // Reshape input to 1x28x28 images. .apply(&self.conv1) // Apply first convolutional layer. .max_pool2d_default(2) // Max pooling. .apply(&self.conv2) // Apply second convolutional layer. .max_pool2d_default(2) // Max pooling. .view([-1, 1024]) // Flatten. .apply(&self.fc1) // Apply first linear layer. .relu() // ReLU activation. .dropout(0.5, train) // Dropout layer for regularization. .apply(&self.fc2) // Final linear layer for classification. } } // Function to train and test the CNN model on the MNIST dataset. fn run_conv() -> Result<()> { // Load the MNIST dataset; this will download if the files are missing. let m = vision::mnist::load_dir("data")?; // Use GPU if available, otherwise use CPU let vs = nn::VarStore::new(Device::cuda_if_available()); let net = Net::new(&vs.root()); // Initialize the CNN model. let mut opt = nn::Adam::default().build(&vs, 1e-4)?; // Set up the optimizer. // Reshape and normalize the training and test images let train_images = m.train_images.view([-1, 1, 28, 28]) / 255.0; let train_labels = m.train_labels; let test_images = m.test_images.view([-1, 1, 28, 28]) / 255.0; let test_labels = m.test_labels; // Training loop for the CNN model. for epoch in 1..=5 { let epoch_start = Instant::now(); // Shuffle and split the training data into batches for (bimages, blabels) in train_images.split(256, 0).into_iter().zip(train_labels.split(256, 0).into_iter()) { let loss = net.forward_t(&bimages, true).cross_entropy_for_logits(&blabels); opt.backward_step(&loss); // Backpropagation step. } // Calculate and print test accuracy at the end of each epoch let test_accuracy = net.batch_accuracy_for_logits(&test_images, &test_labels, vs.device(), 1024); // println!("Epoch: {:4}, Test Accuracy: {:5.2}%", epoch, 100. * test_accuracy); let epoch_duration = epoch_start.elapsed(); println!( "Epoch: {:2} | Accuracy: {:5.2}% | Temps: {:.1}s", epoch, 100. * test_accuracy, epoch_duration.as_secs_f32() ); } Ok(()) } Exécution : root@puppypc8976:~/rust/dlvr/chapter5_1_mnist# time cargo run --release Compiling proc-macro2 v1.0.106 ... Compiling chapter5_1_mnist v0.1.0 (/root/rust/dlvr/chapter5_1_mnist) Finished `release` profile [optimized] target(s) in 8m 21s Running `target/release/chapter5_1_mnist` File data/train-images-idx3-ubyte already exists, skipping download. File data/train-labels-idx1-ubyte already exists, skipping download. File data/t10k-images-idx3-ubyte already exists, skipping download. File data/t10k-labels-idx1-ubyte already exists, skipping download. Epoch: 1 | Accuracy: 78.80% | Temps: 47.3s Epoch: 2 | Accuracy: 87.05% | Temps: 45.3s Epoch: 3 | Accuracy: 89.55% | Temps: 47.1s Epoch: 4 | Accuracy: 90.97% | Temps: 47.5s Epoch: 5 | Accuracy: 92.02% | Temps: 45.3s real 12m26,600s user 15m23,560s sys 0m28,387s C++ Installation de libtorch : cd /opt wget https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.4.0%2Bcpu.zip unzip libtorch-cxx11-abi-shared-with-deps-2.4.0+cpu.zip mv libtorch libtorch-cxx11-abi-2.4.0 Création du projet mkdir mnist cd mnist CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(mnist_cpp) set(CMAKE_CXX_STANDARD 17) find_package(Torch REQUIRED) add_executable(mnist_cnn mnist_cnn.cpp) target_link_libraries(mnist_cnn "${TORCH_LIBRARIES}") set_property(TARGET mnist_cnn PROPERTY CXX_STANDARD 17) mnist_cnn.cpp #include #include #include using namespace torch; struct Net : nn::Module { Net() { conv1 = register_module("conv1", nn::Conv2d(1, 32, 5)); conv2 = register_module("conv2", nn::Conv2d(32, 64, 5)); fc1 = register_module("fc1", nn::Linear(1024, 1024)); fc2 = register_module("fc2", nn::Linear(1024, 10)); } Tensor forward(Tensor x) { x = x.view({-1, 1, 28, 28}); x = torch::relu(conv1->forward(x)); x = torch::max_pool2d(x, 2); x = torch::relu(conv2->forward(x)); x = torch::max_pool2d(x, 2); x = x.view({-1, 1024}); x = torch::relu(fc1->forward(x)); x = torch::dropout(x, 0.5, is_training()); x = fc2->forward(x); return x; } nn::Conv2d conv1{nullptr}, conv2{nullptr}; nn::Linear fc1{nullptr}, fc2{nullptr}; }; int main() { torch::manual_seed(0); torch::Device device = torch::kCPU; std::cout << "Device: CPU" << std::endl; // Dataset MNIST auto train_dataset = torch::data::datasets::MNIST("./data") .map(torch::data::transforms::Stack<>()); auto train_loader = torch::data::make_data_loader( std::move(train_dataset), torch::data::DataLoaderOptions().batch_size(256) ); auto test_dataset = torch::data::datasets::MNIST("./data", torch::data::datasets::MNIST::Mode::kTest) .map(torch::data::transforms::Stack<>()); auto test_loader = torch::data::make_data_loader( std::move(test_dataset), torch::data::DataLoaderOptions().batch_size(1024) ); Net model; model.to(device); torch::optim::Adam optimizer(model.parameters(), torch::optim::AdamOptions(1e-4)); for (int epoch = 1; epoch <= 5; ++epoch) { auto start = std::chrono::high_resolution_clock::now(); model.train(); for (auto& batch : *train_loader) { auto data = batch.data.to(device); auto target = batch.target.to(device).view(-1); optimizer.zero_grad(); auto output = model.forward(data); auto loss = torch::nll_loss(torch::log_softmax(output, 1), target); loss.backward(); optimizer.step(); } // Évaluation model.eval(); int64_t correct = 0; int64_t total = 0; for (auto& batch : *test_loader) { auto data = batch.data.to(device); auto target = batch.target.to(device).view(-1); auto output = model.forward(data); auto pred = output.argmax(1); correct += pred.eq(target).sum().item(); total += target.size(0); } auto duration = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start).count() / 1000.0; std::cout << "Epoch: " << epoch << " | Accuracy: " << (100.0 * correct / total) << "% | Temps: " << duration << "s" << std::endl; } std::cout << "Entraînement terminé !" << std::endl; return 0; } Compilation : mkdir build cd build cmake -DCMAKE_PREFIX_PATH=/opt/libtorch-cxx11-abi-2.4.0/ .. make -j Copie des données : mkdir -p data cp ~/mnist/data/MNIST/raw/train-images-idx3-ubyte data/ cp ~/mnist/data/MNIST/raw/train-labels-idx1-ubyte data/ cp ~/mnist/data/MNIST/raw/t10k-images-idx3-ubyte data/ cp ~/mnist/data/MNIST/raw/t10k-labels-idx1-ubyte data/ Exécution : root@puppypc8976:~/libtorch/mnist/build# time ./mnist_cnn Device: CPU Epoch: 1 | Accuracy: 92.62% | Temps: 63.998s Epoch: 2 | Accuracy: 95.59% | Temps: 54.424s Epoch: 3 | Accuracy: 96.94% | Temps: 52.156s Epoch: 4 | Accuracy: 97.64% | Temps: 64.944s Epoch: 5 | Accuracy: 98.07% | Temps: 58.366s Entraînement terminé ! real 5m11,422s user 8m36,583s sys 0m46,125s