Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

13 changed files with 597 additions and 1145 deletions

5
.gitignore vendored
View File

@ -1,7 +1,4 @@
mnist mnist
runTests runTests
*.o *.o
*.exe *.exe
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json

View File

@ -1,18 +0,0 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "C:/ProgramData/mingw64/mingw64/bin/gcc.exe",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

24
.vscode/launch.json vendored
View File

@ -1,24 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "c:/Users/Max-R/I2Pr/repoKachelto/I2-Pr_neuronalesNetz/info2Praktikum-NeuronalesNetz",
"program": "c:/Users/Max-R/I2Pr/repoKachelto/I2-Pr_neuronalesNetz/info2Praktikum-NeuronalesNetz/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

59
.vscode/settings.json vendored
View File

@ -1,59 +0,0 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

View File

@ -1,2 +0,0 @@
# Projekt 2 für Informatik 2 Praktikum

View File

@ -1,134 +1,22 @@
#include "imageInput.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.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
1. Header prüfen
---------------------------------------------------------- */ // TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
static int readHeader(FILE *file) { GrayScaleImageSeries *readImages(const char *path)
char header[sizeof(FILE_HEADER_STRING)]; {
if (fread(header, 1, sizeof(FILE_HEADER_STRING) - 1, file) != GrayScaleImageSeries *series = NULL;
sizeof(FILE_HEADER_STRING) - 1)
return 0; return series;
header[sizeof(FILE_HEADER_STRING) - 1] = '\0';
return strcmp(header, FILE_HEADER_STRING) == 0;
} }
/* ---------------------------------------------------------- // TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
2. Meta-Daten lesen (unsigned short) void clearSeries(GrayScaleImageSeries *series)
---------------------------------------------------------- */ {
static int readMeta(FILE *file, unsigned short *count, unsigned short *width, }
unsigned short *height) {
if (fread(count, sizeof(unsigned short), 1, file) != 1)
return 0;
if (fread(width, sizeof(unsigned short), 1, file) != 1)
return 0;
if (fread(height, sizeof(unsigned short), 1, file) != 1)
return 0;
return 1;
}
/* ----------------------------------------------------------
3. Einzelbild lesen
---------------------------------------------------------- */
static int readSingleImage(FILE *file, GrayScaleImage *img,
unsigned short width, unsigned short height) {
img->width = width;
img->height = height;
size_t numPixels = (size_t)width * (size_t)height;
img->buffer = malloc(numPixels);
if (!img->buffer)
return 0;
if (fread(img->buffer, 1, numPixels, file) != numPixels) {
free(img->buffer);
img->buffer = NULL;
return 0;
}
return 1;
}
/* ----------------------------------------------------------
4. Label lesen
---------------------------------------------------------- */
static int readLabel(FILE *file, unsigned char *label) {
return fread(label, 1, 1, file) == 1;
}
/* ----------------------------------------------------------
5. Komplette Bildserie lesen
---------------------------------------------------------- */
GrayScaleImageSeries *readImages(const char *path) {
FILE *file = fopen(path, "rb");
if (!file)
return NULL;
if (!readHeader(file)) {
fclose(file);
return NULL;
}
unsigned short count, width, height;
if (!readMeta(file, &count, &width, &height)) {
fclose(file);
return NULL;
}
// printf("%d, %d, %d", count, width, height);
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
if (!series) {
fclose(file);
return NULL;
}
series->count = count;
series->images = malloc(count * sizeof(GrayScaleImage));
series->labels = malloc(count * sizeof(unsigned char));
if (!series->images || !series->labels) {
free(series->images);
free(series->labels);
free(series);
fclose(file);
return NULL;
}
for (unsigned int i = 0; i < count; i++) {
if (!readSingleImage(file, &series->images[i], width, height) ||
!readLabel(file, &series->labels[i])) {
// Aufräumen bei Fehler
for (unsigned int j = 0; j < i; j++) {
free(series->images[j].buffer);
}
free(series->images);
free(series->labels);
free(series);
fclose(file);
return NULL;
}
}
fclose(file);
return series;
}
/* ----------------------------------------------------------
6. Speicher komplett freigeben
---------------------------------------------------------- */
void clearSeries(GrayScaleImageSeries *series) {
if (!series)
return;
for (unsigned int i = 0; i < series->count; i++) {
free(series->images[i].buffer);
}
free(series->images);
free(series->labels);
free(series);
}

View File

