scratch lenet(10): C语言计算log

news2024/10/6 4:09:17

scratch lenet(10): C语言计算log

文章目录

1. 目的

用于复现 LeNet-5 时,损失函数的计算。损失函数中的惩罚项(正则项)出现了 log ⁡ ( ) \log() log() 函数, 而我的设定是复现过程不能用 C 标准库的数学库。

具体的实现依赖于选择的数学公式,了解到的有两种:

  1. 泰勒级数展开。缺点是比较慢。
  2. 结合 IEEE-754 二进制表示法 和 Remez 算法。速度快。

本文使用第二种方法,即 IEEE-754 二进制 + Remez 算法, 考察的数据类型是 float。

2. 原理: x = m ∗ 2 p x = m * 2^p x=m2p

对于 float 类型变量 x, 它可以表示为

x = m ∗ 2 p x = m * 2^p x=m2p

为啥呢?把 y 的二进制展开一下就可以理解了1

   sign  exp        frac
0b 0     [00000000] 00000000000000000000000
value = (-1)^s * m * 2 ^ (exp - 127)

其中:

  • m = 1.0 + 0. f r a c ∈ [ 1 , 2 ] m = 1.0 + 0.frac \in [1, 2] m=1.0+0.frac[1,2]
  • exp 减去 127 是规定,适用于 x > 2 − 126 x > 2^{-126} x>2126
  • p = e x p − 127 p = exp - 127 p=exp127

3. 公式展开

3.1 公式分解

也就是对于 2.1 小节公式,由于等号左右都大于0,可以分别计算对数,得到的公式

ln ( x ) = ln ( m ∗ 2 p ) = ln ( m ) + ln ( 2 p ) = ln ( m ) + ln ( 2 ) p \text{ln}(x) = \text{ln}(m * 2^p) = \text{ln}(m) + \text{ln}(2^p) = \text{ln}(m) + \text{ln}(2)p ln(x)=ln(m2p)=ln(m)+ln(2p)=ln(m)+ln(2)p

其中:

  • m ∈ [ 1 , 2 ] m \in [1, 2] m[1,2], 可以从 x x x 的二进制表示的 “frac” 部分获取到
  • ln ( 2 ) \text{ln}(2) ln(2) 是常数 0.69314718
  • p p p 可以从 x x x 的二进制表示的 “exp” 部分获取到

对于 ln ( m ) \text{ln}(m) ln(m), 由于 m ∈ [ 1 , 2 ] m \in [1, 2] m[1,2], 可用 Remez 算法近似算出。因此剩下的问题为:

  • 获取 m m m
  • 获取 p p p
  • 应用 Remez 算法

3.2 获取 m m m

x x x 的二进制表示中,exp 部分修改为 127, 使得 value 表达式等于:
v a l u e = ( − 1 ) s ∗ M ∗ 2 127 − 127 = ( − 1 ) s ∗ m value = (-1)^s * M * 2 ^ {127 - 127} = (-1)^s * m value=(1)sM2127127=(1)sm
并且 x 的二进制的其他部分是不变的。这就得到了 m 的二进制表示。对应代码如下:

float m_ln(float x)
{
    unsigned int bx = *(unsigned int *) (&x);
    // exp:  00000000
    // frac: 0b11111111111111111111111 = 838607
    unsigned int bm = (127 << 23) | (bx & 8388607);
    float m = *(float *) (&bm);
    printf("m = %f\n", m); 
    ...
}

3.3 获取 p p p

根据 p = exp − 127 p = \text{exp} - 127 p=exp127, 要获取 p p p 就需要先获取 exp. 在 C 语言中标准库已经用了 exp 这个名字,因此我们用 ex 来表示它:

float m_ln(float x)
{
    unsigned int bx = *(unsigned int *) (&x);
    unsigned int ex = bx >> 23;
    signed int p = (signed int)ex - (signed int)127;
    printf("p = %d\n", p);
    ...
}

3.4 Remez 算法

