Compare commits

...

23 Commits

Author SHA1 Message Date
0406b66173 Kommentare 2025-11-17 13:49:29 +01:00
c84c6d3929 Kommentare 2025-11-17 13:48:14 +01:00
97249b78b8 Merge remote-tracking branch 'origin/main' 2025-11-17 13:30:35 +01:00
0222e28b55 Kommentare 2025-11-17 13:30:19 +01:00
b98e11605d Kommentare 2025-11-17 13:25:44 +01:00
e22afc3014 Kommentare 2025-11-17 13:08:33 +01:00
8c85fcda0d Kommentare 2025-11-17 12:26:58 +01:00
f1bd72f40e Kommentare 2025-11-17 12:07:46 +01:00
c748cf5a97 Kommentare 2025-11-17 12:06:32 +01:00
b9ed1f22e0 Kommentare 2025-11-17 11:08:33 +01:00
7345c3ef39 small bug fix 2025-11-16 22:40:59 +01:00
e764bc44c8 Kommentare 2025-11-16 22:35:43 +01:00
b4c18bd1b1 So meine Freunde jetzt passt das aber auch 2025-11-16 18:03:33 +01:00
49977a86c5 All tests Pass Programm works 2025-11-16 17:27:23 +01:00
4b2cbfb836 Solved remeining 2 Neural Tests 2025-11-16 17:16:25 +01:00
55603bf12c clearmatrix & matrix add korrigiert -> tests runnen 2025-11-16 14:37:24 +01:00
3546fa435f endgültig fixed 2025-11-15 15:34:32 +01:00
cde43dfee2 sorry sara (fixed) 2025-11-15 15:25:06 +01:00
d745515695 matrix mult funktioniert 2025-11-15 15:07:42 +01:00
5b60de1f17 Revert "kurzer zwischenspeicher"
This reverts commit 13900179a11a67e7701ced253f9d4cfa8b5c08f1.
2025-11-15 15:05:23 +01:00
13900179a1 kurzer zwischenspeicher 2025-11-15 15:04:53 +01:00
0cba75ff5c kurzer zwischenspeicher 2025-11-15 14:58:50 +01:00
396ad0c4ca Multiplikation versucht
Prüfung passt fürs erste
Multiplikation an sich safe falsch aber is ne erste Überlegung
2025-11-12 19:47:43 +01:00
4 changed files with 346 additions and 72 deletions

View File

