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;
}