generated from freudenreichan/info2Praktikum-NeuronalesNetz
Compare commits
2
Commits
manu-final
..
manu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bae72f4a17 | ||
|
|
fafbda4b03 |
No files matched your search
@@ -37,12 +37,6 @@
|
||||
*.hex
|
||||
Startcode/mnist
|
||||
Startcode/runTests
|
||||
Startcode/runMatrixTests
|
||||
Startcode/runImageInputTests
|
||||
Startcode/runNeuralNetworkTests
|
||||
Startcode/testFile.info2
|
||||
Startcode/testFile.txt
|
||||
Startcode/some*nn_test_file.info2
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Neuronales Netz
|
||||
|
||||
## To-Dos
|
||||
* [ ] Matrixtyp definieren in `matrix.h`
|
||||
* [ ] `Matrix.c` implementieren
|
||||
* [ ] `imageInput.c` implementieren
|
||||
* Matrixtyp definieren in `matrix.h`
|
||||
* `Matrix.c` implementieren
|
||||
* `imageInput.c` implementieren
|
||||
+5
-131
@@ -6,143 +6,17 @@
|
||||
#define BUFFER_SIZE 100
|
||||
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
||||
|
||||
// Liest den Identifikationstag am Dateianfang und vergleicht ihn mit FILE_HEADER_STRING
|
||||
static int checkFileHeader(FILE *file)
|
||||
{
|
||||
int isValid = 0;
|
||||
size_t fileHeaderLen = strlen(FILE_HEADER_STRING);
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
if(fileHeaderLen > BUFFER_SIZE-1)
|
||||
fileHeaderLen = BUFFER_SIZE-1;
|
||||
|
||||
if(fread(buffer, sizeof(char), fileHeaderLen, file) == fileHeaderLen)
|
||||
isValid = strcmp(buffer, FILE_HEADER_STRING) == 0;
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Liest den naechsten unsigned short (Anzahl, Breite bzw. Hoehe); 0 im Fehlerfall.
|
||||
// Kein fseek noetig: fread setzt den Dateizeiger automatisch hinter die gelesenen Bytes.
|
||||
static unsigned short readUnsignedShort(FILE *file)
|
||||
{
|
||||
unsigned short value = 0;
|
||||
|
||||
if(fread(&value, sizeof(unsigned short), 1, file) != 1)
|
||||
value = 0;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static unsigned short readNumberOfImages(FILE *file)
|
||||
{
|
||||
return readUnsignedShort(file);
|
||||
}
|
||||
|
||||
static unsigned short readImageWidth(FILE *file)
|
||||
{
|
||||
return readUnsignedShort(file);
|
||||
}
|
||||
|
||||
static unsigned short readImageHeight(FILE *file)
|
||||
{
|
||||
return readUnsignedShort(file);
|
||||
}
|
||||
|
||||
// Liest die Pixel eines Bildes in einen neuen Puffer; NULL bei Speicher- oder Lesefehler
|
||||
static GrayScalePixelType *readImage(FILE *file, unsigned short imageWidth, unsigned short imageHeight)
|
||||
{
|
||||
size_t numberOfPixels = (size_t)imageWidth * imageHeight;
|
||||
GrayScalePixelType *image = (GrayScalePixelType *)malloc(numberOfPixels * sizeof(GrayScalePixelType));
|
||||
|
||||
if(image != NULL && fread(image, sizeof(GrayScalePixelType), numberOfPixels, file) != numberOfPixels)
|
||||
{
|
||||
free(image);
|
||||
image = NULL;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// Liest das Label, das direkt auf die Pixel eines Bildes folgt; 0 bei Erfolg, sonst -1
|
||||
static int readLabel(FILE *file, unsigned char *label)
|
||||
{
|
||||
return fread(label, sizeof(unsigned char), 1, file) == 1 ? 0 : -1;
|
||||
}
|
||||
|
||||
// Legt eine leere Serie fuer numberOfImages Bilder an (Bildpuffer werden spaeter von readImage geliefert)
|
||||
static GrayScaleImageSeries *createSeries(unsigned short numberOfImages)
|
||||
{
|
||||
GrayScaleImageSeries *series = (GrayScaleImageSeries *)calloc(1, sizeof(GrayScaleImageSeries));
|
||||
|
||||
if(series != NULL)
|
||||
{
|
||||
// calloc: alle Bildpuffer starten als NULL, damit clearSeries jederzeit gefahrlos aufraeumen kann
|
||||
series->images = (GrayScaleImage *)calloc(numberOfImages, sizeof(GrayScaleImage));
|
||||
series->labels = (unsigned char *)calloc(numberOfImages, sizeof(unsigned char));
|
||||
|
||||
if(series->images == NULL || series->labels == NULL)
|
||||
{
|
||||
clearSeries(series);
|
||||
series = NULL;
|
||||
}
|
||||
else
|
||||
series->count = numberOfImages;
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
// 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
|
||||
GrayScaleImageSeries *readImages(const char *path)
|
||||
{
|
||||
GrayScaleImageSeries *series = NULL;
|
||||
FILE *file = fopen(path, "rb");
|
||||
|
||||
if(file != NULL)
|
||||
{
|
||||
if(checkFileHeader(file))
|
||||
{
|
||||
unsigned short numberOfImages = readNumberOfImages(file);
|
||||
unsigned short imageWidth = readImageWidth(file);
|
||||
unsigned short imageHeight = readImageHeight(file);
|
||||
|
||||
if(numberOfImages > 0 && imageWidth > 0 && imageHeight > 0)
|
||||
series = createSeries(numberOfImages);
|
||||
|
||||
for(unsigned int i = 0; series != NULL && i < series->count; i++)
|
||||
{
|
||||
series->images[i].width = imageWidth;
|
||||
series->images[i].height = imageHeight;
|
||||
series->images[i].buffer = readImage(file, imageWidth, imageHeight);
|
||||
|
||||
if(series->images[i].buffer == NULL || readLabel(file, &series->labels[i]) != 0)
|
||||
{
|
||||
clearSeries(series);
|
||||
series = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||
void clearSeries(GrayScaleImageSeries *series)
|
||||
{
|
||||
if(series != NULL)
|
||||
{
|
||||
if(series->images != NULL)
|
||||
{
|
||||
for(unsigned int i = 0; i < series->count; i++)
|
||||
{
|
||||
free(series->images[i].buffer);
|
||||
}
|
||||
}
|
||||
|
||||
free(series->images);
|
||||
free(series->labels);
|
||||
free(series);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,97 +119,6 @@ void test_readImagesFailsOnWrongFileTag(void)
|
||||
remove(path);
|
||||
}
|
||||
|
||||
static void writeRawFile(const char *path, const void *data, size_t size)
|
||||
{
|
||||
FILE *file = fopen(path, "wb");
|
||||
|
||||
if(file != NULL)
|
||||
{
|
||||
fwrite(data, 1, size, file);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
void test_readImagesReturnsCorrectPixelValues(void)
|
||||
{
|
||||
const char *path = "testFile.info2";
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
const unsigned short header[] = {2, 2, 2};
|
||||
const GrayScalePixelType expectedPixels1[] = {0, 64, 128, 255};
|
||||
const GrayScalePixelType expectedPixels2[] = {1, 2, 3, 4};
|
||||
const unsigned char expectedLabels[] = {7, 3};
|
||||
GrayScaleImageSeries *series = NULL;
|
||||
FILE *file = fopen(path, "wb");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(file);
|
||||
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
|
||||
fwrite(header, sizeof(header[0]), 3, file);
|
||||
fwrite(expectedPixels1, sizeof(GrayScalePixelType), 4, file);
|
||||
fwrite(&expectedLabels[0], sizeof(unsigned char), 1, file);
|
||||
fwrite(expectedPixels2, sizeof(GrayScalePixelType), 4, file);
|
||||
fwrite(&expectedLabels[1], sizeof(unsigned char), 1, file);
|
||||
fclose(file);
|
||||
|
||||
series = readImages(path);
|
||||
remove(path);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(series);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedPixels1, series->images[0].buffer, 4);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedPixels2, series->images[1].buffer, 4);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedLabels, series->labels, 2);
|
||||
clearSeries(series);
|
||||
}
|
||||
|
||||
void test_readImagesFailsOnTruncatedImageData(void)
|
||||
{
|
||||
const char *path = "testFile.info2";
|
||||
long fullSize = 0;
|
||||
char *content = NULL;
|
||||
FILE *file = NULL;
|
||||
|
||||
prepareImageFile(path, 8, 8, 3, 1);
|
||||
|
||||
// Datei einlesen und ohne das letzte Byte (Label des letzten Bildes) neu schreiben
|
||||
file = fopen(path, "rb");
|
||||
TEST_ASSERT_NOT_NULL(file);
|
||||
fseek(file, 0, SEEK_END);
|
||||
fullSize = ftell(file);
|
||||
rewind(file);
|
||||
content = (char *)malloc(fullSize);
|
||||
TEST_ASSERT_NOT_NULL(content);
|
||||
TEST_ASSERT_EQUAL_INT(fullSize, fread(content, 1, fullSize, file));
|
||||
fclose(file);
|
||||
writeRawFile(path, content, fullSize - 1);
|
||||
free(content);
|
||||
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_readImagesFailsOnIncompleteHeader(void)
|
||||
{
|
||||
const char *path = "testFile.info2";
|
||||
const char content[] = "__info2_image_file_format__\x02\x00\x08";
|
||||
|
||||
writeRawFile(path, content, sizeof(content) - 1);
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_readImagesFailsOnZeroImages(void)
|
||||
{
|
||||
const char *path = "testFile.info2";
|
||||
prepareImageFile(path, 8, 8, 0, 1);
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_clearSeriesAcceptsNull(void)
|
||||
{
|
||||
clearSeries(NULL);
|
||||
TEST_PASS();
|
||||
}
|
||||
|
||||
void setUp(void) {
|
||||
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
||||
}
|
||||
@@ -229,11 +138,6 @@ int main()
|
||||
RUN_TEST(test_readImagesReturnsCorrectLabels);
|
||||
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
||||
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
||||
RUN_TEST(test_readImagesReturnsCorrectPixelValues);
|
||||
RUN_TEST(test_readImagesFailsOnTruncatedImageData);
|
||||
RUN_TEST(test_readImagesFailsOnIncompleteHeader);
|
||||
RUN_TEST(test_readImagesFailsOnZeroImages);
|
||||
RUN_TEST(test_clearSeriesAcceptsNull);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
+1
-16
@@ -4,21 +4,6 @@
|
||||
#include "mnistVisualization.h"
|
||||
#include "neuralNetwork.h"
|
||||
|
||||
static int classifyImage(const GrayScaleImage *image, const void *context)
|
||||
{
|
||||
const NeuralNetwork *model = (const NeuralNetwork *)context;
|
||||
unsigned char *prediction = predict(*model, image, 1);
|
||||
int label = -1;
|
||||
|
||||
if(prediction != NULL)
|
||||
{
|
||||
label = prediction[0];
|
||||
free(prediction);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const unsigned int windowWidth = 800;
|
||||
@@ -49,7 +34,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
if(predictions != NULL)
|
||||
{
|
||||
showMnist(windowWidth, windowHeight, series, predictions, classifyImage, &model);
|
||||
showMnist(windowWidth, windowHeight, series, predictions);
|
||||
free(predictions);
|
||||
exitCode = EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -54,8 +54,6 @@ neuralNetworkTests: neuralNetwork.o matrix.o neuralNetworkTests.c $(unityfolder)
|
||||
imageInputTests: imageInput.o imageInputTests.c $(unityfolder)/unity.c
|
||||
$(CC) $(CFLAGS) -I$(unityfolder) -o runImageInputTests imageInputTests.c imageInput.o $(unityfolder)/unity.c
|
||||
|
||||
|
||||
|
||||
# --------------------------
|
||||
# Clean
|
||||
# --------------------------
|
||||
|
||||
+52
-125
@@ -2,167 +2,94 @@
|
||||
#include <string.h>
|
||||
#include "matrix.h"
|
||||
|
||||
// TODO Matrix-Funktionen implementieren
|
||||
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
||||
{
|
||||
Matrix m;
|
||||
Matrix matrix = {NULL, 0, 0}; // NUll init
|
||||
|
||||
if (rows == 0 || cols == 0)
|
||||
//Ueberprüfen, ob ungültige Werte für Matrix vorhanden
|
||||
if(rows > 0 && cols > 0)
|
||||
{
|
||||
m.rows = 0;
|
||||
m.cols = 0;
|
||||
m.buffer = NULL;
|
||||
return m;
|
||||
matrix.buffer = (MatrixType *)calloc((size_t)rows * cols, sizeof(MatrixType));
|
||||
|
||||
if(matrix.buffer != NULL) //sichergehn das Buffer holen geklappt hat, dann setzen
|
||||
{
|
||||
matrix.rows = rows;
|
||||
matrix.cols = cols;
|
||||
}
|
||||
}
|
||||
|
||||
m.rows = rows;
|
||||
m.cols = cols;
|
||||
m.buffer = (MatrixType*)calloc(rows*cols, sizeof(MatrixType));
|
||||
//Bei Fehlschalg gibt calloc entweder NULL oder Zeiger verschieden von NULL aus
|
||||
|
||||
if (m.buffer == NULL)
|
||||
//Kein Speicher: leere Matrix zurueckgeben, damit Groesse und Puffer zusammenpassen
|
||||
{
|
||||
m.rows = 0;
|
||||
m.cols = 0;
|
||||
}
|
||||
|
||||
return m;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
void clearMatrix(Matrix *matrix)
|
||||
{
|
||||
if (matrix != NULL)
|
||||
if(matrix != NULL) //Check ob die Matrix nicht eh schon leer ist
|
||||
{
|
||||
free(matrix->buffer);
|
||||
//Speicher leeren
|
||||
matrix->buffer = NULL;
|
||||
free(matrix->buffer); //memory freen
|
||||
matrix->buffer = NULL; // buffer entfernen
|
||||
matrix->rows = 0;
|
||||
matrix->cols = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int isValidIndex(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
//Zeile und Spalte einzeln pruefen: sonst wuerde z.B. (0, cols) faelschlich auf (1, 0) zugreifen
|
||||
// Helper Function, um zuschauen ob Index zugriff valide ist.
|
||||
static int isValidIndex(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx){
|
||||
return matrix.buffer != NULL && rowIdx < matrix.rows && colIdx < matrix.cols;
|
||||
}
|
||||
|
||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
if(isValidIndex(matrix, rowIdx, colIdx))
|
||||
{
|
||||
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||
}
|
||||
}
|
||||
|
||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
MatrixType value = UNDEFINED_MATRIX_VALUE;
|
||||
// Für Zugriff auf Row2, Col2, also m6, muss man im Buffer Die Positionen um 1 row +
|
||||
MatrixType value;
|
||||
|
||||
if(isValidIndex(matrix, rowIdx, colIdx))
|
||||
{
|
||||
value = matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||
}
|
||||
return value;
|
||||
value = matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||
return value;
|
||||
}
|
||||
|
||||
static int haveSameDimensions(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
return matrix1.rows == matrix2.rows && matrix1.cols == matrix2.cols;
|
||||
}
|
||||
|
||||
Matrix addBroadcast(const Matrix matrix, const Matrix vector){
|
||||
// Broadcasting Add von Matrix + Vector
|
||||
Matrix result = createMatrix(0, 0);
|
||||
}
|
||||
|
||||
static int isBroadcastable(const Matrix matrix1, const Matrix matrix2){
|
||||
// Check Broadcastable
|
||||
}
|
||||
|
||||
Matrix addElementwise(const Matrix matrix1, const Matrix matrix2){
|
||||
// addieren der Matrizen
|
||||
Matrix result = createMatrix(0, 0);
|
||||
}
|
||||
|
||||
|
||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
typedef enum {elemt_add, broadcast} Addiermodi;
|
||||
Addiermodi modus;
|
||||
Matrix result = {NULL, 0, 0};
|
||||
|
||||
Matrix m = {NULL, 0, 0};
|
||||
|
||||
if(matrix1.buffer == NULL || matrix2.buffer == NULL)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
|
||||
//Elementweise zuerst pruefen: zwei Spaltenvektoren gleicher Groesse sind ein normaler elementweiser Fall
|
||||
if((matrix1.rows == matrix2.rows) && (matrix1.cols == matrix2.cols))
|
||||
{
|
||||
modus = elemt_add;
|
||||
}
|
||||
else if((matrix1.cols == 1 || matrix2.cols ==1) && (matrix1.rows == matrix2.rows))
|
||||
{
|
||||
modus = broadcast;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m;
|
||||
}
|
||||
|
||||
if(modus == elemt_add)
|
||||
{
|
||||
m = createMatrix(matrix1.rows, matrix1.cols);
|
||||
if(m.buffer == NULL)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i< matrix1.rows; i++)
|
||||
{
|
||||
for(unsigned int j = 0; j < matrix1.cols ; j++)
|
||||
{
|
||||
m.buffer[i*matrix1.cols+j] = matrix1.buffer[i*matrix1.cols+j] + matrix2.buffer[i*matrix1.cols+j];
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
else if(modus == broadcast)
|
||||
{
|
||||
|
||||
if(matrix1.cols == 1)
|
||||
{
|
||||
m = createMatrix(matrix2.rows, matrix2.cols);
|
||||
for(unsigned int i = 0; i< m.rows; i++)
|
||||
{
|
||||
for(unsigned int j = 0; j < m.cols ; j++)
|
||||
{
|
||||
m.buffer[i*matrix2.cols+j] = matrix2.buffer[i*matrix2.cols+j] + matrix1.buffer[i*matrix1.cols];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m = createMatrix(matrix1.rows, matrix1.cols);
|
||||
for(unsigned int i = 0; i< m.rows; i++)
|
||||
{
|
||||
for(unsigned int j = 0; j < m.cols ; j++)
|
||||
{
|
||||
m.buffer[i*matrix1.cols+j] = matrix1.buffer[i*matrix1.cols+j] + matrix2.buffer[i*matrix2.cols];
|
||||
}
|
||||
}
|
||||
if(matrix1.buffer != NULL && matrix2.buffer != NULL){
|
||||
if(haveSameDimensions(matrix1, matrix2)){
|
||||
// result = NULL; //addElementwise
|
||||
} else if(isBroadcastable(matrix1, matrix2)) { // Check ob Matrix2 ein Vector ist
|
||||
// result = NULL; //addBroadcast
|
||||
} else if(isBroadcastable(matrix1, matrix2)) { // Check ob Matrix2 ein Vector ist
|
||||
// result = NULL; //addBroadcast
|
||||
}
|
||||
}
|
||||
return m;
|
||||
return result;
|
||||
}
|
||||
|
||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
if(matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.cols != matrix2.rows)
|
||||
{
|
||||
Matrix failure = {NULL, 0, 0};
|
||||
return failure;
|
||||
}
|
||||
|
||||
Matrix m = createMatrix(matrix1.rows, matrix2.cols);
|
||||
|
||||
//Reihenfolge i-k-j statt i-j-k: matrix2 und m werden zeilenweise am Stueck durchlaufen.
|
||||
//m startet dank calloc mit 0, daher wird direkt aufaddiert.
|
||||
for(unsigned int i = 0; i < m.rows; i++)
|
||||
{
|
||||
for(unsigned int k = 0; k < matrix1.cols; k++)
|
||||
{
|
||||
MatrixType factor = matrix1.buffer[i*matrix1.cols+k];
|
||||
//MatrixType statt int, hat die zahlen gerundet.
|
||||
for(unsigned int j = 0; j < m.cols ; j++)
|
||||
{
|
||||
m.buffer[i*m.cols+j] += factor * matrix2.buffer[k*matrix2.cols+j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
//kann man Matrizen multiplizieren, größen Check
|
||||
|
||||
}
|
||||
+5
-4
@@ -2,15 +2,16 @@
|
||||
#define MATRIX_H
|
||||
|
||||
#define UNDEFINED_MATRIX_VALUE 0
|
||||
#define MATRIX_COLS 4
|
||||
#define MATRIX_ROWS 4
|
||||
|
||||
typedef float MatrixType;
|
||||
|
||||
// TODO Matrixtyp definieren
|
||||
typedef struct Matrix{
|
||||
MatrixType *buffer;
|
||||
typedef struct {
|
||||
MatrixType *buffer;
|
||||
unsigned int rows;
|
||||
unsigned int cols;
|
||||
}Matrix;
|
||||
} Matrix;
|
||||
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||
void clearMatrix(Matrix *matrix);
|
||||
|
||||
@@ -130,28 +130,6 @@ void test_multiplyFailsOnWrongInputDimensions(void)
|
||||
TEST_ASSERT_EQUAL_UINT32(0, result.cols);
|
||||
}
|
||||
|
||||
void test_multiplyKeepsFractionalValues(void)
|
||||
{
|
||||
MatrixType buffer1[] = {0.5f, 0.25f};
|
||||
MatrixType buffer2[] = {0.5f, 0.5f};
|
||||
Matrix matrix1 = {.rows=1, .cols=2, .buffer=buffer1};
|
||||
Matrix matrix2 = {.rows=2, .cols=1, .buffer=buffer2};
|
||||
|
||||
Matrix result = multiply(matrix1, matrix2);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(result.buffer);
|
||||
TEST_ASSERT_EQUAL_FLOAT(0.375f, result.buffer[0]);
|
||||
free(result.buffer);
|
||||
}
|
||||
|
||||
void test_getMatrixAtFailsOnColumnIndexOutOfRange(void)
|
||||
{
|
||||
MatrixType buffer[] = {1, 2, 3, 4, 5, 6};
|
||||
Matrix matrixToTest = {.rows=2, .cols=3, .buffer=buffer};
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(UNDEFINED_MATRIX_VALUE, getMatrixAt(matrixToTest, 0, 3));
|
||||
}
|
||||
|
||||
void test_getMatrixAtReturnsCorrectResult(void)
|
||||
{
|
||||
MatrixType buffer[] = {1, 2, 3, 4, 5, 6};
|
||||
@@ -210,8 +188,6 @@ int main()
|
||||
RUN_TEST(test_addSupportsBroadcasting);
|
||||
RUN_TEST(test_multiplyReturnsCorrectResults);
|
||||
RUN_TEST(test_multiplyFailsOnWrongInputDimensions);
|
||||
RUN_TEST(test_multiplyKeepsFractionalValues);
|
||||
RUN_TEST(test_getMatrixAtFailsOnColumnIndexOutOfRange);
|
||||
RUN_TEST(test_getMatrixAtReturnsCorrectResult);
|
||||
RUN_TEST(test_getMatrixAtFailsOnIndicesOutOfRange);
|
||||
RUN_TEST(test_setMatrixAtSetsCorrectValue);
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "mnistVisualization.h"
|
||||
#include "raylib.h"
|
||||
|
||||
#define MAX_TEXT_LEN 100
|
||||
#define BRUSH_RADIUS 2.0f
|
||||
#define BRUSH_STEP 0.25f
|
||||
#define BROWSE_TEXT "Use left and right key to navigate ... (D: draw mode)"
|
||||
#define DRAW_TEXT "Left mouse: draw Right mouse: erase C: clear D: browse"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
BROWSE_MODE,
|
||||
DRAW_MODE
|
||||
} VisualizationMode;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -34,15 +23,6 @@ typedef struct
|
||||
Color color;
|
||||
} TextLabel;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GrayScaleImage image;
|
||||
Vector2 lastPosition;
|
||||
int isStroking;
|
||||
int isEmpty;
|
||||
int prediction;
|
||||
} DrawingCanvas;
|
||||
|
||||
static TextLabel *createTextLabel(const char *text, unsigned int fontSize, Color color)
|
||||
{
|
||||
TextLabel *label = NULL;
|
||||
@@ -85,185 +65,6 @@ static MnistVisualization *createVisualizationContainer(const GrayScaleImageSeri
|
||||
return container;
|
||||
}
|
||||
|
||||
static void setLabelText(TextLabel *label, const char *text)
|
||||
{
|
||||
strncpy(label->text, text, MAX_TEXT_LEN);
|
||||
label->text[MAX_TEXT_LEN-1] = '\0';
|
||||
}
|
||||
|
||||
static DrawingCanvas *createDrawingCanvas(unsigned int width, unsigned int height)
|
||||
{
|
||||
DrawingCanvas *canvas = (DrawingCanvas *)calloc(1, sizeof(DrawingCanvas));
|
||||
|
||||
if(canvas != NULL)
|
||||
{
|
||||
canvas->image.buffer = (GrayScalePixelType *)calloc(width * height, sizeof(GrayScalePixelType));
|
||||
|
||||
if(canvas->image.buffer != NULL)
|
||||
{
|
||||
canvas->image.width = width;
|
||||
canvas->image.height = height;
|
||||
canvas->isEmpty = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
free(canvas);
|
||||
canvas = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
static void freeDrawingCanvas(DrawingCanvas *canvas)
|
||||
{
|
||||
if(canvas != NULL)
|
||||
{
|
||||
free(canvas->image.buffer);
|
||||
free(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
static void clearDrawingCanvas(DrawingCanvas *canvas)
|
||||
{
|
||||
memset(canvas->image.buffer, 0, canvas->image.width * canvas->image.height * sizeof(GrayScalePixelType));
|
||||
canvas->isEmpty = 1;
|
||||
}
|
||||
|
||||
// Weicher Pinsel: innerer Bereich voll weiss, nach aussen linear auslaufend (aehnlich den MNIST-Strichen)
|
||||
static void paintAt(DrawingCanvas *canvas, Vector2 position, int erase)
|
||||
{
|
||||
int minX = (int)floorf(position.x - BRUSH_RADIUS);
|
||||
int maxX = (int)ceilf(position.x + BRUSH_RADIUS);
|
||||
int minY = (int)floorf(position.y - BRUSH_RADIUS);
|
||||
int maxY = (int)ceilf(position.y + BRUSH_RADIUS);
|
||||
|
||||
for(int y = minY; y <= maxY; y++)
|
||||
{
|
||||
for(int x = minX; x <= maxX; x++)
|
||||
{
|
||||
if(x >= 0 && y >= 0 && x < (int)canvas->image.width && y < (int)canvas->image.height)
|
||||
{
|
||||
float dx = x + 0.5f - position.x;
|
||||
float dy = y + 0.5f - position.y;
|
||||
float strength = 2.0f * (1.0f - sqrtf(dx * dx + dy * dy) / BRUSH_RADIUS);
|
||||
GrayScalePixelType *pixel = &canvas->image.buffer[y * canvas->image.width + x];
|
||||
|
||||
if(strength > 0)
|
||||
{
|
||||
if(erase)
|
||||
*pixel = 0;
|
||||
else
|
||||
{
|
||||
float value = strength >= 1.0f ? 255.0f : 255.0f * strength;
|
||||
|
||||
if(value > *pixel)
|
||||
*pixel = (GrayScalePixelType)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Zwischenpunkte interpolieren, damit bei schnellen Mausbewegungen keine Luecken entstehen
|
||||
static void paintLine(DrawingCanvas *canvas, Vector2 from, Vector2 to, int erase)
|
||||
{
|
||||
float dx = to.x - from.x;
|
||||
float dy = to.y - from.y;
|
||||
int steps = (int)ceilf(sqrtf(dx * dx + dy * dy) / BRUSH_STEP);
|
||||
|
||||
for(int i = 0; i <= steps; i++)
|
||||
{
|
||||
float t = steps > 0 ? (float)i / steps : 0.0f;
|
||||
Vector2 point = {from.x + t * dx, from.y + t * dy};
|
||||
paintAt(canvas, point, erase);
|
||||
}
|
||||
}
|
||||
|
||||
// MNIST-Ziffern sind ueber ihren Schwerpunkt zentriert; die Zeichnung wird fuer die Vorhersage genauso verschoben
|
||||
static void centerByMass(const GrayScaleImage source, GrayScaleImage target)
|
||||
{
|
||||
double sum = 0;
|
||||
double centerX = 0;
|
||||
double centerY = 0;
|
||||
|
||||
memset(target.buffer, 0, target.width * target.height * sizeof(GrayScalePixelType));
|
||||
|
||||
for(unsigned int i = 0; i < source.width * source.height; i++)
|
||||
{
|
||||
sum += source.buffer[i];
|
||||
centerX += (double)(i % source.width) * source.buffer[i];
|
||||
centerY += (double)(i / source.width) * source.buffer[i];
|
||||
}
|
||||
|
||||
if(sum > 0)
|
||||
{
|
||||
int shiftX = (int)lround((source.width - 1) / 2.0 - centerX / sum);
|
||||
int shiftY = (int)lround((source.height - 1) / 2.0 - centerY / sum);
|
||||
|
||||
for(int y = 0; y < (int)source.height; y++)
|
||||
{
|
||||
for(int x = 0; x < (int)source.width; x++)
|
||||
{
|
||||
int newX = x + shiftX;
|
||||
int newY = y + shiftY;
|
||||
|
||||
if(newX >= 0 && newY >= 0 && newX < (int)target.width && newY < (int)target.height)
|
||||
target.buffer[newY * target.width + newX] = source.buffer[y * source.width + x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void predictDrawing(DrawingCanvas *canvas, ClassifierFunctionType classify, const void *classifierContext)
|
||||
{
|
||||
GrayScaleImage centered = canvas->image;
|
||||
centered.buffer = (GrayScalePixelType *)malloc(canvas->image.width * canvas->image.height * sizeof(GrayScalePixelType));
|
||||
canvas->prediction = -1;
|
||||
|
||||
if(centered.buffer != NULL)
|
||||
{
|
||||
centerByMass(canvas->image, centered);
|
||||
canvas->prediction = classify(¢ered, classifierContext);
|
||||
free(centered.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void updateDrawingCanvas(DrawingCanvas *canvas, const MnistVisualization *container, ClassifierFunctionType classify, const void *classifierContext, TextLabel *predictionLabel)
|
||||
{
|
||||
int isDrawing = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
|
||||
int isErasing = IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
|
||||
|
||||
if(IsKeyReleased(KEY_C))
|
||||
clearDrawingCanvas(canvas);
|
||||
|
||||
if(isDrawing || isErasing)
|
||||
{
|
||||
Vector2 mouse = GetMousePosition();
|
||||
Vector2 position = {(mouse.x - container->position.x) / container->pixelSize.x, (mouse.y - container->position.y) / container->pixelSize.y};
|
||||
|
||||
if(canvas->isStroking)
|
||||
paintLine(canvas, canvas->lastPosition, position, isErasing);
|
||||
else
|
||||
paintAt(canvas, position, isErasing);
|
||||
|
||||
canvas->lastPosition = position;
|
||||
canvas->isStroking = 1;
|
||||
canvas->isEmpty = 0;
|
||||
predictDrawing(canvas, classify, classifierContext);
|
||||
}
|
||||
else
|
||||
canvas->isStroking = 0;
|
||||
|
||||
if(canvas->isEmpty)
|
||||
setLabelText(predictionLabel, "Draw a digit ...");
|
||||
else if(canvas->prediction < 0)
|
||||
setLabelText(predictionLabel, "Prediction failed ...");
|
||||
else
|
||||
snprintf(predictionLabel->text, MAX_TEXT_LEN, "Predicted label: %d", canvas->prediction);
|
||||
}
|
||||
|
||||
static void drawTextLabel(const TextLabel *label)
|
||||
{
|
||||
DrawText(label->text, label->position.x, label->position.y, label->fontSize, label->color);
|
||||
@@ -305,24 +106,6 @@ static void drawAll(const MnistVisualization *container, const TextLabel *naviga
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
static void drawCanvasAll(const DrawingCanvas *canvas, const MnistVisualization *container, const TextLabel *navigationLabel, const TextLabel *predictionLabel)
|
||||
{
|
||||
// Hilfsrahmen: MNIST-Ziffern fuellen etwa die inneren 20x20 von 28x28 Pixeln
|
||||
const float margin = canvas->image.width / 7.0f;
|
||||
Rectangle guide = {container->position.x + margin * container->pixelSize.x, container->position.y + margin * container->pixelSize.y,
|
||||
(canvas->image.width - 2 * margin) * container->pixelSize.x, (canvas->image.height - 2 * margin) * container->pixelSize.y};
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
|
||||
drawDigit(canvas->image, container->position, container->pixelSize);
|
||||
DrawRectangleLinesEx(guide, 1, DARKGRAY);
|
||||
drawTextLabel(navigationLabel);
|
||||
drawTextLabel(predictionLabel);
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
static int checkUserInput()
|
||||
{
|
||||
int inputResult = 0;
|
||||
@@ -358,19 +141,17 @@ static void update(MnistVisualization *container, TextLabel *predictionLabel, in
|
||||
updatePredictionLabel(predictionLabel, container->series->labels[container->currentIdx], container->predictions[container->currentIdx]);
|
||||
}
|
||||
|
||||
void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GrayScaleImageSeries *series, const unsigned char predictions[], ClassifierFunctionType classify, const void *classifierContext)
|
||||
void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GrayScaleImageSeries *series, const unsigned char predictions[])
|
||||
{
|
||||
const Vector2 windowSize = {windowWidth, windowHeight};
|
||||
VisualizationMode mode = BROWSE_MODE;
|
||||
|
||||
MnistVisualization *container = createVisualizationContainer(series, predictions, windowSize);
|
||||
DrawingCanvas *canvas = container != NULL ? createDrawingCanvas(series->images[0].width, series->images[0].height) : NULL;
|
||||
TextLabel *navigationLabel = createTextLabel(BROWSE_TEXT, 20, WHITE);
|
||||
TextLabel *navigationLabel = createTextLabel("Use left and right key to navigate ...", 20, WHITE);
|
||||
TextLabel *predictionLabel = createTextLabel("", 20, WHITE);
|
||||
predictionLabel->position.x = 10;
|
||||
predictionLabel->position.y = windowSize.y - 50;
|
||||
|
||||
if(container != NULL && canvas != NULL && classify != NULL && navigationLabel != NULL && predictionLabel != NULL)
|
||||
if(container != NULL && navigationLabel != NULL && predictionLabel != NULL)
|
||||
{
|
||||
InitWindow(windowSize.x, windowSize.y, "MNIST Browser");
|
||||
|
||||
@@ -378,30 +159,15 @@ void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GraySc
|
||||
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
if(IsKeyReleased(KEY_D))
|
||||
{
|
||||
mode = mode == BROWSE_MODE ? DRAW_MODE : BROWSE_MODE;
|
||||
setLabelText(navigationLabel, mode == BROWSE_MODE ? BROWSE_TEXT : DRAW_TEXT);
|
||||
}
|
||||
|
||||
if(mode == BROWSE_MODE)
|
||||
{
|
||||
int updateDirection = checkUserInput();
|
||||
update(container, predictionLabel, updateDirection);
|
||||
drawAll(container, navigationLabel, predictionLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
updateDrawingCanvas(canvas, container, classify, classifierContext, predictionLabel);
|
||||
drawCanvasAll(canvas, container, navigationLabel, predictionLabel);
|
||||
}
|
||||
int updateDirection = checkUserInput();
|
||||
update(container, predictionLabel, updateDirection);
|
||||
drawAll(container, navigationLabel, predictionLabel);
|
||||
}
|
||||
}
|
||||
|
||||
CloseWindow();
|
||||
|
||||
free(container);
|
||||
freeDrawingCanvas(canvas);
|
||||
free(navigationLabel);
|
||||
free(predictionLabel);
|
||||
}
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#include "imageInput.h"
|
||||
|
||||
// Liefert das erkannte Label (0-9) eines Bildes oder -1 im Fehlerfall; context wird unveraendert durchgereicht.
|
||||
// (Die Visualisierung kann neuralNetwork.h nicht einbinden, da raylib.h selbst einen Typ "Matrix" definiert.)
|
||||
typedef int (*ClassifierFunctionType)(const GrayScaleImage *image, const void *context);
|
||||
|
||||
void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GrayScaleImageSeries *series, const unsigned char predictions[], ClassifierFunctionType classify, const void *classifierContext);
|
||||
void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GrayScaleImageSeries *series, const unsigned char predictions[]);
|
||||
|
||||
#endif
|
||||
@@ -262,7 +262,6 @@ void clearModel(NeuralNetwork *model)
|
||||
{
|
||||
clearLayer(&model->layers[i]);
|
||||
}
|
||||
free(model->layers);
|
||||
model->layers = NULL;
|
||||
model->numberOfLayers = 0;
|
||||
}
|
||||
|
||||
@@ -4,37 +4,11 @@
|
||||
#include <math.h>
|
||||
#include "unity.h"
|
||||
#include "neuralNetwork.h"
|
||||
#define FILE_HEADER_STRING "__info2_neural_network_file_format__"
|
||||
|
||||
|
||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||
{
|
||||
FILE *file = fopen(path, "wb");
|
||||
|
||||
if(file != NULL)
|
||||
{
|
||||
const char *fileTag = FILE_HEADER_STRING;
|
||||
|
||||
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
|
||||
|
||||
if(nn.numberOfLayers > 0)
|
||||
{
|
||||
// Eingangsdimension nur einmal: jede weitere Schicht nutzt die Ausgangsdimension der vorherigen
|
||||
int inputDimension = (int)nn.layers[0].weights.cols;
|
||||
fwrite(&inputDimension, sizeof(int), 1, file);
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < nn.numberOfLayers; i++)
|
||||
{
|
||||
const Layer layer = nn.layers[i];
|
||||
int outputDimension = (int)layer.weights.rows;
|
||||
|
||||
fwrite(&outputDimension, sizeof(int), 1, file);
|
||||
fwrite(layer.weights.buffer, sizeof(MatrixType), layer.weights.rows * layer.weights.cols, file);
|
||||
fwrite(layer.biases.buffer, sizeof(MatrixType), layer.biases.rows * layer.biases.cols, file);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||
|
||||
Reference in new issue
Block a user