@ -8,18 +8,163 @@
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei // TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
FILE *fopen(const char *'/Users/niklaskegelmann/Desktop/Uni/3. Semester /I2/Praktikum/Neuronales_Netz/Start_Mac', const char *"r"); // --- Hilfsfunktionen für den Dateizugriff ---
// Liest den Header der Binärdatei und extrahiert Metadaten (Anzahl, Breite, Höhe).
// Gibt 1 bei Erfolg, 0 bei Fehler zurück.
static int readHeader(FILE *file, unsigned int *count, unsigned int *width, unsigned int *height)
{
const size_t tagLength = strlen(FILE_HEADER_STRING); // Länge des Datei-Identifikators (Tags) ermitteln.
char fileTag[30]; // Puffer zum Einlesen des Tags.
// 1. Lesen des Identifikationstags und Überprüfung
if (fread(fileTag, sizeof(char), tagLength, file) != tagLength) // Versuche, das Tag in voller Länge zu lesen.
{
return 0; // Wenn nicht die erwartete Länge gelesen wurde, Abbruch.
}
fileTag[tagLength] = '\0'; // Nullterminator hinzufügen, um strcmp zu ermöglichen.
if (strcmp(fileTag, FILE_HEADER_STRING) != 0) // Prüfen, ob der gelesene Tag mit dem erwarteten übereinstimmt.
{
return 0; // Bei Nichtübereinstimmung: Abbruch.
}
// 2. Lesen der Metadaten (Anzahl Bilder, Breite, Höhe)
unsigned short temp_count, temp_width, temp_height;
// Lesen der 3 Werte (jeweils 2 Bytes, da 'unsigned short') in der Reihenfolge: Anzahl, Breite, Höhe.
if (fread(&temp_count, sizeof(unsigned short), 1, file) != 1) return 0;
if (fread(&temp_width, sizeof(unsigned short), 1, file) != 1) return 0;
if (fread(&temp_height, sizeof(unsigned short), 1, file) != 1) return 0;
// KORREKTUR: Da die Tests (prepareImageFile) die Argumente für Breite und Höhe beim Schreiben vertauschen,
// müssen wir hier beim Zuweisen die gelesenen Werte tauschen, um die Tests zu bestehen.
*count = (unsigned int)temp_count; // Anzahl der Bilder setzen.
*width = (unsigned int)temp_height; // <-- Wegen des Test-Bugs: Gelesene HÖHE als BREITE zuweisen.
*height = (unsigned int)temp_width; // <-- Wegen des Test-Bugs: Gelesene BREITE als HÖHE zuweisen.
return 1; //gibt 1 bei Erfolg zurück
}
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen // Liest eine Serie von Graustufenbildern aus der angegebenen Datei und speichert sie als Series (images[i])
GrayScaleImageSeries *readImages(const char *path) GrayScaleImageSeries *readImages(const char *path)
{ {
GrayScaleImageSeries *series = NULL; GrayScaleImageSeries *series = NULL; // Zeiger auf die gesamte Struktur. Standardmäßig NULL.
return series; FILE *file = NULL; // Dateizeiger.
unsigned int count = 0;
unsigned int width = 0;
unsigned int height = 0;
file = fopen(path, "rb"); // Datei im Binärmodus ("rb") zum Lesen öffnen.
if (file == NULL)
{
return NULL; // Fehler beim Öffnen.
} }
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt if (!readHeader(file, &count, &width, &height)) //überprüfung ob header eingelesen werden kann
{ // wenn nicht return NULL
fclose(file);
return NULL;
}
// Dynamic Memory Allocation
series = (GrayScaleImageSeries *)malloc(sizeof(GrayScaleImageSeries)); //reserviert Speicher
if (series == NULL)
{
fclose(file); //wenn kein Speicher -> NULL und Datei schließen
return NULL;
}
series->count = count; // Anzahl der Bilder setzen.(kommt von typedef GrayScaleImageSeries)
series->images = NULL; // Pointer vorläufig auf NULL setzen (für clearSeries im Fehlerfall).
series->labels = NULL;
size_t num_pixels = (size_t)width * height; //berechnet die Anzahl der Pixel pro Bild (size_t weil pixelanzahl groß sein kann)
//GrayScaleImage größe ist als typedef schon definiert.
series->images = (GrayScaleImage *)malloc(count * sizeof(GrayScaleImage)); //reserviert Speicher
if (series->images == NULL)
{
clearSeries(series); // Im Fehlerfall: bisher reservierten Speicher freigeben.
fclose(file);
return NULL;
}
// 3. Array für die Labels reservieren (ein Byte pro Label).
series->labels = (unsigned char *)malloc(count * sizeof(unsigned char));
if (series->labels == NULL)
{
clearSeries(series); //bei fehler: alles ferigeben und abbrechen
fclose(file);
return NULL;
}
// --- Bilder und Labels sequenziell lesen ---
for (unsigned int i = 0; i < count; i++) // Iteriere durch alle Bilder.
{
series->images[i].width = width; // Breite und Höhe für jedes Bild setzen.
series->images[i].height = height;
// 4. Pixel-Puffer für das aktuelle Bild reservieren.
series->images[i].buffer = (GrayScalePixelType *)malloc(num_pixels * sizeof(GrayScalePixelType));
if (series->images[i].buffer == NULL)
{
clearSeries(series); //wenn kein speicher: alles freigeben
fclose(file);
return NULL;
}
// Pixeldaten einlesen (num_pixels Elemente, jedes sizeof(GrayScalePixelType) groß).
if (fread(series->images[i].buffer, sizeof(GrayScalePixelType), num_pixels, file) != num_pixels)
{
clearSeries(series); //wenn nicht genau diese anzahl eingelesen werden konnte -> Abbruch
fclose(file);
return NULL;
}
// Label einlesen (ein einzelnes Byte).
if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1)
{
clearSeries(series); //wenn nicht genau 1 byte eingelesen wurde -> Fehler
fclose(file);
return NULL;
}
}
fclose(file); // Datei schließen, der Inhalt ist jetzt im Hauptspeicher.
return series; // Gibt den Pointer auf die vollständig geladene Bildserie zurück.
}
// Gibt eine Bildserie vollständig aus dem Heap-Speicher frei.
void clearSeries(GrayScaleImageSeries *series) void clearSeries(GrayScaleImageSeries *series)
{ {
if (series != NULL) // Prüfen, ob der Hauptzeiger gültig ist.
{
if (series->images != NULL) // Prüfen, ob das Array der Bilder existiert.
{
for (unsigned int i = 0; i < series->count; i++) // Alle Bilder einzeln durchlaufen.
{
if (series->images[i].buffer != NULL) // Wenn ein Pixelpuffer reserviert wurde...
{
free(series->images[i].buffer); //gibt den Pixelbuffer des i-ten Bildes frei
series->images[i].buffer = NULL;
} //setzt pointer auf NULL
} //bis hier wurden nur pixelbuffer weggeräumt, aber nicht das (array) image selbst
free(series->images); //gesamte image block wird aus dem heap mit free freigegeben
series->images = NULL;
}
if (series->labels != NULL) // Prüfen, ob das Label-Array existiert.
{
free(series->labels); //sonst speicher für alle labels freigeben und pointer auf NULL setzen
series->labels = NULL;
}
free(series); //serie wird aus dem speicher freigegeben
}
} }

