scratch lenet(9): C语言实现tanh的计算

news2024/11/15 10:38:17

文章目录

1. 目的

用于 LeNet-5 网络中 squashing function 中 tanh() 部分的计算。tanh() 是 hyperbolic tangent 双曲正切三角函数的意思。

LeNet-5 网络的 C1~F6, 每一层都需要对于输出结果应用 squashing function. 后世称作 activation function.

2. tanh ⁡ ( x ) \tanh(x) tanh(x) 的 naive 实现

2.1 数学公式

tanh ⁡ ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}} tanh(x)=ex+exexex

2.2 naive 实现

直接翻译公式得到:

static double m_tanh(double x)
{
    double ep = m_exp(x);  // exponent of positive x
    double en = m_exp(-x); // exponent of negative x
    double up = ep - en;
    double down = ep + en;
    return up / down;
}

其中 m_exp 在前一篇博客1 scratch lenet(8): C语言实现 exp(x) 的计算 给出过:

static double m_fabs(double n)
{
    return n >= 0.0 ? n : -n;
}

double m_exp(double x)
{
    double res = 1;
    double eps = 1e-9;
    double up = 1;
    double down = 1;
    for (int i = 1; ;i++)
    {
        up *= x;
        down *= i;
        double delta = up / down;
        res += delta;
        // printf("i=%d, delta=%lf\n", i, delta);
        if (m_fabs(delta) < eps)
            break;
    }
    return res;
}

3. tanh ⁡ ( x ) \tanh(x) tanh(x) 的快速计算

StackOverFlow 上的一个问答2 给出了好几种近似计算方式。

3.1 Maple 中的近似公式

回答3 给出了一个公式(TL;DR 这一节的公式不靠谱,精度丢失比较多)

The best rational approximation to tanh(x) with numerator and denominator of degree 3 on the interval [0,3.1] (as provided by Maple’s minimax function) is

(-.67436811832e-5+(.2468149110712040+(.583691066395175e-1+.3357335044280075e-1*x)*x)*x)/(.2464845986383725+(.609347197060491e-1+(.1086202599228572+.2874707922475963e-1*x)*x)*x)