@ -1,204 +1,143 @@
#include "imageInput.h"
#include "unity.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include "unity.h"
#include "imageInput.h"
/* ---------------------------------------------------------
Hilfsfunktion: Testdatei vorbereiten
--------------------------------------------------------- */
static void prepareImageFile(const char *path, unsigned int width,
unsigned int height, unsigned int numberOfImages,
unsigned char label) {
FILE *file = fopen(path, "wb");
if (!file)
return;
// Header static void prepareImageFile(const char *path, unsigned short int width, unsigned short int height, unsigned int short numberOfImages, unsigned char label)
const char *fileTag = "__info2_image_file_format__"; {
fwrite(fileTag, 1, strlen(fileTag), file); FILE *file = fopen(path, "wb");
// Meta-Daten als unsigned short if(file != NULL)
unsigned short n = (unsigned short)numberOfImages; {
unsigned short w = (unsigned short)width; const char *fileTag = "__info2_image_file_format__";
unsigned short h = (unsigned short)height; GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
fwrite(&n, sizeof(unsigned short), 1, file);
fwrite(&w, sizeof(unsigned short), 1, file);
fwrite(&h, sizeof(unsigned short), 1, file);
// Pixelbuffer if(zeroBuffer != NULL)
GrayScalePixelType *buffer = {
calloc(width * height, sizeof(GrayScalePixelType)); fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
if (!buffer) { fwrite(&numberOfImages, sizeof(numberOfImages), 1, file);
fclose(file); fwrite(&width, sizeof(width), 1, file);
return; fwrite(&height, sizeof(height), 1, file);
}
for (unsigned int i = 0; i < width * height; i++)
buffer[i] = (GrayScalePixelType)i;
// Jedes Bild schreiben: Pixel + Label for(int i = 0; i < numberOfImages; i++)
for (unsigned int img = 0; img < numberOfImages; img++) { {
fwrite(buffer, sizeof(GrayScalePixelType), width * height, file); fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file);
fwrite(&label, sizeof(unsigned char), 1, file); fwrite(&label, sizeof(unsigned char), 1, file);
} }
free(buffer); free(zeroBuffer);
fclose(file); }
fclose(file);
}
} }
/* ---------------------------------------------------------
Unit Tests
--------------------------------------------------------- */
void test_readImagesReturnsCorrectNumberOfImages(void) { void test_readImagesReturnsCorrectNumberOfImages(void)
GrayScaleImageSeries *series = NULL; {
const unsigned int expectedNumberOfImages = 2; GrayScaleImageSeries *series = NULL;
const char *path = "testFile.info2"; const unsigned short expectedNumberOfImages = 2;
prepareImageFile(path, 8, 8, expectedNumberOfImages, 1); const char *path = "testFile.info2";
series = readImages(path); prepareImageFile(path, 8, 8, expectedNumberOfImages, 1);
TEST_ASSERT_NOT_NULL(series); series = readImages(path);
TEST_ASSERT_EQUAL_UINT(expectedNumberOfImages, series->count); TEST_ASSERT_NOT_NULL(series);
clearSeries(series); TEST_ASSERT_EQUAL_UINT16(expectedNumberOfImages, series->count);
remove(path); clearSeries(series);
remove(path);
} }
void test_readImagesReturnsCorrectImageWidth(void) { void test_readImagesReturnsCorrectImageWidth(void)
GrayScaleImageSeries *series = NULL; {
const unsigned int expectedWidth = 10; GrayScaleImageSeries *series = NULL;
const char *path = "testFile.info2"; const unsigned short expectedWidth = 10;
prepareImageFile(path, expectedWidth, 8, 2, 1); const char *path = "testFile.info2";
series = readImages(path); prepareImageFile(path, expectedWidth, 8, 2, 1);
TEST_ASSERT_NOT_NULL(series); series = readImages(path);
TEST_ASSERT_NOT_NULL(series->images); TEST_ASSERT_NOT_NULL(series);
TEST_ASSERT_EQUAL_UINT(2, series->count); TEST_ASSERT_NOT_NULL(series->images);
TEST_ASSERT_EQUAL_UINT(expectedWidth, series->images[0].width); TEST_ASSERT_EQUAL_UINT16(2, series->count);
TEST_ASSERT_EQUAL_UINT(expectedWidth, series->images[1].width); TEST_ASSERT_EQUAL_UINT16(expectedWidth, series->images[0].width);
clearSeries(series); TEST_ASSERT_EQUAL_UINT16(expectedWidth, series->images[1].width);
remove(path); clearSeries(series);
remove(path);
} }
void test_readImagesReturnsCorrectImageHeight(void) { void test_readImagesReturnsCorrectImageHeight(void)
GrayScaleImageSeries *series = NULL; {
const unsigned int expectedHeight = 10; GrayScaleImageSeries *series = NULL;
const char *path = "testFile.info2"; const unsigned short expectedHeight = 10;
prepareImageFile(path, 8, expectedHeight, 2, 1); const char *path = "testFile.info2";
series = readImages(path); prepareImageFile(path, 8, expectedHeight, 2, 1);
TEST_ASSERT_NOT_NULL(series); series = readImages(path);
TEST_ASSERT_NOT_NULL(series->images); TEST_ASSERT_NOT_NULL(series);
TEST_ASSERT_EQUAL_UINT(2, series->count); TEST_ASSERT_NOT_NULL(series->images);
TEST_ASSERT_EQUAL_UINT(expectedHeight, series->images[0].height); TEST_ASSERT_EQUAL_UINT16(2, series->count);
TEST_ASSERT_EQUAL_UINT(expectedHeight, series->images[1].height); TEST_ASSERT_EQUAL_UINT16(expectedHeight, series->images[0].height);
clearSeries(series); TEST_ASSERT_EQUAL_UINT16(expectedHeight, series->images[1].height);
remove(path); clearSeries(series);
remove(path);
} }
void test_readImagesReturnsCorrectLabels(void) { void test_readImagesReturnsCorrectLabels(void)
const unsigned char expectedLabel = 15; {
const unsigned char expectedLabel = 15;
GrayScaleImageSeries *series = NULL; GrayScaleImageSeries *series = NULL;
const char *path = "testFile.info2"; const char *path = "testFile.info2";
prepareImageFile(path, 8, 8, 2, expectedLabel); prepareImageFile(path, 8, 8, 2, expectedLabel);
series = readImages(path); series = readImages(path);
TEST_ASSERT_NOT_NULL(series); TEST_ASSERT_NOT_NULL(series);
TEST_ASSERT_NOT_NULL(series->labels); TEST_ASSERT_NOT_NULL(series->labels);
TEST_ASSERT_EQUAL_UINT(2, series->count); TEST_ASSERT_EQUAL_UINT16(2, series->count);
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
TEST_ASSERT_EQUAL_UINT8(expectedLabel, series->labels[i]); TEST_ASSERT_EQUAL_UINT8(expectedLabel, series->labels[i]);
} }
clearSeries(series); clearSeries(series);
remove(path); remove(path);
} }
void test_readImagesReturnsNullOnNotExistingPath(void) { void test_readImagesReturnsNullOnNotExistingPath(void)
const char *path = "testFile.txt"; {
remove(path); const char *path = "testFile.txt";
TEST_ASSERT_NULL(readImages(path)); remove(path);
}
void test_readImagesFailsOnWrongFileTag(void) {
const char *path = "testFile.info2";
FILE *file = fopen(path, "w");
if (file != NULL) {
fprintf(file, "some_tag ");
fclose(file);
TEST_ASSERT_NULL(readImages(path)); TEST_ASSERT_NULL(readImages(path));
}
remove(path);
} }
void test_read_GrayScale_Pixel(void) { void test_readImagesFailsOnWrongFileTag(void)
GrayScaleImageSeries *series = NULL; {
const char *path = "testFile.info2"; const char *path = "testFile.info2";
FILE *file = fopen(path, "w");
prepareImageFile(path, 8, 8, 1, 1); if(file != NULL)
series = readImages(path); {
fprintf(file, "some_tag ");
TEST_ASSERT_NOT_NULL(series); fclose(file);
TEST_ASSERT_NOT_NULL(series->images); TEST_ASSERT_NULL(readImages(path));
TEST_ASSERT_EQUAL_UINT(1, series->count); }
remove(path);
for (int i = 0; i < (8 * 8); i++) {
TEST_ASSERT_EQUAL_UINT8((GrayScalePixelType)i, series->images[0].buffer[i]);
}
clearSeries(series);
remove(path);
} }
/* --------------------------------------------------------- void setUp(void) {
Optional: Mehrere Bilder gleichzeitig testen // Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
--------------------------------------------------------- */
void test_readImagesMultipleImagesContent(void) {
GrayScaleImageSeries *series = NULL;
const char *path = "testFile.info2";
const unsigned int numberOfImages = 3;
const unsigned int width = 4;
const unsigned int height = 4;
const unsigned char label = 7;
prepareImageFile(path, width, height, numberOfImages, label);
series = readImages(path);
TEST_ASSERT_NOT_NULL(series);
TEST_ASSERT_NOT_NULL(series->images);
TEST_ASSERT_NOT_NULL(series->labels);
TEST_ASSERT_EQUAL_UINT(numberOfImages, series->count);
for (unsigned int img = 0; img < numberOfImages; img++) {
for (unsigned int i = 0; i < width * height; i++)
TEST_ASSERT_EQUAL_UINT8((GrayScalePixelType)i,
series->images[img].buffer[i]);
TEST_ASSERT_EQUAL_UINT8(label, series->labels[img]);
}
clearSeries(series);
remove(path);
} }
/* --------------------------------------------------------- void tearDown(void) {
Setup / Teardown // Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden
--------------------------------------------------------- */
void setUp(void) {}
void tearDown(void) {}
/* ---------------------------------------------------------
main()
--------------------------------------------------------- */
int main(void) {
UNITY_BEGIN();
printf("\n============================\nImage input "
"tests\n============================\n");
RUN_TEST(test_readImagesReturnsCorrectNumberOfImages);
RUN_TEST(test_readImagesReturnsCorrectImageWidth);
RUN_TEST(test_readImagesReturnsCorrectImageHeight);
RUN_TEST(test_readImagesReturnsCorrectLabels);
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
RUN_TEST(test_readImagesFailsOnWrongFileTag);
RUN_TEST(test_read_GrayScale_Pixel);
RUN_TEST(test_readImagesMultipleImagesContent);
return UNITY_END();
} }
int main()
{
UNITY_BEGIN();
printf("\n============================\nImage input tests\n============================\n");
RUN_TEST(test_readImagesReturnsCorrectNumberOfImages);
RUN_TEST(test_readImagesReturnsCorrectImageWidth);
RUN_TEST(test_readImagesReturnsCorrectImageHeight);
RUN_TEST(test_readImagesReturnsCorrectLabels);
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
RUN_TEST(test_readImagesFailsOnWrongFileTag);
return UNITY_END();
}

