4 Commits
Author SHA1 Message Date
kachelto100370 786aa2e6d8 working on imageinput 2025-11-25 10:53:28 +01:00
kachelto100370 58df4199b5 smal gitignore 2025-11-23 16:33:44 +01:00
kachelto100370 fb18b75b60 working on imputimage 0 test passing 2025-11-23 16:33:22 +01:00
kachelto100370 7f3c6d1d3f first pass matrix add, ohne broadcasting 2025-11-20 16:04:01 +01:00
7 changed files with 511 additions and 694 deletions
+6
View File
@@ -2,3 +2,9 @@ mnist
runTests runTests
*.o *.o
*.exe *.exe
.vscode/settings.json
.vscode/launch.json
.vscode/settings.json
.vscode/settings.json
runImageInputTests
testFile.info2
+29 -3
View File
@@ -15,10 +15,36 @@ GrayScaleImage readImage()
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen // TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
GrayScaleImageSeries *readImages(const char *path) GrayScaleImageSeries *readImages(const char *path)
{ {
unsigned short * numImages;
unsigned short * breiteBilder;
unsigned short * laengeBilder;
GrayScaleImageSeries *series = NULL; GrayScaleImageSeries *series = NULL;
FILE *file = fopen("mnist_test.info2","rb"); FILE *file = fopen(*path,"rb");
char headOfFile; char * headOfFile;
series = malloc();
fread(headOfFile, sizeof(FILE_HEADER_STRING),1, file); //liest den header ein und überprüft ob korrekte datei
if(strcmp(headOfFile, FILE_HEADER_STRING) != 0)
return NULL;
// liest numIMages, breite und länge der Bilder ein
fseek(file, sizeof(FILE_HEADER_STRING), SEEK_SET);
fread(numImages, sizeof(short), 1, file);
fseek(file, sizeof(short), SEEK_CUR);
fread(breiteBilder, sizeof(short), 1, file);
fseek(file, sizeof(short), SEEK_CUR);
fread(laengeBilder, sizeof(short), 1, file);
series = malloc(*numImages * *breiteBilder * *laengeBilder * sizeof(short));
for(int i = 0; i < numImages; i++)
{
}
return series; return series;
} }
+3
View File
@@ -63,3 +63,6 @@ ifeq ($(OS),Windows_NT)
else else
rm -f *.o mnist runMatrixTests runNeuralNetworkTests runImageInputTests rm -f *.o mnist runMatrixTests runNeuralNetworkTests runImageInputTests
endif endif
clean für windows
clean:
rm -f *.o *.exe
+19 -199
View File
@@ -1,8 +1,6 @@
#include "matrix.h" #include "matrix.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
// TODO Matrix-Funktionen implementieren // TODO Matrix-Funktionen implementieren
/*typedef struct { /*typedef struct {
unsigned int rows; //Zeilen unsigned int rows; //Zeilen
@@ -10,12 +8,6 @@
MatrixType *buffer; //Zeiger auf Speicherbereich Reihen*Spalten MatrixType *buffer; //Zeiger auf Speicherbereich Reihen*Spalten
} Matrix;*/ } Matrix;*/
Matrix createMatrix(unsigned int rows, unsigned int cols) { Matrix createMatrix(unsigned int rows, unsigned int cols) {
Matrix errorMatrix = {0, 0, NULL};
if (rows == 0 || cols == 0) {
return errorMatrix;
}
MatrixType *buffer = MatrixType *buffer =
malloc(rows * cols * sizeof(MatrixType)); // Speicher reservieren, malloc malloc(rows * cols * sizeof(MatrixType)); // Speicher reservieren, malloc
// liefert Zeiger auf Speicher // liefert Zeiger auf Speicher
@@ -28,16 +20,10 @@ void clearMatrix(Matrix *matrix) {
matrix->cols = UNDEFINED_MATRIX_VALUE; matrix->cols = UNDEFINED_MATRIX_VALUE;
free((*matrix).buffer); // Speicher freigeben free((*matrix).buffer); // Speicher freigeben
} }
void setMatrixAt(const MatrixType value, Matrix matrix, void setMatrixAt(const MatrixType value, Matrix matrix,
const unsigned int rowIdx, // Kopie der Matrix wird übergeben const unsigned int rowIdx, // Kopie der Matrix wird übergeben
const unsigned int colIdx) { const unsigned int colIdx) {
if (rowIdx >= matrix.rows ||
colIdx >= matrix.cols) { // Speichergröße nicht überschreiten
return;
}
matrix.buffer[rowIdx * matrix.cols + colIdx] = matrix.buffer[rowIdx * matrix.cols + colIdx] =
value; // rowIdx * matrix.cols -> Beginn der Zeile colIdx ->Spalte value; // rowIdx * matrix.cols -> Beginn der Zeile colIdx ->Spalte
// innerhalb der Zeile // innerhalb der Zeile
@@ -54,195 +40,29 @@ MatrixType getMatrixAt(const Matrix matrix,
return value; return value;
} }
Matrix broadCastCols(const Matrix matrix, const unsigned int rows,
const unsigned int cols) {
Matrix copy = createMatrix(
rows, cols); // Matrix 1 Kopie erstellen mit Dimensionen von Matrix2
for (int r = 0; r < rows; r++) {
MatrixType value = getMatrixAt(matrix, r, 0);
for (int c = 0; c < cols; c++) {
setMatrixAt(value, copy, r, c);
}
}
return copy;
}
Matrix broadCastRows(const Matrix matrix, const unsigned int rows,
const unsigned int cols) {
Matrix copy = createMatrix(rows, cols);
for (int c = 0; c < cols; c++) {
MatrixType value = getMatrixAt(matrix, 0, c);
for (int r = 0; r < rows; r++) {
setMatrixAt(value, copy, r, c);
}
}
return copy;
}
Matrix add(const Matrix matrix1, const Matrix matrix2) { Matrix add(const Matrix matrix1, const Matrix matrix2) {
Matrix result;
// Broadcasting nur bei Vektor und Matrix, Fehlermeldung bei zwei unpassenden const int cols1 = matrix1.cols;
// Matrizen const int rows1 = matrix1.rows;
const int cols2 = matrix2.cols;
const unsigned int rows1 = matrix1.rows; const int rows2 = matrix2.rows;
const unsigned int rows2 = matrix2.rows; const int colsEqu = (matrix1.cols == matrix2.cols) ? 1 : 0;
const unsigned int cols1 = matrix1.cols; const int rowsEqu = (matrix1.rows == matrix2.rows) ? 1 : 0;
const unsigned int cols2 = matrix2.cols; if(colsEqu && rowsEqu)
const int rowsEqual = ((rows1 == rows2) ? 1 : 0);
const int colsEqual = ((cols1 == cols2) ? 1 : 0);
if (rowsEqual && colsEqual) // addieren
{ {
Matrix result = createMatrix(rows1, cols1); // Speicher reservieren Matrix result = createMatrix(matrix1.rows, matrix1.cols);
for(int i = 0; i < rows1; i++)
for (int i = 0; i < (rows1 * cols1); i++) { // addieren {
for (int j = 0; j < cols1; j++)
result.buffer[i] = {
(matrix1.buffer[i] + int valueM1 = getMatrixAt(matrix1, i, j);
matrix2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse = int valueM2 =getMatrixAt(matrix2, i, j);
// Startadresse + (i * sizeof(MatrixType)) int sum = valueM1 + valueM2;
setMatrixAt(sum, result,i,j);
}
} }
return result; // zurückgeben
}
else if (rowsEqual && !colsEqual) {
if (cols1 == 1) {
Matrix result = createMatrix(rows2, cols2);
Matrix copy1 = broadCastCols(matrix1, rows2, cols2);
for (int i = 0; i < (rows2 * cols2); i++) { // addieren
result.buffer[i] =
(copy1.buffer[i] +
matrix2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse =
// Startadresse + (i * sizeof(MatrixType))
}
return result; return result;
// add und return
} else if (cols2 == 1) {
Matrix result = createMatrix(rows1, cols1);
Matrix copy2 = broadCastCols(matrix2, rows1, cols1);
for (int i = 0; i < (rows1 * cols1); i++) { // addieren
result.buffer[i] =
(matrix1.buffer[i] +
copy2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse =
// Startadresse + (i * sizeof(MatrixType))
}
return result;
// add und return
}
else {
printf("Fehlermeldung"); // vielleicht Fehlermeldung ändern zu
// Programmabbruch
Matrix error = {0, 0, NULL};
return error;
}
}
else if (!rowsEqual && colsEqual) {
if (rows1 == 1) {
Matrix result = createMatrix(rows2, cols2);
Matrix copy1 = broadCastRows(matrix1, rows2, cols2);
for (int i = 0; i < (rows2 * cols2); i++) { // addieren
result.buffer[i] =
(copy1.buffer[i] +
matrix2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse =
// Startadresse + (i * sizeof(MatrixType))
}
return result;
// add und return
} else if (rows2 == 1) {
Matrix result = createMatrix(rows1, cols1);
Matrix copy2 = broadCastCols(matrix2, rows1, cols1);
// add und return
for (int i = 0; i < (rows1 * cols1); i++) { // addieren
result.buffer[i] =
(matrix1.buffer[i] +
copy2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse =
// Startadresse + (i * sizeof(MatrixType))
}
return result;
}
else {
printf("Fehlermeldung"); // vielleicht Fehlermeldung ändern zu
// Programmabbruch
Matrix error = {0, 0, NULL};
return error;
}
}
else {
printf(
"Fehlermeldung"); // vielleicht Fehlermeldung ändern zu Programmabbruch
Matrix error = {0, 0, NULL};
return error;
}
}
Matrix multiply(const Matrix matrix1, const Matrix matrix2) {
// Spalten1 müssen gleich zeilen2 sein! dann multiplizieren
if (matrix1.cols == matrix2.rows) {
Matrix multMatrix = createMatrix(matrix1.rows, matrix2.cols);
// durch neue matrix iterieren
for (int r = 0; r < matrix1.rows; r++) {
for (int c = 0; c < matrix2.cols; c++) {
MatrixType sum = 0.0;
// skalarprodukte berechnen, k damit die ganze zeile mal die ganze
// spalte genommen wird quasi
for (int k = 0; k < matrix1.cols; k++) {
// sum+=
// matrix1.buffer[r*matrix1.cols+k]*matrix2.buffer[k*matrix2.cols+c];
sum += getMatrixAt(matrix1, r, k) * getMatrixAt(matrix2, k, c);
}
// Ergebnisse in neue matrix speichern
setMatrixAt(sum, multMatrix, r, c);
}
}
return multMatrix;
}
// sonst fehler, kein multiply möglich
else {
Matrix errorMatrix = {0, 0, NULL};
return errorMatrix;
} }
} }
Matrix multiply(const Matrix matrix1, const Matrix matrix2) { return matrix1; }
-5
View File
@@ -19,11 +19,6 @@ void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx,
unsigned int colIdx); unsigned int colIdx);
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx,
unsigned int colIdx); unsigned int colIdx);
Matrix broadCastCols(const Matrix matrix, const unsigned int rows,
const unsigned int cols);
Matrix broadCastRows(const Matrix matrix, const unsigned int rows,
const unsigned int cols);
Matrix add(const Matrix matrix1, const Matrix matrix2); Matrix add(const Matrix matrix1, const Matrix matrix2);
Matrix multiply(const Matrix matrix1, const Matrix matrix2); Matrix multiply(const Matrix matrix1, const Matrix matrix2);
+93 -60
View File
@@ -1,29 +1,35 @@
#include "neuralNetwork.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h> #include <string.h>
#include "neuralNetwork.h"
#define BUFFER_SIZE 100 #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(matrix->cols, sizeof(double)); double *colSums = (double *)calloc(matrix->cols, sizeof(double));
if (colSums != NULL) { if(colSums != NULL)
for (int colIdx = 0; colIdx < matrix->cols; colIdx++) { {
for (int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) { for(int colIdx = 0; colIdx < matrix->cols; colIdx++)
{
for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++)
{
MatrixType expValue = 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;
} }
} }
for (int colIdx = 0; colIdx < matrix->cols; colIdx++) { for(int colIdx = 0; colIdx < matrix->cols; colIdx++)
for (int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) { {
MatrixType normalizedValue = for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++)
getMatrixAt(*matrix, rowIdx, colIdx) / colSums[colIdx]; {
MatrixType normalizedValue = getMatrixAt(*matrix, rowIdx, colIdx) / colSums[colIdx];
setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx); setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx);
} }
} }
@@ -32,13 +38,16 @@ static void softmax(Matrix *matrix) {
} }
} }
static void relu(Matrix *matrix) { static void relu(Matrix *matrix)
for (int i = 0; i < matrix->rows * matrix->cols; i++) { {
for(int i = 0; i < matrix->rows * matrix->cols; i++)
{
matrix->buffer[i] = matrix->buffer[i] >= 0 ? matrix->buffer[i] : 0; matrix->buffer[i] = matrix->buffer[i] >= 0 ? matrix->buffer[i] : 0;
} }
} }
static int checkFileHeader(FILE *file) { static int checkFileHeader(FILE *file)
{
int isValid = 0; int isValid = 0;
int fileHeaderLen = strlen(FILE_HEADER_STRING); int fileHeaderLen = strlen(FILE_HEADER_STRING);
char buffer[BUFFER_SIZE] = {0}; char buffer[BUFFER_SIZE] = {0};
@@ -52,7 +61,8 @@ static int checkFileHeader(FILE *file) {
return isValid; return isValid;
} }
static unsigned int readDimension(FILE *file) { static unsigned int readDimension(FILE *file)
{
int dimension = 0; int dimension = 0;
if(fread(&dimension, sizeof(int), 1, file) != 1) if(fread(&dimension, sizeof(int), 1, file) != 1)
@@ -61,20 +71,21 @@ static unsigned int readDimension(FILE *file) {
return dimension; return dimension;
} }
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) { if(matrix.buffer != NULL)
if (fread(matrix.buffer, sizeof(MatrixType), rows * cols, file) != {
rows * cols) if(fread(matrix.buffer, sizeof(MatrixType), rows*cols, file) != rows*cols)
clearMatrix(&matrix); clearMatrix(&matrix);
} }
return matrix; return matrix;
} }
static Layer readLayer(FILE *file, unsigned int inputDimension, static Layer readLayer(FILE *file, unsigned int inputDimension, unsigned int outputDimension)
unsigned int outputDimension) { {
Layer layer; Layer layer;
layer.weights = readMatrix(file, outputDimension, inputDimension); layer.weights = readMatrix(file, outputDimension, inputDimension);
layer.biases = readMatrix(file, outputDimension, 1); layer.biases = readMatrix(file, outputDimension, 1);
@@ -82,22 +93,25 @@ static Layer readLayer(FILE *file, unsigned int inputDimension,
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 || layer.weights.rows == 0 || 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.cols == 0 || layer.weights.buffer == NULL;
} }
static void clearLayer(Layer *layer) { static void clearLayer(Layer *layer)
if (layer != NULL) { {
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)
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;
} }
@@ -105,31 +119,36 @@ static void assignActivations(NeuralNetwork model) {
model.layers[model.numberOfLayers-1].activation = softmax; 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) { if(file != NULL)
if (checkFileHeader(file)) { {
if(checkFileHeader(file))
{
unsigned int inputDimension = readDimension(file); unsigned int inputDimension = readDimension(file);
unsigned int outputDimension = readDimension(file); unsigned int outputDimension = readDimension(file);
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; Layer *layerBuffer = NULL;
if (isEmptyLayer(layer)) { if(isEmptyLayer(layer))
{
clearLayer(&layer); clearLayer(&layer);
clearModel(&model); clearModel(&model);
break; break;
} }
layerBuffer = (Layer *)realloc( layerBuffer = (Layer *)realloc(model.layers, (model.numberOfLayers + 1) * sizeof(Layer));
model.layers, (model.numberOfLayers + 1) * sizeof(Layer));
if(layerBuffer != NULL) if(layerBuffer != NULL)
model.layers = layerBuffer; model.layers = layerBuffer;
else { else
{
clearModel(&model); clearModel(&model);
break; break;
} }
@@ -149,16 +168,20 @@ NeuralNetwork loadModel(const char *path) {
return model; return model;
} }
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
unsigned int count) { {
Matrix matrix = {0, 0, NULL}; // falsch herum Matrix matrix = {NULL, 0, 0};
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 (int i = 0; i < count; i++) { {
for (int j = 0; j < images[i].width * images[i].height; j++) { for(int i = 0; i < count; i++)
{
for(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);
} }
} }
@@ -168,11 +191,14 @@ static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[],
return matrix; return matrix;
} }
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) { if(result.buffer != NULL)
for (int i = 0; i < model.numberOfLayers; i++) { {
for(int i = 0; i < model.numberOfLayers; i++)
{
Matrix biasResult; Matrix biasResult;
Matrix weightResult; Matrix weightResult;
@@ -190,19 +216,23 @@ static Matrix forward(const NeuralNetwork model, Matrix inputBatch) {
return result; return result;
} }
unsigned char *argmax(const Matrix matrix) { unsigned char *argmax(const Matrix matrix)
{
unsigned char *maxIdx = NULL; unsigned char *maxIdx = NULL;
if (matrix.rows > 0 && matrix.cols > 0) { if(matrix.rows > 0 && matrix.cols > 0)
{
maxIdx = (unsigned char *)malloc(sizeof(unsigned char) * matrix.cols); maxIdx = (unsigned char *)malloc(sizeof(unsigned char) * matrix.cols);
if (maxIdx != NULL) { if(maxIdx != NULL)
for (int colIdx = 0; colIdx < matrix.cols; colIdx++) { {
for(int colIdx = 0; colIdx < matrix.cols; colIdx++)
{
maxIdx[colIdx] = 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, maxIdx[colIdx], colIdx)) if(getMatrixAt(matrix, rowIdx, colIdx) > getMatrixAt(matrix, maxIdx[colIdx], colIdx))
maxIdx[colIdx] = rowIdx; maxIdx[colIdx] = rowIdx;
} }
} }
@@ -212,8 +242,8 @@ unsigned char *argmax(const Matrix matrix) {
return maxIdx; return maxIdx;
} }
unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[], unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[], unsigned int numberOfImages)
unsigned int numberOfImages) { {
Matrix inputBatch = imageBatchToMatrixOfImageVectors(images, numberOfImages); Matrix inputBatch = imageBatchToMatrixOfImageVectors(images, numberOfImages);
Matrix outputBatch = forward(model, inputBatch); Matrix outputBatch = forward(model, inputBatch);
@@ -224,9 +254,12 @@ unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[],
return result; return result;
} }
void clearModel(NeuralNetwork *model) { void clearModel(NeuralNetwork *model)
if (model != NULL) { {
for (int i = 0; i < model->numberOfLayers; i++) { if(model != NULL)
{
for(int i = 0; i < model->numberOfLayers; i++)
{
clearLayer(&model->layers[i]); clearLayer(&model->layers[i]);
} }
model->layers = NULL; model->layers = NULL;
+49 -115
View File
@@ -1,76 +1,18 @@
#include "neuralNetwork.h"
#include "unity.h"
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h>
#include "unity.h"
#include "neuralNetwork.h"
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) {
FILE *f = fopen(path, "wb");
if (f == NULL)
return;
/* 1) Header: exakt das String, ohne '\n' oder abschließendes '\0' */ static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
const char header[] = "__info2_neural_network_file_format__"; {
fwrite(header, sizeof(char), strlen(header), f); // TODO
/* Wenn es keine Layer gibt, kein Dimensionspaar schreiben (loadModel
wird beim Lesen dann 0 zurückgeben). Aber wir können auch frühzeitig
mit einem 0-Int terminieren — beides ist in Ordnung. */
if (nn.numberOfLayers == 0) {
/* optional: schreibe ein 0 als next outputDimension (nicht nötig) */
int zero = 0;
fwrite(&zero, sizeof(int), 1, f);
fclose(f);
return;
} }
/* 2) Für die erste Layer schreiben wir inputDimension und outputDimension */ void test_loadModelReturnsCorrectNumberOfLayers(void)
/* inputDimension == weights.cols, outputDimension == weights.rows */ {
int inputDim = (int)nn.layers[0].weights.cols;
int outputDim = (int)nn.layers[0].weights.rows;
fwrite(&inputDim, sizeof(int), 1, f);
fwrite(&outputDim, sizeof(int), 1, f);
/* 3) Für jede Layer in Reihenfolge: Gewichte (output x input), Biases (output
x 1). Zwischen Layern wird nur die nächste outputDimension (int)
geschrieben. */
for (int i = 0; i < nn.numberOfLayers; i++) {
Layer layer = nn.layers[i];
int wrows = (int)layer.weights.rows;
int wcols = (int)layer.weights.cols;
int wcount = wrows * wcols;
int bcount =
layer.biases.rows * layer.biases.cols; /* normalerweise rows * 1 */
/* Gewichte (MatrixType binär) */
if (wcount > 0 && layer.weights.buffer != NULL) {
fwrite(layer.weights.buffer, sizeof(MatrixType), (size_t)wcount, f);
}
/* Biases (MatrixType binär) */
if (bcount > 0 && layer.biases.buffer != NULL) {
fwrite(layer.biases.buffer, sizeof(MatrixType), (size_t)bcount, f);
}
/* Für die nächste Layer: falls vorhanden, schreibe deren outputDimension */
if (i + 1 < nn.numberOfLayers) {
int nextOutput = (int)nn.layers[i + 1].weights.rows;
fwrite(&nextOutput, sizeof(int), 1, f);
} else {
/* Letzte Layer: wir können das Ende signalisieren, indem wir ein 0
schreiben. loadModel liest dann outputDimension = 0 und beendet die
Schleife. */
int zero = 0;
fwrite(&zero, sizeof(int), 1, f);
}
}
fclose(f);
}
void test_loadModelReturnsCorrectNumberOfLayers(void) {
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6}; MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
MatrixType buffer2[] = {1, 2, 3, 4, 5, 6}; MatrixType buffer2[] = {1, 2, 3, 4, 5, 6};
@@ -80,8 +22,7 @@ void test_loadModelReturnsCorrectNumberOfLayers(void) {
MatrixType buffer4[] = {1, 2}; MatrixType buffer4[] = {1, 2};
Matrix biases1 = {.buffer=buffer3, .rows=3, .cols=1}; Matrix biases1 = {.buffer=buffer3, .rows=3, .cols=1};
Matrix biases2 = {.buffer=buffer4, .rows=2, .cols=1}; Matrix biases2 = {.buffer=buffer4, .rows=2, .cols=1};
Layer layers[] = {{.weights = weights1, .biases = biases1}, Layer layers[] = {{.weights=weights1, .biases=biases1}, {.weights=weights2, .biases=biases2}};
{.weights = weights2, .biases = biases2}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2}; NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
@@ -91,12 +32,12 @@ void test_loadModelReturnsCorrectNumberOfLayers(void) {
netUnderTest = loadModel(path); netUnderTest = loadModel(path);
remove(path); remove(path);
TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, netUnderTest.numberOfLayers);
netUnderTest.numberOfLayers);
clearModel(&netUnderTest); clearModel(&netUnderTest);
} }
void test_loadModelReturnsCorrectWeightDimensions(void) { void test_loadModelReturnsCorrectWeightDimensions(void)
{
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
@@ -113,14 +54,13 @@ void test_loadModelReturnsCorrectWeightDimensions(void) {
remove(path); remove(path);
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
netUnderTest.layers[0].weights.rows); TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols,
netUnderTest.layers[0].weights.cols);
clearModel(&netUnderTest); clearModel(&netUnderTest);
} }
void test_loadModelReturnsCorrectBiasDimensions(void) { void test_loadModelReturnsCorrectBiasDimensions(void)
{
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
@@ -137,14 +77,13 @@ void test_loadModelReturnsCorrectBiasDimensions(void) {
remove(path); remove(path);
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.rows, TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.rows, netUnderTest.layers[0].biases.rows);
netUnderTest.layers[0].biases.rows); TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.cols, netUnderTest.layers[0].biases.cols);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.cols,
netUnderTest.layers[0].biases.cols);
clearModel(&netUnderTest); clearModel(&netUnderTest);
} }
void test_loadModelReturnsCorrectWeights(void) { void test_loadModelReturnsCorrectWeights(void)
{
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
@@ -161,18 +100,15 @@ void test_loadModelReturnsCorrectWeights(void) {
remove(path); remove(path);
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
netUnderTest.layers[0].weights.rows); TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, int n = netUnderTest.layers[0].weights.rows * netUnderTest.layers[0].weights.cols;
netUnderTest.layers[0].weights.cols); TEST_ASSERT_EQUAL_INT_ARRAY(expectedNet.layers[0].weights.buffer, netUnderTest.layers[0].weights.buffer, n);
int n =
netUnderTest.layers[0].weights.rows * netUnderTest.layers[0].weights.cols;
TEST_ASSERT_EQUAL_INT_ARRAY(expectedNet.layers[0].weights.buffer,
netUnderTest.layers[0].weights.buffer, n);
clearModel(&netUnderTest); clearModel(&netUnderTest);
} }
void test_loadModelReturnsCorrectBiases(void) { void test_loadModelReturnsCorrectBiases(void)
{
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
@@ -189,23 +125,21 @@ void test_loadModelReturnsCorrectBiases(void) {
remove(path); remove(path);
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
netUnderTest.layers[0].weights.rows); TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, int n = netUnderTest.layers[0].biases.rows * netUnderTest.layers[0].biases.cols;
netUnderTest.layers[0].weights.cols); TEST_ASSERT_EQUAL_INT_ARRAY(expectedNet.layers[0].biases.buffer, netUnderTest.layers[0].biases.buffer, n);
int n =
netUnderTest.layers[0].biases.rows * netUnderTest.layers[0].biases.cols;
TEST_ASSERT_EQUAL_INT_ARRAY(expectedNet.layers[0].biases.buffer,
netUnderTest.layers[0].biases.buffer, n);
clearModel(&netUnderTest); clearModel(&netUnderTest);
} }
void test_loadModelFailsOnWrongFileTag(void) { void test_loadModelFailsOnWrongFileTag(void)
{
const char *path = "some_nn_test_file.info2"; const char *path = "some_nn_test_file.info2";
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
FILE *file = fopen(path, "wb"); FILE *file = fopen(path, "wb");
if (file != NULL) { if(file != NULL)
{
const char *fileTag = "info2_neural_network_file_format"; const char *fileTag = "info2_neural_network_file_format";
fwrite(fileTag, sizeof(char), strlen(fileTag), file); fwrite(fileTag, sizeof(char), strlen(fileTag), file);
@@ -221,7 +155,8 @@ void test_loadModelFailsOnWrongFileTag(void) {
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers); TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
} }
void test_clearModelSetsMembersToNull(void) { void test_clearModelSetsMembersToNull(void)
{
const char *path = "some__nn_test_file.info2"; const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
@@ -244,23 +179,23 @@ void test_clearModelSetsMembersToNull(void) {
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers); TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
} }
static void someActivation(Matrix *matrix) { static void someActivation(Matrix *matrix)
for (int i = 0; i < matrix->rows * matrix->cols; i++) { {
for(int i = 0; i < matrix->rows * matrix->cols; i++)
{
matrix->buffer[i] = fabs(matrix->buffer[i]); matrix->buffer[i] = fabs(matrix->buffer[i]);
} }
} }
void test_predictReturnsCorrectLabels(void) { void test_predictReturnsCorrectLabels(void)
{
const unsigned char expectedLabels[] = {4, 2}; const unsigned char expectedLabels[] = {4, 2};
GrayScalePixelType imageBuffer1[] = {10, 30, 25, 17}; GrayScalePixelType imageBuffer1[] = {10, 30, 25, 17};
GrayScalePixelType imageBuffer2[] = {20, 40, 10, 128}; GrayScalePixelType imageBuffer2[] = {20, 40, 10, 128};
GrayScaleImage inputImages[] = { GrayScaleImage inputImages[] = {{.buffer=imageBuffer1, .width=2, .height=2}, {.buffer=imageBuffer2, .width=2, .height=2}};
{.buffer = imageBuffer1, .width = 2, .height = 2},
{.buffer = imageBuffer2, .width = 2, .height = 2}};
MatrixType weightsBuffer1[] = {1, -2, 3, -4, 5, -6, 7, -8}; MatrixType weightsBuffer1[] = {1, -2, 3, -4, 5, -6, 7, -8};
MatrixType weightsBuffer2[] = {-9, 10, 11, 12, 13, 14}; MatrixType weightsBuffer2[] = {-9, 10, 11, 12, 13, 14};
MatrixType weightsBuffer3[] = {-15, 16, 17, 18, -19, 20, 21, 22, MatrixType weightsBuffer3[] = {-15, 16, 17, 18, -19, 20, 21, 22, 23, -24, 25, 26, 27, -28, -29};
23, -24, 25, 26, 27, -28, -29};
Matrix weights1 = {.buffer=weightsBuffer1, .rows=2, .cols=4}; Matrix weights1 = {.buffer=weightsBuffer1, .rows=2, .cols=4};
Matrix weights2 = {.buffer=weightsBuffer2, .rows=3, .cols=2}; Matrix weights2 = {.buffer=weightsBuffer2, .rows=3, .cols=2};
Matrix weights3 = {.buffer=weightsBuffer3, .rows=5, .cols=3}; Matrix weights3 = {.buffer=weightsBuffer3, .rows=5, .cols=3};
@@ -270,9 +205,8 @@ void test_predictReturnsCorrectLabels(void) {
Matrix biases1 = {.buffer=biasBuffer1, .rows=2, .cols=1}; Matrix biases1 = {.buffer=biasBuffer1, .rows=2, .cols=1};
Matrix biases2 = {.buffer=biasBuffer2, .rows=3, .cols=1}; Matrix biases2 = {.buffer=biasBuffer2, .rows=3, .cols=1};
Matrix biases3 = {.buffer=biasBuffer3, .rows=5, .cols=1}; Matrix biases3 = {.buffer=biasBuffer3, .rows=5, .cols=1};
Layer layers[] = { Layer layers[] = {{.weights=weights1, .biases=biases1, .activation=someActivation}, \
{.weights = weights1, .biases = biases1, .activation = someActivation}, {.weights=weights2, .biases=biases2, .activation=someActivation}, \
{.weights = weights2, .biases = biases2, .activation = someActivation},
{.weights=weights3, .biases=biases3, .activation=someActivation}}; {.weights=weights3, .biases=biases3, .activation=someActivation}};
NeuralNetwork netUnderTest = {.layers=layers, .numberOfLayers=3}; NeuralNetwork netUnderTest = {.layers=layers, .numberOfLayers=3};
unsigned char *predictedLabels = predict(netUnderTest, inputImages, 2); unsigned char *predictedLabels = predict(netUnderTest, inputImages, 2);
@@ -290,11 +224,11 @@ void tearDown(void) {
// Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden // Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden
} }
int main() { int main()
{
UNITY_BEGIN(); UNITY_BEGIN();
printf("\n============================\nNeural network " printf("\n============================\nNeural network tests\n============================\n");
"tests\n============================\n");
RUN_TEST(test_loadModelReturnsCorrectNumberOfLayers); RUN_TEST(test_loadModelReturnsCorrectNumberOfLayers);
RUN_TEST(test_loadModelReturnsCorrectWeightDimensions); RUN_TEST(test_loadModelReturnsCorrectWeightDimensions);
RUN_TEST(test_loadModelReturnsCorrectBiasDimensions); RUN_TEST(test_loadModelReturnsCorrectBiasDimensions);