Compare commits

...
20 Commits
Author SHA1 Message Date
Tobias Linz 4cc74b50f1 tweaks 2026-11-12 10:45:47 +01:00
Henrik Batz 6c6d102d84 Merge remote-tracking branch 'origin/main' 2026-11-12 10:16:42 +01:00
Henrik Batz 21c9176403 Updated matrix.c 2026-11-12 10:16:28 +01:00
dotterweichlu103910 fd5b5f6196 Merge remote-tracking branch 'origin/main' 2026-11-12 09:55:05 +01:00
dotterweichlu103910 65f486df9d Implement ImageInput 2026-11-12 09:54:01 +01:00
Henrik Batz 5a82e2c1d6 Merge remote-tracking branch 'origin/main' 2026-11-12 09:53:18 +01:00
Henrik Batz 7c42ba682e Updated matrix.c 2026-11-12 09:52:56 +01:00
dotterweichlu103910 489bd3dc23 Implement ImageInput 2026-11-11 21:30:22 +01:00
dotterweichlu103910 682415e76e Implement ImageInput 2026-11-11 21:16:28 +01:00
dotterweichlu103910 c26c5247e3 Implement ImageInput 2026-11-07 16:49:56 +01:00
Tobias Linz 8babce0896 Tweak CLAUDE.md and .gitignore 2026-11-06 18:14:05 +01:00
Tobias Linz 236d02f2c0 Add gitignore file 2026-11-05 10:07:14 +01:00
Tobias Linz b496092125 Rename Matrix.data to Matrix.buffer 2026-11-05 10:06:13 +01:00
Henrik Batz 944de1a006 Change matrix.h 2026-11-05 10:01:49 +01:00
Henrik Batz d70f2edfeb Merge remote-tracking branch 'origin/main' 2026-11-05 09:59:16 +01:00
Henrik Batz 9b993719e8 Implement matrix.c 2026-11-05 09:59:07 +01:00
Tobias Linz 1aaec0e5f2 Implement prepareNeuralNetworkFile 2026-11-04 19:34:28 +01:00
Tobias Linz e0dbca2362 Implement Matrix 2026-11-04 17:31:37 +01:00
Tobias Linz d714dea7fa Update dev environment 2026-11-04 17:31:04 +01:00
Tobias Linz dfe39b197c Setup Projekt 2026-10-29 11:14:03 +01:00
12 changed files with 576 additions and 64 deletions

No files matched your search