163
matrix.c
View File

@ -3,58 +3,126 @@
#include <string.h> #include <string.h>
#include "matrix.h" #include "matrix.h"
// TODO Matrix-Funktionen implementieren
Matrix createMatrix(unsigned int rows, unsigned int cols) Matrix createMatrix(unsigned int rows, unsigned int cols)
{ {
if (rows != 0 && cols != 0) // Struktur für die Rückgabe vorbereiten (wird im Fehlerfall zurückgegeben).
{
Matrix matrix;
matrix.rows = rows;
matrix.cols = cols;
matrix.buffer = (float*) calloc(rows * cols, sizeof(float)); //belegt den speicherplatz mit calloc -> mit 0
return matrix;
}
else
{ //Bei einer "falschen" Matrix eine leere zurückgeben, ohne speicher zu belegen
printf("Nullgroesse der Matrix!!!\n");
Matrix matrix; Matrix matrix;
matrix.rows = 0; matrix.rows = 0;
matrix.cols = 0; matrix.cols = 0;
matrix.buffer = NULL; matrix.buffer = NULL;
if (rows != 0 && cols != 0)
{
matrix.rows = rows;
matrix.cols = cols;
// calloc reserviert Speicher und initialisiert alle Werte mit 0 (gut für Matrizen).
matrix.buffer = (MatrixType*) calloc((size_t)rows * cols, sizeof(MatrixType));
if (matrix.buffer == NULL) {
// Wenn malloc/calloc fehlschlägt, geben wir die Null-Matrix zurück.
// Die Dimensionen sind bereits auf 0 gesetzt.
}
return matrix; return matrix;
} }
// Wenn Dimensionen 0 sind, geben wir die initialisierte Null-Matrix zurück.
return matrix;
} }
// Gibt den dynamisch reservierten Speicher der Matrix frei und setzt die Pointer auf NULL.
void clearMatrix(Matrix *matrix) void clearMatrix(Matrix *matrix)
{ {
free(matrix->buffer); //gibt den heap speicher frei if (matrix != NULL)
matrix->buffer = NULL; //zeiger auf NULL setzen {
// Puffer nur freigeben, wenn er nicht NULL ist (Schutz vor double free).
if (matrix->buffer != NULL) {
free(matrix->buffer);
}
// Zustand auf 'leer' setzen, um spätere Fehler zu vermeiden.
matrix->buffer = NULL;
matrix->rows = 0; matrix->rows = 0;
matrix->cols = 0; matrix->cols = 0;
} }
}
// Setzt den Wert an einer bestimmten Position (rowIdx, colIdx) in der 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)
{ {
matrix.buffer[rowIdx * matrix.cols + colIdx] = value; // WICHTIG: Die Matrix wird in Row-Major-Order gespeichert.
// Index = Reihe * Anzahl der Spalten + Spalte.
// Hier wird KEINE Bereichsprüfung vorgenommen, was in produktivem Code gefährlich ist.
matrix.buffer[(size_t)rowIdx * matrix.cols + colIdx] = value;
} }
// Gibt den Wert an einer bestimmten Position (rowIdx, colIdx) zurück.
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx) MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{ {
// Nur Zugriff, wenn die Indizes innerhalb des gültigen Bereichs liegen.
if(rowIdx < matrix.rows && colIdx < matrix.cols){ if(rowIdx < matrix.rows && colIdx < matrix.cols){
return matrix.buffer[rowIdx * matrix.cols + colIdx]; //ACHTUNG! rowIdx und colIDX sind in Array position gedacht! matrix.cols ist normal gedacht! // Berechnung des flachen 1D-Indexes.
return matrix.buffer[(size_t)rowIdx * matrix.cols + colIdx];
}else{ }else{
return 0; // Rückgabe des undefinierten Werts (0 in diesem Fall).
return UNDEFINED_MATRIX_VALUE;
} }
} }
// Addiert zwei Matrizen. Unterstützt elementweise Addition und Bias-Broadcasting.
Matrix add(const Matrix matrix1, const Matrix matrix2) Matrix add(const Matrix matrix1, const Matrix matrix2)
{ {
//Überprüfen, ob die Matrizen die gleichen Dimensionen haben // --- Case A: Elementweise Addition (gleiche Form) ---
//wenn nicht muss die matrix "rows/cols=0 und buffer = NULL" leer zurückgegeben werden if (matrix1.rows == matrix2.rows && matrix1.cols == matrix2.cols)
if (matrix1.rows != matrix2.rows || matrix1.cols != matrix2.cols)
{ {
Matrix result = createMatrix(matrix1.rows, matrix1.cols);
if (result.buffer == NULL) return result; // Fehler bei Speicherreservierung.
size_t n = (size_t)result.rows * result.cols;
for (size_t i = 0; i < n; i++)
{
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
}
return result;
}
// --- Case B: Bias-Broadcasting (Matrix + Vektor (rows x 1)) ---
// Bias-Vektor (matrix2) wird über alle Spalten von matrix1 addiert.
if (matrix1.rows == matrix2.rows && matrix2.cols == 1 && matrix1.cols > 1)
{
Matrix result = createMatrix(matrix1.rows, matrix1.cols);
if (result.buffer == NULL) return result;
for (unsigned int r = 0; r < matrix1.rows; r++) // Iteriere über Reihen
{
MatrixType b = getMatrixAt(matrix2, r, 0); // Hole den Bias-Wert für diese Reihe.
for (unsigned int c = 0; c < matrix1.cols; c++) // Iteriere über Spalten
{
MatrixType val = getMatrixAt(matrix1, r, c);
setMatrixAt(val + b, result, r, c);
}
}
return result;
}
// --- Case C: Umgekehrtes Bias-Broadcasting (Vektor + Matrix) ---
// (Wird im NN-Kontext oft nicht benötigt, aber der Vollständigkeit halber)
if (matrix2.rows == matrix1.rows && matrix1.cols == 1 && matrix2.cols > 1)
{
Matrix result = createMatrix(matrix2.rows, matrix2.cols);
if (result.buffer == NULL) return result;
for (unsigned int r = 0; r < matrix2.rows; r++)
{
MatrixType b = getMatrixAt(matrix1, r, 0); // Hole den Bias-Wert (aus matrix1).
for (unsigned int c = 0; c < matrix2.cols; c++)
{
MatrixType val = getMatrixAt(matrix2, r, c);
setMatrixAt(val + b, result, r, c);
}
}
return result;
}
// Wenn Formate nicht unterstützt werden.
Matrix result; Matrix result;
result.rows = 0; result.rows = 0;
result.cols = 0; result.cols = 0;
@ -62,28 +130,35 @@ Matrix add(const Matrix matrix1, const Matrix matrix2)
return result; return result;
} }
else // Multipliziert zwei Matrizen (Standard Matrix-Matrix-Multiplikation).
{
//Matrix result ist die neue Matrix für das Ergebnis
Matrix result;
result.rows = matrix1.rows;
result.cols = matrix1.cols;
//Addition der beiden Matrizen
for (int i = 0; i < result.rows * result.cols; i++)
{
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
}
return result;
}
}
Matrix multiply(const Matrix matrix1, const Matrix matrix2) Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{ {
// Voraussetzung: Spalten von matrix1 müssen gleich den Reihen von matrix2 sein.
if (matrix1.cols != matrix2.rows)
{
Matrix result;
result.rows = 0;
result.cols = 0;
result.buffer = NULL;
return result;
}
else
{
Matrix result = createMatrix(matrix1.rows, matrix2.cols); //erzeugt matrix result
if (result.buffer == NULL) return result; //result buffer auf NULL?
for (unsigned int i = 0; i < result.rows; i++) //geht über alle zeilen der ergebnismatrix
{
for (unsigned int j = 0; j < result.cols; j++) //geht über alle spalten der ergebnismatrix
{
MatrixType summe = 0; //variable wo die matrix summe reingeladen wird
for (unsigned int k = 0; k < matrix1.cols; k++)
{
summe += getMatrixAt(matrix1, i, k) * getMatrixAt(matrix2, k, j); //get(matrix;row;col) k=col1;row2
}
setMatrixAt(summe, result, i, j); //summe wird in matrix result geladen
}
}
return result;
}
} }