Remez 算法被 Maple 等数值计算软件采用,精度基本够用,速度够快,解决了给定区间 x ∈ [ a , b ] x \in [a, b] x[a,b] 上用多项式逼近给定函数 f ( x ) f(x) f(x)minimax() 问题的解。理论发展过程大概是: Chebyshev 证明了迭代求解过程的收敛性, Remez 则给出了具体的迭代求解过程。

网友 Crouching Kitten 提供了 Remez 算法的具体使用 2 3.

Remez 算法用于 sin(x) 的快速计算

B站视频 【硬核】从零开始自己编写FOC 算法篇3:快速正弦算法4 则给出了一些理论公式,以及基于 Remez C++ 库 LolRemez5 算出了一些数据,从而在 DSP 上得到了比 DSP 数学库还快还准的 sin(x) 的实现。公式:

在这里插入图片描述

在这里插入图片描述

Remez 算法用于 exp 的近似

博客6 给出了 exp 的 Remez 的 Octave/MatLab 实现。

% https://loki-pup.github.io/posts/2019-9-30-Remez%E7%AE%97%E6%B3%95%E7%9A%84MATLAB%E5%AE%9E%E7%8E%B0-2019
function [] = remez(y)
    w = 10^(-6);
    r4 = 1;
    o = 0;
    while r4 >= w
        o=o+1;
        z = inp(y);
        [s,r4] = comp(z);
        if r4 < w
            break;
        end
        y = subs(y,s,z);
    end
    o
    fprintf('%f*x^2+%f*x+%f\n',z(4),z(3),z(2))
    r4
end

function [ x ] = inp( y )
    %UNTITLED 此处显示有关此函数的摘要
    %   此处显示详细说明
    a = zeros(4,4);
    b = zeros(4,1);
    for i =1:4
        a(i,1) = (-1)^(i-1);
        a(i,2) = 1;
        a(i,3) = y(i);
        a(i,4) = y(i)^(2);
        b(i,1) = exp(y(i));
    end
    x = pinv(a)*b;
end

function [s,r4] = comp(q)
    f1 = @(x)exp(x)-q(2)-q(3)*x-q(4)*x^2;
    f2 = @(x)q(4)*x^2+q(2)+q(3)*x-exp(x);
    [s1,r1] = fminbnd(f1,-1,1);
    [s2,r2] = fminbnd(f2,-1,1);
    if abs(r1)<=abs(r2)
        r3 = abs(r2);
    else
        r3 = abs(r1);
    end
    s = [s1,s2];
    r4 = r3 - abs(q(1));
end

function [y] = subs(y,s,q)     %替代,选取新点组,单一交换法
    p = length(s);
    for i = 1:p
        if s(i)>y(1)||s(i)<y(4)
        for j = 2:4
            if s(i)<y(j)
                c1 = sign(exp(y(j-1))-q(2)-q(3)*y(j-1)-q(4)*(y(j-1))^2);
                c2 = sign(exp(s(i))-q(2)-q(3)*s(i)-q(4)*(s(i))^2);
                if c1 == c2
                    y(j-1) = s(i);
                else
                    y(j) = s(i);
                end
            end
        end
        else if s(i)> -1 && s(i)<y(1)
                c1 = sign(exp(y(1))-q(2)-q(3)*y(1)-q(4)*y(1)^2);
                c2 = sign(exp(s(i))-q(2)-q(3)*s(i)-q(4)*(s(i))^2);
                if c1 == c2
                    y(1) = s(i);
                else
                    y(4) = s(i);
                end
        else if s(i)> y(4) && s(i)<1
                c1 = sign(exp(y(4))-q(2)-q(3)*y(4)-q(4)*y(4)^2);
                c2 = sign(exp(s(i))-q(2)-q(3)*s(i)-q(4)*(s(i))^2);
                if c1 == c2
                    y(4) = s(i);
                else
                    y(1) = s(i);
                end
            end
            end
        end
    end
end

Remez 用于自然对数 ln(x) 的计算

