Building a Professional Neural Network Framework: Full C++ Implementation with Windows GUI and Real-Time Training Visualization

Joined
Jun 12, 2020
Messages
62
Reaction score
3
C++:
/*


MIT License


Copyright (c) 2025 CoTon_TiGe_MoUaRf



Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:



The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.




THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


*/
#include <windows.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <ctime>
#include <stdarg.h>
#include <cstdio>
#include <iostream>
#include <cmath>
#include <iomanip>
#include <cstdlib>
#include <algorithm>
#include <fstream>
#include <sstream>




#ifndef _tWinMain
    #ifdef UNICODE
        #define _tWinMain wWinMain
    #else
        #define _tWinMain WinMain
    #endif
#endif


#define WM_UPDATE_TRAINING (WM_USER + 1)
#define WM_UPDATE_INPUTS (WM_USER + 2)


struct Neurone {
    double* poids;
    double biais;
    double activation;
    double delta;
    double z;
};

struct LayerNorm {
    double* gamma;
    double* beta;
    double epsilon;
};

struct BatchNorm {
    double* gamma;
    double* beta;
    double* running_mean;
    double* running_var;
    double momentum;
    double epsilon;
    double* batch_mean;
    double* batch_var;
};

struct Couche {
    Neurone* neurones;
    int nombre_neurones;
    int nombre_entrees;
    bool use_layer_norm;
    bool use_batch_norm;
    LayerNorm ln;
    BatchNorm bn;
    double* activations_post_norm;
};

struct ReseauNeuronal {
    Couche* couches;
    int nombre_couches;
    double taux_apprentissage;
    bool use_relu;
};

struct EchantillonEntrainement {
    std::vector<double> features;
    double sortie_attendue;
};


struct DynamicControls {
    HWND btnEntrainer;
    HWND btnCharger;
    HWND btnPredire;
    std::vector<HWND> editEntrees;
    std::vector<HWND> labelEntrees;
    HWND textResultat;
    HWND textStatus;
    HWND textEntrainement;
    HWND editEpochs;
    HWND editLR;
    HWND hStatusBar;
    HWND labelResultat;
};

struct TrainUpdateInfo {
    int epoch;
    double mse;
    double lr;
    int nan_count;
    bool is_final;
};


ReseauNeuronal gReseau;
DynamicControls gControls;
std::vector<int> gTopologie;
int gNbCouches;
std::string gFichierPoids = "poids_entraines.csv";
std::vector<EchantillonEntrainement> gDonnees;
bool gReseauInitialise = false;
HWND gHwndMain = NULL;
int gNombreEpochs = 5000;
int gNombreEntrees = 3;


HBRUSH hBrushBackground = NULL;
HBRUSH hBrushGroup = NULL;
HPEN hPenBorder = NULL;
HFONT hFontTitle = NULL;
HFONT hFontNormal = NULL;
HFONT hFontSmall = NULL;


HWND CreateStaticLabel(HWND hwndParent, const char* text, int x, int y, int w, int h, HFONT hFont) {
    HWND hStatic = CreateWindowA("STATIC", text, WS_VISIBLE | WS_CHILD,
                                 x, y, w, h, hwndParent, NULL,
                                 GetModuleHandle(NULL), NULL);
    if (hFont) {
        SendMessage(hStatic, WM_SETFONT, (WPARAM)hFont, TRUE);
    }
    return hStatic;
}


double relu(double x) { return (x > 0) ? x : 0.0; }
double relu_derivee(double x) { return (x > 0) ? 1.0 : 0.0; }
double sigmoid(double x) {
    if (x > 100) return 1.0;
    if (x < -100) return 0.0;
    return 1.0 / (1.0 + std::exp(-x));
}
double sigmoid_derivee(double y) { return y * (1.0 - y); }


void initialiser_layer_norm(LayerNorm& ln, int taille) {
    ln.gamma = new double[taille];
    ln.beta = new double[taille];
    ln.epsilon = 1e-5;
    for (int i = 0; i < taille; i++) {
        ln.gamma[i] = 1.0;
        ln.beta[i] = 0.0;
    }
}

void initialiser_batch_norm(BatchNorm& bn, int taille) {
    bn.gamma = new double[taille];
    bn.beta = new double[taille];
    bn.running_mean = new double[taille];
    bn.running_var = new double[taille];
    bn.batch_mean = new double[taille];
    bn.batch_var = new double[taille];
    bn.momentum = 0.9;
    bn.epsilon = 1e-5;
    for (int i = 0; i < taille; i++) {
        bn.gamma[i] = 1.0;
        bn.beta[i] = 0.0;
        bn.running_mean[i] = 0.0;
        bn.running_var[i] = 1.0;
    }
}

void initialiser_couche(Couche& couche, int nombre_neurones, int nombre_entrees, bool use_layer_norm, bool use_batch_norm) {
    couche.nombre_neurones = nombre_neurones;
    couche.nombre_entrees = nombre_entrees;
    couche.use_layer_norm = use_layer_norm;
    couche.use_batch_norm = use_batch_norm;
    couche.neurones = new Neurone[nombre_neurones];
    couche.activations_post_norm = new double[nombre_neurones];

    double limite = std::sqrt(6.0 / (nombre_entrees + nombre_neurones));
    for (int i = 0; i < nombre_neurones; i++) {
        couche.neurones[i].poids = new double[nombre_entrees];
        for (int j = 0; j < nombre_entrees; j++) {
            couche.neurones[i].poids[j] = (2.0 * rand() / RAND_MAX - 1.0) * limite * 2.0;
        }
        couche.neurones[i].biais = 0.01 * (2.0 * rand() / RAND_MAX - 1.0);
        couche.neurones[i].activation = 0.0;
        couche.neurones[i].delta = 0.0;
        couche.neurones[i].z = 0.0;
    }

    if (use_layer_norm) initialiser_layer_norm(couche.ln, nombre_neurones);
    if (use_batch_norm) initialiser_batch_norm(couche.bn, nombre_neurones);
}

void initialiser_reseau(ReseauNeuronal& reseau, const std::vector<int>& topologie, bool use_relu, bool use_layer_norm, bool use_batch_norm) {
    reseau.nombre_couches = topologie.size();
    reseau.use_relu = use_relu;
    reseau.couches = new Couche[reseau.nombre_couches];

    for (int i = 0; i < reseau.nombre_couches; i++) {
        int nombre_entrees = (i == 0) ? topologie[i] : topologie[i - 1];
        bool ln = use_layer_norm && (i > 0 && i < reseau.nombre_couches - 1);
        bool bn = use_batch_norm && (i > 0 && i < reseau.nombre_couches - 1);
        initialiser_couche(reseau.couches[i], topologie[i], nombre_entrees, ln, bn);
    }
}


