scratch lenet(5): 快速生成随机数的C语言实现

1. 目的

用于 lenet 网络训练开始时, weight 和 bias 的初始化。

使用C语言,一方面不想用C标准库的 rand(), 希望沿袭 deepdream_c 的风格; 另一方面, rand() 的跨平台性不太够, RAND_MAX 取值和编译器版本相关,有些编译器下无法得到均等概率的均匀分布。考虑用最少的代码, 实现一个精度相当可以的、性能不算太慢的均匀分布的随机数生成器。

2. 使用 rand() 的正确姿势

假设你的目标平台是唯一的,并且觉得 rand() 的参数 min, max 的范围也是确定的, 使得可以得到比较好的均等概率的均匀分布, 那你可以这样实现:

static float get_random(float min, float max)
{
    return rand() / ((RAND_MAX + 1U) / (max - min + 1)) + min;
}

3. 使用 TAOCP 公式

代码来自 github, 作者 Bob Adolf. 见参考[2]. ncnn 的单元测试工具, 使用了 prng.h 。这里稍作修改,放在 lenet.c 中:

3.1 实现

//-----------------------------------------------------------------------------------------
// Random Number
//-----------------------------------------------------------------------------------------
// Portable pseudo random number generator by Bob Adolf
// Based on TAOCP, 3.2.2(7), for j=24, k=55, m=2^64
#define LAG1               (UINT16_C(24))
#define LAG2               (UINT16_C(55))
#define RAND_SSIZE         ((UINT16_C(1)) << 6)
#define RAND_SMASK         (RAND_SSIZE - 1)
#define RAND_EXHAUST_LIMIT LAG2
// 10x is a heuristic, it just needs to be large enough to remove correlation
#define RAND_REFILL_COUNT ((LAG2 * 10) - RAND_EXHAUST_LIMIT)
struct prng_rand_t
{
    uint64_t s[RAND_SSIZE]; // Lags
    uint_fast16_t i;        // Location of the current lag
    uint_fast16_t c;        // Exhaustion count
};

#define PRNG_RAND_MAX UINT64_MAX

static inline uint64_t prng_rand(struct prng_rand_t* state)
{
    uint_fast16_t i;
    uint_fast16_t r, new_rands = 0;

    if (!state->c)
    {   // Randomness exhausted, run forward to refill
        new_rands += RAND_REFILL_COUNT + 1;
        state->c = RAND_EXHAUST_LIMIT - 1;
    }
    else
    {
        new_rands = 1;
        state->c--;
    }

    for (r = 0; r < new_rands; r++)
    {
        i = state->i;
        state->s[i & RAND_SMASK] = state->s[(i + RAND_SSIZE - LAG1) & RAND_SMASK]
                                   + state->s[(i + RAND_SSIZE - LAG2) & RAND_SMASK];
        state->i++;
    }
    return state->s[i & RAND_SMASK];
}

static inline void prng_srand(uint64_t seed, struct prng_rand_t* state)
{
    uint_fast16_t i;
    // Naive seed
    state->c = RAND_EXHAUST_LIMIT;
    state->i = 0;

    state->s[0] = seed;
    for (i = 1; i < RAND_SSIZE; i++)
    {
        // Arbitrary magic, mostly to eliminate the effect of low-value seeds.
        // Probably could be better, but the run-up obviates any real need to.
        state->s[i] = i * (UINT64_C(2147483647)) + seed;
    }

    // Run forward 10,000 numbers
    for (i = 0; i < 10000; i++)
    {
        prng_rand(state);
    }
}

// Clean up our macros
#undef LAG1
#undef LAG2
#undef RAND_SSIZE
#undef RAND_SMASK
#undef RAND_EXHAUST_LIMIT
#undef RAND_REFILL_COUNT

// PRNG_RAND_MAX is exported

static struct prng_rand_t g_prng_rand_state;
#define PRNG_SRAND(seed) prng_srand(seed, &g_prng_rand_state)
#define PRNG_RAND()      prng_rand(&g_prng_rand_state)

static float get_random(float a, float b)
{
    //return rand() / ((RAND_MAX + 1U) / (max - min + 1)) + min;
    float random = ((float)PRNG_RAND()) / (float)(PRNG_RAND_MAX); //RAND_MAX;
    float diff = b - a;
    float r = random * diff;
    return a + r;
}
// End of Random Number
//-----------------------------------------------------------------------------------------

3.2 使用

最简单的用法如下:

void test_random_number()
{
    PRNG_SRAND(7767517);
    float val = get_random(0.f, 233);
    printf("%.6f\n", val);
}

4. 随机数:用于 Xavier Glorot 初始化

4.1 Xavier Glorot 初始化是什么

根据参考[3]知道,如果使用均匀分布初始化,随机数范围是 [ − x , x ] [-x, x] [x,x], 则
x = 6.0 fan in + fan out x = \sqrt{\frac{6.0}{\text{fan}_{\text{in}} + \text{fan}_{\text{out}}}} x=fanin+fanout6.0

如果用于高斯分布 (均值 μ = 0 \mu = 0 μ=0),对应的标准差为
x = 2.0 fan in + fan out x = \sqrt{\frac{2.0}{\text{fan}_{\text{in}} + \text{fan}_{\text{out}}}} x=fanin+fanout2.0

  • fan in \text{fan}_{\text{in}} fanin (float) - 当前网络层的输入神经元个数
  • fan out \text{fan}_{\text{out}} fanout (float) - 当前网络层的输出神经元个数

4.2 使用C语言执行 Xavier Glorot 初始化

这里只考虑均匀分布的情况, 因为本文使用的 prng.h 的代码是生成均匀分布的随机数。

以 lenet-5 的第一层 C1 卷积层为例, 输入为 32x32 单通道图像, 有6个kernel, 每个 kernel 为 5x5 大小。这里仅考虑第一个 kernel, 用 Xavier Glorot 方式初始化它。

输入数量 fan_in = 1, 输出数量 fan_out = 6。kernel 大小 5x5。对每个 kernel 元素, 赋予同样范围的随机数:

    float kernel[5 * 5]; // TODO: initialize

    int fan_in = 1;
    int fan_out = 6;
    float range_abs = m_sqrt(6.0f / (5 * 5 * (fan_in + fan_out)));
    for (int i = 0; i < 25; i++)
    {
        kernel[i] = get_random(-range_abs, range_abs);
    }

使用该 kernel 做卷积, 输入:
在这里插入图片描述

得到的卷积结果(单通道)如下:
在这里插入图片描述

5. References

  1. C/C++随机数用哪个函数?
  2. <github.com/rdadolf/prng/blob/master/prng.h>
  3. https://www.bookstack.cn/read/paddlepaddle-1.6/3f4d0d9266a7a5c8.md
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeNet-5神经网络 C源代码,这个写的比较好,可以用gcc编译去跑,结合理论可以对深度学习有更深刻的了解 介绍 根据YANN LECUN的论文《Gradient-based Learning Applied To Document Recognition》设计的LeNet-5神经网络,C语言写成,不依赖任何第三方库。 MNIST手写字符集初代训练识别率97%,多代训练识别率98%。 DEMO main.c文件为MNIST数据集的识别DEMO,直接编译即可运行,训练集60000张,测试集10000张。 项目环境 该项目为VISUAL STUDIO 2015项目,用VISUAL STUDIO 2015 UPDATE1及以上直接打开即可编译。采用ANSI C编写,因此源码无须修改即可在其它平台上编译。 如果因缺少openmp无法编译,请将lenet.c中的#include和#pragma omp parallel for删除掉即可。 API #####批量训练 lenet: LeNet5的权值的指针,LeNet5神经网络的核心 inputs: 要训练的多个图片对应unsigned char二维数组的数组,指向的二维数组的batchSize倍大小内存空间指针。在MNIST测试DEMO中二维数组为28x28,每个二维数组数值分别为对应位置图像像素灰度值 resMat:结果向量矩阵 labels:要训练的多个图片分别对应的标签数组。大小为batchSize batchSize:批量训练输入图像(二维数组)的数量 void TrainBatch(LeNet5 *lenet, image *inputs, const char(*resMat)[OUTPUT],uint8 *labels, int batchSize); #####单个训练 lenet: LeNet5的权值的指针,LeNet5神经网络的核心 input: 要训练的图片对应二维数组 resMat:结果向量矩阵 label: 要训练的图片对应的标签 void Train(LeNet5 *lenet, image input, const char(*resMat)[OUTPUT],uint8 label); #####预测 lenet: LeNet5的权值的指针,LeNet5神经网络的核心 input: 输入的图像的数据 labels: 结果向量矩阵指针 count: 结果向量个数 return 返回值为预测的结果 int Predict(LeNet5 *lenet, image input, const char(*labels)[LAYER6], int count); #####初始化 lenet: LeNet5的权值的指针,LeNet5神经网络的核心

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值