对于 l n ( m ) , m ∈ [ 1 , 2 ] ln(m), m \in [1, 2] ln(m),m[1,2] 而言, Remez 的4阶展开为:(来自2)
− 1.7417939 + ( 2.8212026 + ( − 1.4699568 + ( 0.44717955 − 0.056570851 ∗ x ) ∗ x ) ∗ x ) ∗ x ; -1.7417939 + (2.8212026 + (-1.4699568 + (0.44717955 - 0.056570851 * x) * x) * x) * x; 1.7417939+(2.8212026+(1.4699568+(0.447179550.056570851x)x)x)x;
也可以用3阶的展开,精度差一点,计算量少一点:
− 1.49278 + ( 2.11263 + ( − 0.729104 + 0.10969 ∗ x ) ∗ x ) ∗ x ; -1.49278 + (2.11263 +(-0.729104 + 0.10969 * x) * x) * x; 1.49278+(2.11263+(0.729104+0.10969x)x)x;

尝试使用 Remez C++ 库 LolRemez5 获取同样的结果, 不过编译失败了, 而 automake / P4 那一套我完全不熟悉,暂时放弃这个库。

也可以使用 boost 中提供的 remez 函数 1 7, 不过新版 boost 已经找不到 remez.hpp 头文件, 需要使用 b o o s t < = 1.56 boost <= 1.56 boost<=1.56 的版本,暂时没有尝试

#include <boost/math/tools/remez.hpp>

boost::math::tools::remez_minimax<double> approx(
    [](const double& x) { return log(x); },
    4, 0, 1, 2, false, 0, 0, 64
);

4. 结果

4.1 代码

// Author: Zhuo Zhang
// Homepage: https://github.com/zchrissirhcz
// Create Date: 2023.06.24

// logarithm for natural number `e`
float m_log(float x)
{
    // x = m * 2^p, m \in [1, 2]
    // ln(x) = ln(m * 2^p) = ln(m) + ln(2) * p
    
    // determine p
    unsigned int bx = *(unsigned int *) (&x);
    unsigned int ex = bx >> 23;
    signed int p = (signed int)ex - (signed int)127;

    // determine m
    // exp:  00000000
    // frac: 0b11111111111111111111111 = 838607
    unsigned int bm = (127 << 23) | (bx & 8388607);
    float m = *(float *) (&bm);
    // printf("m = %f\n", m); 

    // determine ln(m) by Remez algorithm for m in [1, 2]
    float ln_m_approx_4th_order = -1.7417939 + (2.8212026 + (-1.4699568 + (0.44717955 - 0.056570851 * m) * m) * m) * m;
    float ln_m_approx_3rd_order = -1.49278 + (2.11263 +(-0.729104 + 0.10969 * m) * m) * m;
    float ln_m_approx = ln_m_approx_4th_order;

    // combine the result
    const float ln2 = 0.6931471806;
    float res = ln_m_approx + ln2 * p;
    return res;
}


#include <stdio.h>
#include <stdbool.h>
#include <math.h>
int main()
{
    float x;
    while (true)
    {
        scanf("%f", &x);
        float y1 = m_log(x);
        float y2 = log(x);
        printf("m_log(%f) = %f\n", x, y1);
        printf("log(%f) = %f\n", x, y2);
    }
    return 0;
}

4.2 运行结果

zz@Legion-R7000P% gcc log.c -lm
zz@Legion-R7000P% ./a.out 
0.0123
m_log(0.012300) = -4.398208
log(0.012300) = -4.398156
1.234
m_log(1.234000) = 0.210295
log(1.234000) = 0.210261
2.345
m_log(2.345000) = 0.852274
log(2.345000) = 0.852285
3.456
m_log(3.456000) = 1.240081
log(3.456000) = 1.240112

5. References


  1. https://gist.github.com/LingDong-/7e4c4cae5cbbc44400a05fba65f06f23 ↩︎ ↩︎

  2. Efficient implementation of natural logarithm (ln) and exponentiation - Crouching Kitten’s Answer ↩︎ ↩︎

  3. https://en.wikipedia.org/wiki/Remez_algorithm ↩︎

  4. 【硬核】从零开始自己编写FOC 算法篇3:快速正弦算法 ↩︎

  5. https://github.com/samhocevar/lolremez ↩︎ ↩︎

  6. Remez算法的MATLAB实现 ↩︎

  7. How to Find a Fast Floating-Point atan2 Approximation ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/680248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL数据库】存储过程