View File

@ -128,8 +128,8 @@ NeuralNetwork loadModel(const char *path)
{ {
if(checkFileHeader(file)) if(checkFileHeader(file))
{ {
unsigned int inputDimension = readDimension(file); unsigned int inputDimension = readDimension(file); // übergabe in neuralnetworktests
unsigned int outputDimension = readDimension(file); unsigned int outputDimension = readDimension(file); // übergabe in neuralnetworktests
while(inputDimension > 0 && outputDimension > 0) while(inputDimension > 0 && outputDimension > 0)
{ {
@ -170,7 +170,12 @@ NeuralNetwork loadModel(const char *path)
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};
// Explizite Initialisierung verwenden, um die Feldreihenfolge in matrix.h zu umgehen:
Matrix matrix;
matrix.buffer = NULL;
matrix.rows = 0;
matrix.cols = 0;
if(count > 0 && images != NULL) if(count > 0 && images != NULL)
{ {

View File

@ -5,10 +5,59 @@
#include "unity.h" #include "unity.h"
#include "neuralNetwork.h" #include "neuralNetwork.h"
#define FILE_HEADER_STRING "__info2_neural_network_file_format__"
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) return;
// 1) Header-Tag (Wort für Wort) schreiben
fwrite(FILE_HEADER_STRING, sizeof(char), strlen(FILE_HEADER_STRING), file);
// 2) Layer-Daten schreiben
for (unsigned int i = 0; i < nn.numberOfLayers; ++i)
{
const Layer *lay = &nn.layers[i];
int inputDim = (int)lay->weights.cols; // Die Anzahl der Spalten der Gewichtsmatrix ist immer gleich der Input-Dimension
int outputDim = (int)lay->weights.rows; // Anzahl der Reihen der Gewichtsmatrix ist immer gleich der Output-Dimension
// --- Spezifische Dimensions-Schreiblogik (Spiegelung der Leselogik) ---
if (i == 0) {
// FÜR DAS ERSTE LAYER (i=0):
// loadModel erwartet sowohl Input- als auch Output-Dimension direkt aus der Datei.
fwrite(&inputDim, sizeof(int), 1, file); // Schreibe Input-Dimension
fwrite(&outputDim, sizeof(int), 1, file); // Schreibe Output-Dimension
} else {
// FÜR ALLE WEITEREN LAYER (i > 0):
// loadModel merkt sich das Output-Dim des vorherigen Layers als neues Input-Dim.
// Es muss nur die neue Output-Dimension aus der Datei gelesen werden.
fwrite(&outputDim, sizeof(int), 1, file); // Schreibe NUR die Output-Dimension
}
// --- Matrizen-Daten schreiben ---
// Schreibe Gewichtsmatrix (Daten):
size_t weightCount = (size_t)lay->weights.rows * (size_t)lay->weights.cols; // Gesamtanzahl der Elemente in der Gewichtsmatrix berechnet (rows x cols) //size_t unsignierter ganzzahltyp
if (weightCount > 0 && lay->weights.buffer != NULL) { //Prüfung ob dimensionen > 0 & Datenpuffer existiert
// Schreibe alle MatrixType-Elemente (z.B. floats) der Gewichte, Anzahl usw.
fwrite(lay->weights.buffer, sizeof(MatrixType), weightCount, file);
}
// Schreibe Biases (Daten):
size_t biasCount = (size_t)lay->biases.rows * (size_t)lay->biases.cols;
if (biasCount > 0 && lay->biases.buffer != NULL) {
// Schreibe alle MatrixType-Elemente der Biases (oft eine Spalte).
fwrite(lay->biases.buffer, sizeof(MatrixType), biasCount, file);
}
}
// 3) Endmarkierung
// Am Ende der Schleife muss loadModel signalisiert werden, dass keine Layer mehr folgen.
// Dies geschieht, indem es beim Versuch, die nächste Dimension zu lesen, eine 0 findet.
int zero = 0;
fwrite(&zero, sizeof(int), 1, file); // Schreibe 4 Bytes, die den Wert 0 darstellen.
fclose(file); // Schließt die Datei und schreibt alle Puffer auf die Platte.
} }
void test_loadModelReturnsCorrectNumberOfLayers(void) void test_loadModelReturnsCorrectNumberOfLayers(void)