void layer_norm_forward(LayerNorm& ln, double* x, double* y, int taille) {
    double mean = 0.0, M2 = 0.0;
    for (int i = 0; i < taille; i++) {
        double delta = x[i] - mean;
        mean += delta / (i + 1);
        double delta2 = x[i] - mean;
        M2 += delta * delta2;
    }
    double variance = (taille > 1) ? M2 / taille : 0.0;
    double denom = std::sqrt(variance + ln.epsilon);
    if (!std::isfinite(denom) || denom < 1e-10) denom = ln.epsilon;

    for (int i = 0; i < taille; i++) {
        double normalized = (x[i] - mean) / denom;
        normalized = std::max(-10.0, std::min(10.0, normalized));
        y[i] = ln.gamma[i] * normalized + ln.beta[i];
    }
}


void forward_propagation(ReseauNeuronal& reseau, const std::vector<double>& entrees, bool training_mode = true) {
    if (entrees.size() != (size_t)reseau.couches[0].nombre_neurones) {
        std::cerr << "Erreur : nombre d'entrees (" << entrees.size()
                  << ") != nombre de neurones de la couche d'entree ("
                  << reseau.couches[0].nombre_neurones << ")\n";
        return;
    }

    
    for (int i = 0; i < reseau.couches[0].nombre_neurones; i++) {
        reseau.couches[0].neurones[i].activation = entrees[i];
    }

    
    for (int c = 1; c < reseau.nombre_couches; c++) {
        Couche& couche_precedente = reseau.couches[c - 1];
        Couche& couche_actuelle = reseau.couches[c];

        
        for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
            double somme = couche_actuelle.neurones[i].biais;
            double compensation = 0.0;
            
            for (int j = 0; j < couche_actuelle.nombre_entrees; j++) {
                double terme = couche_actuelle.neurones[i].poids[j] * couche_precedente.neurones[j].activation;
                double y_temp = terme - compensation;
                double t_temp = somme + y_temp;
                compensation = (t_temp - somme) - y_temp;
                somme = t_temp;
            }
            
            
            couche_actuelle.neurones[i].z = somme;
            couche_actuelle.activations_post_norm[i] = somme;
        }

        
        if (couche_actuelle.use_batch_norm && training_mode) {
            
            double batch_mean = 0.0;
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                batch_mean += couche_actuelle.activations_post_norm[i];
            }
            batch_mean /= couche_actuelle.nombre_neurones;
            
            
            double batch_var = 0.0;
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                double diff = couche_actuelle.activations_post_norm[i] - batch_mean;
                batch_var += diff * diff;
            }
            batch_var /= couche_actuelle.nombre_neurones;
            
            
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                couche_actuelle.bn.batch_mean[i] = batch_mean;
                couche_actuelle.bn.batch_var[i] = batch_var;
            }
            
            
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                couche_actuelle.bn.running_mean[i] =
                    couche_actuelle.bn.momentum * couche_actuelle.bn.running_mean[i] +
                    (1.0 - couche_actuelle.bn.momentum) * batch_mean;
                couche_actuelle.bn.running_var[i] =
                    couche_actuelle.bn.momentum * couche_actuelle.bn.running_var[i] +
                    (1.0 - couche_actuelle.bn.momentum) * (batch_var + couche_actuelle.bn.epsilon);
            }
            
            
            double denom = std::sqrt(batch_var + couche_actuelle.bn.epsilon);
            if (!std::isfinite(denom) || denom < 1e-10) {
                denom = couche_actuelle.bn.epsilon;
            }
            
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                double normalized = (couche_actuelle.activations_post_norm[i] - batch_mean) / denom;
                couche_actuelle.activations_post_norm[i] =
                    couche_actuelle.bn.gamma[i] * normalized + couche_actuelle.bn.beta[i];
            }
        } else if (couche_actuelle.use_batch_norm && !training_mode) {
            
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                double denom = std::sqrt(couche_actuelle.bn.running_var[i] + couche_actuelle.bn.epsilon);
                if (!std::isfinite(denom) || denom < 1e-10) {
                    denom = couche_actuelle.bn.epsilon;
                }
                
                double normalized = (couche_actuelle.activations_post_norm[i] - couche_actuelle.bn.running_mean[i]) / denom;
                couche_actuelle.activations_post_norm[i] =
                    couche_actuelle.bn.gamma[i] * normalized + couche_actuelle.bn.beta[i];
            }
        }

        
        if (couche_actuelle.use_layer_norm) {
            layer_norm_forward(couche_actuelle.ln, couche_actuelle.activations_post_norm, couche_actuelle.activations_post_norm, couche_actuelle.nombre_neurones);
        }

        
        for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
            double activation_val = couche_actuelle.activations_post_norm[i];
            
            
            if (!std::isfinite(activation_val)) {
                couche_actuelle.neurones[i].activation = 0.0;
                continue;
            }
            
            
            activation_val = std::max(-50.0, std::min(50.0, activation_val));
            
            if (c < reseau.nombre_couches - 1) {
                
                if (reseau.use_relu) {
                    couche_actuelle.neurones[i].activation = std::max(0.0, activation_val);
                } else {
                    
                    if (activation_val >= 0) {
                        double exp_neg_x = std::exp(-activation_val);
                        couche_actuelle.neurones[i].activation = 1.0 / (1.0 + exp_neg_x);
                    } else {
                        double exp_x = std::exp(activation_val);
                        couche_actuelle.neurones[i].activation = exp_x / (1.0 + exp_x);
                    }
                }
            } else {
                
                couche_actuelle.neurones[i].activation = activation_val;
            }
        }
    }
}