目录 一、存储过程1.1概述1.2优点 二、存储过程实战2.1创建存储过程2.2存储过程的参数2.3条件语句 if-then-else end if2.4循环语句while end while 一、存储过程 1.1概述 存储过程是一组为了完成特定功能的SQL语句集合。存储过程在使用过程中是将常用或者复杂的工作预先使…

CSS基础学习--25 border(边框)进阶

一、边框常见属性 border-radiusbox-shadowborder-image 属性说明CSSborder-image设置所有边框图像的速记属性。3border-radius一个用于设置所有四个边框- *-半径属性的速记属性3box-shadow附加一个或多个下拉框的阴影3 二、border-radius 圆角 在 CSS2 中添加圆角棘手。我…

网络协议TCP/IP 协议学习笔记一

T C P / I P通常被认 为是一个四层协议系统&#xff0c;每一层负责不同的功能&#xff1a; 1) 链路层&#xff0c;有时也称作数据链路层或网络接口层&#xff0c; 通常包括操作系统中的设备驱动程序和计算机 中对应的网络接口卡。它们一起处理与电缆&#xff08;或其他任何传输…

CSS基础学习--26 渐变(Gradients)

CSS3 渐变&#xff08;gradients&#xff09;可以让你在两个或多个指定的颜色之间显示平稳的过渡。以前&#xff0c;你必须使用图像来实现这些效果。但是&#xff0c;通过使用 CSS3 渐变&#xff08;gradients&#xff09;&#xff0c;你可以减少下载的时间和宽带的使用。此外&…

Linux tracing之基于uprobe/kprobe的调试调优浅析

经过长期的发展, kprobes/uprobes 机制在事件(events)的基础上分别为内核态和用户态提供了追踪调试的功能, 这也构成了 tracepoint 机制的基础, 后期的很多工具, 比如 perf_events, ftrace 等都是在其基础上演化而来. 参考由 Brendan Gregg 提供的资料来看, kprobes/uprobes 在…

Minecraft我的世界服务器搭建之Linux系统,我的世界服务器推荐

Minecraft 是一个流行的沙箱独立游戏&#xff0c;由瑞典程序员 Markus “Notch” Perssion 首先创造&#xff0c;后来由 Mojang 开发并发布。这是一款关于打碎和放置砖块的游戏。首先&#xff0c;人们建造建筑物来抵抗夜晚的怪物&#xff0c;随着游戏的发展&#xff0c;玩家一起…

Spark Stream操作Kafka总结

kafka集群搭建 搭建参考 https://www.toutiao.com/article/6496743889053942286/?log_fromd5d6394cf75d_1687599146327 zk下载位置 国内&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/ 国外&#xff1a;Apache ZooKeeper kafka位置 国内&#xff…

Kubernetes(k8s)容器编排Pod介绍和使用

目录 1 Pod 特点1.1 网络1.2 存储 2 使用方式2.1 自主式Pod2.2 控制器管理的Pod 3 自主运行Pod3.1 创建资源清单3.1.1 参数描述 3.2 创建Pod3.3 Pod操作3.3.1 查看Pod列表3.3.2 查看描述信息3.3.3 访问pod3.3.4 删除Pod 4 控制器运行Pod4.1 创建资源清单4.2 参数描述4.2.1 Repl…

【IDEA】Directory创建多级目录的正确写法

在resource下创建包的时候&#xff0c;右键resourcenew的时候并没有Package,只有Directory 我们也可以用Directory创建包&#xff0c;但写法与在Package下创建包的写法会不一样 例如&#xff1a; 在directory创建包 我们在去看文件的时候 如果是用&#xff08; com.dao.m…

【数据结构】树以及堆的讲解

(这里写自定义目录标题) 提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、树的概念&#xff1f;二、树的表示方法三、树的实际应用四、二叉树概念以及结构1.概念2.特殊的二叉树3.二叉树的性质4.二叉树的存储…

