So meine Freunde jetzt passt das aber auch

This commit is contained in:
Jonas Stamm 2025-11-16 18:03:33 +01:00
parent 49977a86c5
commit b4c18bd1b1
2 changed files with 150 additions and 167 deletions

View File

@ -2,24 +2,24 @@
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
#include <stdint.h>
#include "neuralNetwork.h" #include "neuralNetwork.h"
#define BUFFER_SIZE 200 #define BUFFER_SIZE 100
#define FILE_HEADER_STRING "__info2_neural_network_file_format__" #define FILE_HEADER_STRING "__info2_neural_network_file_format__"
static void softmax(Matrix *matrix) static void softmax(Matrix *matrix)
{ {
if(matrix->cols > 0) if(matrix->cols > 0)
{ {
double *colSums = (double *)calloc((size_t)matrix->cols, sizeof(double)); double *colSums = (double *)calloc(matrix->cols, sizeof(double));
if(colSums == NULL) return;
if(colSums != NULL)
{
for(int colIdx = 0; colIdx < matrix->cols; colIdx++) for(int colIdx = 0; colIdx < matrix->cols; colIdx++)
{ {
for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++)
{ {
MatrixType expValue = (MatrixType)exp(getMatrixAt(*matrix, rowIdx, colIdx)); MatrixType expValue = exp(getMatrixAt(*matrix, rowIdx, colIdx));
setMatrixAt(expValue, *matrix, rowIdx, colIdx); setMatrixAt(expValue, *matrix, rowIdx, colIdx);
colSums[colIdx] += expValue; colSums[colIdx] += expValue;
} }
@ -27,17 +27,15 @@ static void softmax(Matrix *matrix)
for(int colIdx = 0; colIdx < matrix->cols; colIdx++) for(int colIdx = 0; colIdx < matrix->cols; colIdx++)
{ {
double s = colSums[colIdx];
if(s == 0.0) s = 1.0;
for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++)
{ {
MatrixType normalizedValue = (MatrixType)(getMatrixAt(*matrix, rowIdx, colIdx) / s); MatrixType normalizedValue = getMatrixAt(*matrix, rowIdx, colIdx) / colSums[colIdx];
setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx); setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx);
} }
} }
free(colSums); free(colSums);
} }
}
} }
static void relu(Matrix *matrix) static void relu(Matrix *matrix)
@ -48,54 +46,40 @@ static void relu(Matrix *matrix)
} }
} }
/* Prüft den Dateikopf. Liefert 1 bei Erfolg, 0 bei Fehler. */
static int checkFileHeader(FILE *file) static int checkFileHeader(FILE *file)
{ {
if(file == NULL) return 0; int isValid = 0;
int fileHeaderLen = strlen(FILE_HEADER_STRING);
char buffer[BUFFER_SIZE] = {0};
size_t headerLen = strlen(FILE_HEADER_STRING); if(BUFFER_SIZE-1 < fileHeaderLen)
if(headerLen == 0 || headerLen >= BUFFER_SIZE) return 0; fileHeaderLen = BUFFER_SIZE-1;
char buffer[BUFFER_SIZE]; if(fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen)
if(fseek(file, 0, SEEK_SET) != 0) return 0; isValid = strcmp(buffer, FILE_HEADER_STRING) == 0;
if(fread(buffer, sizeof(char), headerLen, file) != headerLen) return 0;
if(memcmp(buffer, FILE_HEADER_STRING, headerLen) != 0) return 0;
return 1; return isValid;
} }
/* Liest eine Dimension (wie vom Test-Writer mit sizeof(int) geschrieben) und prüft Plausibilität.
Liefert 0 bei Lesefehler oder wenn der gelesene Wert offensichtlich nicht in die verbleibende Dateigröße passt.
*/
static unsigned int readDimension(FILE *file) static unsigned int readDimension(FILE *file)
{ {
if (file == NULL) return 0; int dimension = 0;
int value = 0; if(fread(&dimension, sizeof(int), 1, file) != 1)
if (fread(&value, sizeof(int), 1, file) != 1) { dimension = 0;
return 0;
}
if (value < 0) return 0; return dimension;
return (unsigned int)value;
} }
/* Liest eine Matrix rows x cols; wenn Einlese-Fehler: leere Matrix zurückgeben */
static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int cols) static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int cols)
{ {
Matrix matrix = createMatrix(rows, cols); Matrix matrix = createMatrix(rows, cols);
if(matrix.buffer == NULL) return matrix;
size_t toRead = (size_t)rows * cols; if(matrix.buffer != NULL)
if(toRead > 0)
{
size_t readCount = fread(matrix.buffer, sizeof(MatrixType), toRead, file);
if(readCount != toRead)
{ {
if(fread(matrix.buffer, sizeof(MatrixType), rows*cols, file) != rows*cols)
clearMatrix(&matrix); clearMatrix(&matrix);
} }
}
return matrix; return matrix;
} }
@ -103,113 +87,91 @@ static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int cols)
static Layer readLayer(FILE *file, unsigned int inputDimension, unsigned int outputDimension) static Layer readLayer(FILE *file, unsigned int inputDimension, unsigned int outputDimension)
{ {
Layer layer; Layer layer;
layer.activation = NULL;
layer.weights = readMatrix(file, outputDimension, inputDimension); layer.weights = readMatrix(file, outputDimension, inputDimension);
layer.biases = readMatrix(file, outputDimension, 1); layer.biases = readMatrix(file, outputDimension, 1);
return layer; return layer;
} }
static int isEmptyLayer(const Layer layer) static int isEmptyLayer(const Layer layer)
{ {
return layer.biases.cols == 0 || layer.biases.rows == 0 || layer.biases.buffer == NULL || return layer.biases.cols == 0 || layer.biases.rows == 0 || layer.biases.buffer == NULL || layer.weights.rows == 0 || layer.weights.cols == 0 || layer.weights.buffer == NULL;
layer.weights.rows == 0 || layer.weights.cols == 0 || layer.weights.buffer == NULL;
} }
static void clearLayer(Layer *layer) static void clearLayer(Layer *layer)
{ {
if(layer == NULL) return; if(layer != NULL)
{
clearMatrix(&layer->weights); clearMatrix(&layer->weights);
clearMatrix(&layer->biases); clearMatrix(&layer->biases);
layer->activation = NULL; layer->activation = NULL;
}
} }
static void assignActivations(NeuralNetwork model) static void assignActivations(NeuralNetwork model)
{ {
if(model.numberOfLayers == 0) return; for(int i = 0; i < (int)model.numberOfLayers-1; i++)
for(int i = 0; i < (int)model.numberOfLayers - 1; i++) {
model.layers[i].activation = relu; model.layers[i].activation = relu;
model.layers[model.numberOfLayers - 1].activation = softmax; }
if(model.numberOfLayers > 0)
model.layers[model.numberOfLayers-1].activation = softmax;
} }
NeuralNetwork loadModel(const char *path) NeuralNetwork loadModel(const char *path)
{ {
NeuralNetwork model = {NULL, 0}; NeuralNetwork model = {NULL, 0};
FILE *file = fopen(path, "rb"); FILE *file = fopen(path, "rb");
if(file == NULL) return model;
if(fseek(file, 0, SEEK_SET) != 0) if(file != NULL)
{ {
fclose(file); if(checkFileHeader(file))
return model;
}
if(!checkFileHeader(file))
{ {
fclose(file);
return model;
}
/* Lese erstes Dimensions-Paar (input, output) */
unsigned int inputDimension = readDimension(file); unsigned int inputDimension = readDimension(file);
unsigned int outputDimension = readDimension(file); unsigned int outputDimension = readDimension(file);
//fprintf(stderr, "[loadModel] first dims: input=%u output=%u\n", inputDimension, outputDimension); while(inputDimension > 0 && outputDimension > 0)
while (inputDimension > 0 && outputDimension > 0)
{ {
Layer layer = readLayer(file, inputDimension, outputDimension); Layer layer = readLayer(file, inputDimension, outputDimension);
Layer *layerBuffer = NULL;
if (isEmptyLayer(layer)) if(isEmptyLayer(layer))
{ {
clearLayer(&layer); clearLayer(&layer);
clearModel(&model); clearModel(&model);
break; break;
} }
Layer *tmp = (Layer *)realloc(model.layers, (model.numberOfLayers + 1) * sizeof(Layer)); layerBuffer = (Layer *)realloc(model.layers, (model.numberOfLayers + 1) * sizeof(Layer));
if (tmp == NULL)
if(layerBuffer != NULL)
model.layers = layerBuffer;
else
{ {
clearLayer(&layer);
clearModel(&model); clearModel(&model);
break; break;
} }
model.layers = tmp;
model.layers[model.numberOfLayers] = layer; model.layers[model.numberOfLayers] = layer;
model.numberOfLayers++; model.numberOfLayers++;
/* fprintf(stderr, "[loadModel] loaded layer %d: weights %u x %u, biases %u x %u\n", inputDimension = outputDimension;
model.numberOfLayers, outputDimension = readDimension(file);
layer.weights.rows, layer.weights.cols,
layer.biases.rows, layer.biases.cols); */
/* Lese das nächste Dimensions-Paar (writer schreibt für jede Schicht ein Paar) */
unsigned int nextInput = readDimension(file);
unsigned int nextOutput = readDimension(file);
//fprintf(stderr, "[loadModel] next raw dims read: nextInput=%u nextOutput=%u\n", nextInput, nextOutput);
/* Wenn nächstes Paar (0,0) -> Ende */
if (nextInput == 0 || nextOutput == 0)
{
inputDimension = 0;
outputDimension = 0;
break;
} }
/* Setze für die nächste Iteration */
inputDimension = nextInput;
outputDimension = nextOutput;
//fprintf(stderr, "[loadModel] next dims: input=%u output=%u\n", inputDimension, outputDimension);
} }
fclose(file); fclose(file);
assignActivations(model); assignActivations(model);
}
return model; return model;
} }
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count) static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
{ {
//Matrix matrix = {NULL, 0, 0};
// Explizite Initialisierung verwenden, um die Feldreihenfolge in matrix.h zu umgehen:
Matrix matrix; Matrix matrix;
matrix.buffer = NULL; matrix.buffer = NULL;
matrix.rows = 0; matrix.rows = 0;
@ -218,15 +180,18 @@ static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], un
if(count > 0 && images != NULL) if(count > 0 && images != NULL)
{ {
matrix = createMatrix(images[0].height * images[0].width, count); matrix = createMatrix(images[0].height * images[0].width, count);
if(matrix.buffer != NULL) if(matrix.buffer != NULL)
{ {
for(unsigned int i = 0; i < count; i++) for(int i = 0; i < count; i++)
{
for(int j = 0; j < images[i].width * images[i].height; j++)
{ {
for(unsigned int j = 0; j < images[i].width * images[i].height; j++)
setMatrixAt((MatrixType)images[i].buffer[j], matrix, j, i); setMatrixAt((MatrixType)images[i].buffer[j], matrix, j, i);
} }
} }
} }
}
return matrix; return matrix;
} }
@ -234,42 +199,51 @@ static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], un
static Matrix forward(const NeuralNetwork model, Matrix inputBatch) static Matrix forward(const NeuralNetwork model, Matrix inputBatch)
{ {
Matrix result = inputBatch; Matrix result = inputBatch;
if(result.buffer == NULL) return result;
if(result.buffer != NULL)
{
for(int i = 0; i < model.numberOfLayers; i++) for(int i = 0; i < model.numberOfLayers; i++)
{ {
Matrix weightResult = multiply(model.layers[i].weights, result); Matrix biasResult;
clearMatrix(&result); Matrix weightResult;
Matrix biasResult = add(weightResult, model.layers[i].biases); weightResult = multiply(model.layers[i].weights, result);
clearMatrix(&result);
biasResult = add(model.layers[i].biases, weightResult);
clearMatrix(&weightResult); clearMatrix(&weightResult);
if(model.layers[i].activation != NULL) if(model.layers[i].activation != NULL)
model.layers[i].activation(&biasResult); model.layers[i].activation(&biasResult);
result = biasResult; result = biasResult;
} }
}
return result; return result;
} }
unsigned char *argmax(const Matrix matrix) unsigned char *argmax(const Matrix matrix)
{ {
if(matrix.rows == 0 || matrix.cols == 0) return NULL; unsigned char *maxIdx = NULL;
unsigned char *maxIdx = (unsigned char *)malloc((size_t)matrix.cols * sizeof(unsigned char)); if(matrix.rows > 0 && matrix.cols > 0)
if(maxIdx == NULL) return NULL; {
maxIdx = (unsigned char *)malloc(sizeof(unsigned char) * matrix.cols);
if(maxIdx != NULL)
{
for(int colIdx = 0; colIdx < matrix.cols; colIdx++) for(int colIdx = 0; colIdx < matrix.cols; colIdx++)
{ {
int best = 0; maxIdx[colIdx] = 0;
for(int rowIdx = 1; rowIdx < matrix.rows; rowIdx++) for(int rowIdx = 1; rowIdx < matrix.rows; rowIdx++)
{ {
if(getMatrixAt(matrix, rowIdx, colIdx) > getMatrixAt(matrix, best, colIdx)) if(getMatrixAt(matrix, rowIdx, colIdx) > getMatrixAt(matrix, maxIdx[colIdx], colIdx))
best = rowIdx; maxIdx[colIdx] = rowIdx;
} }
maxIdx[colIdx] = (unsigned char)best;
} }
}
}
return maxIdx; return maxIdx;
} }
@ -277,17 +251,23 @@ unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[],
{ {
Matrix inputBatch = imageBatchToMatrixOfImageVectors(images, numberOfImages); Matrix inputBatch = imageBatchToMatrixOfImageVectors(images, numberOfImages);
Matrix outputBatch = forward(model, inputBatch); Matrix outputBatch = forward(model, inputBatch);
unsigned char *result = argmax(outputBatch); unsigned char *result = argmax(outputBatch);
clearMatrix(&outputBatch); clearMatrix(&outputBatch);
return result; return result;
} }
void clearModel(NeuralNetwork *model) void clearModel(NeuralNetwork *model)
{ {
if(model == NULL) return; if(model != NULL)
{
for(int i = 0; i < model->numberOfLayers; i++) for(int i = 0; i < model->numberOfLayers; i++)
{
clearLayer(&model->layers[i]); clearLayer(&model->layers[i]);
free(model->layers); }
model->layers = NULL; model->layers = NULL;
model->numberOfLayers = 0; model->numberOfLayers = 0;
}
} }