void backward_propagation(ReseauNeuronal& reseau, double sortie_attendue) {
    const double GRADIENT_CLIP_MAX = 5.0;
    const double MIN_DERIVATIVE = 1e-8;
    int derniere_couche = reseau.nombre_couches - 1;
    Couche& couche_sortie = reseau.couches[derniere_couche];

    
    for (int i = 0; i < couche_sortie.nombre_neurones; i++) {
        double erreur = sortie_attendue - couche_sortie.neurones[i].activation;
        
        couche_sortie.neurones[i].delta = erreur;
        couche_sortie.neurones[i].delta = std::max(-GRADIENT_CLIP_MAX,
                                                    std::min(GRADIENT_CLIP_MAX,
                                                    couche_sortie.neurones[i].delta));
    }

    
    for (int c = derniere_couche - 1; c > 0; c--) {
        Couche& couche_actuelle = reseau.couches[c];
        Couche& couche_suivante = reseau.couches[c + 1];

        
        double* deltas_apres_ln = new double[couche_actuelle.nombre_neurones];
        
        if (couche_actuelle.use_layer_norm) {
            
            double mean = 0.0, M2 = 0.0;
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                double delta = couche_actuelle.activations_post_norm[i] - mean;
                mean += delta / (i + 1);
                double delta2 = couche_actuelle.activations_post_norm[i] - mean;
                M2 += delta * delta2;
            }
            double variance = (couche_actuelle.nombre_neurones > 1) ?
                            M2 / couche_actuelle.nombre_neurones : 0.0;
            double denom = std::sqrt(variance + couche_actuelle.ln.epsilon);
            if (!std::isfinite(denom) || denom < 1e-10) denom = couche_actuelle.ln.epsilon;

            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                
                deltas_apres_ln[i] = couche_actuelle.neurones[i].delta *
                                    couche_actuelle.ln.gamma[i] / denom;
            }
        } else {
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                deltas_apres_ln[i] = couche_actuelle.neurones[i].delta;
            }
        }

        
        double* deltas_apres_bn = new double[couche_actuelle.nombre_neurones];
        
        if (couche_actuelle.use_batch_norm) {
            double batch_mean = couche_actuelle.bn.batch_mean[0];
            double batch_var = couche_actuelle.bn.batch_var[0];
            double denom = std::sqrt(batch_var + couche_actuelle.bn.epsilon);
            if (!std::isfinite(denom) || denom < 1e-10) denom = couche_actuelle.bn.epsilon;

            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                
                double dgamma = deltas_apres_ln[i] *
                    (couche_actuelle.activations_post_norm[i] - batch_mean) / denom;
                
                
                double x_normalized = (couche_actuelle.activations_post_norm[i] - batch_mean) / denom;
                double dbeta = deltas_apres_ln[i];
                
                
                couche_actuelle.bn.gamma[i] -= reseau.taux_apprentissage * dgamma * 0.01;
                couche_actuelle.bn.beta[i] -= reseau.taux_apprentissage * dbeta * 0.01;
                
                
                double dx_normalized = deltas_apres_ln[i] * couche_actuelle.bn.gamma[i];
                double dvar = dx_normalized * (couche_actuelle.activations_post_norm[i] - batch_mean) *
                            (-0.5) * std::pow(denom, -3.0);
                double dmean = -dx_normalized / denom + dvar * (-2.0) *
                            (couche_actuelle.activations_post_norm[i] - batch_mean) / couche_actuelle.nombre_neurones;
                
                deltas_apres_bn[i] = dx_normalized / denom + dmean / couche_actuelle.nombre_neurones +
                                    dvar * 2.0 * (couche_actuelle.activations_post_norm[i] - batch_mean) /
                                    couche_actuelle.nombre_neurones;
            }
        } else {
            for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
                deltas_apres_bn[i] = deltas_apres_ln[i];
            }
        }

        
        for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
            double somme_deltas = 0.0;
            double compensation = 0.0;
            
            
            for (int j = 0; j < couche_suivante.nombre_neurones; j++) {
                double terme = couche_suivante.neurones[j].delta *
                              couche_suivante.neurones[j].poids[i];
                double y_temp = terme - compensation;
                double t_temp = somme_deltas + y_temp;
                compensation = (t_temp - somme_deltas) - y_temp;
                somme_deltas = t_temp;
            }

            
            double derivee_activation = 1.0;
            
            if (reseau.use_relu) {
                
                derivee_activation = (couche_actuelle.neurones[i].z > 0.0) ? 1.0 : 0.0;
            } else {
                
                double activation = couche_actuelle.neurones[i].activation;
                
                
                if (activation > 0.9999) {
                    derivee_activation = 1e-4;
                } else if (activation < 0.0001) {
                    derivee_activation = 1e-4;
                } else {
                    derivee_activation = activation * (1.0 - activation);
                }
            }
            
            
            if (!std::isfinite(derivee_activation) || std::abs(derivee_activation) < MIN_DERIVATIVE) {
                derivee_activation = MIN_DERIVATIVE;
            }

            
            couche_actuelle.neurones[i].delta = somme_deltas * derivee_activation;
            
            
            if (couche_actuelle.use_batch_norm || couche_actuelle.use_layer_norm) {
                couche_actuelle.neurones[i].delta *= deltas_apres_bn[i];
            }
            
            
            couche_actuelle.neurones[i].delta =
                std::max(-GRADIENT_CLIP_MAX,
                         std::min(GRADIENT_CLIP_MAX, couche_actuelle.neurones[i].delta));
        }

        delete[] deltas_apres_ln;
        delete[] deltas_apres_bn;
    }
}


void mettre_a_jour_poids(ReseauNeuronal& reseau) {
    for (int c = 1; c < reseau.nombre_couches; c++) {
        Couche& couche_precedente = reseau.couches[c - 1];
        Couche& couche_actuelle = reseau.couches[c];
        for (int i = 0; i < couche_actuelle.nombre_neurones; i++) {
            couche_actuelle.neurones[i].biais += reseau.taux_apprentissage * couche_actuelle.neurones[i].delta;
            for (int j = 0; j < couche_actuelle.nombre_entrees; j++) {
                double gradient = couche_actuelle.neurones[i].delta * couche_precedente.neurones[j].activation;
                couche_actuelle.neurones[i].poids[j] += reseau.taux_apprentissage * gradient;
            }
        }
    }
}


void clipper_gradients(ReseauNeuronal& reseau, double max_grad = 10.0) {
    double norm_l2_squared = 0.0;
    for (int c = 1; c < reseau.nombre_couches; c++) {
        Couche& couche = reseau.couches[c];
        for (int i = 0; i < couche.nombre_neurones; i++) {
            norm_l2_squared += couche.neurones[i].delta * couche.neurones[i].delta;
        }
    }
    double norm_l2 = std::sqrt(norm_l2_squared);
    if (norm_l2 > max_grad && norm_l2 > 0.0) {
        double facteur = max_grad / norm_l2;
        for (int c = 1; c < reseau.nombre_couches; c++) {
            Couche& couche = reseau.couches[c];
            for (int i = 0; i < couche.nombre_neurones; i++) {
                couche.neurones[i].delta *= facteur;
            }
        }
    }
}


