diff --git a/neuralNetwork.c b/neuralNetwork.c index 577735b..9cb6de8 100644 --- a/neuralNetwork.c +++ b/neuralNetwork.c @@ -2,41 +2,39 @@ #include #include #include -#include #include "neuralNetwork.h" -#define BUFFER_SIZE 200 +#define BUFFER_SIZE 100 #define FILE_HEADER_STRING "__info2_neural_network_file_format__" static void softmax(Matrix *matrix) { if(matrix->cols > 0) { - double *colSums = (double *)calloc((size_t)matrix->cols, sizeof(double)); - if(colSums == NULL) return; + double *colSums = (double *)calloc(matrix->cols, sizeof(double)); - for(int colIdx = 0; colIdx < matrix->cols; colIdx++) + if(colSums != NULL) { - for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) + for(int colIdx = 0; colIdx < matrix->cols; colIdx++) { - MatrixType expValue = (MatrixType)exp(getMatrixAt(*matrix, rowIdx, colIdx)); - setMatrixAt(expValue, *matrix, rowIdx, colIdx); - colSums[colIdx] += expValue; + for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) + { + MatrixType expValue = exp(getMatrixAt(*matrix, rowIdx, colIdx)); + setMatrixAt(expValue, *matrix, rowIdx, colIdx); + colSums[colIdx] += expValue; + } } - } - 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 colIdx = 0; colIdx < matrix->cols; colIdx++) { - MatrixType normalizedValue = (MatrixType)(getMatrixAt(*matrix, rowIdx, colIdx) / s); - setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx); + for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) + { + MatrixType normalizedValue = getMatrixAt(*matrix, rowIdx, colIdx) / colSums[colIdx]; + setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx); + } } + free(colSums); } - - free(colSums); } } @@ -48,53 +46,39 @@ static void relu(Matrix *matrix) } } -/* Prüft den Dateikopf. Liefert 1 bei Erfolg, 0 bei Fehler. */ 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(headerLen == 0 || headerLen >= BUFFER_SIZE) return 0; + if(BUFFER_SIZE-1 < fileHeaderLen) + fileHeaderLen = BUFFER_SIZE-1; - char buffer[BUFFER_SIZE]; - if(fseek(file, 0, SEEK_SET) != 0) return 0; - if(fread(buffer, sizeof(char), headerLen, file) != headerLen) return 0; - if(memcmp(buffer, FILE_HEADER_STRING, headerLen) != 0) return 0; + if(fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen) + isValid = strcmp(buffer, FILE_HEADER_STRING) == 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) { - if (file == NULL) return 0; + int dimension = 0; - int value = 0; - if (fread(&value, sizeof(int), 1, file) != 1) { - return 0; - } + if(fread(&dimension, sizeof(int), 1, file) != 1) + dimension = 0; - if (value < 0) return 0; - return (unsigned int)value; + return dimension; } - -/* Liest eine Matrix rows x cols; wenn Einlese-Fehler: leere Matrix zurückgeben */ static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int cols) { Matrix matrix = createMatrix(rows, cols); - if(matrix.buffer == NULL) return matrix; - size_t toRead = (size_t)rows * cols; - if(toRead > 0) + if(matrix.buffer != NULL) { - 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); - } } 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) { Layer layer; - layer.activation = NULL; layer.weights = readMatrix(file, outputDimension, inputDimension); - layer.biases = readMatrix(file, outputDimension, 1); + layer.biases = readMatrix(file, outputDimension, 1); + return layer; } static int isEmptyLayer(const Layer layer) { - 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; + 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; } static void clearLayer(Layer *layer) { - if(layer == NULL) return; - clearMatrix(&layer->weights); - clearMatrix(&layer->biases); - layer->activation = NULL; + if(layer != NULL) + { + clearMatrix(&layer->weights); + clearMatrix(&layer->biases); + layer->activation = NULL; + } } 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[model.numberOfLayers - 1].activation = softmax; + } + + if(model.numberOfLayers > 0) + model.layers[model.numberOfLayers-1].activation = softmax; } NeuralNetwork loadModel(const char *path) { NeuralNetwork model = {NULL, 0}; FILE *file = fopen(path, "rb"); - if(file == NULL) return model; - if(fseek(file, 0, SEEK_SET) != 0) + if(file != NULL) { + if(checkFileHeader(file)) + { + unsigned int inputDimension = readDimension(file); + unsigned int outputDimension = readDimension(file); + + while(inputDimension > 0 && outputDimension > 0) + { + Layer layer = readLayer(file, inputDimension, outputDimension); + Layer *layerBuffer = NULL; + + if(isEmptyLayer(layer)) + { + clearLayer(&layer); + clearModel(&model); + break; + } + + layerBuffer = (Layer *)realloc(model.layers, (model.numberOfLayers + 1) * sizeof(Layer)); + + if(layerBuffer != NULL) + model.layers = layerBuffer; + else + { + clearModel(&model); + break; + } + + model.layers[model.numberOfLayers] = layer; + model.numberOfLayers++; + + inputDimension = outputDimension; + outputDimension = readDimension(file); + } + } fclose(file); - return model; + + assignActivations(model); } - if(!checkFileHeader(file)) - { - fclose(file); - return model; - } - - /* Lese erstes Dimensions-Paar (input, output) */ - unsigned int inputDimension = readDimension(file); - unsigned int outputDimension = readDimension(file); - - //fprintf(stderr, "[loadModel] first dims: input=%u output=%u\n", inputDimension, outputDimension); - - while (inputDimension > 0 && outputDimension > 0) - { - Layer layer = readLayer(file, inputDimension, outputDimension); - - if (isEmptyLayer(layer)) - { - clearLayer(&layer); - clearModel(&model); - break; - } - - Layer *tmp = (Layer *)realloc(model.layers, (model.numberOfLayers + 1) * sizeof(Layer)); - if (tmp == NULL) - { - clearLayer(&layer); - clearModel(&model); - break; - } - model.layers = tmp; - model.layers[model.numberOfLayers] = layer; - model.numberOfLayers++; - - /* fprintf(stderr, "[loadModel] loaded layer %d: weights %u x %u, biases %u x %u\n", - model.numberOfLayers, - 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); - assignActivations(model); return model; } 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.buffer = NULL; matrix.rows = 0; @@ -218,12 +180,15 @@ static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], un if(count > 0 && images != NULL) { matrix = createMatrix(images[0].height * images[0].width, count); + if(matrix.buffer != NULL) { - for(unsigned int i = 0; i < count; i++) + for(int i = 0; i < count; i++) { - for(unsigned int j = 0; j < images[i].width * images[i].height; j++) + for(int j = 0; j < images[i].width * images[i].height; j++) + { setMatrixAt((MatrixType)images[i].buffer[j], matrix, j, i); + } } } } @@ -234,20 +199,23 @@ static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], un static Matrix forward(const NeuralNetwork model, Matrix inputBatch) { Matrix result = inputBatch; - if(result.buffer == NULL) return result; - for(int i = 0; i < model.numberOfLayers; i++) + if(result.buffer != NULL) { - Matrix weightResult = multiply(model.layers[i].weights, result); - clearMatrix(&result); + for(int i = 0; i < model.numberOfLayers; i++) + { + Matrix biasResult; + Matrix weightResult; - Matrix biasResult = add(weightResult, model.layers[i].biases); - clearMatrix(&weightResult); + weightResult = multiply(model.layers[i].weights, result); + clearMatrix(&result); + biasResult = add(model.layers[i].biases, weightResult); + clearMatrix(&weightResult); - if(model.layers[i].activation != NULL) - model.layers[i].activation(&biasResult); - - result = biasResult; + if(model.layers[i].activation != NULL) + model.layers[i].activation(&biasResult); + result = biasResult; + } } return result; @@ -255,21 +223,27 @@ static Matrix forward(const NeuralNetwork model, Matrix inputBatch) 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(maxIdx == NULL) return NULL; - - for(int colIdx = 0; colIdx < matrix.cols; colIdx++) + if(matrix.rows > 0 && matrix.cols > 0) { - int best = 0; - for(int rowIdx = 1; rowIdx < matrix.rows; rowIdx++) + maxIdx = (unsigned char *)malloc(sizeof(unsigned char) * matrix.cols); + + if(maxIdx != NULL) { - if(getMatrixAt(matrix, rowIdx, colIdx) > getMatrixAt(matrix, best, colIdx)) - best = rowIdx; + for(int colIdx = 0; colIdx < matrix.cols; colIdx++) + { + maxIdx[colIdx] = 0; + + for(int rowIdx = 1; rowIdx < matrix.rows; rowIdx++) + { + if(getMatrixAt(matrix, rowIdx, colIdx) > getMatrixAt(matrix, maxIdx[colIdx], colIdx)) + maxIdx[colIdx] = rowIdx; + } + } } - maxIdx[colIdx] = (unsigned char)best; } + return maxIdx; } @@ -277,17 +251,23 @@ unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[], { Matrix inputBatch = imageBatchToMatrixOfImageVectors(images, numberOfImages); Matrix outputBatch = forward(model, inputBatch); + unsigned char *result = argmax(outputBatch); + clearMatrix(&outputBatch); + return result; } void clearModel(NeuralNetwork *model) { - if(model == NULL) return; - for(int i = 0; i < model->numberOfLayers; i++) - clearLayer(&model->layers[i]); - free(model->layers); - model->layers = NULL; - model->numberOfLayers = 0; -} + if(model != NULL) + { + for(int i = 0; i < model->numberOfLayers; i++) + { + clearLayer(&model->layers[i]); + } + model->layers = NULL; + model->numberOfLayers = 0; + } +} \ No newline at end of file diff --git a/neuralNetworkTests.c b/neuralNetworkTests.c index c7817a3..2d151f5 100644 --- a/neuralNetworkTests.c +++ b/neuralNetworkTests.c @@ -9,48 +9,51 @@ static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) { - // TODO FILE *file = fopen(path, "wb"); if (!file) return; // 1) Header-Tag WORTGENAU (OHNE Nullterminator) schreiben 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 - // inputDim == weights.cols - // outputDim == weights.rows + // 2) Layer-Daten im Format, das loadModel() erwartet for (unsigned int i = 0; i < nn.numberOfLayers; ++i) { const Layer *lay = &nn.layers[i]; - unsigned int inputDim = lay->weights.cols; - unsigned int outputDim = lay->weights.rows; + int inputDim = (int)lay->weights.cols; // cols == inputDimension + int outputDim = (int)lay->weights.rows; // rows == outputDimension - // schreibe Dimensionen (4-Byte int / unsigned int) - fwrite(&inputDim, sizeof(unsigned int), 1, file); - fwrite(&outputDim, sizeof(unsigned int), 1, file); + if (i == 0) { + // Erstes Paar: input und output schreiben + 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; if (weightCount > 0 && lay->weights.buffer != NULL) { 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; if (biasCount > 0 && lay->biases.buffer != NULL) { fwrite(lay->biases.buffer, sizeof(MatrixType), biasCount, file); } } - // 3) Endmarkierung: nächstes Input / Output = 0, damit loadModel() die Schleife beendet - unsigned int zero = 0; - fwrite(&zero, sizeof(unsigned int), 1, file); - fwrite(&zero, sizeof(unsigned int), 1, file); + // 3) Endmarkierung: EINE 0 (als int) schreiben + int zero = 0; + fwrite(&zero, sizeof(int), 1, file); fclose(file); } + + void test_loadModelReturnsCorrectNumberOfLayers(void) { const char *path = "some__nn_test_file.info2";