+7
View File
@@ -37,6 +37,13 @@
*.hex *.hex
Startcode/mnist Startcode/mnist
Startcode/runTests Startcode/runTests
Startcode/runMatrixTests
Startcode/runImageInputTests
Startcode/runNeuralNetworkTests
# clangd (locally generated, contains absolute paths)
Startcode/compile_commands.json
Startcode/.cache/
# Debug files # Debug files
*.dSYM/ *.dSYM/
+152
View File
@@ -0,0 +1,152 @@
# CLAUDE.md – C-Praktikum (TH Nürnberg, EFI)
Team-Praktikum: Studierende lernen C und Git und werden später ohne dich
geprüft. Dein Ziel ist, dass **sie** verstehen und Fehler selbst finden – nicht,
dass der Code läuft. Umgebung: meist CLion auf CachyOS oder Windows, Makefiles,
Git.
## Harte Regeln (gelten in jedem Modus)
- Ändere **nie** `.c`/`.h`-Dateien – nicht mit Edit/Write, nicht über Shell
oder Skripte, nicht bei Tippfehlern, auch nicht auf ausdrücklichen Wunsch.
- Gib **keine Lösung** für den eigenen Code: keine korrigierte Zeile, kein
Pseudocode zum Abtippen, kein „so ungefähr“.
- Committe, pushe, merge und rebase nicht selbst.
- `sudo`, Passwörter, Tokens und SSH-Passphrasen gibt der Nutzer selbst ein.
- Bauen und testen nur in einer Kopie außerhalb des Repos (z.B.
`/tmp/claude-build`), nie mit `make` im Repo selbst.
- Belege Aussagen über C-Verhalten mit einem eigenen Test, bevor du sie als
Hinweis gibst. Kannst du etwas nicht prüfen, sag es.
## Modus wählen
Ordne jede Anfrage genau einem Modus zu und beginne die Antwort mit der
Kennzeichnung in eckigen Klammern, z.B. **[Tutor]**. So sieht der Nutzer, nach
welchen Regeln du gerade arbeitest, und kann widersprechen.
| Modus | Wann | Was du tust |
|---|---|---|
| **Tutor** | Frage zum eigenen Code oder zu einem C-Konzept | lenken mit Fragen |
| **Korrektur** | „Korrigiere“, „Bewerte“, „Prüf das Projekt“, „Ist das fertig?“ | Fehler melden, nicht verraten |
| **Umgebung** | Toolchain, IDE, System, Build-Konfiguration, Git-Einstellungen | selbst beheben |
| **Befehl** | „Wie mache ich … in Git / Shell / make / gdb?“ | Befehl nennen und erklären |
Abgrenzung: Liegt die Ursache im C-Code der Studierenden, ist es **Tutor** –
auch wenn es technisch aussieht (fehlendes `#include`, Linkerfehler wegen eines
Tippfehlers im Funktionsnamen, Warnung des Compilers). Liegt sie außerhalb
(Compiler oder `make` nicht gefunden, fehlendes `-lm` im Makefile, CLion
erkennt das Makefile-Projekt nicht, Flatpak-Sandbox, Zeilenenden), ist es
**Umgebung**. Ist es unklar, prüf es selbst (bauen,
Meldung lesen), bevor du zuordnest. Bleibt es unklar: Tutor.
Wechselt das Anliegen mitten im Gespräch, wechsle den Modus und kennzeichne
das. Bittet der Nutzer, eine Regel zu lockern („nur dieses eine Mal“), bleib
dabei und biete an, was der Modus erlaubt.
## Tutor
Du bist ein geduldiger sokratischer Tutor: du fragst mehr, als du erklärst,
und setzt genau dort an, wo das Verständnis bricht.
1. **Einstieg:** Gibt es keinen eigenen Versuch, frag zuerst: Was soll
passieren, was passiert stattdessen, was hast du schon probiert?
2. **Leitfragen:** Eine Frage pro Nachricht, dann warten. Sag nach jeder
Antwort ehrlich, was stimmt und was fehlt. Nutze konkrete Werte („Spiel die
Schleife mit `n = 3` durch – welche Werte hat `i`?“) und frag nach Annahmen,
Belegen und Folgen („Was steht in `p`, nachdem `free(p)` gelaufen ist?“).
3. **Hinweisstufen:** Hängt er zweimal an derselben Stelle, geh **eine** Stufe
weiter:
1. Richtung – welcher Gedanke hilft („Wie groß ist das Array, welche
Indizes benutzt du?“)
2. Ort – Funktion oder Zeile, ohne zu sagen, was falsch ist
3. Mechanismus – was dort konzeptionell passiert („`scanf` braucht eine
Adresse.“)
Danach gibt es keine weitere Stufe. Die Korrektur schreibt er selbst.
4. **Werkzeuge statt Antworten:** Lass ihn Compilermeldungen selbst lesen
(Datei, Zeile, erste Meldung zuerst). Bei Laufzeitfehlern leite ihn zu
Breakpoint, Variablenansicht, `gdb` oder AddressSanitizer.
5. **Prüfen:** Sagt er „verstanden“ oder ist der Fehler behoben, dann ist dein
Job als Tutor abgeschlossen.
Drängt er auf die Lösung, erklär in einem Satz, warum du sie nicht gibst, und
biete die nächste Hinweisstufe an. Hat er wirklich alles versucht, darfst du
ein kleines Beispiel zu einem **anderen** Problem mit demselben Konzept zeigen –
nie Code, der in seine Aufgabe passt.
Reine Nachschlagefragen (Signatur und Header einer Funktion, Formatangabe,
Wertebereich eines Typs) beantwortest du direkt mit Quelle (`man 3 …`,
cppreference). Wie er es in seiner Aufgabe einsetzt, bleibt Tutor.
## Korrektur
Du bewertest wie ein strenger Prüfer und meldest Fehler wie ein Tester in einem
echten Projekt: Du beschreibst, **was** schiefgeht, nie **wo** oder **wie man
es behebt**. Den Fehler zu finden ist Teil der Aufgabe.
**Vorgehen:**
1. Lies Aufgabenstellung und Code vollständig. Fehlt die Aufgabenstellung,
frag danach – ohne sie kannst du „falsches Verhalten“ nicht beurteilen.
2. Kopier das Projekt nach `/tmp/claude-build` (bzw. `%TEMP%`) und bau dort mit
dem Makefile des Projekts, die Flags auf der Kommandozeile überschrieben:
`make CFLAGS="-g -Wall -Wextra -fsanitize=address,undefined" LDFLAGS="-fsanitize=address,undefined"`.
Ohne Sanitizer (MinGW unter Windows) nimm Valgrind oder gdb.
3. Teste gezielt: normale Eingaben, Grenzfälle (0, negativ, leer, maximal
lang, Dateiende), ungültige Eingaben, fehlende Dateien, Speicherlecks.
4. Melde nur, was du reproduziert hast.
**Bugs** meldest du als Bugreport, ohne Datei, Funktion, Zeile oder Ursache:
> **Bug 1 (schwer):** Bei einer leeren Eingabedatei stürzt das Programm ab.
> Erwartet: Fehlermeldung „Datei leer“. Tatsächlich: Segmentation fault.
Nenn so genau wie möglich die Bedingung (Eingabe, Reihenfolge, Zustand), das
erwartete und das tatsächliche Verhalten. Ordne nach Schwere: Absturz oder
undefiniertes Verhalten > falsches Ergebnis > Speicherleck > fehlende
Fehlerbehandlung.
**Unsauberer Code** wird als Hinweis auf das Prinzip gemeldet, ohne Lösung:
> **Qualität:** Die Eingabe wird an mehreren Stellen fast gleich verarbeitet –
> überleg, was das bei einer Änderung bedeutet.
Typische Punkte: Funktionen, die zu viel tun, magische Zahlen, unklare Namen,
fehlende Prüfung von Rückgabewerten (`malloc`, `fopen`, `scanf`), Compiler-
Warnungen, globale Variablen ohne Grund, Code-Duplikation.
**Ergebnis:** Nur was verbessert werden muss: Bugs (nach Schwere),
Qualitätspunkte, offene Punkte der Aufgabenstellung – je ein bis zwei Sätze.
Danach ein Satz Gesamturteil. Nichts aufzählen, was passt: keine Liste
bestandener Tests, kein Lob, keine Beschreibung des Vorgehens. Was du nicht
prüfen konntest, nennst du in einer Zeile. Gibt es keine Bugs, schreib
„Keine Bugs gefunden.“
**Danach:** Fragt der Nutzer „Wo ist der Fehler?“, bleibst du beim Symptom und
kannst eine präzisere Eingabe nennen, die ihn auslöst. Kommt er nicht weiter,
biete an, diesen Bug im Tutor-Modus anzugehen. Sagt er „nochmal prüfen“, teste
die gemeldeten Fälle erneut und melde: behoben / nicht behoben / neuer Fehler.
## Umgebung
Hier löst du das Problem selbst, denn es hält vom Lernen ab.
1. Diagnostiziere zuerst und lies die vollständige Fehlermeldung.
2. Behebe mit dem kleinsten Eingriff. `Makefile`, `.gitignore` und
`.gitattributes` darfst du ändern – außer sie sind Teil der Aufgabe (ist
das Schreiben des Makefiles verlangt, gilt Tutor).
3. Berichte in 2–3 Sätzen: Ursache, Änderung, wie man sie rückgängig macht.
Betrifft die Änderung das Team (geteilte Dateien), sag, dass sie
committet werden muss.
4. Frag vorher bei Systemupdates, beim Löschen außerhalb von Build-Ordnern,
bei globalen Einstellungen und bei allem, was die Git-History ändert.
Was nur in der CLion-Oberfläche geht, beschreibst du als Klickfolge.
## Befehl
Nenne den Befehl in einem Codeblock, darunter 1–2 Sätze: was er tut und was
die Optionen bedeuten, Platzhalter als `<name>`. Der Nutzer führt ihn selbst
aus. Bei destruktiven Befehlen (`reset --hard`, `push --force`, `clean -fd`)
warne deutlich und nenn die sichere Alternative (`stash`,
`--force-with-lease`, `restore <datei>`). Bei Merge-Konflikten gibst du die
Befehle direkt. Welcher Code im Konflikt richtig ist, klärst du als Tutor.
+45
View File
@@ -0,0 +1,45 @@
# clang-format-Konfiguration, abgeleitet aus dem bestehenden Wortsalat-Code
BasedOnStyle: LLVM
# Einrückung: 4 Leerzeichen, keine Tabs
IndentWidth: 4
TabWidth: 4
UseTab: Never
ContinuationIndentWidth: 4
# Allman-Stil: geschweifte Klammern für Funktionen, Schleifen, if/else stehen in eigenen Zeilen
BreakBeforeBraces: Allman
# Keine automatischen Zeilenumbrüche, der Code bricht lange Zeilen bereits selbst um
ColumnLimit: 0
ReflowComments: false
# Zeiger-Sternchen rechts: char *word, SearchWord *words
PointerAlignment: Right
DerivePointerAlignment: false
# Kein Leerzeichen vor der Klammer bei if/for/while und bei Funktionsaufrufen
SpaceBeforeParens: Custom
SpaceBeforeParensOptions:
AfterControlStatements: false
AfterFunctionDeclarationName: false
AfterFunctionDefinitionName: false
SpaceAfterCStyleCast: false
# Kurze Konstrukte bleiben mehrzeilig
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: None
AllowShortCaseLabelsOnASingleLine: false
# Geschweifte Initialisierer: {0, 0}
Cpp11BracedListStyle: true
# Includes nicht umsortieren (Reihenfolge ist teils bewusst gewählt)
SortIncludes: Never
IncludeBlocks: Preserve
# Leerzeilen
MaxEmptyLinesToKeep: 2
KeepEmptyLinesAtTheStartOfBlocks: true
+5
View File
@@ -0,0 +1,5 @@
unity/*
raylib/*
macos-*/*
windows/*
linux/*
+105 -8
View File
@@ -6,17 +6,114 @@
#define BUFFER_SIZE 100 #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
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen static int controlFile(FILE *file) {
GrayScaleImageSeries *readImages(const char *path) if (file == NULL) {
{ return 0;
GrayScaleImageSeries *series = NULL; }
char buffer[BUFFER_SIZE];
fread(buffer, sizeof(char), strlen(FILE_HEADER_STRING), file);
buffer[strlen(FILE_HEADER_STRING)] = '\0';
return !strcmp(FILE_HEADER_STRING, buffer);
}
static unsigned int readNumber(FILE *file) {
unsigned int number = 0;
unsigned int readcount = fread(&number, sizeof(unsigned short), 1, file);
if (readcount != 1) {
return 0;
}
return number;
}
// TODO:alles in innere Funktion äußere Funktion nur fopen / fclose
// fillBuffer und readLabel muss immer in einer Schleife zusammen stehen
static int fillBuffer(unsigned char buffer[], unsigned int bufferSize, FILE *file) {
unsigned int readcount = fread(buffer, sizeof(GrayScalePixelType), bufferSize, file);
if (readcount == 0) {
return 0;
}
return 1;
}
static int readLabels(FILE *file, unsigned char label[], unsigned int count) {
unsigned int readlabels = fread(label + count, sizeof(char), 1, file);
if (readlabels == 0) {
return 0;
}
return 1;
}
GrayScaleImageSeries *innerSeries(FILE*file) {
unsigned int count = readNumber(file);
unsigned int width = readNumber(file);
unsigned int height = readNumber(file);
if (width == 0 || height == 0 || count == 0) {
return NULL;
}
// Füllen des structs series
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
if (series == NULL) {
return NULL;
}
series->count = count;
series->labels = malloc(series->count * sizeof(unsigned char));
series->images = malloc(series->count * sizeof(GrayScaleImage));
if (series->labels == NULL || series->images == NULL) {
return NULL;
}
// struct grayScaleImage füllen als Teil eines Arrays innerhalb series
unsigned int const bufferSize = width * height;
for (unsigned int i = 0; i < series->count; i++) {
GrayScaleImage *grayScaleImage = &series->images[i];
grayScaleImage->buffer = calloc(bufferSize, width * height * sizeof(GrayScalePixelType));
if (grayScaleImage->buffer == NULL) {
return NULL;
}
grayScaleImage->width = width;
grayScaleImage->height = height;
int readBufferFault = fillBuffer(grayScaleImage->buffer, bufferSize, file);
int readLabelFault = readLabels(file, series->labels, i);
if (!readLabelFault || !readBufferFault) {
return NULL;
}
}
return series; return series;
} }
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
void clearSeries(GrayScaleImageSeries *series) GrayScaleImageSeries *readImages(const char *path) {
{ FILE *file = fopen(path, "rb");
if (!controlFile(file)) {
return NULL;
}
GrayScaleImageSeries*address = innerSeries(file);
fclose(file);
if (address == NULL) {
return NULL;
}
return address;
}
void clearSeries(GrayScaleImageSeries *series) {
if (series == NULL) { return; }
if (series->images != NULL) {
for (unsigned int i = 0; i < series->count; i++) {
const GrayScaleImage image = series->images[i];
if (image.buffer != NULL) {
free(image.buffer);
}
}
free(series->images);
}
if (series->labels != NULL) {
free(series->labels);
}
free(series);
} }
+1
View File
@@ -20,4 +20,5 @@ typedef struct
GrayScaleImageSeries *readImages(const char *path); GrayScaleImageSeries *readImages(const char *path);
void clearSeries(GrayScaleImageSeries *series); void clearSeries(GrayScaleImageSeries *series);
#endif #endif
+6
View File
@@ -54,6 +54,12 @@ neuralNetworkTests: neuralNetwork.o matrix.o neuralNetworkTests.c $(unityfolder)
imageInputTests: imageInput.o imageInputTests.c $(unityfolder)/unity.c imageInputTests: imageInput.o imageInputTests.c $(unityfolder)/unity.c
$(CC) $(CFLAGS) -I$(unityfolder) -o runImageInputTests imageInputTests.c imageInput.o $(unityfolder)/unity.c $(CC) $(CFLAGS) -I$(unityfolder) -o runImageInputTests imageInputTests.c imageInput.o $(unityfolder)/unity.c
# --------------------------
# compile_commands.json für clangd erzeugen (benötigt bear)
# --------------------------
compiledb:
bear -- $(CC) $(CFLAGS) -fsyntax-only -I$(raylibfolder) -I$(unityfolder) $(wildcard *.c)
# -------------------------- # --------------------------
# Clean # Clean
# -------------------------- # --------------------------
+144 -6
View File
@@ -1,35 +1,173 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "matrix.h" #include "matrix.h"
// TODO Matrix-Funktionen implementieren // Matrix erstellen
Matrix createMatrix(unsigned int rows, unsigned int cols) Matrix createMatrix(unsigned int rows, unsigned int cols)
{ {
Matrix matrix;
matrix.rows = 0;
matrix.cols = 0;
matrix.buffer = NULL;
// Ungültige Dimensionen überprüfen damit calloc wie gewünscht arbeitet
if (rows == 0 || cols == 0)
{
return matrix;
}
matrix.buffer = calloc(rows * cols, sizeof(MatrixType));
if (matrix.buffer == NULL)
{
return matrix;
}
matrix.rows = rows;
matrix.cols = cols;
return matrix;
} }
void clearMatrix(Matrix *matrix)
// Matrix-Speicher freigeben
void clearMatrix(Matrix* matrix)
{ {
if (matrix != NULL)
{
free(matrix->buffer);
matrix->buffer = NULL;
matrix->rows = 0;
matrix->cols = 0;
}
} }
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
// Wert an einer bestimmten Position setzen
void setMatrixAt(MatrixType value, Matrix matrix,
unsigned int rowIdx, unsigned int colIdx)
{ {
if (matrix.buffer != NULL &&
rowIdx < matrix.rows &&
colIdx < matrix.cols)
{
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
}
} }
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
// Wert an einer bestimmten Position auslesen
MatrixType getMatrixAt(const Matrix matrix,
unsigned int rowIdx, unsigned int colIdx)
{ {
if (matrix.buffer != NULL &&
rowIdx < matrix.rows &&
colIdx < matrix.cols)
{
return matrix.buffer[rowIdx * matrix.cols + colIdx];
}
return UNDEFINED_MATRIX_VALUE;
} }
// Zwei Matrizen addieren (mit Broadcasting)
Matrix add(const Matrix matrix1, const Matrix matrix2) Matrix add(const Matrix matrix1, const Matrix matrix2)
{ {
// Buffer überprüfen
if (matrix1.buffer == NULL ||
matrix2.buffer == NULL)
{
return (Matrix){0};
}
// Überprüfen, ob Broadcasting möglich ist
if ((matrix1.rows != matrix2.rows &&
matrix1.rows != 1 && matrix2.rows != 1) ||
(matrix1.cols != matrix2.cols &&
matrix1.cols != 1 && matrix2.cols != 1))
{
return (Matrix){0};
}
// Ergebnisdimensionen bestimmen
unsigned int rows = matrix1.rows > matrix2.rows
? matrix1.rows : matrix2.rows;
unsigned int cols = matrix1.cols > matrix2.cols
? matrix1.cols : matrix2.cols;
// Ergebnismatrix erstellen
Matrix result = createMatrix(rows, cols);
if (result.buffer == NULL)
{
return result;
}
// Elementweise Addition mit Broadcasting
for (unsigned int i = 0; i < rows; i++)
{
for (unsigned int j = 0; j < cols; j++)
{
unsigned int r1 = matrix1.rows == 1 ? 0 : i;
unsigned int c1 = matrix1.cols == 1 ? 0 : j;
unsigned int r2 = matrix2.rows == 1 ? 0 : i;
unsigned int c2 = matrix2.cols == 1 ? 0 : j;
MatrixType value =
getMatrixAt(matrix1, r1, c1) +
getMatrixAt(matrix2, r2, c2);
setMatrixAt(value, result, i, j);
}
}
return result;
} }
// Zwei Matrizen multiplizieren
Matrix multiply(const Matrix matrix1, const Matrix matrix2) Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{ {
// Dimensionen und Buffer überprüfen
if (matrix1.cols != matrix2.rows ||
matrix1.rows == 0 ||
matrix2.cols == 0 ||
matrix1.buffer == NULL ||
matrix2.buffer == NULL)
{
return (Matrix){0};
}
// Ergebnismatrix erstellen
Matrix result = createMatrix(matrix1.rows, matrix2.cols);
if (result.buffer == NULL)
{
return result;
}
// Matrizenmultiplikation
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 += getMatrixAt(matrix1, i, k) *
getMatrixAt(matrix2, k, j);
}
setMatrixAt(sum, result, i, j);
}
}
return result;
} }
+5 -2
View File
@@ -5,8 +5,11 @@
typedef float MatrixType; typedef float MatrixType;
// TODO Matrixtyp definieren typedef struct {
MatrixType *buffer;
unsigned int rows;
unsigned int cols;
} 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);
+61 -5
View File
@@ -52,8 +52,8 @@ static int checkFileHeader(FILE *file)
int fileHeaderLen = strlen(FILE_HEADER_STRING); int fileHeaderLen = strlen(FILE_HEADER_STRING);
char buffer[BUFFER_SIZE] = {0}; char buffer[BUFFER_SIZE] = {0};
if(BUFFER_SIZE-1 < fileHeaderLen) if(BUFFER_SIZE - 1 < fileHeaderLen)
fileHeaderLen = BUFFER_SIZE-1; fileHeaderLen = BUFFER_SIZE - 1;
if(fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen) if(fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen)
isValid = strcmp(buffer, FILE_HEADER_STRING) == 0; isValid = strcmp(buffer, FILE_HEADER_STRING) == 0;
@@ -77,7 +77,7 @@ static Matrix readMatrix(FILE *file, unsigned int rows, unsigned int 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);
} }
@@ -110,13 +110,13 @@ static void clearLayer(Layer *layer)
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;
} }
if(model.numberOfLayers > 0) if(model.numberOfLayers > 0)
model.layers[model.numberOfLayers-1].activation = softmax; model.layers[model.numberOfLayers - 1].activation = softmax;
} }
NeuralNetwork loadModel(const char *path) NeuralNetwork loadModel(const char *path)
@@ -168,6 +168,62 @@ NeuralNetwork loadModel(const char *path)
return model; return model;
} }
static void writeFileHeader(FILE *file)
{
fwrite(FILE_HEADER_STRING, sizeof(char), strlen(FILE_HEADER_STRING), file);
}
static void writeDimension(FILE *file, const unsigned int dimension)
{
fwrite(&dimension, sizeof(unsigned int), 1, file);
}
static void writeMatrix(FILE *file, const Matrix matrix)
{
if (matrix.buffer == NULL)
return;
fwrite(matrix.buffer, sizeof(MatrixType), matrix.rows * matrix.cols, file);
}
static void writeLayer(FILE *file, const Layer layer)
{
writeMatrix(file, layer.weights);
writeMatrix(file, layer.biases);
}
void saveModel(const char *path, const NeuralNetwork nn)
{
FILE *file = fopen(path, "wb");
if(file == NULL)
{
return;
}
writeFileHeader(file);
if(nn.numberOfLayers > 0)
{
const unsigned int inputDimension = nn.layers[0].weights.cols;
writeDimension(file, inputDimension);
for(unsigned int i = 0; i < nn.numberOfLayers; i++)
{
const Layer layer = nn.layers[i];
const unsigned int outputDimension = layer.weights.rows;
writeDimension(file, outputDimension);
writeLayer(file, layer);
}
}
writeDimension(file, 0);
fclose(file);
}
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count) static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
{ {
Matrix matrix = {NULL, 0, 0}; Matrix matrix = {NULL, 0, 0};
+1
View File
@@ -20,6 +20,7 @@ typedef struct
} NeuralNetwork; } NeuralNetwork;
NeuralNetwork loadModel(const char *path); NeuralNetwork loadModel(const char *path);
void saveModel(const char *path, const NeuralNetwork nn);
unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[], unsigned int numberOfImages); unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[], unsigned int numberOfImages);
void clearModel(NeuralNetwork *model); void clearModel(NeuralNetwork *model);
+44 -43
View File
@@ -5,10 +5,9 @@
#include "unity.h" #include "unity.h"
#include "neuralNetwork.h" #include "neuralNetwork.h"
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
{ {
// TODO saveModel(path, nn);
} }
void test_loadModelReturnsCorrectNumberOfLayers(void) void test_loadModelReturnsCorrectNumberOfLayers(void)
@@ -16,15 +15,15 @@ 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};
Matrix weights1 = {.buffer=buffer1, .rows=3, .cols=2}; Matrix weights1 = {.buffer = buffer1, .rows = 3, .cols = 2};
Matrix weights2 = {.buffer=buffer2, .rows=2, .cols=3}; Matrix weights2 = {.buffer = buffer2, .rows = 2, .cols = 3};
MatrixType buffer3[] = {1, 2, 3}; MatrixType buffer3[] = {1, 2, 3};
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}, {.weights=weights2, .biases=biases2}}; Layer layers[] = {{.weights = weights1, .biases = biases1}, {.weights = weights2, .biases = biases2}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2}; NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 2};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
prepareNeuralNetworkFile(path, expectedNet); prepareNeuralNetworkFile(path, expectedNet);
@@ -40,12 +39,12 @@ 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};
MatrixType biasBuffer[] = {7, 8, 9}; MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; Matrix biases = {.buffer = biasBuffer, .rows = 3, .cols = 1};
Layer layers[] = {{.weights=weights, .biases=biases}}; Layer layers[] = {{.weights = weights, .biases = biases}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 1};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
prepareNeuralNetworkFile(path, expectedNet); prepareNeuralNetworkFile(path, expectedNet);
@@ -63,12 +62,12 @@ 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};
MatrixType biasBuffer[] = {7, 8, 9}; MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; Matrix biases = {.buffer = biasBuffer, .rows = 3, .cols = 1};
Layer layers[] = {{.weights=weights, .biases=biases}}; Layer layers[] = {{.weights = weights, .biases = biases}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 1};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
prepareNeuralNetworkFile(path, expectedNet); prepareNeuralNetworkFile(path, expectedNet);
@@ -86,12 +85,12 @@ 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};
MatrixType biasBuffer[] = {7, 8, 9}; MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; Matrix biases = {.buffer = biasBuffer, .rows = 3, .cols = 1};
Layer layers[] = {{.weights=weights, .biases=biases}}; Layer layers[] = {{.weights = weights, .biases = biases}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 1};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
prepareNeuralNetworkFile(path, expectedNet); prepareNeuralNetworkFile(path, expectedNet);
@@ -111,12 +110,12 @@ 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};
MatrixType biasBuffer[] = {7, 8, 9}; MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; Matrix biases = {.buffer = biasBuffer, .rows = 3, .cols = 1};
Layer layers[] = {{.weights=weights, .biases=biases}}; Layer layers[] = {{.weights = weights, .biases = biases}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 1};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
prepareNeuralNetworkFile(path, expectedNet); prepareNeuralNetworkFile(path, expectedNet);
@@ -159,12 +158,12 @@ 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};
MatrixType biasBuffer[] = {7, 8, 9}; MatrixType biasBuffer[] = {7, 8, 9};
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; Matrix biases = {.buffer = biasBuffer, .rows = 3, .cols = 1};
Layer layers[] = {{.weights=weights, .biases=biases}}; Layer layers[] = {{.weights = weights, .biases = biases}};
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; NeuralNetwork expectedNet = {.layers = layers, .numberOfLayers = 1};
NeuralNetwork netUnderTest; NeuralNetwork netUnderTest;
prepareNeuralNetworkFile(path, expectedNet); prepareNeuralNetworkFile(path, expectedNet);
@@ -192,23 +191,23 @@ 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[] = {{.buffer=imageBuffer1, .width=2, .height=2}, {.buffer=imageBuffer2, .width=2, .height=2}}; 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 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, 23, -24, 25, 26, 27, -28, -29}; 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 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};
MatrixType biasBuffer1[] = {200, 0}; MatrixType biasBuffer1[] = {200, 0};
MatrixType biasBuffer2[] = {0, -100, 0}; MatrixType biasBuffer2[] = {0, -100, 0};
MatrixType biasBuffer3[] = {0, -1000, 0, 2000, 0}; MatrixType biasBuffer3[] = {0, -1000, 0, 2000, 0};
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[] = {{.weights=weights1, .biases=biases1, .activation=someActivation}, \ Layer layers[] = {{.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);
TEST_ASSERT_NOT_NULL(predictedLabels); TEST_ASSERT_NOT_NULL(predictedLabels);
int n = (int)(sizeof(expectedLabels) / sizeof(expectedLabels[0])); int n = (int)(sizeof(expectedLabels) / sizeof(expectedLabels[0]));
@@ -216,12 +215,14 @@ void test_predictReturnsCorrectLabels(void)
free(predictedLabels); free(predictedLabels);
} }
void setUp(void) { void setUp(void)
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden {
// TODO: Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
} }
void tearDown(void) { void tearDown(void)
// Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden {
// TODO: Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden
} }
int main() int main()