void sauvegarder_poids(ReseauNeuronal& reseau, const std::string& nom_fichier) {
    std::ofstream fichier(nom_fichier);
    if (!fichier.is_open()) {
        std::cerr << "Erreur : impossible d'ouvrir le fichier " << nom_fichier << std::endl;
        return;
    }
    fichier << "couche,neurone,type,index,valeur\n";
    for (int c = 1; c < reseau.nombre_couches; c++) {
        Couche& couche = reseau.couches[c];
        for (int i = 0; i < couche.nombre_neurones; i++) {
            fichier << c << "," << i << ",biais,0," << std::setprecision(15) << couche.neurones[i].biais << "\n";
        }
        for (int i = 0; i < couche.nombre_neurones; i++) {
            for (int j = 0; j < couche.nombre_entrees; j++) {
                fichier << c << "," << i << ",poids," << j << "," << std::setprecision(15) << couche.neurones[i].poids[j] << "\n";
            }
        }
    }
    fichier.close();
}

void charger_poids(ReseauNeuronal& reseau, const std::string& nom_fichier) {
    std::ifstream fichier(nom_fichier);
    if (!fichier.is_open()) {
        std::cerr << "Erreur : impossible d'ouvrir le fichier " << nom_fichier << std::endl;
        return;
    }
    std::string ligne;
    std::getline(fichier, ligne);
    while (std::getline(fichier, ligne)) {
        if (ligne.empty()) continue;
        for (char& c : ligne) if (c == ',') c = ' ';
        std::istringstream flux(ligne);
        int couche_idx, neurone_idx, index;
        std::string type;
        double valeur;
        if (flux >> couche_idx >> neurone_idx >> type >> index >> valeur) {
            if (couche_idx < reseau.nombre_couches && neurone_idx < reseau.couches[couche_idx].nombre_neurones) {
                if (type == "biais") {
                    reseau.couches[couche_idx].neurones[neurone_idx].biais = valeur;
                } else if (type == "poids" && index < reseau.couches[couche_idx].nombre_entrees) {
                    reseau.couches[couche_idx].neurones[neurone_idx].poids[index] = valeur;
                }
            }
        }
    }
    fichier.close();
}


std::vector<EchantillonEntrainement> charger_csv(const std::string& nom_fichier) {
    std::vector<EchantillonEntrainement> donnees;
    std::ifstream fichier(nom_fichier);
    if (!fichier.is_open()) {
        std::cerr << "Erreur : impossible d'ouvrir le fichier " << nom_fichier << std::endl;
        return donnees;
    }
    std::string ligne;
    std::getline(fichier, ligne);
    while (std::getline(fichier, ligne)) {
        if (ligne.empty()) continue;
        for (char& c : ligne) if (c == ',') c = ' ';
        std::istringstream flux(ligne);
        std::vector<double> valeurs;
        double val;
        while (flux >> val) valeurs.push_back(val);
        if (valeurs.empty()) continue;
        EchantillonEntrainement echantillon;
        echantillon.sortie_attendue = valeurs.back();
        echantillon.features.assign(valeurs.begin(), valeurs.end() - 1);
        donnees.push_back(echantillon);
    }
    fichier.close();
    std::cout << "✓ " << donnees.size() << " échantillons chargés depuis " << nom_fichier << "\n";
    return donnees;
}


bool contient_nan(double valeur) {
    return std::isnan(valeur) || std::isinf(valeur);
}


void EnvoyerUpdateEntrainement(int epoch, double mse, double lr, int nan_count, bool is_final = false) {
    if (gHwndMain == NULL) return;
    TrainUpdateInfo* info = new TrainUpdateInfo{epoch, mse, lr, nan_count, is_final};
    PostMessage(gHwndMain, WM_UPDATE_TRAINING, 0, (LPARAM)info);
}

void AfficherMessage(HWND hwnd, const std::string& message, const std::string& type) {
    if (gControls.textStatus) SetWindowTextA(gControls.textStatus, message.c_str());
    if (gControls.textEntrainement && type == "TRAIN") {
        
        char buffer[24576];
        GetWindowTextA(gControls.textEntrainement, buffer, sizeof(buffer));
        std::string ancien = buffer;
        ancien += message + "\n";
        
        if (ancien.length() > 30000) ancien = ancien.substr(ancien.length() - 10000);
        SetWindowTextA(gControls.textEntrainement, ancien.c_str());
        SendMessage(gControls.textEntrainement, EM_SETSEL, -1, -1);
        SendMessage(gControls.textEntrainement, EM_SCROLLCARET, 0, 0);
    }
}

void AfficherMessageThreadSafe(HWND hwnd, const std::string& message, bool append = false) {
    if (!hwnd || !gControls.textStatus) return;
    if (!append) {
        SetWindowTextA(gControls.textStatus, message.c_str());
    } else {
        int len = GetWindowTextLengthA(gControls.textEntrainement);
        SendMessageA(gControls.textEntrainement, EM_SETSEL, len, len);
        SendMessageA(gControls.textEntrainement, EM_REPLACESEL, FALSE, (LPARAM)message.c_str());
        SendMessageA(gControls.textEntrainement, EM_REPLACESEL, FALSE, (LPARAM)"\r\n");
        SendMessageA(gControls.textEntrainement, EM_SCROLLCARET, 0, 0);
    }
}

void Log(const char* type, const char* format, ...) {
    va_list args;
    va_start(args, format);
    printf("[%s] ", type);
    vprintf(format, args);
    va_end(args);
    fflush(stdout);
}


void AfficherStatusCouleur(int type, const char* message) {
    if (!gControls.textStatus) return;

    SetWindowTextA(gControls.textStatus, message);
    
    
    HDC hdc = GetDC(gControls.textStatus);
    HBRUSH hColor;
    
    switch(type) {
        case 1: hColor = CreateSolidBrush(RGB(0, 200, 0)); break;
        case 2: hColor = CreateSolidBrush(RGB(255, 0, 0)); break;
        case 3: hColor = CreateSolidBrush(RGB(255, 165, 0)); break;
        default: hColor = CreateSolidBrush(RGB(200, 200, 200)); break;
    }
    
    RECT rect;
    GetClientRect(gControls.textStatus, &rect);
    FillRect(hdc, &rect, hColor);
    
    DeleteObject(hColor);
    ReleaseDC(gControls.textStatus, hdc);
}



