使用 C 语言编写 ANN 实现 XOR 异或函数

发布于 2024-01-30 08:13:39 字数 5802 浏览 30 评论 0

介绍用纯 C 语言实现一个简单的 BP 网络,并使用 XOR 数据测试。

完整代码可参考: https://github.com/howardlau1999/learning-machine-learning/blob/master/mlp_work.c

辅助函数

double random_(double low, double high) { return (((high - low) * (double)rand() / RAND_MAX) + low); }
double sigmoid(double x) { return 1 / (1 + exp(-x)); }
double d_sigmoid(double x) { return sigmoid(x) * (1 - sigmoid(x)); }

初始化网络

实现 BP 网络需要存储网络的每一层的权重以及偏置,而实现 BP 算法则需要存储神经元的输入、激活输出以及误差,因此,定义数据结构如下:

struct mlp {
    // biases[l][i] is the bias of the i-th neuron in layer l + 1
    double** biases;
    // weights[l][i][j] is the weight of the edge
    // connecting the j-th neuron in layer l and i-th neuron in layer l + 1
    double*** weights;
};

struct bp {
    // input from previous layer
    double **z;
    // output after activation
    double **a;
    // back-prop error
    double **delta;
};

其中 weights 权重数组需要随机初始化,其他数组初始化为全 0 即可。

double** alloc_biases() {
    double** arr = malloc((layers - 1) * sizeof(double*));
    for (int l = 0; l < layers - 1; ++l) {
        arr[l] = malloc(sizes[l + 1] * sizeof(double));
        for (int i = 0; i < sizes[l + 1]; ++i) {
            arr[l][i] = 0;
        }
    }
    return arr;
}

double*** alloc_weights() {
    double*** weights = malloc(layers * sizeof(double**));
    for (int l = 0; l < layers - 1; ++l) {
        weights[l] = malloc(sizes[l + 1] * sizeof(double*));
        for (int i = 0; i < sizes[l + 1]; ++i) {
            weights[l][i] = malloc(sizes[l] * sizeof(double));
            for (int j = 0; j < sizes[l]; ++j) {
                weights[l][i][j] = random_(-5, 5);
            }
        }
    }

    return weights;
}

double** alloc_cache() {
    double** arr = malloc(layers * sizeof(double*));
    for (int l = 0; l < layers; ++l) {
        arr[l] = malloc(sizes[l] * sizeof(double));
        for (int i = 0; i < sizes[l]; ++i) {
            arr[l][i] = 0;
        }
    }
    return arr;
}

前向传播

void forward(double* input, double* output, struct mlp* net, struct bp* cache) {
    for (int i = 0; i < sizes[0]; ++i) {
        cache->a[0][i] = input[i];
    }

    for (int l = 1; l < layers; ++l) {
        for (int i = 0; i < sizes[l]; ++i) {
            cache->z[l][i] = net->biases[l - 1][i];
            for (int j = 0; j < sizes[l - 1]; ++j) {
                cache->z[l][i] +=
                    net->weights[l - 1][i][j] * cache->a[l - 1][j];
            }
            cache->a[l][i] = sigmoid(cache->z[l][i]);
        }
    }

    for (int i = 0; i < sizes[layers - 1]; ++i) {
        output[i] = cache->z[layers - 1][i];
    }
}

需要注意的是,输出层不需要激活函数。

损失函数

double loss(double* output, double* ground_truth) {
    double sum = 0;

    for (int i = 0; i < sizes[layers - 1]; ++i) {
        sum += (output[i] - ground_truth[i]) * (output[i] - ground_truth[i]);
    }

    return .5 * sum;
}

误差回传

void backward(double* ground_truth, struct mlp* net, struct bp* cache) {
    for (int i = 0; i < sizes[layers - 1]; ++i) {
        cache->delta[layers - 1][i] =
            -(ground_truth[i] - cache->z[layers - 1][i]);
    }

    for (int l = layers - 2; l >= 0; --l) {
        for (int i = 0; i < sizes[l]; ++i) {
            double sum = 0;
            for (int j = 0; j < sizes[l + 1]; ++j) {
                sum +=
                    cache->delta[l + 1][j] * net->weights[l][j][i];
            }
            cache->delta[l][i] = sum * d_sigmoid(cache->z[l][i]);
        }
    }
}

参数更新

void optimize(double lr, struct mlp* net, struct bp* cache) {
    for (int l = 0; l < layers - 1; ++l) {
        for (int i = 0; i < sizes[l + 1]; ++i) {
            for (int j = 0; j < sizes[l]; ++j) {
                net->weights[l][i][j] -= lr * (cache->delta[l + 1][i] * cache->a[l][j]);
            }
            net->biases[l][i] -= lr * cache->delta[l + 1][i]; 
        }
    }
}

训练代码

// input_layer, hidden_layers..., output_layer
int sizes[] = {2, 3, 4, 3, 1};
int layers = sizeof(sizes) / sizeof(int);

int main() {
    double input[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
    double ground_truth[4][1] = {{0}, {1}, {1}, {0}};
    double output[1];

    cache.z = alloc_cache();
    cache.a = alloc_cache();
    cache.delta = alloc_cache();

    net.biases = alloc_biases();
    net.weights = alloc_weights();

    for (int epoch = 0; epoch < 50000; ++epoch) {
        for (int n = 0; n < 4; ++n) {
            forward(input[n], output, &net, &cache);
            if (epoch % 1000 == 0)
                printf("epoch %d, sample %d, loss %.6f\n", epoch, n, loss(output, ground_truth[n]));
            backward(ground_truth[n], &net, &cache);
            optimize(0.3, &net, &cache);
            zero_grad(&cache);
        }
    }
}

完整代码可参考: https://github.com/howardlau1999/learning-machine-learning/blob/master/mlp_work.c

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

奢望

暂无简介

0 文章
0 评论
831 人气
更多

推荐作者

qq_E2Iff7

文章 0 评论 0

Archangel

文章 0 评论 0

freedog

文章 0 评论 0

Hunk

文章 0 评论 0

18819270189

文章 0 评论 0

wenkai

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文