View File

@ -9,48 +9,51 @@
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
{ {
// TODO
FILE *file = fopen(path, "wb"); FILE *file = fopen(path, "wb");
if (!file) return; if (!file) return;
// 1) Header-Tag WORTGENAU (OHNE Nullterminator) schreiben // 1) Header-Tag WORTGENAU (OHNE Nullterminator) schreiben
fwrite(FILE_HEADER_STRING, sizeof(char), strlen(FILE_HEADER_STRING), file); fwrite(FILE_HEADER_STRING, sizeof(char), strlen(FILE_HEADER_STRING), file);
// 2) Für jeden Layer die Paare (inputDim, outputDim) und dann Gewichte & Biases schreiben // 2) Layer-Daten im Format, das loadModel() erwartet
// inputDim == weights.cols
// outputDim == weights.rows
for (unsigned int i = 0; i < nn.numberOfLayers; ++i) for (unsigned int i = 0; i < nn.numberOfLayers; ++i)
{ {
const Layer *lay = &nn.layers[i]; const Layer *lay = &nn.layers[i];
unsigned int inputDim = lay->weights.cols; int inputDim = (int)lay->weights.cols; // cols == inputDimension
unsigned int outputDim = lay->weights.rows; int outputDim = (int)lay->weights.rows; // rows == outputDimension
// schreibe Dimensionen (4-Byte int / unsigned int) if (i == 0) {
fwrite(&inputDim, sizeof(unsigned int), 1, file); // Erstes Paar: input und output schreiben
fwrite(&outputDim, sizeof(unsigned int), 1, file); fwrite(&inputDim, sizeof(int), 1, file);
fwrite(&outputDim, sizeof(int), 1, file);
} else {
// Ab dem zweiten Layer: NUR das neue outputDimension schreiben
fwrite(&outputDim, sizeof(int), 1, file);
}
// schreibe Gewichtsmatrix (row-major: rows*cols Elemente vom Typ MatrixType) // Gewichtsmatrix (row-major)
size_t weightCount = (size_t)lay->weights.rows * (size_t)lay->weights.cols; size_t weightCount = (size_t)lay->weights.rows * (size_t)lay->weights.cols;
if (weightCount > 0 && lay->weights.buffer != NULL) { if (weightCount > 0 && lay->weights.buffer != NULL) {
fwrite(lay->weights.buffer, sizeof(MatrixType), weightCount, file); fwrite(lay->weights.buffer, sizeof(MatrixType), weightCount, file);
} }
// schreibe Biasmatrix (rows * cols) - normalerweise rows x 1 // Biases (rows x 1)
size_t biasCount = (size_t)lay->biases.rows * (size_t)lay->biases.cols; size_t biasCount = (size_t)lay->biases.rows * (size_t)lay->biases.cols;
if (biasCount > 0 && lay->biases.buffer != NULL) { if (biasCount > 0 && lay->biases.buffer != NULL) {
fwrite(lay->biases.buffer, sizeof(MatrixType), biasCount, file); fwrite(lay->biases.buffer, sizeof(MatrixType), biasCount, file);
} }
} }
// 3) Endmarkierung: nächstes Input / Output = 0, damit loadModel() die Schleife beendet // 3) Endmarkierung: EINE 0 (als int) schreiben
unsigned int zero = 0; int zero = 0;
fwrite(&zero, sizeof(unsigned int), 1, file); fwrite(&zero, sizeof(int), 1, file);
fwrite(&zero, sizeof(unsigned int), 1, file);
fclose(file); fclose(file);
} }
void test_loadModelReturnsCorrectNumberOfLayers(void) void test_loadModelReturnsCorrectNumberOfLayers(void)
{ {
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";