指针与数组--动态数组(2)[1、长度可变的一维动态数组 2、长度可变的二维动态数组]

目录 一、长度可变的一维动态数组 二、长度可变的二维动态数组 由上篇文章的理论&#xff0c;接下来使用例题来阐述。 一、长度可变的一维动态数组 例题1、编程输入某班学生的某门课成绩&#xff0c;计算并输出平均值。学生人数由键盘输入。 #include <stdio.h> #i…

Apache服务器

文章目录 Apache服务器Linux安装ApacheApache文件结构Apache主配置文件案例 配置一台Web服务器 启动用户的个人网站虚拟主机的设定基于IP的虚拟主机基于域名的虚拟主机基于端口的虚拟主机 rewrite重写rewrite使用详解使用案例 域名跳转单个域名跳转多个域名跳转 status状态页ap…

“插入排序:小数据量排序的王者“

文章目录 &#x1f50d;什么是插入排序&#xff1f;&#x1f511;插入排序的优缺点&#x1f680;实现插入排序 &#x1f50d;什么是插入排序&#xff1f; 插入排序是一种简单的排序算法&#xff0c;它的基本思想是&#xff1a;将待排序的元素&#xff0c;从第二个元素开始&…

阿里架构师整理的Java经典面试题1220道(附答案)

学习如逆水行舟&#xff0c;尤其是 IT 行业有着日新月异的节奏&#xff0c;我们更要抓紧每一次可以学习和进步的机会。所以&#xff0c;没有撤退可言 即使是面试跳槽&#xff0c;那也是一个学习的过程。只有全面的复习&#xff0c;才能让我们更好的充实自己&#xff0c;武装自…

内网隧道代理技术(五)之 Netcat反弹Shell

Netcat反弹Shell Netcat简称NC,是一个简单、可靠的网络工具,被誉为网络界的瑞士军刀。通NC可以进行端口扫描、反弹Shell、端口监听和文件传输等操作,常用参数如下&#xff1a; 参数作用-c指定连接后要执行的shell命令-e指定连接后要执行的文件名-k配置 Socket一直存活(若不想…

一文了解远程桌面连接

一文了解远程桌面连接 一、引言1.1、远程桌面连接的概述1.2、远程桌面连接的应用场景 二、远程桌面连接的基本原理2.1、远程桌面连接的工作方式2.2、远程桌面连接的安全性 三、远程桌面连接的实现方法3.1、Windows自带的远程桌面连接3.2、第三方远程桌面连接工具 四、远程桌面连…

一阶低通滤波器(CODESYS FC和FB应用介绍)

一阶RC低通滤波器详细算法介绍请参看下面文章链接: PLC信号处理系列之一阶低通(RC)滤波器算法_plc计算滤波频率_RXXW_Dor的博客-CSDN博客1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波器的缺点; 1、在模拟常数要求较大的场合这…

数据挖掘——甘肃省县(区)域农业综合实力研究(论文)

《数据挖掘与分析》课程论文 题目&#xff1a;甘肃省县&#xff08;区&#xff09;域农业综合实力研究 xx学院xx专业xx班&#xff1a;xx 2023年6月 甘肃省县&#xff08;区&#xff09;域农业综合实力研究 xx (xx学院 xx学院) 摘要&#xff1a;本文主要研究甘肃省各县&#…

C语言数组指针和指针数组

文章目录 1 数组指针和指针数组的区别2 数组首地址和数组首元素地址的区别参考 1 数组指针和指针数组的区别 对指针数组和数组指针的概念&#xff0c;相信很多C程序员都会混淆。下面通过两个简单的语句来分析一下二者之间的区别&#xff0c;示例代码如下所示&#xff1a; int…

C/C++的发展历程和未来趋势

文章目录 C/C的起源C/C的应用C/C开发的工具C/C未来趋势 C/C的起源 C语言 C语言是一种通用的高级编程语言&#xff0c;由美国计算机科学家Dennis Ritchie在20世纪70年代初期开发出来。起初&#xff0c;C语言是作为操作系统UNIX的开发语言而创建的。C语言的设计目标是提供一种功…