void CreerControlsEntrees(HWND hwnd, int nombre_entrees) {
    
    for (auto hwndEdit : gControls.editEntrees) DestroyWindow(hwndEdit);
    for (auto hwndLabel : gControls.labelEntrees) DestroyWindow(hwndLabel);
    gControls.editEntrees.clear();
    gControls.labelEntrees.clear();

    
    if (gControls.labelResultat) {
        DestroyWindow(gControls.labelResultat);
        gControls.labelResultat = NULL;
    }
    if (gControls.textResultat) {
        DestroyWindow(gControls.textResultat);
        gControls.textResultat = NULL;
    }
    if (gControls.btnPredire) {
        DestroyWindow(gControls.btnPredire);
        gControls.btnPredire = NULL;
    }

    
    int col2 = 350;
    int row = 55;
    int hauteur_input = 28;
    int espacement = 40;

    for (int i = 0; i < nombre_entrees; i++) {
        char labelText[64];
        sprintf_s(labelText, sizeof(labelText), "Entrée %d :", i + 1);

        HWND hwndLabel = CreateStaticLabel(hwnd, labelText, col2 + 15, row, 100, 20, hFontSmall);
        gControls.labelEntrees.push_back(hwndLabel);

        HWND hwndEdit = CreateWindowA("EDIT", "0.5", WS_VISIBLE | WS_CHILD | WS_BORDER,
                              col2 + 120, row, 100, hauteur_input, hwnd,
                              (HMENU)((intptr_t)(3001 + i)),
                              GetModuleHandle(NULL), NULL);
        gControls.editEntrees.push_back(hwndEdit);
        SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hFontSmall, TRUE);

        row += espacement;
    }

    
    int row_button = row + 10;
    gControls.btnPredire = CreateWindowA("BUTTON", "Prédire",
                                         WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
                                         col2 + 15, row_button, 200, 32, hwnd, (HMENU)1003,
                                         GetModuleHandle(NULL), NULL);
    SendMessage(gControls.btnPredire, WM_SETFONT, (WPARAM)hFontNormal, TRUE);

    
    gControls.labelResultat = CreateStaticLabel(hwnd, "Résultat :", col2 + 15, row_button + 40, 100, 20, hFontSmall);
    gControls.textResultat = CreateWindowA("EDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_READONLY,
                                          col2 + 120, row_button + 40, 100, hauteur_input, hwnd, (HMENU)2002,
                                          GetModuleHandle(NULL), NULL);
    SendMessage(gControls.textResultat, WM_SETFONT, (WPARAM)hFontSmall, TRUE);
}


void entrainer(ReseauNeuronal& reseau, EchantillonEntrainement donnees[], int nombre_samples, int nombre_epochs, const std::string& fichier_poids = "poids_entraines.csv") {
    if (nombre_samples <= 0 || nombre_epochs <= 0) {
        std::cerr << "Erreur : nombre_samples et nombre_epochs doivent être > 0\n";
        return;
    }

    std::cout << "=== Début de l'entraînement ===\n";
    std::cout << "Nombre d'entrées: " << donnees[0].features.size() << "\n";
    std::cout << "Taux d'apprentissage: " << reseau.taux_apprentissage << "\n";
    std::cout << "Nombre d'epochs: " << nombre_epochs << "\n";
    std::cout << "Nombre de samples: " << nombre_samples << "\n\n";

    EnvoyerUpdateEntrainement(0, 0.0, reseau.taux_apprentissage, 0, false);

    double gradient_clip = 5.0;
    double lr_decay = 0.95;

    for (int epoch = 0; epoch < nombre_epochs; epoch++) {
        double erreur_totale = 0.0;
        int erreurs_nan = 0;

        // Mélanger les données
        for (int i = nombre_samples - 1; i > 0; i--) {
            int j = rand() % (i + 1);
            std::swap(donnees[i], donnees[j]);
        }

        for (int i = 0; i < nombre_samples; i++) {
            bool donnees_valides = true;
            for (double val : donnees[i].features) {
                if (contient_nan(val)) {
                    donnees_valides = false;
                    break;
                }
            }
            if (!donnees_valides || contient_nan(donnees[i].sortie_attendue)) {
                erreurs_nan++;
                continue;
            }

            forward_propagation(reseau, donnees[i].features, true);
            double prediction = reseau.couches[reseau.nombre_couches - 1].neurones[0].activation;
            if (contient_nan(prediction)) {
                erreurs_nan++;
                continue;
            }

            backward_propagation(reseau, donnees[i].sortie_attendue);
            clipper_gradients(reseau, gradient_clip);
            mettre_a_jour_poids(reseau);

            double erreur = donnees[i].sortie_attendue - prediction;
            if (!contient_nan(erreur)) erreur_totale += erreur * erreur;
            else erreurs_nan++;
        }

        int samples_valides = nombre_samples - erreurs_nan;
        if (samples_valides > 0) erreur_totale /= samples_valides;
        else {
            std::cerr << "Erreur : tous les samples contiennent des NaN à l'epoch " << epoch << "\n";
            erreur_totale = 0.0;
        }

        if (contient_nan(erreur_totale)) {
            std::cerr << "ARRÊT : MSE est devenu NaN à l'epoch " << epoch << "\n";
            break;
        }

        if (epoch % 1000 == 0 && epoch > 0) reseau.taux_apprentissage *= lr_decay;

        if (epoch % 100 == 0) {
            std::cout << "Epoch " << std::setw(6) << epoch
                      << " | MSE: " << std::scientific << std::setprecision(6) << erreur_totale
                      << " | LR: " << std::scientific << std::setprecision(6) << reseau.taux_apprentissage;
            if (erreurs_nan > 0) std::cout << " | Samples NaN: " << erreurs_nan;
            std::cout << "\n";
            EnvoyerUpdateEntrainement(epoch, erreur_totale, reseau.taux_apprentissage, erreurs_nan, false);
        }

        if ((epoch + 1) % 5000 == 0) std::cout << "✓ " << epoch + 1 << " epochs complétés\n";
    }

    std::cout << "\n=== Entraînement terminé ===\n";
    double mse_final = 0.0;
    int nb_samples_test = std::min(100, nombre_samples);
    for (int i = 0; i < nb_samples_test; i++) {
        forward_propagation(reseau, donnees[i].features, false);
        double prediction = reseau.couches[reseau.nombre_couches - 1].neurones[0].activation;
        double erreur = donnees[i].sortie_attendue - prediction;
        mse_final += erreur * erreur;
    }
    mse_final /= nb_samples_test;
    EnvoyerUpdateEntrainement(nombre_epochs, mse_final, reseau.taux_apprentissage, 0, true);
    sauvegarder_poids(reseau, fichier_poids);
}