View File

@ -63,4 +63,4 @@ ifeq ($(OS),Windows_NT)
else else
rm -f *.o mnist runMatrixTests runNeuralNetworkTests runImageInputTests rm -f *.o mnist runMatrixTests runNeuralNetworkTests runImageInputTests
endif endif

219
matrix.c
View File

@ -1,208 +1,35 @@
#include "matrix.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "matrix.h"
/*typedef struct { // TODO Matrix-Funktionen implementieren
unsigned int rows; //Zeilen
unsigned int cols; //Spalten
MatrixType *buffer; //Zeiger auf Speicherbereich Reihen*Spalten
} Matrix;*/
Matrix createMatrix(unsigned int rows, unsigned int cols) { Matrix createMatrix(unsigned int rows, unsigned int cols)
if (cols == 0 || rows == 0) { {
Matrix errorMatrix = {0, 0, NULL};
return errorMatrix;
}
MatrixType *buffer =
malloc(rows * cols * sizeof(MatrixType)); // Speicher reservieren, malloc
// liefert Zeiger auf Speicher
Matrix newMatrix = {rows, cols, buffer}; // neue Matrix nach struct
return newMatrix;
}
void clearMatrix(Matrix *matrix) {
if (matrix->buffer != NULL) {
free((*matrix).buffer);
matrix->buffer = NULL;
}
matrix->rows = 0;
matrix->cols = 0;
} }
void setMatrixAt(const MatrixType value, Matrix matrix, void clearMatrix(Matrix *matrix)
const unsigned int rowIdx, // Kopie der Matrix wird übergeben {
const unsigned int colIdx) {
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) {
// Speichergröße nicht überschreiten
return;
}
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
// rowIdx * matrix.cols -> Beginn der Zeile colIdx ->Spalte
// innerhalb der Zeile
} }
MatrixType getMatrixAt(const Matrix matrix,
unsigned int rowIdx, // Kopie der Matrix wird übergeben
unsigned int colIdx) {
if (rowIdx >= matrix.rows || colIdx >= matrix.cols ||
matrix.buffer == NULL) { // Speichergröße nicht überschreiten
return UNDEFINED_MATRIX_VALUE;
}
MatrixType value = matrix.buffer[rowIdx * matrix.cols + colIdx]; void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{
return value;
} }
Matrix broadcastingCols(const Matrix matrix, const unsigned int cols) {
Matrix copy1 = createMatrix(matrix.rows, cols);
for (int r = 0; r < matrix.rows; r++) {
MatrixType valueMatrix1 = getMatrixAt(matrix, r, 0);
for (int c = 0; c < cols; c++) {
setMatrixAt(valueMatrix1, copy1, r, c);
}
}
return copy1;
}
Matrix broadcastingRows(const Matrix matrix, const unsigned int rows) {
Matrix copy1 = createMatrix(rows, matrix.cols);
for (int c = 0; c < matrix.cols; c++) {
MatrixType valueMatrix1 = getMatrixAt(matrix, 0, c);
for (int r = 0; r < rows; r++) {
setMatrixAt(valueMatrix1, copy1, r, c);
}
}
return copy1;
}
Matrix add(const Matrix matrix1, const Matrix matrix2) {
// Ergebnismatrix MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
Matrix result; {
const int cols1 = matrix1.cols;
const int rows1 = matrix1.rows;
const int cols2 = matrix2.cols;
const int rows2 = matrix2.rows;
const int rowsEqual = (matrix1.rows == matrix2.rows) ? 1 : 0;
const int colsEqual = (matrix1.cols == matrix2.cols) ? 1 : 0;
// Broadcasting nur bei Vektor und Matrix, Fehlermeldung bei zwei unpassender
// Matrix
if (rowsEqual == 1 && colsEqual == 1) {
Matrix result = createMatrix(matrix1.rows, matrix1.cols);
if (result.buffer == NULL) {
return (Matrix){0, 0, NULL};
}
for (int i = 0; i < rows1; i++) {
for (int j = 0; j < cols1; j++) {
int valueM1 = getMatrixAt(matrix1, i, j);
int valueM2 = getMatrixAt(matrix2, i, j);
int sum = valueM1 + valueM2;
setMatrixAt(sum, result, i, j);
}
}
return result;
} else if (rowsEqual == 1 && (cols1 == 1 || cols2 == 1)) {
if (cols1 == 1) { // broadcasting von vektor 1 zu matrix 1, add
Matrix newMatrix = broadcastingCols(matrix1, cols2);
// add
Matrix result = createMatrix(newMatrix.rows, newMatrix.cols);
if (result.buffer == NULL) {
return (Matrix){0, 0, NULL};
}
for (int i = 0; i < rows1; i++) {
for (int j = 0; j < cols2; j++) {
int valueM1 = getMatrixAt(newMatrix, i, j);
int valueM2 = getMatrixAt(matrix2, i, j);
int sum = valueM1 + valueM2;
setMatrixAt(sum, result, i, j);
}
}
return result;
} else {
Matrix newMatrix2 = broadcastingCols(matrix2, cols1);
// add
Matrix result = createMatrix(newMatrix2.rows, newMatrix2.cols);
if (result.buffer == NULL) {
return (Matrix){0, 0, NULL};
}
for (int i = 0; i < rows1; i++) {
for (int j = 0; j < cols1; j++) {
int valueM1 = getMatrixAt(matrix1, i, j);
int valueM2 = getMatrixAt(newMatrix2, i, j);
int sum = valueM1 + valueM2;
setMatrixAt(sum, result, i, j);
}
}
return result;
}
}
else if ((rows1 == 1 || rows2 == 1) && colsEqual == 1) {
if (rows1 == 1) {
Matrix newMatrix = broadcastingRows(matrix1, rows2);
// add
Matrix result = createMatrix(newMatrix.rows, newMatrix.cols);
if (result.buffer == NULL) {
return (Matrix){0, 0, NULL};
}
for (int i = 0; i < rows2; i++) {
for (int j = 0; j < cols1; j++) {
int valueM1 = getMatrixAt(newMatrix, i, j);
int valueM2 = getMatrixAt(matrix2, i, j);
int sum = valueM1 + valueM2;
setMatrixAt(sum, result, i, j);
}
}
return result;
} else {
Matrix newMatrix2 = broadcastingRows(matrix2, rows1);
// add
Matrix result = createMatrix(newMatrix2.rows, newMatrix2.cols);
if (result.buffer == NULL) {
return (Matrix){0, 0, NULL};
}
for (int i = 0; i < rows1; i++) {
for (int j = 0; j < cols1; j++) {
int valueM1 = getMatrixAt(matrix1, i, j);
int valueM2 = getMatrixAt(newMatrix2, i, j);
int sum = valueM1 + valueM2;
setMatrixAt(sum, result, i, j);
}
}
return result;
}
} else {
// kein add möglich
Matrix errorMatrix = {0, 0, NULL};
return errorMatrix;
}
return result;
} }
Matrix multiply(const Matrix matrix1, const Matrix matrix2) {
// Spalten1 müssen gleich zeilen2 sein! dann multiplizieren Matrix add(const Matrix matrix1, const Matrix matrix2)
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)
{
}

