snapkitty
machine-learning
c
File size: 5,459 Bytes
8b41169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* mnist.c β€” SnapKitty MLC: MNIST digit classifier example
 *
 * Architecture:
 *   Input (784) β†’ W0(16Γ—784) ReLU β†’ W1(16Γ—16) ReLU [+ skip] β†’ W2(10Γ—16) Softmax
 *   Loss: cross-entropy
 *   Init: Xavier uniform (Glorot)
 *   Train: SGD, 10 epochs, batch=50, lr=0.01
 *
 * Data: run examples/data_convert.py first to generate .mat files.
 *
 * Authors: Ahmad Ali Parr, Jessica L. Williams (SNAPKITTYWEST)
 * Source:  SNAPKITTYAGENT9NOVA/MLC
 * License: BSL-1.1 / AGPL-3.0 / MPL-2.0
 */

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>
#include <math.h>

#include "../include/sk_defs.h"
#include "../include/sk_arena.h"
#include "../include/sk_random.h"
#include "../include/sk_matrix.h"
#include "../include/sk_model.h"

/* ── ANSI terminal: render digit as coloured blocks ──────────────── */
static void draw_mnist_digit(const f32* data) {
    for (u32 y = 0; y < 28; y++) {
        for (u32 x = 0; x < 28; x++) {
            f32 num = data[x + y * 28];
            u32 col = 232 + (u32)(num * 23);
            printf("\x1b[48;5;%dm  ", col);
        }
        printf("\x1b[0m\n");
    }
    printf("\x1b[0m");
}

/* ── Build the MNIST model ────────────────────────────────────────── */
static void create_mnist_model(sk_arena* arena, sk_model* model) {
    /* Xavier uniform init bounds */
    f32 b0 = sqrtf(6.0f / (784 + 16));
    f32 b1 = sqrtf(6.0f / ( 16 + 16));
    f32 b2 = sqrtf(6.0f / ( 16 + 10));

    sk_model_var* input = sk_mv_create(arena, model, 784, 1, SK_MV_INPUT);

    sk_model_var* W0 = sk_mv_create(arena, model, 16, 784, SK_MV_REQUIRES_GRAD | SK_MV_PARAMETER);
    sk_model_var* W1 = sk_mv_create(arena, model, 16,  16, SK_MV_REQUIRES_GRAD | SK_MV_PARAMETER);
    sk_model_var* W2 = sk_mv_create(arena, model, 10,  16, SK_MV_REQUIRES_GRAD | SK_MV_PARAMETER);
    sk_mat_fill_rand(W0->val, -b0, b0);
    sk_mat_fill_rand(W1->val, -b1, b1);
    sk_mat_fill_rand(W2->val, -b2, b2);

    sk_model_var* b0v = sk_mv_create(arena, model, 16, 1, SK_MV_REQUIRES_GRAD | SK_MV_PARAMETER);
    sk_model_var* b1v = sk_mv_create(arena, model, 16, 1, SK_MV_REQUIRES_GRAD | SK_MV_PARAMETER);
    sk_model_var* b2v = sk_mv_create(arena, model, 10, 1, SK_MV_REQUIRES_GRAD | SK_MV_PARAMETER);

    /* Layer 0: W0 Γ— input + b0 β†’ ReLU */
    sk_model_var* z0  = sk_mv_matmul(arena, model, W0, input, 0);
    sk_model_var* z0b = sk_mv_add(arena, model, z0, b0v, 0);
    sk_model_var* a0  = sk_mv_relu(arena, model, z0b, 0);

    /* Layer 1: W1 Γ— a0 + b1 β†’ ReLU + skip */
    sk_model_var* z1  = sk_mv_matmul(arena, model, W1, a0, 0);
    sk_model_var* z1b = sk_mv_add(arena, model, z1, b1v, 0);
    sk_model_var* z1r = sk_mv_relu(arena, model, z1b, 0);
    sk_model_var* a1  = sk_mv_add(arena, model, a0, z1r, 0);  /* skip connection */

    /* Layer 2: W2 Γ— a1 + b2 β†’ Softmax */
    sk_model_var* z2  = sk_mv_matmul(arena, model, W2, a1, 0);
    sk_model_var* z2b = sk_mv_add(arena, model, z2, b2v, 0);
    sk_model_var* out = sk_mv_softmax(arena, model, z2b, SK_MV_OUTPUT);

    /* Labels + loss */
    sk_model_var* y    = sk_mv_create(arena, model, 10, 1, SK_MV_DESIRED_OUTPUT);
    sk_model_var* cost = sk_mv_cross_entropy(arena, model, y, out, SK_MV_COST);

    (void)cost;
}

/* ── main ─────────────────────────────────────────────────────────── */
int main(void) {
    sk_arena* perm = sk_arena_create(GiB(1), MiB(1));

    sk_matrix* train_images = sk_mat_load(perm, 60000, 784, "train_images.mat");
    sk_matrix* test_images  = sk_mat_load(perm, 10000, 784, "test_images.mat");
    sk_matrix* train_labels = sk_mat_create(perm, 60000, 10);
    sk_matrix* test_labels  = sk_mat_create(perm, 10000, 10);

    {
        sk_matrix* tl = sk_mat_load(perm, 60000, 1, "train_labels.mat");
        sk_matrix* tst = sk_mat_load(perm, 10000, 1, "test_labels.mat");
        for (u32 i = 0; i < 60000; i++)
            train_labels->data[i * 10 + (u32)tl->data[i]] = 1.0f;
        for (u32 i = 0; i < 10000; i++)
            test_labels->data[i * 10 + (u32)tst->data[i]] = 1.0f;
    }

    draw_mnist_digit(test_images->data);
    printf("Label: ");
    for (u32 i = 0; i < 10; i++) printf("%.0f ", test_labels->data[i]);
    printf("\n\n");

    sk_model* model = sk_model_create(perm);
    create_mnist_model(perm, model);
    sk_model_compile(perm, model);

    memcpy(model->input->val->data, test_images->data, sizeof(f32) * 784);
    sk_model_feedforward(model);
    printf("Pre-training: ");
    for (u32 i = 0; i < 10; i++) printf("%.2f ", model->output->val->data[i]);
    printf("\n");

    sk_training_desc desc = {
        .train_images  = train_images,
        .train_labels  = train_labels,
        .test_images   = test_images,
        .test_labels   = test_labels,
        .epochs        = 10,
        .batch_size    = 50,
        .learning_rate = 0.01f,
    };
    sk_model_train(model, &desc);

    memcpy(model->input->val->data, test_images->data, sizeof(f32) * 784);
    sk_model_feedforward(model);
    printf("Post-training: ");
    for (u32 i = 0; i < 10; i++) printf("%.4f ", model->output->val->data[i]);
    printf("\nPredicted: %llu\n", (unsigned long long)sk_mat_argmax(model->output->val));

    sk_arena_destroy(perm);
    return 0;
}