tensorflow新建op (cpp)

为什么使用cpp新建op

  1. 一些操作表示成现有操作的组合不好实现或者无法实现。
  2. 已有操作的组合效率不高。
  3. 想要自定义一些基本操作的组合,因为未来编译器做这种融合可能会比较困难。

如何使用cpp新建op

  1. 注册op,注册op会定义一个接口(规范),比如定义op的名称和它的输入输出、shape函数(用于获取张量的形状)
  2. 实现op,对于CPU和GPU可以有不同的实现
  3. 为op编写一个函数来计算梯度(可选)

其实通过前两个步骤,我们就可以编写出一个可用的op,只是神经网络的反向传播需要计算梯度,因此涉及到反向传播求梯度操作时,我们还需要为op编写梯度计算函数,如果自定义的op不涉及求梯度,则无需编写梯度计算函数。

新建矩阵乘法op

注册矩阵乘法op

您可以在 这里 看到完整的代码

#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/shape_inference.h"using namespace tensorflow;REGISTER_OP("Mymatmul").Attr("T: {float, int32, int64, double}").Input("matrix1: T").Input("matrix2: T").Output("matmuled: T").SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c){auto N = c->Dim(c->input(0), 0);auto M = c->Dim(c->input(1), 1);c->set_output(0, c->MakeShape({N,M}));return Status::OK();});
  • 这里通过.Attr为输入输出添加多种类型,从而达到多态的目的
  • 我们可以从InferenceContext*类型的上下文参数中获取输入以及它们的形状
  • SetShapeFn用来确定输出的形状
    • c->input(0): 获取第一个输入参数
    • c->Dim(X, 0):获取X的第一个维度
    • c->set_output(0, ...):设置第一个输出的形状

实现op

实现op的大体框架如下:

template<typename T>
class MymatmulOp : public OpKernel {
public:explicit MymatmulOp(OpKernelConstruction* context) : OpKernel(context) {}void Compute(OpKernelContext* context) override {// ...}

我们要创建一个继承自OpKernel的类,并重载Compute方法

Compute方法有一个类型为OpKernelContext*的参数context,从中可以访问输入输出张量等有用的信息

接下来我们要在这个框架中完成具体的op实现

创建输入输出张量

我们可以从context中直接读取输入张量以及它们的形状,根据它们的形状来计算输出张量的形状,进而为输出张量分配内存:

O u t p u t N × M = I n p u t 1 N × K × I n p u t 2 K × M Output_{N\times M} = Input1_{N\times K} \times Input2_{K\times M} OutputN×M=Input1N×K×Input2K×M

⇊ \downdownarrows

[ N , K ] × [ K , M ] → [ N , M ] [N, K] \times [K, M] \to [N, M] [N,K]×[K,M][N,M]

具体实现如下:

    // create input tensorconst Tensor& input_tensor1 = context->input(0);const Tensor& input_tensor2 = context->input(1);const TensorShape& input1_shape = input_tensor1.shape();const TensorShape& input2_shape = input_tensor2.shape();// create output tensorTensorShape output_shape;const int N = input1_shape.dim_size(0);const int M = input2_shape.dim_size(1);output_shape.AddDim(N);output_shape.AddDim(M);Tensor* output_tensor = NULL;OP_REQUIRES_OK(context, context->allocate_output(0, output_shape, &output_tensor));
实现矩阵乘法

根据下面公式实现矩阵乘法

o u t p u t i j = i n p u t 1 i k × i n p u t 2 k j { output_{ij} = input1_{ik} \times input2_{kj} } outputij=input1ik×input2kj

    auto input1 = input_tensor1.matrix<T>();auto input2 = input_tensor2.matrix<T>();auto output = output_tensor->template matrix<T>();// matmulfor(int i = 0; i < N; i++) {for(int j = 0; j < M; j++) {output(i,j) = 0;for(int k = 0; k < input1_shape.dim_size(1); k++) {output(i,j) += input1(i, k) * input2(k, j);}}}
添加约束条件

添加约束条件主要考虑到两点:

  1. 自定义的op可能有多种实现,比如针对CPU和GPU有不同的实现
  2. 定义了多态,需要向TensorFlow系统指明本次注册的op实现是针对哪一种类型的
#define REGISTER_KERNEL(type)                                     \REGISTER_KERNEL_BUILDER(                                        \Name("Mymatmul").Device(DEVICE_CPU).TypeConstraint<type>("T"),\MymatmulOp<type>)REGISTER_KERNEL(int32)REGISTER_KERNEL(int64)REGISTER_KERNEL(float)REGISTER_KERNEL(double)

这里定义了REGISTER_KERNEL宏,方便我们注册多种类型的op

构建库文件

本文提供了g++的构建方式,可使用下面的命令构建op的库文件

TF_CFLAGS=( $(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_compile_flags()))') )
TF_LFLAGS=( $(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_link_flags()))') )g++ -std=c++11 -shared op_mymatmul.cc -o op_mymatmul.so -fPIC ${TF_CFLAGS[@]} ${TF_LFLAGS[@]} -O2

TF_CFLAGSTF_LFLAGS分别为构建op所需要的头文件路径和库文件路径

验证op可行性

import tensorflow as tf
m = tf.load_op_library('./op_mymatmul.so')a = tf.constant([[1., 2],[3, 4],[1, 1]])b = tf.constant([[1., 2, 1],[3, 4, 1]])with tf.Session('') as s:print(s.run(m.mymatmul(a, b)))print(s.run(tf.matmul(a, b)))

mymatmul和TensorFlow自带的matmul输出结果一致。

为op添加梯度计算

如果将构建好的op应用到tensorflow搭建好的神经网络中,比如mnist手写数字识别,我们将得到梯度未定义的错误

LookupError: No gradient define for operation ‘Mymatmul’ (op type Mymatmul)

因此我们还需要为op添加梯度计算

注册op

与前面流程类似,我们仍然需要先注册梯度op

REGISTER_OP("MymatmulGrad").Attr("T: {float, int32, int64, double}").Input("grad: T").Input("input1: T").Input("input2: T").Output("grad_input1: T").Output("grad_input2: T");

这里会接受三个输入,grad为矩阵乘法op输出的梯度,input1input2为参与矩阵乘法的两个矩阵,输出为两个矩阵的梯度

实现op

已知输出梯度,求输入梯度,经典的反向传播求梯度

假设输出误差为 L L L,输出为 y y y, 两个输入矩阵分别 W W W x x x

y = W x y = Wx y=Wx

∂ L ∂ x = ∂ y ∂ x ∂ L ∂ y = W ∂ L ∂ y \dfrac{\partial L}{\partial x} = \dfrac{\partial y}{\partial x} \dfrac{\partial L}{\partial y} = W\dfrac{\partial L}{\partial y} xL=xyyL=WyL

根据公式编写代码如下

template<typename T>
class MymatmulGradOp : public OpKernel {
public:explicit MymatmulGradOp(OpKernelConstruction* context) : OpKernel(context) {}void Compute(OpKernelContext* context) override {// create input tensor ...// create output tensor ...// initfor(int j = 0; j < K; j++) {for(int i = 0; i < N; i++) {grad_input1(i, j) = 0.0;}}for(int j = 0; j < M; j++) {for(int i = 0; i < K; i++) {grad_input2(i, j) = 0.0;}}// matmulfor(int i = 0; i < N; i++) {for(int j = 0; j < M; j++) {for(int k = 0; k < K; k++) {grad_input1(i, k) += input2(k, j) * grad(i, j);grad_input2(k, j) += input1(i, k) * grad(i, j);}}}}
};

给op添加约束条件以及构建库文件同前面一样

注册梯度计算

# FILE: op_mymatmul_grad.py
import tensorflow as tf
from tensorflow.python.framework import ops
m = tf.load_op_library('./op_mymatmul_grad.so')@ops.RegisterGradient("Mymatmul")
def mymatmul_grad_cc(op, grad):return m.mymatmul_grad(grad, op.inputs[0], op.inputs[1])

这里需要注意,@ops.RegisterGradient(...)里面传的是自定义op的名字,而不是梯度op的名字,因为我们要将梯度和自定义op绑定在一起

验证可用性

# ...import op_mymatmul_grad# ...# In addition to replacing matrix multiplication with mymatmul, just write your neural network model normallym = tf.load_op_library('./op_mymatmul.so')m.mymatmul(...)

最终结果正常。

项目地址

StubbornVegeta/tensorflow-custom-op

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

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

相关文章

Mac M1Pro 安装Java性能监控工具VisualVM 2.1.9

本地已经安装了java8&#xff0c;在终端输入jvisualvm提示没有安装 zhiniansara ~ % jvisualvm The operation couldn’t be completed. Unable to locate a Java Runtime that supports jvisualvm. Please visit http://www.java.com for information on installing Java.官网…

RPA自动化流程机器人助力企业财务数字化转型

在数字经济时代&#xff0c;企业需要快速响应市场变化&#xff0c;而财务数字化转型是企业适应现代商业环境、提升竞争力的必要步骤。财务数字化转型不仅涉及企业财务能力的提升&#xff0c;推动了财务管理与决策模式的转变。RPA自动化流程机器人因其能通过自动化技术帮助企业实…

[云计算] 虚拟化笔记

原著&#xff1a; 韩冰&#xff0c;[云计算课程]&#xff0c; 有删改。 目的 对 IT 资源简化&#xff0c;用户通过标准接口访问。 资源是提高一定功能的实现 。可以是硬件&#xff0c; 如CPU, 也可以是软件。 发展史 1961 IBM CPU 分时间片&#xff0c; 一个CPU 虚拟化为多…

【Nature】在科研中应用ChatGPT:如何与数据对话

随着人工智能技术的迅猛发展&#xff0c;大型语言模型&#xff08;LLMs&#xff09;正逐渐成为科研领域的一种创新工具。这些模型通过自然语言处理技术&#xff0c;使得研究人员能够以直观的方式与数据进行交互&#xff0c;从而简化了数据分析和解释的过程。在《自然》杂志2024…

Matlab自学笔记三十四:表table的排序、查找、提取、删除、计算、与结构数组的转换

1.表格的统计分析 表的统计分析包括计算均值、方差等&#xff0c;这些参数可以通过函数summary一次计算出来&#xff0c;程序示例如下&#xff1a; xingming{zhangsan;lisi;wangwu}; %首先创建表变量 xuehao{1001;1002;1003}; chengji[89 95;90 87;88 84]; ttable(xingmin…

当外接硬盘接入到macOS上,只读不可写时,应当格式化

当windows磁盘格式例如 NTFS 的硬盘接入到macOS上时&#xff0c;会发现无法新建文件夹&#xff0c;无法删除、重命名。原因是磁盘格式对不上macOS&#xff0c;需要进行格式化。格式化时请注意备份重要数据。具体做法如下&#xff0c;在macOS中找到磁盘工具&#xff0c;然后对磁…

QT Quick QML 实例之定制 TableView

QT Quick QML 实例之定制 TableView 一、演示二、C关键步骤1. beginInsertRows()&#xff08;用户插入行&#xff09;2. roleNames() &#xff08;表格中列映射&#xff09;3. data() &#xff08;用户获取数据&#xff09;4. headerData() &#xff08;表头&#xff09;5. fla…

影视会员官方渠道api对接

API对接是指两个不同的软件系统或应用程序之间通过API&#xff08;应用程序编程接口&#xff09;进行交互的过程。这种交互允许数据和功能的共享&#xff0c;而不必暴露系统的内部工作原理。在影视会员充值场景中&#xff0c;API对接具有以下几个关键特点和优势&#xff1a; 数…

【从Qwen2,Apple Intelligence Foundation,Gemma 2,Llama 3.1看大模型的性能提升之路】

从早期的 GPT 模型到如今复杂的开放式 LLM&#xff0c;大型语言模型 (LLM) 的发展已经取得了长足的进步。最初&#xff0c;LLM 训练过程仅侧重于预训练&#xff0c;但后来扩展到包括预训练和后训练。后训练通常包括监督指令微调和校准&#xff0c;这是由 ChatGPT 推广的。 自 …

11、Redis高级:Key设置、BigKey解决、批处理优化、集群下批处理、慢查询

Redis高级篇之最佳实践 今日内容 Redis键值设计批处理优化服务端优化集群最佳实践 1、Redis键值设计 1.1、优雅的key结构 Redis的Key虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过…

浅说数据链

一支军队能否制胜战场&#xff1f;影响因素有很多&#xff0c;高效的信息采集、传送、交换就是其中之一。从冷兵器时代的流星探马、八百里加急&#xff0c;到绵延千里的烽火狼烟&#xff1b;从近现代战场上“滴滴、滴滴滴”声不断的电报&#xff0c;到枪林弹雨中官兵手中的电话…

沉浸式解压小视频在哪找?非常减压的几个视频素材网站分享

沉浸式解压小视频&#xff0c;以其独特的舒缓音乐、宁静自然景观和柔和动态图像&#xff0c;成为了迅速消解压力的有效途径。这些视频能够帮助我们暂时离开紧张的现实&#xff0c;重获内心的平和。如果你正在寻找优质的解压视频素材&#xff0c;不用担心&#xff0c;接下来我会…

【HarmonyOS NEXT星河版开发学习】综合测试案例-各平台评论部分

目录 前言 功能展示 整体页面布局 最新和最热 写评论 点赞功能 界面构建 初始数据的准备 列表项部分的渲染 底部区域 index部分 知识点概述 List组件 List组件简介 ListItem组件详解 ListItemGroup组件介绍 ForEach循环渲染 列表分割线设置 列表排列方向设…

图像分割论文阅读:BCU-Net: Bridging ConvNeXt and U-Net for medical image segmentation

本文提出了一种集合ConvNeXt和U-Net优势的网络模型来分割医学图像。 当然&#xff0c;模型整体结构就是并列双分支&#xff0c;如果只是这些内容&#xff0c;不值得拿出来讲。 主要有意思的部分是其融合两分支的多标签召回模块&#xff08;multilabel recall loss module&…

如何使用midjourney?MidJourney订阅计划及国内订阅教程

国内如何订阅MidJourney 第三方代理 参考&#xff1a; zhangfeidezhu.com/?p474 使用信用卡订阅教程 办理国外信用卡&#xff1a; 这个各自找国外的银行办理就好了。 登录MidJourney&#xff1a; 登录MidJourney网站&#xff0c;进入订阅中心。如果是在Discord频道&#x…

ES 模糊查询 wildcard 的替代方案探索

一、Wildcard 概述 Wildcard 是一种支持通配符的模糊检索方式。在 Elasticsearch 中&#xff0c;它使用星号 * 代表零个或多个字符&#xff0c;问号 ? 代表单个字符。 其使用方式多样&#xff0c;例如可以通过 {"wildcard": {"field_name": "value&…

IP in IP 协议

IP in IP 是一种多重IP协议&#xff0c;即&#xff1a;客户机可以发送一个IP协议内部在嵌套一个IP协议到某个特定的主机上&#xff0c;在由具体的主机作为路由进行转发的协议。 例如&#xff1a; IP in IP帧协议结构为&#xff0c;第一层为发送到IP in IP 路由主机的报文&…

Vmware Workstation Pro 17.5.2最新版安装-免费使用

安装要求&#xff1a; Windows 10 或 11 操作系统&#xff08;64位&#xff09; 兼容的多核 64 位&#xff08;x86&#xff09;处理器&#xff08;1.3GHz 或更高&#xff09; 至少 4GB 内存&#xff08;建议越大越好&#xff09; 至少 1.2GB 可用磁盘空间 BIOS/UEFI 中开启…

Leetcode 3266. Final Array State After K Multiplication Operations II

Leetcode 3266. Final Array State After K Multiplication Operations II 1. 解题思路2. 代码实现 题目链接&#xff1a;3266. Final Array State After K Multiplication Operations II 1. 解题思路 这一题是题目3264. Final Array State After K Multiplication Operatio…

epoll+线程池模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 负载均衡技术 …