double predire(ReseauNeuronal& reseau, const std::vector<double>& entrees) {
    forward_propagation(reseau, entrees, false);
    return reseau.couches[reseau.nombre_couches - 1].neurones[0].activation;
}


void detruire_reseau(ReseauNeuronal& reseau) {
    for (int c = 0; c < reseau.nombre_couches; c++) {
        Couche& couche = reseau.couches[c];
        for (int i = 0; i < couche.nombre_neurones; i++) delete[] couche.neurones[i].poids;
        delete[] couche.neurones;
        delete[] couche.activations_post_norm;
        if (couche.use_layer_norm) { delete[] couche.ln.gamma; delete[] couche.ln.beta; }
        if (couche.use_batch_norm) {
            delete[] couche.bn.gamma; delete[] couche.bn.beta;
            delete[] couche.bn.running_mean; delete[] couche.bn.running_var;
            delete[] couche.bn.batch_mean; delete[] couche.bn.batch_var;
        }
    }
    delete[] reseau.couches;
}


DWORD WINAPI EntrainementThread(LPVOID lpParam) {
    try {
        if (!gDonnees.empty()) {
            int nombre_entrees = gDonnees[0].features.size();
            gNombreEntrees = nombre_entrees;
            gTopologie = {nombre_entrees, 16, 8, 1};
            gNbCouches = gTopologie.size();

            
            PostMessage(gHwndMain, WM_UPDATE_INPUTS, nombre_entrees, 0);

            initialiser_reseau(gReseau, gTopologie, true, false, false);

            char bufferStart[512];
            sprintf_s(bufferStart, sizeof(bufferStart),
                "[INFO] Initialisation OK\r\n"
                "[INFO] Début de l'entraînement avec:\r\n"
                "- Epochs: %d\r\n"
                "- LR: %.6f\r\n"
                "- Samples: %d\r\n"
                "- Entrées: %d\r\n",
                gNombreEpochs, gReseau.taux_apprentissage, (int)gDonnees.size(), nombre_entrees);
            AfficherMessage(gHwndMain, bufferStart, "TRAIN");

            AfficherStatusCouleur(3, "Entraînement en cours...");

            entrainer(gReseau, gDonnees.data(), gDonnees.size(), gNombreEpochs, gFichierPoids);
            AfficherMessage(gHwndMain, "[OK] Entraînement terminé!\n", "TRAIN");
            AfficherStatusCouleur(1, "Entraînement terminé!");
            gReseauInitialise = true;
            EnableWindow(gControls.btnPredire, TRUE);
        }
    } catch (...) {
        AfficherMessage(gHwndMain, "[ERREUR] Problème lors de l'entraînement", "TRAIN");
        AfficherStatusCouleur(2, "Erreur lors de l'entraînement");
    }
    return 0;
}


LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_CREATE: {
            Log("INFO", "Interface neuronale créée\n");

            
            hBrushBackground = CreateSolidBrush(RGB(240, 240, 240));
            hBrushGroup = CreateSolidBrush(RGB(255, 255, 255));       
            hPenBorder = CreatePen(PS_SOLID, 1, RGB(100, 100, 100));

            hFontTitle = CreateFontA(16, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
                                     ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                                     DEFAULT_QUALITY, DEFAULT_PITCH | FF_MODERN, "Courier New");
            hFontNormal = CreateFontA(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                                      ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                                      DEFAULT_QUALITY, DEFAULT_PITCH | FF_MODERN, "Courier New");
            hFontSmall = CreateFontA(10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                                     ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                                     DEFAULT_QUALITY, DEFAULT_PITCH | FF_MODERN, "Courier New");

            int col1 = 20, col2 = 350, col3 = 680;
            int row = 20, hauteur_input = 28, espacement = 40;

          
            HWND groupTrain = CreateWindowA("BUTTON", " ENTRAÎNEMENT ",
                                          WS_VISIBLE | WS_CHILD | BS_GROUPBOX,
                                          col1, row, 300, 420, hwnd, (HMENU)5001,
                                          GetModuleHandle(NULL), NULL);
            SendMessage(groupTrain, WM_SETFONT, (WPARAM)hFontTitle, TRUE);

            row += 35;
            gControls.btnEntrainer = CreateWindowA("BUTTON", "Entraîner Nouveau Modèle",
                                                  WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
                                                  col1 + 15, row, 270, 32, hwnd, (HMENU)1001,
                                                  GetModuleHandle(NULL), NULL);
            SendMessage(gControls.btnEntrainer, WM_SETFONT, (WPARAM)hFontNormal, TRUE);
            row += espacement + 5;

            gControls.btnCharger = CreateWindowA("BUTTON", "Charger Modèle Existant",
                                                WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
                                                col1 + 15, row, 270, 32, hwnd, (HMENU)1002,
                                                GetModuleHandle(NULL), NULL);
            SendMessage(gControls.btnCharger, WM_SETFONT, (WPARAM)hFontNormal, TRUE);
            row += espacement;

            CreateStaticLabel(hwnd, "Nombre d'Epochs :", col1 + 15, row, 120, 20, hFontSmall);
            gControls.editEpochs = CreateWindowA("EDIT", "5000",
                                                WS_VISIBLE | WS_CHILD | WS_BORDER,
                                                col1 + 140, row, 100, hauteur_input, hwnd, (HMENU)4001,
                                                GetModuleHandle(NULL), NULL);
            SendMessage(gControls.editEpochs, WM_SETFONT, (WPARAM)hFontSmall, TRUE);
            CreateStaticLabel(hwnd, "(5000-50000 recommandé)", col1 + 140, row + 22, 200, 15, hFontSmall);

            row += espacement;
            CreateStaticLabel(hwnd, "Taux d'Apprentissage :", col1 + 15, row, 120, 20, hFontSmall);
            gControls.editLR = CreateWindowA("EDIT", "0.0005",
                                            WS_VISIBLE | WS_CHILD | WS_BORDER,
                                            col1 + 140, row, 100, hauteur_input, hwnd, (HMENU)4002,
                                            GetModuleHandle(NULL), NULL);
            SendMessage(gControls.editLR, WM_SETFONT, (WPARAM)hFontSmall, TRUE);
            CreateStaticLabel(hwnd, "(0.0001 - 0.01 recommandé)", col1 + 140, row + 22, 200, 15, hFontSmall);

            row += espacement + 10;
            CreateStaticLabel(hwnd, "Logs d'Entraînement :", col1 + 15, row, 200, 20, hFontSmall);

row += 25;

gControls.textEntrainement = CreateWindowA("EDIT", "",

                                          WS_VISIBLE | WS_CHILD | WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_READONLY,

                                          col1 + 15, row, 270, 120, hwnd, (HMENU)2001,

                                          GetModuleHandle(NULL), NULL);
            SendMessage(gControls.textEntrainement, WM_SETFONT, (WPARAM)hFontSmall, TRUE);

          
            row = 20;
            HWND groupPredict = CreateWindowA("BUTTON", " PRÉDICTION ",
                                              WS_VISIBLE | WS_CHILD | BS_GROUPBOX,
                                              col2, row, 300, 420, hwnd, (HMENU)5002,
                                              GetModuleHandle(NULL), NULL);
            SendMessage(groupPredict, WM_SETFONT, (WPARAM)hFontTitle, TRUE);

            
            CreerControlsEntrees(hwnd, 3);

          
            row = 20;
            HWND groupStatus = CreateWindowA("BUTTON", " STATUT ",
                                             WS_VISIBLE | WS_CHILD | BS_GROUPBOX,
                                             col3, row, 200, 420, hwnd, (HMENU)5003,
                                             GetModuleHandle(NULL), NULL);
            SendMessage(groupStatus, WM_SETFONT, (WPARAM)hFontTitle, TRUE);

            row += 30;
            CreateStaticLabel(hwnd, "Statut :", col3 + 15, row, 80, 20, hFontSmall);
            gControls.textStatus = CreateWindowA("EDIT", "Prêt",
                                                WS_VISIBLE | WS_CHILD | WS_BORDER | ES_READONLY,
                                                col3 + 15, row + 25, 170, 20, hwnd, (HMENU)2003,
                                                GetModuleHandle(NULL), NULL);
            SendMessage(gControls.textStatus, WM_SETFONT, (WPARAM)hFontSmall, TRUE);

          
            gControls.hStatusBar = CreateWindowA("STATIC", "",
                                                WS_VISIBLE | WS_CHILD,
                                                col3 + 15, row + 50, 170, 5, hwnd, NULL,
                                                GetModuleHandle(NULL), NULL);

            AfficherMessageThreadSafe(hwnd, "Application prête. Chargez ou entraînez d'abord.", false);
            AfficherStatusCouleur(0, "Prêt");
            break;
        }

        case WM_UPDATE_INPUTS: {
            int nombre_entrees = (int)wParam;
            CreerControlsEntrees(hwnd, nombre_entrees);
            break;
        }

        case WM_UPDATE_TRAINING: {
            TrainUpdateInfo* info = (TrainUpdateInfo*)lParam;
            if (info) {
                char buffer[512];
                if (info->is_final) {
                    sprintf_s(buffer, sizeof(buffer),
                        "[TERMINÉ] Entraînement terminé!\r\n"
                        "Epoch total: %d\r\n"
                        "MSE final: %.6f\r\n"
                        "LR final: %.6f",
                        gNombreEpochs, info->mse, info->lr);
                    AfficherMessageThreadSafe(hwnd, buffer, true);
                    EnableWindow(gControls.btnEntrainer, TRUE);
                    EnableWindow(gControls.btnCharger, TRUE);
                    EnableWindow(gControls.btnPredire, TRUE);
                    gReseauInitialise = true;
                    AfficherStatusCouleur(1, "Entraînement terminé!");
                } else {
                    sprintf_s(buffer, sizeof(buffer), "Epoch %5d | MSE: %.6f | LR: %.6f", info->epoch, info->mse, info->lr);
                    AfficherMessageThreadSafe(hwnd, buffer, true);
                }
                delete info;
            }
            return 0;
        }

        case WM_COMMAND: {
            int id = LOWORD(wParam);
            if (id == 1001) {
                char bufferEpochs[256];
                GetWindowTextA(gControls.editEpochs, bufferEpochs, sizeof(bufferEpochs));
                gNombreEpochs = atoi(bufferEpochs);
                if (gNombreEpochs <= 0) gNombreEpochs = 5000;

                char bufferLR[256];
                GetWindowTextA(gControls.editLR, bufferLR, sizeof(bufferLR));
                double taux_apprentissage = atof(bufferLR);
                if (taux_apprentissage <= 0.0) taux_apprentissage = 0.0005;

                char bufferInfo[512];
                sprintf_s(bufferInfo, sizeof(bufferInfo), "[INFO] Epochs: %d | LR: %.6f", gNombreEpochs, taux_apprentissage);
                AfficherMessageThreadSafe(hwnd, bufferInfo, false);

                gDonnees = charger_csv("data.csv");
                if (gDonnees.empty()) {
                    AfficherMessageThreadSafe(hwnd, "[ERREUR] Fichier data.csv non trouvé!", false);
                    AfficherStatusCouleur(2, "Erreur: data.csv introuvable");
                } else {
                    EnableWindow(gControls.btnEntrainer, FALSE);
                    EnableWindow(gControls.btnCharger, FALSE);
                    gReseau.taux_apprentissage = taux_apprentissage;
                    AfficherStatusCouleur(3, "Initialisation...");
                    HANDLE hThread = CreateThread(NULL, 0, EntrainementThread, NULL, 0, NULL);
                    if (hThread) CloseHandle(hThread);
                }
            }
            else if (id == 1002) {
                AfficherMessageThreadSafe(hwnd, "[INFO] Initialisation du réseau...", false);
                AfficherStatusCouleur(3, "Chargement...");
                gDonnees = charger_csv("data.csv");
                if (gDonnees.empty()) {
                    AfficherMessageThreadSafe(hwnd, "[ERREUR] Fichier data.csv non trouvé!", false);
                    AfficherStatusCouleur(2, "Erreur: data.csv introuvable");
                } else {
                    int nombre_entrees = gDonnees[0].features.size();
                    gNombreEntrees = nombre_entrees;
                    gTopologie = {nombre_entrees, 16, 8, 1};
                    gNbCouches = gTopologie.size();
                    initialiser_reseau(gReseau, gTopologie, true, false, false);
                    gReseau.taux_apprentissage = 0.001;
                    charger_poids(gReseau, gFichierPoids);
                    PostMessage(hwnd, WM_UPDATE_INPUTS, nombre_entrees, 0);
                    AfficherMessageThreadSafe(hwnd, "[OK] Modèle chargé avec succès!", false);
                    AfficherStatusCouleur(1, "Modèle chargé!");
                    gReseauInitialise = true;
                    EnableWindow(gControls.btnPredire, TRUE);
                }
            }
            else if (id == 1003) {
                if (gReseauInitialise) {
                    std::vector<double> entrees;
                    for (const auto& hwndEdit : gControls.editEntrees) {
                        char buffer[256];
                        GetWindowTextA(hwndEdit, buffer, sizeof(buffer));
                        entrees.push_back(atof(buffer));
                    }
                    if (entrees.size() != (size_t)gNombreEntrees) {
                        MessageBoxA(hwnd, "Nombre d'entrées incorrect !", "Erreur", MB_OK | MB_ICONWARNING);
                        AfficherStatusCouleur(2, "Erreur: entrées incorrectes");
                        return 0;
                    }
                    double resultat = predire(gReseau, entrees);
                    char buffer[512];
                    sprintf_s(buffer, sizeof(buffer), "%.6f", resultat);
                    SetWindowTextA(gControls.textResultat, buffer);
                    Log("INFO", "Prédiction: %.6f\n", resultat);
                    AfficherStatusCouleur(1, "Prédiction réussie!");
                } else {
                    MessageBoxA(hwnd, "Chargez ou entraînez d'abord un modèle !", "Erreur", MB_OK | MB_ICONWARNING);
                    AfficherStatusCouleur(2, "Erreur: modèle non initialisé");
                }
            }
            break;
        }

        case WM_CTLCOLORSTATIC: {
            HDC hdcStatic = (HDC)wParam;
            SetBkColor(hdcStatic, RGB(255, 255, 255));
            SetTextColor(hdcStatic, RGB(50, 50, 50));
            return (LRESULT)hBrushGroup;
        }

        case WM_CTLCOLOREDIT: {
            HDC hdcEdit = (HDC)wParam;
            SetBkColor(hdcEdit, RGB(255, 255, 255));
            SetTextColor(hdcEdit, RGB(0, 0, 0));
            return (LRESULT)hBrushGroup;
        }

        case WM_DESTROY: {
            detruire_reseau(gReseau);
            DeleteObject(hBrushBackground);
            DeleteObject(hBrushGroup);
            DeleteObject(hPenBorder);
            DeleteObject(hFontTitle);
            DeleteObject(hFontNormal);
            DeleteObject(hFontSmall);
            PostQuitMessage(0);
            break;
        }
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
    HWND hConsole = GetConsoleWindow();
    if (hConsole != NULL) ShowWindow(hConsole, SW_HIDE);

    srand(time(NULL));

    const wchar_t CLASS_NAME[] = L"NeuralNetworkGUI";
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClass(&wc)) {
        MessageBoxW(NULL, L"Erreur lors de l'enregistrement de la classe", L"Erreur", MB_OK | MB_ICONERROR);
        return 1;
    }

    gHwndMain = CreateWindowExW(0, CLASS_NAME, L"Réseau Neuronal", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 900, 550, NULL, NULL, hInstance, NULL);
    if (!gHwndMain) {
        MessageBoxW(NULL, L"Erreur création fenêtre", L"Erreur", MB_OK | MB_ICONERROR);
        return 1;
    }

    ShowWindow(gHwndMain, nCmdShow);
    UpdateWindow(gHwndMain);

    Log("INFO", "========== RÉSEAU NEURONAL - INTERFACE GUI ==========\n");
    Log("INFO", "Topologie: dynamique (détectée depuis data.csv)\n");
    Log("INFO", "Fichier poids: %s\n", gFichierPoids.c_str());

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    Log("INFO", "========== PROGRAMME TERMINÉ ==========\n");
    return (int)msg.wParam;
}
 