View File

@ -6,25 +6,14 @@
typedef float MatrixType; typedef float MatrixType;
// TODO Matrixtyp definieren // TODO Matrixtyp definieren
typedef struct {
unsigned int rows;
unsigned int cols;
MatrixType *buffer;
} Matrix;
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, void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
unsigned int colIdx); MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx,
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);
#endif #endif

View File

@ -1,29 +0,0 @@
Inhalte: Dynamische Speicherverwaltung, Strukturen, Dateien lesen.
Ziel: Die Bilder aus mnist_test.info 2 auslesen
Struktur für einlesen des Strings am Anfang der Datei:
int AnzahlBilder
int breiteBilder
int LaengeBilder
Struktur für Bilder:
unsinged int array Breite * Höhe
unsigned int Klasse (Label 0 - 9)
Speicher für Bilder dynamisch allokieren
GrayScaleImageSeries:
datei einlesen
header String aus der Datei lesen
mit header String den benötigten Speicher freigeben
in den Speicher die Datei einschreiben (mit Hilfsfunktion)
Hilfsfunktion (saveFile)
gehe zum Anfang des Strings
speicher alles der Reihe nach ein
clearSeries:
pointer der be malloc kommt nehemen
free()

View File

@ -1,235 +1,268 @@
#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) { {
double *colSums = (double *)calloc(matrix->cols, sizeof(double)); if(matrix->cols > 0)
{
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++)
MatrixType expValue = exp(getMatrixAt(*matrix, rowIdx, colIdx)); {
setMatrixAt(expValue, *matrix, rowIdx, colIdx); for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++)
colSums[colIdx] += expValue; {
MatrixType expValue = exp(getMatrixAt(*matrix, rowIdx, colIdx));
setMatrixAt(expValue, *matrix, rowIdx, colIdx);
colSums[colIdx] += expValue;
}
}
for(int colIdx = 0; colIdx < matrix->cols; colIdx++)
{
for(int rowIdx = 0; rowIdx < matrix->rows; rowIdx++)
{
MatrixType normalizedValue = getMatrixAt(*matrix, rowIdx, colIdx) / colSums[colIdx];
setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx);
}
}
free(colSums);
} }
} }
}
for (int colIdx = 0; colIdx < matrix->cols; colIdx++) { static void relu(Matrix *matrix)
for (int rowIdx = 0; rowIdx < matrix->rows; rowIdx++) { {
MatrixType normalizedValue = for(int i = 0; i < matrix->rows * matrix->cols; i++)
getMatrixAt(*matrix, rowIdx, colIdx) / colSums[colIdx]; {
setMatrixAt(normalizedValue, *matrix, rowIdx, colIdx); matrix->buffer[i] = matrix->buffer[i] >= 0 ? matrix->buffer[i] : 0;
}
}
static int checkFileHeader(FILE *file)
{
int isValid = 0;
int fileHeaderLen = strlen(FILE_HEADER_STRING);
char buffer[BUFFER_SIZE] = {0};
if(BUFFER_SIZE-1 < fileHeaderLen)
fileHeaderLen = BUFFER_SIZE-1;
if(fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen)
isValid = strcmp(buffer, FILE_HEADER_STRING) == 0;
return isValid;
}
static unsigned int readDimension(FILE *file)
{
int dimension = 0;
if(fread(&dimension, sizeof(int), 1, file) != 1)
dimension = 0;
return dimension;
}
static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int cols)
{
Matrix matrix = createMatrix(rows, cols);
if(matrix.buffer != NULL)
{
if(fread(matrix.buffer, sizeof(MatrixType), rows*cols, file) != rows*cols)
clearMatrix(&matrix);
}
return matrix;
}
static Layer readLayer(FILE *file, unsigned int inputDimension, unsigned int outputDimension)
{
Layer layer;
layer.weights = readMatrix(file, outputDimension, inputDimension);
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;
}
static void clearLayer(Layer *layer)
{
if(layer != NULL)
{
clearMatrix(&layer->weights);
clearMatrix(&layer->biases);
layer->activation = NULL;
}
}
static void assignActivations(NeuralNetwork model)
{
for(int i = 0; i < (int)model.numberOfLayers-1; i++)
{
model.layers[i].activation = relu;
}
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)
{
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);
free(colSums);
assignActivations(model);
} }
}
return model;
} }
static void relu(Matrix *matrix) { static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
for (int i = 0; i < matrix->rows * matrix->cols; i++) { {
matrix->buffer[i] = matrix->buffer[i] >= 0 ? matrix->buffer[i] : 0; Matrix matrix = {NULL, 0, 0};
}
}
static int checkFileHeader(FILE *file) { if(count > 0 && images != NULL)
int isValid = 0; {
int fileHeaderLen = strlen(FILE_HEADER_STRING); matrix = createMatrix(images[0].height * images[0].width, count);
char buffer[BUFFER_SIZE] = {0};
if (BUFFER_SIZE - 1 < fileHeaderLen) if(matrix.buffer != NULL)
fileHeaderLen = BUFFER_SIZE - 1; {
for(int i = 0; i < count; i++)
if (fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen) {
isValid = strcmp(buffer, FILE_HEADER_STRING) == 0; for(int j = 0; j < images[i].width * images[i].height; j++)
{
return isValid; setMatrixAt((MatrixType)images[i].buffer[j], matrix, j, i);
} }
}
static unsigned int readDimension(FILE *file) {
int dimension = 0;
if (fread(&dimension, sizeof(int), 1, file) != 1)
dimension = 0;
return dimension;
}
static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int cols) {
Matrix matrix = createMatrix(rows, cols);
if (matrix.buffer != NULL) {
if (fread(matrix.buffer, sizeof(MatrixType), rows * cols, file) !=
rows * cols)
clearMatrix(&matrix);
}
return matrix;
}
static Layer readLayer(FILE *file, unsigned int inputDimension,
unsigned int outputDimension) {
Layer layer;
layer.weights = readMatrix(file, outputDimension, inputDimension);
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;
}
static void clearLayer(Layer *layer) {
if (layer != NULL) {
clearMatrix(&layer->weights);
clearMatrix(&layer->biases);
layer->activation = NULL;
}
}
static void assignActivations(NeuralNetwork model) {
for (int i = 0; i < (int)model.numberOfLayers - 1; i++) {
model.layers[i].activation = relu;
}
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) {
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( return matrix;
model.layers, (model.numberOfLayers + 1) * sizeof(Layer)); }
if (layerBuffer != NULL) static Matrix forward(const NeuralNetwork model, Matrix inputBatch)
model.layers = layerBuffer; {
else { Matrix result = inputBatch;
clearModel(&model);
break; if(result.buffer != NULL)
{
for(int i = 0; i < model.numberOfLayers; i++)
{
Matrix biasResult;
Matrix 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;
} }
model.layers[model.numberOfLayers] = layer;
model.numberOfLayers++;
inputDimension = outputDimension;
outputDimension = readDimension(file);
}
} }
fclose(file);
assignActivations(model); return result;
}
return model;
} }
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned char *argmax(const Matrix matrix)
unsigned int count) { {
Matrix matrix = {0, 0, NULL}; // falsch herum unsigned char *maxIdx = NULL;
if (count > 0 && images != NULL) { if(matrix.rows > 0 && matrix.cols > 0)
matrix = createMatrix(images[0].height * images[0].width, count); {
maxIdx = (unsigned char *)malloc(sizeof(unsigned char) * matrix.cols);
if (matrix.buffer != NULL) { if(maxIdx != NULL)
for (int i = 0; i < count; i++) { {
for (int j = 0; j < images[i].width * images[i].height; j++) { for(int colIdx = 0; colIdx < matrix.cols; colIdx++)
setMatrixAt((MatrixType)images[i].buffer[j], matrix, j, i); {
maxIdx[colIdx] = 0;
for(int rowIdx = 1; rowIdx < matrix.rows; rowIdx++)
{
if(getMatrixAt(matrix, rowIdx, colIdx) > getMatrixAt(matrix, maxIdx[colIdx], colIdx))
maxIdx[colIdx] = rowIdx;
}
}
} }
}
} }
}
return matrix; return maxIdx;
} }
static Matrix forward(const NeuralNetwork model, Matrix inputBatch) { unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[], unsigned int numberOfImages)
Matrix result = inputBatch; {
Matrix inputBatch = imageBatchToMatrixOfImageVectors(images, numberOfImages);
Matrix outputBatch = forward(model, inputBatch);
if (result.buffer != NULL) { unsigned char *result = argmax(outputBatch);
for (int i = 0; i < model.numberOfLayers; i++) {
Matrix biasResult; clearMatrix(&outputBatch);
Matrix weightResult;
return result;
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;
}
}
return result;
} }
unsigned char *argmax(const Matrix matrix) { void clearModel(NeuralNetwork *model)
unsigned char *maxIdx = NULL; {
if(model != NULL)
if (matrix.rows > 0 && matrix.cols > 0) { {
maxIdx = (unsigned char *)malloc(sizeof(unsigned char) * matrix.cols); for(int i = 0; i < model->numberOfLayers; i++)
{
if (maxIdx != NULL) { clearLayer(&model->layers[i]);
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;
} }
} model->layers = NULL;
model->numberOfLayers = 0;
} }
}
return maxIdx;
}
unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[],
unsigned int numberOfImages) {
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) {
for (int i = 0; i < model->numberOfLayers; i++) {
clearLayer(&model->layers[i]);
}
model->layers = NULL;
model->numberOfLayers = 0;
}
} }

