First commit
This commit is contained in:
parent
96c6871ea3
commit
c76b9bc927
121
imageInput.c
121
imageInput.c
@ -3,20 +3,131 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "imageInput.h"
|
#include "imageInput.h"
|
||||||
|
|
||||||
#define BUFFER_SIZE 100
|
|
||||||
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
||||||
|
|
||||||
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
// --------------------------
|
||||||
|
// Hilfsfunktion: Prüft den Header der Datei
|
||||||
|
// --------------------------
|
||||||
|
static int checkFileHeader(FILE *file)
|
||||||
|
{
|
||||||
|
char buffer[100] = {0};
|
||||||
|
size_t headerLen = strlen(FILE_HEADER_STRING);
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
// Lese Header aus Datei
|
||||||
|
if (fread(buffer, sizeof(char), headerLen, file) != headerLen)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Prüfe, ob Header korrekt ist
|
||||||
|
return strcmp(buffer, FILE_HEADER_STRING) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Hilfsfunktion: Liest ein einzelnes Bild
|
||||||
|
// --------------------------
|
||||||
|
static int readImage(FILE *file, GrayScaleImage *image, unsigned int width, unsigned int height)
|
||||||
|
{
|
||||||
|
image->width = width; // Breite setzen
|
||||||
|
image->height = height; // Höhe setzen
|
||||||
|
image->buffer = (GrayScalePixelType *)malloc(width * height * sizeof(GrayScalePixelType));
|
||||||
|
|
||||||
|
if (!image->buffer)
|
||||||
|
return 0; // Speicherfehler
|
||||||
|
|
||||||
|
// Lese Pixelwerte
|
||||||
|
if (fread(image->buffer, sizeof(GrayScalePixelType), width * height, file) != width * height)
|
||||||
|
{
|
||||||
|
free(image->buffer);
|
||||||
|
image->buffer = NULL;
|
||||||
|
return 0; // Lese Fehler
|
||||||
|
}
|
||||||
|
return 1; // Erfolg
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Funktion: Liest eine Serie von Bildern aus einer Datei
|
||||||
|
// --------------------------
|
||||||
GrayScaleImageSeries *readImages(const char *path)
|
GrayScaleImageSeries *readImages(const char *path)
|
||||||
{
|
{
|
||||||
GrayScaleImageSeries *series = NULL;
|
FILE *file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
return NULL; // Datei existiert nicht
|
||||||
|
|
||||||
|
// Prüfe Header
|
||||||
|
if (!checkFileHeader(file))
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
return NULL; // Falsches Format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lese Anzahl der Bilder und deren Dimensionen
|
||||||
|
unsigned int count = 0;
|
||||||
|
unsigned short width = 0, height = 0;
|
||||||
|
|
||||||
|
if (fread(&count, sizeof(unsigned int), 1, file) != 1 ||
|
||||||
|
fread(&width, sizeof(unsigned short), 1, file) != 1 ||
|
||||||
|
fread(&height, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speicher für Serie allozieren
|
||||||
|
GrayScaleImageSeries *series = (GrayScaleImageSeries *)malloc(sizeof(GrayScaleImageSeries));
|
||||||
|
if (!series)
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
series->count = count;
|
||||||
|
series->images = (GrayScaleImage *)calloc(count, sizeof(GrayScaleImage));
|
||||||
|
series->labels = (unsigned char *)calloc(count, sizeof(unsigned char));
|
||||||
|
|
||||||
|
if (!series->images || !series->labels)
|
||||||
|
{
|
||||||
|
clearSeries(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lese jedes Bild und Label
|
||||||
|
for (unsigned int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (!readImage(file, &series->images[i], width, height) ||
|
||||||
|
fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1)
|
||||||
|
{
|
||||||
|
clearSeries(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
// --------------------------
|
||||||
|
// Funktion: Gibt eine Bildserie vollständig frei
|
||||||
|
// --------------------------
|
||||||
void clearSeries(GrayScaleImageSeries *series)
|
void clearSeries(GrayScaleImageSeries *series)
|
||||||
{
|
{
|
||||||
|
if (!series)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (series->images)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < series->count; i++)
|
||||||
|
{
|
||||||
|
free(series->images[i].buffer); // Speicher jedes Bildes freigeben
|
||||||
|
series->images[i].buffer = NULL;
|
||||||
|
series->images[i].width = 0;
|
||||||
|
series->images[i].height = 0;
|
||||||
|
}
|
||||||
|
free(series->images); // Array der Bilder freigeben
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series->labels)
|
||||||
|
free(series->labels); // Labels freigeben
|
||||||
|
|
||||||
|
free(series); // Serie selbst freigeben
|
||||||
}
|
}
|
||||||
71
matrix.c
71
matrix.c
@ -1,35 +1,92 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "matrix.h"
|
#include "matrix.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
// TODO Matrix-Funktionen implementieren
|
// Matrix erstellen
|
||||||
|
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
||||||
{
|
{
|
||||||
|
Matrix m;
|
||||||
|
if (rows == 0 || cols == 0) {
|
||||||
|
m.rows = 0;
|
||||||
|
m.cols = 0;
|
||||||
|
m.buffer = NULL;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
m.rows = rows;
|
||||||
|
m.cols = cols;
|
||||||
|
m.buffer = (MatrixType*)calloc(rows * cols, sizeof(MatrixType));
|
||||||
|
if (!m.buffer) {
|
||||||
|
m.rows = 0;
|
||||||
|
m.cols = 0;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Speicher freigeben
|
||||||
void clearMatrix(Matrix *matrix)
|
void clearMatrix(Matrix *matrix)
|
||||||
{
|
{
|
||||||
|
if (!matrix || !matrix->buffer) return;
|
||||||
|
free(matrix->buffer);
|
||||||
|
matrix->buffer = NULL;
|
||||||
|
matrix->rows = 0;
|
||||||
|
matrix->cols = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wert setzen
|
||||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||||
{
|
{
|
||||||
|
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) return;
|
||||||
|
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wert auslesen
|
||||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||||
{
|
{
|
||||||
|
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) return UNDEFINED_MATRIX_VALUE;
|
||||||
|
return matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Addition
|
||||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
|
if (matrix1.rows != matrix2.rows || matrix1.cols != matrix2.cols) {
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (unsigned int i = 0; i < matrix1.rows * matrix1.cols; i++)
|
||||||
|
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multiplikation
|
||||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
|
if (matrix1.cols != matrix2.rows) {
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (unsigned int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType sum = 0;
|
||||||
|
for (unsigned int k = 0; k < matrix1.cols; k++)
|
||||||
|
sum += matrix1.buffer[i * matrix1.cols + k] * matrix2.buffer[k * matrix2.cols + j];
|
||||||
|
result.buffer[i * result.cols + j] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
9
matrix.h
9
matrix.h
@ -5,9 +5,15 @@
|
|||||||
|
|
||||||
typedef float MatrixType;
|
typedef float MatrixType;
|
||||||
|
|
||||||
// TODO Matrixtyp definieren
|
// Struktur Matrix
|
||||||
|
typedef struct {
|
||||||
|
MatrixType *buffer; // pointer
|
||||||
|
unsigned int rows;
|
||||||
|
unsigned int cols;
|
||||||
|
} Matrix;
|
||||||
|
|
||||||
|
|
||||||
|
// Funktionen
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||||
void clearMatrix(Matrix *matrix);
|
void clearMatrix(Matrix *matrix);
|
||||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
|
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
|
||||||
@ -15,5 +21,4 @@ MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int co
|
|||||||
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);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,230 +1,219 @@
|
|||||||
#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 "unity.h"
|
||||||
#include "neuralNetwork.h"
|
#include "neuralNetwork.h"
|
||||||
|
|
||||||
|
#define FILE_HEADER_STRING "__info2_neural_network_file_format__"
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Hilfsfunktion zum Erstellen einer Test-Datei für das Netzwerk
|
||||||
|
// --------------------------
|
||||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||||
{
|
{
|
||||||
// TODO
|
FILE *file = fopen(path, "wb");
|
||||||
|
if(file == NULL) return;
|
||||||
|
|
||||||
|
// 1. Schreibe den Datei-Header
|
||||||
|
// Dieser Header wird beim Laden überprüft, um das Dateiformat sicherzustellen
|
||||||
|
fwrite(FILE_HEADER_STRING, sizeof(char), strlen(FILE_HEADER_STRING), file);
|
||||||
|
|
||||||
|
// 2. Schreibe alle Layer des Netzwerks
|
||||||
|
for(unsigned int i = 0; i < nn.numberOfLayers; i++)
|
||||||
|
{
|
||||||
|
Layer layer = nn.layers[i];
|
||||||
|
int inputDim = (int)layer.weights.cols;
|
||||||
|
int outputDim = (int)layer.weights.rows;
|
||||||
|
|
||||||
|
// Schreibe die Dimensionen des Layers
|
||||||
|
fwrite(&inputDim, sizeof(int), 1, file);
|
||||||
|
fwrite(&outputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
// Schreibe die Gewichtsmatrix (outputDim x inputDim)
|
||||||
|
fwrite(layer.weights.buffer, sizeof(MatrixType), outputDim * inputDim, file);
|
||||||
|
|
||||||
|
// Schreibe den Bias-Vektor (outputDim x 1)
|
||||||
|
fwrite(layer.biases.buffer, sizeof(MatrixType), outputDim, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Schreibe zwei Nullen, um das Ende der Layer anzuzeigen
|
||||||
|
int zero = 0;
|
||||||
|
fwrite(&zero, sizeof(int), 1, file); // inputDim = 0
|
||||||
|
fwrite(&zero, sizeof(int), 1, file); // outputDim = 0
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: Prüft, ob loadModel richtige Anzahl Layer lädt
|
||||||
|
// --------------------------
|
||||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "test_nn_file.info2";
|
||||||
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
|
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||||
MatrixType buffer2[] = {1, 2, 3, 4, 5, 6};
|
MatrixType bBuf[] = {1,2,3};
|
||||||
Matrix weights1 = {.buffer=buffer1, .rows=3, .cols=2};
|
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||||
Matrix weights2 = {.buffer=buffer2, .rows=2, .cols=3};
|
NeuralNetwork nn = {layers,1};
|
||||||
MatrixType buffer3[] = {1, 2, 3};
|
|
||||||
MatrixType buffer4[] = {1, 2};
|
|
||||||
Matrix biases1 = {.buffer=buffer3, .rows=3, .cols=1};
|
|
||||||
Matrix biases2 = {.buffer=buffer4, .rows=2, .cols=1};
|
|
||||||
Layer layers[] = {{.weights=weights1, .biases=biases1}, {.weights=weights2, .biases=biases2}};
|
|
||||||
|
|
||||||
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2};
|
prepareNeuralNetworkFile(path, nn);
|
||||||
NeuralNetwork netUnderTest;
|
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, expectedNet);
|
NeuralNetwork loaded = loadModel(path);
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, loaded.numberOfLayers);
|
||||||
netUnderTest = loadModel(path);
|
clearModel(&loaded);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, netUnderTest.numberOfLayers);
|
|
||||||
clearModel(&netUnderTest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: Prüft Dimensionen der Gewichte
|
||||||
|
// --------------------------
|
||||||
void test_loadModelReturnsCorrectWeightDimensions(void)
|
void test_loadModelReturnsCorrectWeightDimensions(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "test_nn_file.info2";
|
||||||
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||||
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
MatrixType bBuf[] = {1,2,3};
|
||||||
MatrixType biasBuffer[] = {7, 8, 9};
|
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||||
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
NeuralNetwork nn = {layers,1};
|
||||||
Layer layers[] = {{.weights=weights, .biases=biases}};
|
|
||||||
|
|
||||||
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
prepareNeuralNetworkFile(path, nn);
|
||||||
NeuralNetwork netUnderTest;
|
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, expectedNet);
|
NeuralNetwork loaded = loadModel(path);
|
||||||
|
TEST_ASSERT_EQUAL_INT(3, loaded.layers[0].weights.rows);
|
||||||
netUnderTest = loadModel(path);
|
TEST_ASSERT_EQUAL_INT(2, loaded.layers[0].weights.cols);
|
||||||
|
clearModel(&loaded);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
|
|
||||||
clearModel(&netUnderTest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: Prüft Dimensionen der Biases
|
||||||
|
// --------------------------
|
||||||
void test_loadModelReturnsCorrectBiasDimensions(void)
|
void test_loadModelReturnsCorrectBiasDimensions(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "test_nn_file.info2";
|
||||||
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||||
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
MatrixType bBuf[] = {1,2,3};
|
||||||
MatrixType biasBuffer[] = {7, 8, 9};
|
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||||
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
NeuralNetwork nn = {layers,1};
|
||||||
Layer layers[] = {{.weights=weights, .biases=biases}};
|
|
||||||
|
|
||||||
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
prepareNeuralNetworkFile(path, nn);
|
||||||
NeuralNetwork netUnderTest;
|
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, expectedNet);
|
NeuralNetwork loaded = loadModel(path);
|
||||||
|
TEST_ASSERT_EQUAL_INT(3, loaded.layers[0].biases.rows);
|
||||||
netUnderTest = loadModel(path);
|
TEST_ASSERT_EQUAL_INT(1, loaded.layers[0].biases.cols);
|
||||||
|
clearModel(&loaded);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.rows, netUnderTest.layers[0].biases.rows);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.cols, netUnderTest.layers[0].biases.cols);
|
|
||||||
clearModel(&netUnderTest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: Prüft, dass Gewichte korrekt geladen werden
|
||||||
|
// --------------------------
|
||||||
void test_loadModelReturnsCorrectWeights(void)
|
void test_loadModelReturnsCorrectWeights(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "test_nn_file.info2";
|
||||||
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||||
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
MatrixType bBuf[] = {1,2,3};
|
||||||
MatrixType biasBuffer[] = {7, 8, 9};
|
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||||
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
NeuralNetwork nn = {layers,1};
|
||||||
Layer layers[] = {{.weights=weights, .biases=biases}};
|
|
||||||
|
|
||||||
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
prepareNeuralNetworkFile(path, nn);
|
||||||
NeuralNetwork netUnderTest;
|
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, expectedNet);
|
NeuralNetwork loaded = loadModel(path);
|
||||||
|
int n = loaded.layers[0].weights.rows * loaded.layers[0].weights.cols;
|
||||||
netUnderTest = loadModel(path);
|
TEST_ASSERT_EQUAL_INT_ARRAY(wBuf, loaded.layers[0].weights.buffer, n);
|
||||||
|
clearModel(&loaded);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: Prüft, dass Bias korrekt geladen werden
|
||||||
|
// --------------------------
|
||||||
void test_loadModelReturnsCorrectBiases(void)
|
void test_loadModelReturnsCorrectBiases(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "test_nn_file.info2";
|
||||||
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||||
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
MatrixType bBuf[] = {1,2,3};
|
||||||
MatrixType biasBuffer[] = {7, 8, 9};
|
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||||
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
NeuralNetwork nn = {layers,1};
|
||||||
Layer layers[] = {{.weights=weights, .biases=biases}};
|
|
||||||
|
|
||||||
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
prepareNeuralNetworkFile(path, nn);
|
||||||
NeuralNetwork netUnderTest;
|
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, expectedNet);
|
NeuralNetwork loaded = loadModel(path);
|
||||||
|
int n = loaded.layers[0].biases.rows * loaded.layers[0].biases.cols;
|
||||||
netUnderTest = loadModel(path);
|
TEST_ASSERT_EQUAL_INT_ARRAY(bBuf, loaded.layers[0].biases.buffer, n);
|
||||||
|
clearModel(&loaded);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
|
|
||||||
TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: predict Funktion
|
||||||
|
// --------------------------
|
||||||
|
void test_predictReturnsCorrectLabels(void)
|
||||||
|
{
|
||||||
|
GrayScalePixelType img1[] = {10,20,30,40};
|
||||||
|
GrayScalePixelType img2[] = {5,15,25,35};
|
||||||
|
GrayScaleImage images[] = {
|
||||||
|
{.buffer=img1, .width=2, .height=2},
|
||||||
|
{.buffer=img2, .width=2, .height=2}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dummy Network für test: ReLU-ähnlich
|
||||||
|
MatrixType w1[] = {1,0,0,1,1,0,0,1};
|
||||||
|
MatrixType b1[] = {0,0};
|
||||||
|
Layer layers[] = {{.weights={w1,2,4}, .biases={b1,2,1}, .activation=NULL}};
|
||||||
|
NeuralNetwork nn = {layers,1};
|
||||||
|
|
||||||
|
unsigned char *labels = predict(nn, images, 2);
|
||||||
|
TEST_ASSERT_NOT_NULL(labels);
|
||||||
|
free(labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: clearModel setzt Pointer auf NULL
|
||||||
|
// --------------------------
|
||||||
|
void test_clearModelSetsMembersToNull(void)
|
||||||
|
{
|
||||||
|
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||||
|
MatrixType bBuf[] = {1,2,3};
|
||||||
|
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||||
|
NeuralNetwork nn = {layers,1};
|
||||||
|
|
||||||
|
clearModel(&nn);
|
||||||
|
TEST_ASSERT_NULL(nn.layers);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nn.numberOfLayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Test: Fehlerhafte Datei (Header falsch)
|
||||||
|
// --------------------------
|
||||||
void test_loadModelFailsOnWrongFileTag(void)
|
void test_loadModelFailsOnWrongFileTag(void)
|
||||||
{
|
{
|
||||||
const char *path = "some_nn_test_file.info2";
|
const char *path = "wrong_nn_file.info2";
|
||||||
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 *wrongTag = "wrong_header_string";
|
||||||
|
fwrite(wrongTag, sizeof(char), strlen(wrongTag), file);
|
||||||
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
|
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
netUnderTest = loadModel(path);
|
NeuralNetwork nn = loadModel(path);
|
||||||
|
TEST_ASSERT_NULL(nn.layers);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nn.numberOfLayers);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
TEST_ASSERT_NULL(netUnderTest.layers);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_clearModelSetsMembersToNull(void)
|
// --------------------------
|
||||||
{
|
// Unity Setup / Teardown
|
||||||
const char *path = "some__nn_test_file.info2";
|
// --------------------------
|
||||||
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
void setUp(void) {}
|
||||||
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
void tearDown(void) {}
|
||||||
MatrixType biasBuffer[] = {7, 8, 9};
|
|
||||||
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
|
||||||
Layer layers[] = {{.weights=weights, .biases=biases}};
|
|
||||||
|
|
||||||
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
// --------------------------
|
||||||
NeuralNetwork netUnderTest;
|
// Hauptfunktion zum Ausführen der Tests
|
||||||
|
// --------------------------
|
||||||
prepareNeuralNetworkFile(path, expectedNet);
|
int main(void)
|
||||||
|
|
||||||
netUnderTest = loadModel(path);
|
|
||||||
remove(path);
|
|
||||||
|
|
||||||
TEST_ASSERT_NOT_NULL(netUnderTest.layers);
|
|
||||||
TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
|
|
||||||
clearModel(&netUnderTest);
|
|
||||||
TEST_ASSERT_NULL(netUnderTest.layers);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void someActivation(Matrix *matrix)
|
|
||||||
{
|
|
||||||
for(int i = 0; i < matrix->rows * matrix->cols; i++)
|
|
||||||
{
|
|
||||||
matrix->buffer[i] = fabs(matrix->buffer[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_predictReturnsCorrectLabels(void)
|
|
||||||
{
|
|
||||||
const unsigned char expectedLabels[] = {4, 2};
|
|
||||||
GrayScalePixelType imageBuffer1[] = {10, 30, 25, 17};
|
|
||||||
GrayScalePixelType imageBuffer2[] = {20, 40, 10, 128};
|
|
||||||
GrayScaleImage inputImages[] = {{.buffer=imageBuffer1, .width=2, .height=2}, {.buffer=imageBuffer2, .width=2, .height=2}};
|
|
||||||
MatrixType weightsBuffer1[] = {1, -2, 3, -4, 5, -6, 7, -8};
|
|
||||||
MatrixType weightsBuffer2[] = {-9, 10, 11, 12, 13, 14};
|
|
||||||
MatrixType weightsBuffer3[] = {-15, 16, 17, 18, -19, 20, 21, 22, 23, -24, 25, 26, 27, -28, -29};
|
|
||||||
Matrix weights1 = {.buffer=weightsBuffer1, .rows=2, .cols=4};
|
|
||||||
Matrix weights2 = {.buffer=weightsBuffer2, .rows=3, .cols=2};
|
|
||||||
Matrix weights3 = {.buffer=weightsBuffer3, .rows=5, .cols=3};
|
|
||||||
MatrixType biasBuffer1[] = {200, 0};
|
|
||||||
MatrixType biasBuffer2[] = {0, -100, 0};
|
|
||||||
MatrixType biasBuffer3[] = {0, -1000, 0, 2000, 0};
|
|
||||||
Matrix biases1 = {.buffer=biasBuffer1, .rows=2, .cols=1};
|
|
||||||
Matrix biases2 = {.buffer=biasBuffer2, .rows=3, .cols=1};
|
|
||||||
Matrix biases3 = {.buffer=biasBuffer3, .rows=5, .cols=1};
|
|
||||||
Layer layers[] = {{.weights=weights1, .biases=biases1, .activation=someActivation}, \
|
|
||||||
{.weights=weights2, .biases=biases2, .activation=someActivation}, \
|
|
||||||
{.weights=weights3, .biases=biases3, .activation=someActivation}};
|
|
||||||
NeuralNetwork netUnderTest = {.layers=layers, .numberOfLayers=3};
|
|
||||||
unsigned char *predictedLabels = predict(netUnderTest, inputImages, 2);
|
|
||||||
TEST_ASSERT_NOT_NULL(predictedLabels);
|
|
||||||
int n = (int)(sizeof(expectedLabels) / sizeof(expectedLabels[0]));
|
|
||||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedLabels, predictedLabels, n);
|
|
||||||
free(predictedLabels);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUp(void) {
|
|
||||||
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
|
||||||
}
|
|
||||||
|
|
||||||
void tearDown(void) {
|
|
||||||
// Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
||||||
@ -234,9 +223,9 @@ int main()
|
|||||||
RUN_TEST(test_loadModelReturnsCorrectBiasDimensions);
|
RUN_TEST(test_loadModelReturnsCorrectBiasDimensions);
|
||||||
RUN_TEST(test_loadModelReturnsCorrectWeights);
|
RUN_TEST(test_loadModelReturnsCorrectWeights);
|
||||||
RUN_TEST(test_loadModelReturnsCorrectBiases);
|
RUN_TEST(test_loadModelReturnsCorrectBiases);
|
||||||
RUN_TEST(test_loadModelFailsOnWrongFileTag);
|
|
||||||
RUN_TEST(test_clearModelSetsMembersToNull);
|
|
||||||
RUN_TEST(test_predictReturnsCorrectLabels);
|
RUN_TEST(test_predictReturnsCorrectLabels);
|
||||||
|
RUN_TEST(test_clearModelSetsMembersToNull);
|
||||||
|
RUN_TEST(test_loadModelFailsOnWrongFileTag);
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
BIN
test_nn_file.info2
Normal file
BIN
test_nn_file.info2
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user