Joined
Jun 12, 2020
Messages
62
Reaction score
3
This code implements a Windows graphical application for training and using an artificial neural network. It is a complete interface with a graphical user interface (GUI) that allows loading data, training a neural network model, saving and loading network weights, and making predictions.
The application uses the Windows API to create a graphical window with multiple control areas. It integrates a neural network with multiple layers, supporting different activation functions (ReLU and sigmoid), as well as normalization mechanisms (Layer Normalization and Batch Normalization). The network can be configured with a dynamic topology detected from a data file.
Network training is performed through an optimization loop using gradient backpropagation. The code includes important numerical stability mechanisms such as gradient clipping, handling of NaN/Inf values, and normalization techniques to improve convergence. The interface allows real-time visualization of training progress with display of MSE (Mean Squared Error) and learning rate.
The program also handles loading and saving network weights to a CSV file, as well as loading training data from a CSV file. The graphical interface is composed of three main sections: one for model training, one for prediction, and one for displaying system status.
The input data file (data.csv) must be formatted as a CSV with column headers representing input features and an output column. Each row contains numerical values corresponding to the input variables and their expected output value. The trained weights file (poids_entraines.csv) stores the network parameters in CSV format with columns specifying the layer number, neuron index, parameter type (bias or weight), the weight index within that neuron, and the numerical value of the parameter. This structure allows complete reconstruction of the trained network model.
The code is structured with functions for each aspect of the neural network (forward propagation, backpropagation, weight updates) as well as functions dedicated to the graphical interface. It uses threads to run training in the background without blocking the user interface. Memory management is also taken into account with appropriate destruction functions to free dynamically allocated resources.
The application includes robustness mechanisms such as NaN value checking, gradient clipping, and runtime error handling. It displays colored status messages to indicate system state (success, error, warning) and provides detailed training logs in a dedicated text area.
The code is licensed under the MIT License and includes all required legal notices. It uses modern Windows programming techniques such as custom Windows messages for inter-thread communication, and custom fonts and colors to improve user interface ergonomics.
 
Joined
Sep 20, 2022
Messages
306
Reaction score
41
After reading an article about a person struggling to get a neural network to play tic-tac-toe, I can see why source code like this is public domain.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,446
Messages
2,571,717
Members
48,796
Latest member
Greg L.
Top