View File

@ -1,331 +1,242 @@
#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"
/*typedef struct
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
{ {
Matrix weights; // TODO
Matrix biases; }
ActivationFunctionType activation;
} Layer;
typedef struct void test_loadModelReturnsCorrectNumberOfLayers(void)
{ {
Layer *layers; const char *path = "some__nn_test_file.info2";
unsigned int numberOfLayers; MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
} NeuralNetwork;*/ MatrixType buffer2[] = {1, 2, 3, 4, 5, 6};
Matrix weights1 = {.buffer=buffer1, .rows=3, .cols=2};
Matrix weights2 = {.buffer=buffer2, .rows=2, .cols=3};
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}};
/*Layer: Ebene im neuronalen Netzwerk, besteht aus mehreren Neuronen NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2};
Input-Layer: Eingabedatei NeuralNetwork netUnderTest;
Hidden-Layer: verarbeiten die Daten
Output-Layer: Ergebnis
prepareNeuralNetworkFile(path, expectedNet);
Gewichte: bestimmen, wie stark ein Eingangssignal auf ein Neuron wirkt netUnderTest = loadModel(path);
remove(path);
Dimension: Form der Matrizen für einen Layer*/ TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, netUnderTest.numberOfLayers);
clearModel(&netUnderTest);
}
// speichert NeuralNetwork nn in binäre Datei->erzeugt Dateiformat void test_loadModelReturnsCorrectWeightDimensions(void)
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) { {
FILE *fptr = fopen(path, "wb"); // Binärdatei zum Schreiben öffnen const char *path = "some__nn_test_file.info2";
if (fptr == NULL) MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
return; // file konnte nicht geöffnet werden Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
Layer layers[] = {{.weights=weights, .biases=biases}};
// Header ist Erkennungsstring am Anfang der Datei, loadmodel erkennt NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
// Dateiformat NeuralNetwork netUnderTest;
const char header[] =
"__info2_neural_network_file_format__"; // header vor jedem Layer
fwrite(header, sizeof(char), strlen(header), fptr);
// Wenn es keine Layer gibt, 0 eintragen, LoadModel gibt 0 zurück prepareNeuralNetworkFile(path, expectedNet);
if (nn.numberOfLayers == 0) {
int zero = 0;
fwrite(&zero, sizeof(int), 1, fptr);
fclose(fptr);
return;
}
// Layer 0, inputDimension: Anzahl Input-Neuronen, outputDimension: Anzahl netUnderTest = loadModel(path);
// Output-Neuronen remove(path);
int inputDim = (int)nn.layers[0].weights.cols;
int outputDim = (int)nn.layers[0].weights.rows;
fwrite(&inputDim, sizeof(int), 1, fptr);
fwrite(&outputDim, sizeof(int), 1, fptr);
/* 3) Für jede Layer in Reihenfolge: Gewichte (output x input), Biases (output TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0);
x 1). Zwischen Layern wird nur die nächste outputDimension (int) TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows);
geschrieben. */ TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols);
for (int i = 0; i < nn.numberOfLayers; i++) { clearModel(&netUnderTest);
Layer layer = nn.layers[i]; }
int wrows = (int)layer.weights.rows; void test_loadModelReturnsCorrectBiasDimensions(void)
int wcols = (int)layer.weights.cols; {
int wcount = wrows * wcols; const char *path = "some__nn_test_file.info2";
int bcount = MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
layer.biases.rows * layer.biases.cols; /* normalerweise rows * 1 */ Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
Layer layers[] = {{.weights=weights, .biases=biases}};
/* Gewichte (MatrixType binär) */ NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
if (wcount > 0 && layer.weights.buffer != NULL) { NeuralNetwork netUnderTest;
fwrite(layer.weights.buffer, sizeof(MatrixType), (size_t)wcount, fptr);
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelReturnsCorrectWeights(void)
{
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
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;
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelReturnsCorrectBiases(void)
{
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
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;
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelFailsOnWrongFileTag(void)
{
const char *path = "some_nn_test_file.info2";
NeuralNetwork netUnderTest;
FILE *file = fopen(path, "wb");
if(file != NULL)
{
const char *fileTag = "info2_neural_network_file_format";
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
fclose(file);
} }
/* Biases (MatrixType binär) */ netUnderTest = loadModel(path);
if (bcount > 0 && layer.biases.buffer != NULL) {
fwrite(layer.biases.buffer, sizeof(MatrixType), (size_t)bcount, fptr); remove(path);
TEST_ASSERT_NULL(netUnderTest.layers);
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
}
void test_clearModelSetsMembersToNull(void)
{
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
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;
prepareNeuralNetworkFile(path, expectedNet);
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]);
} }
/* 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, fptr);
} 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, fptr);
}
}
fclose(fptr);
} }
void test_loadModelReturnsCorrectNumberOfLayers(void) { void test_predictReturnsCorrectLabels(void)
const char *path = "some__nn_test_file.info2"; {
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6}; const unsigned char expectedLabels[] = {4, 2};
MatrixType buffer2[] = {1, 2, 3, 4, 5, 6}; GrayScalePixelType imageBuffer1[] = {10, 30, 25, 17};
Matrix weights1 = {.buffer = buffer1, .rows = 3, .cols = 2}; GrayScalePixelType imageBuffer2[] = {20, 40, 10, 128};
Matrix weights2 = {.buffer = buffer2, .rows = 2, .cols = 3}; GrayScaleImage inputImages[] = {{.buffer=imageBuffer1, .width=2, .height=2}, {.buffer=imageBuffer2, .width=2, .height=2}};
MatrixType buffer3[] = {1, 2, 3}; MatrixType weightsBuffer1[] = {1, -2, 3, -4, 5, -6, 7, -8};
MatrixType buffer4[] = {1, 2}; MatrixType weightsBuffer2[] = {-9, 10, 11, 12, 13, 14};
Matrix biases1 = {.buffer = buffer3, .rows = 3, .cols = 1}; MatrixType weightsBuffer3[] = {-15, 16, 17, 18, -19, 20, 21, 22, 23, -24, 25, 26, 27, -28, -29};
Matrix biases2 = {.buffer = buffer4, .rows = 2, .cols = 1}; Matrix weights1 = {.buffer=weightsBuffer1, .rows=2, .cols=4};
Layer layers[] = {{.weights = weights1, .biases = biases1}, Matrix weights2 = {.buffer=weightsBuffer2, .rows=3, .cols=2};
{.weights = weights2, .biases = biases2}}; Matrix weights3 = {.buffer=weightsBuffer3, .rows=5, .cols=3};
MatrixType biasBuffer1[] = {200, 0};
NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 2}; MatrixType biasBuffer2[] = {0, -100, 0};
NeuralNetwork netUnderTest; MatrixType biasBuffer3[] = {0, -1000, 0, 2000, 0};
Matrix biases1 = {.buffer=biasBuffer1, .rows=2, .cols=1};
prepareNeuralNetworkFile(path, expectedNet); Matrix biases2 = {.buffer=biasBuffer2, .rows=3, .cols=1};
Matrix biases3 = {.buffer=biasBuffer3, .rows=5, .cols=1};
netUnderTest = loadModel(path); Layer layers[] = {{.weights=weights1, .biases=biases1, .activation=someActivation}, \
remove(path); {.weights=weights2, .biases=biases2, .activation=someActivation}, \
{.weights=weights3, .biases=biases3, .activation=someActivation}};
TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, NeuralNetwork netUnderTest = {.layers=layers, .numberOfLayers=3};
netUnderTest.numberOfLayers); unsigned char *predictedLabels = predict(netUnderTest, inputImages, 2);
clearModel(&netUnderTest); TEST_ASSERT_NOT_NULL(predictedLabels);
} int n = (int)(sizeof(expectedLabels) / sizeof(expectedLabels[0]));
TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedLabels, predictedLabels, n);
void test_loadModelReturnsCorrectWeightDimensions(void) { free(predictedLabels);
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer = weightBuffer, .rows = 3, .cols = 2};
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;
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelReturnsCorrectBiasDimensions(void) {
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer = weightBuffer, .rows = 3, .cols = 2};
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;
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelReturnsCorrectWeights(void) {
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer = weightBuffer, .rows = 3, .cols = 2};
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;
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelReturnsCorrectBiases(void) {
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer = weightBuffer, .rows = 3, .cols = 2};
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;
prepareNeuralNetworkFile(path, expectedNet);
netUnderTest = loadModel(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);
}
void test_loadModelFailsOnWrongFileTag(void) {
const char *path = "some_nn_test_file.info2";
NeuralNetwork netUnderTest;
FILE *file = fopen(path, "wb");
if (file != NULL) {
const char *fileTag = "info2_neural_network_file_format";
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
fclose(file);
}
netUnderTest = loadModel(path);
remove(path);
TEST_ASSERT_NULL(netUnderTest.layers);
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
}
void test_clearModelSetsMembersToNull(void) {
const char *path = "some__nn_test_file.info2";
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
Matrix weights = {.buffer = weightBuffer, .rows = 3, .cols = 2};
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;
prepareNeuralNetworkFile(path, expectedNet);
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) { void setUp(void) {
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden // Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
} }
void tearDown(void) { 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); RUN_TEST(test_loadModelReturnsCorrectWeights);
RUN_TEST(test_loadModelReturnsCorrectWeights); RUN_TEST(test_loadModelReturnsCorrectBiases);
RUN_TEST(test_loadModelReturnsCorrectBiases); RUN_TEST(test_loadModelFailsOnWrongFileTag);
RUN_TEST(test_loadModelFailsOnWrongFileTag); RUN_TEST(test_clearModelSetsMembersToNull);
RUN_TEST(test_clearModelSetsMembersToNull); RUN_TEST(test_predictReturnsCorrectLabels);
RUN_TEST(test_predictReturnsCorrectLabels);
return UNITY_END(); return UNITY_END();
} }