This (call it f(x)) has maximum error .2735944241730870e-4, which is considerably less than 2^(-8).
On the interval [−3.1,3.1], use sgn(x)f(|x|

double fast_tanh_by_maple(double x)
{
    return (-.67436811832e-5+(.2468149110712040+(.583691066395175e-1+.3357335044280075e-1*x)*x)*x)/(.2464845986383725+(.609347197060491e-1+(.1086202599228572+.2874707922475963e-1*x)*x)*x);
}
zz@Legion-R7000P% ./a.out 
Please input a real number: 0.345
 tanh(0.345000) = 0.331934
 tanh_c3(0.345000) = 0.331935
 m_tanh(0.345000) = 0.331934
 fast_tanh_by_maple(0.345000) = 0.331907

我尝试后,发现精度差的有点多,并不是所谓的“精度损失小于 .2735944241730870e-4”, 而是肉眼可见的有精度损失:

>>> e1 = .2735944241730870e-4
>>> e2 = 0.331934 - 0.331907
>>> e1 < e2
False
>>> 

3.2 tan_c3()

jenkas 给出了一个更好的近似公式和实现4.

float tanh_c3(float v)
{
    const float c1 = 0.03138777F;
    const float c2 = 0.276281267F;
    const float c_log2f = 1.442695022F;
    v *= c_log2f;
    int intPart = (int)v;
    float x = (v - intPart);
    float xx = x * x;
    float v1 = c_log2f + c2 * xx;
    float v2 = x + xx * c1 * x;
    float v3 = (v2 + v1);
    *((int*)&v3) += intPart << 24;
    float v4 = v2 - v1;
    return (v3 + v4) / (v3 - v4);
}

暂时没搞懂这个实现对应的公式

v = I + x // 整数部分 + 小数部分
xx = x * x // 小数部分的平方
v1 = c_log2f + c2 * xx
v2 = x + xx * c1 * x
v3 = v2 + v1 = x + xx * c1 * x + c_log2f + c2 * xx
             = c_log2f + x + c1 * x * x * x + c2 * x * x
v4 = v2 - v1 = x + xx * c1 * x - c_log2f - c2 * xx
             = -c_log2f + x - c2 * x * 2 + c1 * x * x * x

3.3 Gauss 连分数公式 (Continued Fraction)

1812年高斯给出的双曲正切函数 tanh ⁡ ( x ) \tanh(x) tanh(x) 的连分数展开公式 (continued fraction for the hyperbolic tangent 5)

tanh ⁡ ( x ) = x 1 + x 2 3 + x 2 5 + . . . \tanh(x) = \frac{x}{1 + \frac{x^2}{3 + \frac{x^2}{5 + ...}}} tanh(x)=1+3+5+...x2x2x

我们使用展开到 9 + x 2 11 9 + \frac{x^2}{11} 9+11x2 的这一项, 作为 tanh 的近似6
在这里插入图片描述

发现结果非常准确(至少对于 x = 0.345 x=0.345 x=0.345 来说, 和 C 标准库结果一样)

double approx_tanh_by_continues_fraction(double x)
{
    double s = x * x;
    double y = 9 + s / 11;
    y = 7 + s / y;
    y = 5 + s / y;
    y = 3 + s / y;
    y = 1 + s / y;
    y = x / y;
    return y;
}

4. 最终代码和运行结果

代码

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

double tanh_c3(float v)
{
    const float c1 = 0.03138777F;
    const float c2 = 0.276281267F;
    const float c_log2f = 1.442695022F;
    v *= c_log2f;
    int intPart = (int)v;
    float x = (v - intPart);
    float xx = x * x;
    float v1 = c_log2f + c2 * xx;
    float v2 = x + xx * c1 * x;
    float v3 = (v2 + v1);
    *((int*)&v3) += intPart << 24;
    float v4 = v2 - v1;
    return (v3 + v4) / (v3 - v4);
}

static double m_fabs(double n)
{
    return n >= 0.0 ? n : -n;
}

double m_exp(double x)
{
    double res = 1;
    double eps = 1e-9;
    double up = 1;
    double down = 1;
    for (int i = 1; ;i++)
    {
        up *= x;
        down *= i;
        double delta = up / down;
        res += delta;
        // printf("i=%d, delta=%lf\n", i, delta);
        if (m_fabs(delta) < eps)
            break;
    }
    return res;
}

static double m_tanh(double x)
{
    double ep = m_exp(x);  // exponent of positive x
    double en = m_exp(-x); // exponent of negative x
    double up = ep - en;
    double down = ep + en;
    return up / down;
}

double fast_tanh_by_maple(double x)
{
    return (-.67436811832e-5+(.2468149110712040+(.583691066395175e-1+.3357335044280075e-1*x)*x)*x)/(.2464845986383725+(.609347197060491e-1+(.1086202599228572+.2874707922475963e-1*x)*x)*x);
}

double approx_tanh_by_continues_fraction(double x)
{
    double s = x * x;
    double y = 9 + s / 11;
    y = 7 + s / y;
    y = 5 + s / y;
    y = 3 + s / y;
    y = 1 + s / y;
    y = x / y;
    return y;
}

int main()
{
    double x;
    while (true)
    {
        printf("Please input a real number: ");
        scanf("%lf", &x);
        double y1 = tanh(x);
        double y2 = tanh_c3(x);
        double y3 = m_tanh(x);
        double y4 = fast_tanh_by_maple(x);
        double y5 = approx_tanh_by_continues_fraction(x);
        printf(" tanh(%lf) = %lf\n", x, y1);
        printf(" tanh_c3(%lf) = %lf\n", x, y2);
        printf(" m_tanh(%lf) = %lf\n", x, y3);
        printf(" fast_tanh_by_maple(%lf) = %lf\n", x, y4);
        printf(" approx_tanh_by_continues_fraction(%lf) = %lf\n", x, y5);
    }

    return 0;
}

运行结果

gcc tanh.c -lm
zz@Legion-R7000P% ./a.out 
Please input a real number: 0.345
 tanh(0.345000) = 0.331934
 tanh_c3(0.345000) = 0.331935
 m_tanh(0.345000) = 0.331934
 fast_tanh_by_maple(0.345000) = 0.331907
 approx_tanh_by_continues_fraction(0.345000) = 0.331934

也尝试了其他输入如 x=257, 整体上看 Gauss 给出的 Continued Fraction 的精度损失更小一些,速度也还算快,打算在 lenet-5 代码中使用它:

double approx_tanh_by_continues_fraction(double x)
{
    double s = x * x;
    double y = 9 + s / 11;
    y = 7 + s / y;
    y = 5 + s / y;
    y = 3 + s / y;
    y = 1 + s / y;
    y = x / y;
    return y;
}

5. 其他

K-Tanh 7 基于 AVX512 指令给出了5倍加速的实现。

[【Tanh的标量实现】]8 则考虑了 Inf/Nan 等情况, 并使用了
tanh ⁡ ( x ) = e 2 x − 1 e 2 x + 1 = 1 − 2 e 2 x + 1 \tanh(x) = \frac{e^{2x}-1}{e^{2x} + 1} = 1 - \frac{2}{e^{2x}+1} tanh(x)=e2x+1e2x1=1e2x+12
这一等效公式计算。

References


  1. scratch lenet(8): C语言实现 exp(x) 的计算 ↩︎

  2. Rapid approximation of tanh(x) ↩︎

  3. https://math.stackexchange.com/a/107302 ↩︎

  4. https://math.stackexchange.com/a/3485944 ↩︎

  5. continued fraction for the hyperbolic tangent ↩︎

  6. https://math.stackexchange.com/a/107295 ↩︎

  7. K-TANH: EFFICIENT TANH FOR DEEP LEARNING ↩︎

  8. 【Tanh的标量实现】 ↩︎

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

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

相关文章

使用python制作常用图表

案例01 制作柱形图展示数据的对比关系——员工销售业绩统计表.xlsx import xlwings as xw app xw.App(visibleTrue, add_bookFalse) workbook app.books.open(员工销售业绩统计表.xlsx) # 打开要制作图表的工作簿 for i in workbook.sheets: # 遍历工作簿中的工作表chart i…

node.js安装及配置教程(win11)

node.js安装及配置教程&#xff08;win11&#xff09; 一、下载二、安装三、环境配置 一、下载 官网下载&#xff1a;点击下载 根据自己电脑的位数选择对应的版本即可 网盘下载&#xff1a;点击下载 二、安装 下载完成后&#xff0c;双击运行程序&#xff0c;点击next 勾…

iOS自动化环境搭建(超详细)

1.macOS相关库安装 libimobiledevice > brew install libimobiledevice 使用本机与苹果iOS设备的服务进行通信的库。 ideviceinstaller brew install ideviceinstaller 获取设备udid、安装app、卸载app、获取bundleid carthage > brew install carthage 第三方库…

【Redis】Redis最佳实践/经验总结

【Redis】Redis最佳实践/经验总结 文章目录 【Redis】Redis最佳实践/经验总结1. Redis键值设计1.1 优雅的key结构1.2 拒绝BigKey1.2.1 BigKey的危害1.2.2 如何发现BigKey1.2.3 如何删除BigKey 1.3 恰当的数据类型1.3.1 例11.3.2 例2 2. 批处理优化2.1 Pipeline2.1.1 单个命令的…

ROM和RAM的工作原理(DRAM和DROM)以及DRAM的刷新方法

只读存储器ROM: ROM和RAM都是支持随机存取的存储器&#xff0c;其中SRAM和DRAM均为易失性半导体存储器。而ROM中一旦有了信息&#xff0c;就不能轻易改变&#xff0c;即使掉电也不会丢失&#xff0c;它在计算机系统中是只供读出的存储器。ROM器件有两个显著的优点: 1)结构简单&…

RabbitMQ 消息丢失的场景,如何保证消息不丢失?

一.RabbitMQ消息丢失的三种情况 第一种&#xff1a;生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候&#xff0c;可能数据就在半路给搞丢了&#xff0c;因为网络问题啥的&#xff0c;都有可能。 第二种&#xff1a;RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了 第三种…

软考A计划-系统集成项目管理工程师--一般常识-中

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【深度学习推荐系统 理论篇】一、Wide Deep Learning for Recommender Systems

前言 在搜广推业务做了3年工程&#xff0c;最近终于有空整理下&#xff0c;完整的梳理下自己的知识架构&#xff08;预计分为理论篇/工程篇&#xff09; Wide & Deep论文链接&#xff1a;https://arxiv.org/abs/1606.07792 另外王喆老师《深度学习推荐系统》中&#xff…

安装 Nginx 服务

一.安装 Nginx 服务 1.关闭防火墙 开机自启起 安全机制 systemctl stop firewalld systemctl disable firewalld setenforce 0 2、安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make 3、创建运行用户 useradd -M -s /sbin/nologin nginx 4、编译安装 cd …

总结908

学习目标&#xff1a; 月目标&#xff1a;6月&#xff08;线性代数强化9讲&#xff0c;背诵15篇短文&#xff0c;考研核心词过三遍&#xff09; 周目标&#xff1a;线性代数强化3讲&#xff0c;英语背3篇文章并回诵&#xff0c;检测 每日必复习&#xff08;5分钟&#xff09;…

永磁同步直线电机学习笔记——直线电机的数学模型

永磁直线电机数学模型的建立&#xff0c;是进行后续控制仿真和实验的前提。为了实现永磁同步直线电机的矢量控制&#xff0c;需要把永磁同步直线电机假想成永磁同步旋转电机&#xff0c;借鉴旋转电机的电流分析方式&#xff0c;实现dq轴电流控制的解耦&#xff0c;并且把永磁同…

为什么常见电路板GND与外壳GND之间接一个电阻一个电容

集电极开路是指集电极电路中出现了断路的情况&#xff0c;导致电路无法正常工作。在集电极开路的情况下&#xff0c;电路中的电流无法通过集电极流过&#xff0c;导致电路无法正常放大信号或者控制其他器件的工作。 集电极开路的原因有很多&#xff0c;可能是器件本身的故障、…

C++进阶—继承(下)菱形(虚拟)继承分析虚拟继承存储对象模型

目录 0. 前言 1. 普通多继承下&#xff0c;基类和派生类复制转换底层细节(切片) 2. 多继承下的复杂菱形继承 3. 菱形虚拟继承(虚基类)重点 3.1 菱形非虚拟继承对象存储模型 3.2 菱形虚拟继承对象存储模型 3.3 虚拟继承对象存储模型 3.4 多对象继承关系分析其虚基类&…

Redis原理 - 内存策略

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis 本身是一个典型的 key-value 内存存储数据库&#xff0c;因此所有的 key、value 都保存在之前学习过的 Dict 结构中。不过在其 database 结构体中&#xff0c;有两个 Dict &#xff1a;一个用来记录 key-value&#xf…

【计算机网络详解】——软件定义网络SDN(学习笔记)

目录 &#x1f552; 1. 概念&#x1f552; 2. OpenFlow 协议 &#x1f552; 1. 概念 软件定义网络&#xff08;Software Defined Network&#xff0c;SDN&#xff09;的概念最早由斯坦福大学的Nick McKeown教授于2009年提出。SDN最初只是学术界讨论的一种新型网络体系结构。SD…

基于JAVA实现的简易学生信息管理系统(附源码)

一、前言 最近在学习JAVA&#xff0c;这几天跟着网上的视频学完基础知识之后&#xff0c;做了一个学生信息管理系统&#xff0c;写的比较普通&#xff0c;没太大亮点&#xff0c;希望可以给初学者一些参考经验&#xff0c;另外&#xff0c;如有不恰当的地方还请各位指正&am…

论文解读:End-to-End Object Detection with Transformers

发表时间&#xff1a;2020 论文地址&#xff1a;https://arxiv.org/pdf/2005.12872.pdf 项目地址&#xff1a;https://github.com/facebookresearch/detr 提出了一种将对象检测视为集合预测问题的新方法。我们的方法简化了检测流程&#xff0c;有效地消除了许多手工设计的组件…

解决跨域问题的两种方案

说明&#xff1a;跨域是指&#xff0c;在A向B发送请求时&#xff0c;如果A和B的协议、端口号和域名有一个不相同。跨域问题是指&#xff0c;浏览器出于安全&#xff0c;会阻止跨域的异步请求&#xff08;如Ajax&#xff09;&#xff0c;而在分布式的开发环境下&#xff0c;跨域…

ChatGPT在媒体与娱乐领域的沉浸式场景:虚拟主持人和创意助手的新应用探索

第一章&#xff1a;引言 在当今数字化时代&#xff0c;人工智能技术在媒体与娱乐领域的应用日益广泛。ChatGPT作为一种先进的自然语言处理模型&#xff0c;具备强大的对话生成能力和创造力&#xff0c;为媒体与娱乐产业带来了新的创意和可能性。本文将探讨ChatGPT在媒体与娱乐…

学了那么长时间的编程,C语言的各种操作符都搞不懂?点开这里有详细的介绍—>

目录 前言 一、原码、反码、补码的基础概念 1.原码 2.反码 3.补码 二、原码、反码、补码的计算方法 1.原码 2.反码 3.补码 三、算术操作符 四、移位操作符 1. 左移操作符 移位规则&#xff1a; 2. 右移操作符 移位规则&#xff1a; &#xff08;1&#xff09; …