阅读更多

1顶
1踩

编程语言

原创新闻 使用Go语言来理解Tensorflow

2017-06-07 10:15 by 副主编 jihong10102006 评论(0) 有28237人浏览
引用
原文:Understanding Tensorflow using Go
作者:P. Galeone
翻译:雁惊寒

译者注:本文通过一个简单的Go绑定实例,让读者一步一步地学习到Tensorflow有关ID、作用域、类型等方面的知识。以下是译文。

Tensorflow并不是机器学习方面专用的库,而是一个使用图来表示计算的通用计算库。它的核心是用C++实现的,并且还有不同语言的绑定。Go语言绑定是一个非常有用的工具,它与Python绑定不同,用户不仅可以通过Go语言使用Tensorflow,还可以了解Tensorflow的底层实现。

绑定

Tensorflow的开发者正式发布了:
  • C++源代码:真正的Tensorflow核心,实现了具体的高级和低级操作。
  • Python绑定和Python库:这个绑定是由C++实现自动生成的,这样我们可以使用Python来调用C++函数。此外,这个库将调用融合到了绑定中,以便定义更高级别的API。
  • Java绑定。
  • Go绑定。
作为一个Go开发者而不是一个Java爱好者,我开始关注Go绑定,以便了解他们创建了什么样的任务。

Go绑定

地鼠与Tensorflow的徽标

首先要注意的是,Go API缺少对Variable的支持:该API旨在使用已经训练过的模型,而不是从头开始训练模型。安装Tensorflow for Go的时候已经明确说明了:
引用
TensorFlow提供了可用于Go程序的API。这些API特别适合于加载用Python创建并需要在Go程序中执行的模型。

如果我们对培训ML模型不感兴趣,万岁!相反,如果你对培训模型感兴趣,那就有一个建议:
引用
作为一个真正的Go开发者,保持简单!使用Python定义并训练模型;你可以随时使用Go来加载并使用训练过的模型!

简而言之,go绑定可用于导入和定义常量图;在这种情况下,常量指的是没有经过训练的过程,因此没有可训练的变量。

现在,开始用Go来深入学习Tensorflow吧:让我们来创建第一个应用程序。

在下文中,我假设读者已经准备好Go环境,并按照README中的说明编译并安装了Tensorflow绑定。

理解Tensorflow结构

让我们来重复一下什么是Tensorflow:
引用
TensorFlow™是一款使用数据流图进行数值计算的开源软件库。图中的节点表示数学运算,而图的边表示在节点之间传递的多维数据数组(张量)。

我们可以把Tensorflow视为一种描述性语言,这有点像SQL,你可以在其中描述你想要的内容,并让底层引擎(数据库)解析你的查询、检查句法和语义错误、将其转换为内部表示形式、进行优化并计算出结果:所有这一切都会给你正确的结果。

因此,当我们使用任何一个API时,我们真正做的是描述一个图:当我们把图放到Session中并显式地在Session中运行图时,图的计算就开始了。

知道了这一点之后,让我们试着来定义一个计算图并在一个Session中进行计算吧。API文档为我们提供了tensorflow(简写为 tf)和op包中所有方法的列表。

我们可以看到,这两个包包含了我们需要定义和计算图形的所有内容。

前者包含了构建一个基本的“空”结构(就像Graph本身)的功能,后者是包含由C++实现自动生成绑定的最重要的包。

然而,假设我们要计算A与x的矩阵乘法,其中

我假设读者已经熟悉了tensorflow图定义的基本思想,并且知道占位符是什么以及它们如何工作。下面的代码是对Tensorflow Python绑定的第一次尝试。我们来调用这个文件attempt1.go
package main

import (
    "fmt"
    tf "github.com/tensorflow/tensorflow/tensorflow/go"
    "github.com/tensorflow/tensorflow/tensorflow/go/op"
)

func main() {
    // 这里,我们打算要: 创建图

    // 我们要定义两个占位符用于在运行的时候传入
    // 第一个占位符 A 将是一个 [2, 2] 的整数张量
    // 第二个占位符 x 将是一个 [2, 1] 的整数张量

    // 然后,我们要计算 Y = Ax

    // 创建图的第一个节点: 一个空的节点,位于图的根
    root := op.NewScope()

    // 定义两个占位符
    A := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 2)))
    x := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 1)))

    // 定义接收A和x作为输入参数的操作节点
    product := op.MatMul(root, A, x)

    // 每次我们把一个`Scope`穿给操作符的时候,把操作放在作用域的下面。
    // 这样,我们有了一个空的作用域(由NewScope创建):空的作用域是图的根,因此可以用“/”来表示。

    // 现在,我们让tensorflow按照我们的定义来创建图。
    // 把作用域和OP结合起来,创建具体的图。

    graph, err := root.Finalize()
    if err != nil {
        // 这里没办法处理这个错误:
        // 如果我们错误的定义了图,我们必须手工修改这个定义。

        // 这就跟SQL查询一样:如果查询语句在语法上有问题,我们只能重新写
        panic(err.Error())
    }

    // 如果在这里,图在语法上是正确的。
    // 我们就可以把它放到一个Session里,并执行。

    var sess *tf.Session
    sess, err = tf.NewSession(graph, &tf.SessionOptions{})
    if err != nil {
        panic(err.Error())
    }

    // 要使用占位符,我们必须创建一个Tensors,这个Tensors包含要反馈到网络的数值
    var matrix, column *tf.Tensor

    // A = [ [1, 2], [-1, -2] ]
    if matrix, err = tf.NewTensor([2][2]int64{ {1, 2}, {-1, -2} }); err != nil {
        panic(err.Error())
    }
    // x = [ [10], [100] ]
    if column, err = tf.NewTensor([2][1]int64{ {10}, {100} }); err != nil {
        panic(err.Error())
    }

    var results []*tf.Tensor
    if results, err = sess.Run(map[tf.Output]*tf.Tensor{
        A: matrix,
        x: column,
    }, []tf.Output{product}, nil); err != nil {
        panic(err.Error())
    }
    for _, result := range results {
        fmt.Println(result.Value().([][]int64))
    }
}

代码注释的很详细,希望读者能阅读每一行注释。

现在,Tensorflow-Python用户期望该代码进行编译并正常工作。我们来看看它是否正确:
go run attempt1.go

这是他看到的结果:
panic: failed to add operation "Placeholder": Duplicate node name in graph: 'Placeholder'

等等,这里发生了什么? 显然,存在两个名称都为“Placeholder”的操作。

第一节课: 节点ID

每当我们调用一个方法来定义一个操作时,Python API都会生成不同的节点,无论是否已经被调用过。下面的代码返回3。
import tensorflow as tf
a = tf.placeholder(tf.int32, shape=())
b = tf.placeholder(tf.int32, shape=())
add = tf.add(a,b)
sess = tf.InteractiveSession()
print(sess.run(add, feed_dict={a: 1,b: 2}))

我们可以通过打印占位符的名称来验证此程序是否创建了两个不同的节点:print(a.name,b.name)生成Placeholder:0 Placeholder_1:0,因此,b占位符是Placeholder_1:0,而a占位符是Placeholder:0。

在Go中,相反,之前的程序会执行失败,因为A和x都命名为Placeholder。我们可以得出这样的结论:

Go API不会在每次调用函数来定义操作的时候自动生成新的名字:操作的名字是固定的,我们无法修改。

提问时间:
  • 关于Tensorflow架构,我们学到了哪些东西?图中的每个节点都必须具有唯一的名称。每个节点都用名称来标识。
  • 节点的名称与用名字来定义的操作相同吗?是的,但还有更好的答案,不完全是,节点的名称只是操作的一部分。
为了详细说明第二个答案,我们来解决节点名重复的问题。

第二节课: 作用域

正如我们刚刚看到的那样,每定义一个操作时,Python API都会自动创建一个新的名称。在底层,Python API调用类Scope的C++方法WithOpName。以下是方法的文档及其签名,保存在scope.h中:
/// Return a new scope. All ops created within the returned scope will have
/// names of the form <name>/<op_name>[_<suffix].
Scope WithOpName(const string& op_name) const;

我们注意到,这个用于命名节点的方法返回了一个Scope,因此,节点名实际上是一个Scope。Scope是从根 /(空的图)到op_name的完整路径

当我们尝试添加一个具有与/到op_name相同路径的节点时,WithOpName方法会添加一个后缀_<suffix>(其中<suffix>是一个计数器),因此它将成为同一范围内的重复的节点。

知道了这一点之后,为了解决重复节点名的问题,我们期望在Scope类型中找到WithOpName方法。可悲的是,这种方法并不存在。

相反,查看Scope类型的文档,我们可以看到唯一的一个方法:SubScope,它返回一个新的Scope。

文档里是这么说的:
引用
SubScope返回一个新的Scope,这将导致添加到图中的所有操作都将以“namespace”为命名空间。如果命名空间与作用域内现有的命名空间冲突,则会添加一个后缀。

使用后缀的冲突管理与C++的WithOpName不同:WithOpName是在操作名之后添加后缀,但还是在同一作用域内(因此占位符变为了Placeholder_1),而Go的SubScope是在作用域名称后添加后缀。

这种差异会产生完全不同的图,但它们在计算上是等效的。

我们来改变占位符的定义,以此来定义两个不同的节点,此外,我们来打印一下作用域的名称。

让我们创建文件attempt2.go,把这几行从:
A := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 2)))
x := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 1)))

改成:
// define 2 subscopes of the root subscopes, called "input". In this
// way we expect to have a input/ and a input_1/ scope under the root scope
A := op.Placeholder(root.SubScope("input"), tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 2)))
x := op.Placeholder(root.SubScope("input"), tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 1)))
fmt.Println(A.Op.Name(), x.Op.Name())

编译并运行:go run attempt2.go,输出结果:
input/Placeholder input_1/Placeholder

提问时间:

关于Tensorflow的架构,我们学到了什么?节点完全是由被定义的作用域来标识的。作用域是我们从图的根到达节点的路径。有两种定义节点的方法:在不同的作用域(Go语言)中定义操作或更改操作名称。

我们解决了重复节点名称的问题,但另一个问题显示在我们的终端上。
panic: failed to add operation "MatMul": Value for attr 'T' of int64 is not in the list of allowed values: half, float, double, int32, complex64, complex128

为什么MatMul节点会出现错误?我们只是想增加两个tf.int64矩阵!从这段错误提示来看,int64是MatMul唯一不接受的类型。
引用
int64类型的attr ‘T’的值不在允许的值列表中:half,float,double,int32,complex64,complex128

这个列表是什么?为什么我们可以做两个int32类型矩阵的乘法,而不是int64?

我们来解决这个问题,了解为什么会出现这种情况。

第三节课:Tensorflow的类型系统

我们来看一下源代码,寻找MatMul操作的C++声明:
REGISTER_OP("MatMul")
    .Input("a: T")
    .Input("b: T")
    .Output("product: T")
    .Attr("transpose_a: bool = false")
    .Attr("transpose_b: bool = false")
    .Attr("T: {half, float, double, int32, complex64, complex128}")
    .SetShapeFn(shape_inference::MatMulShape)
    .Doc(R"doc(
Multiply the matrix "a" by the matrix "b".
The inputs must be two-dimensional matrices and the inner dimension of
"a" (after being transposed if transpose_a is true) must match the
outer dimension of "b" (after being transposed if transposed_b is
true).
*Note*: The default kernel implementation for MatMul on GPUs uses
cublas.
transpose_a: If true, "a" is transposed before multiplication.
transpose_b: If true, "b" is transposed before multiplication.
)doc");

该行定义了MatMul操作的接口:特别注意到代码里使用了REGISTER_OP宏来声明了op的:
  • 名称:MatMul
  • 参数:a,b
  • 属性(可选参数):transpose_a,transpose_b
  • 模板T支持的类型:half,float,double,int32,complex64,complex128
  • 输出形状:自动推断
  • 说明文档
这个宏调用不包含任何C++代码,但它告诉我们,在定义一个操作时,尽管它使用了模板,但是我们必须为指定的类型T(或属性)指定一个类型列表中的类型。实际上,属性.Attr("T: {half, float, double, int32, complex64, complex128}")是将类型T约束为该列表的一个值。

我们可以从教程中阅读到,即使在使用模板T时,我们也必须对每个支持的重载显式地注册内核。内核是以CUDA方式对C/C++函数进行的引用,这些函数将会并行执行。

因此,MatMul的作者决定仅支持上面列出的类型,而不支持int64。有两个可能的原因:
  • 疏忽了:这很有可能,因为Tensorflow的作者是人类!
  • 对尚未完全支持int64操作的设备兼容,因此内核的这种具体实现不足以在每个支持的硬件上运行。
回到刚才的错误提示:修改方法是显而易见的。我们必须将参数以支持的类型传递给MatMul。

我们来创建attempt3.go,把所有引用int64的行改为int32。

有一点需要注意:Go绑定有自己的一组类型,与Go的类型的一一对应。当我们将值输入到图中时,我们必须关注映射关系。从图形中获取值时,必须做同样的事情。

执行go run attempt3.go。结果:
input/Placeholder input_1/Placeholder
[[210] [-210]]

万岁!

提问时间

关于Tensorflow的架构,我们学到了什么?每个操作都与自己的一组内核相关联。被视为描述性语言的Tensorflow是一种强大的类型语言。它不仅要遵守C++类型规则,而且还要在op的注册阶段只实现某些指定类型的能力。

结论

使用Go来定义并执行一个图,使我们有机会更好地了解Tensorflow的底层结构。使用试错法,我们解决了这个简单的问题,我们一步一步地学到了有关图、节点和类型系统这些新东西。
  • 大小: 34.1 KB
  • 大小: 4 KB
1
1
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 用 Go 语言理解 Tensorflow

    Tensorflow 并不是一个严格意义上的机器学习库,它是一个使用图来表示计算的通用计算库。它的核心功能由 C++ 实现,通过封装,能在各种不同的语言下运行。它的 Golang 版和 Python 版不同,Golang 版 Tens...

  • 基于Go语言来理解Tensorflow

    与Python绑定不同的是,Go编程语言绑定不仅允许用户在Go环境当中使用TensorFlow,同时亦可帮助大家深入了解TensorFlow的内部运作原理。\\什么是绑定?\\从官方说明的角度来看,TensorFlow的开发者们公布了:\\C++...

  • Go 语言——Tensorflow

    之前的博客中,翻译过 Go 语言可以通过 Tensorflow 的 go 客户端进行操作,但是其中有两个问题很容易在编码时遇到下面的问题。 Scope:每次调用定义操作的函数时,Go API 并不会自动生成新的节点名称。会出现 ...

  • 【java毕业设计】智慧社区在线教育平台(源代码+论文+PPT模板).zip

    zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。

  • 基于深度强化学习的德州扑克AI算法优化详细文档+全部资料+源码.zip

    【资源说明】 基于深度强化学习的德州扑克AI算法优化详细文档+全部资料+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

  • 无需安装MobaXterm简约版本,远程工具

    无需安装MobaXterm简约版本,远程工具

  • 蓝桥杯Python组的初级到中级选手练习python案例

    这个脚本包含了常见的算法和编程概念,适合蓝桥杯Python组的初级到中级选手练习。通过这些练习,选手可以: 理解递归算法(如斐波那契数列和汉诺塔问题)。 熟悉基本数据结构和操作(如字符串操作、列表处理)。 学习基本的数学算法(如求最大公约数)。 掌握Python语言的基本语法和内置函数的使用。 比赛中可能会遇到更复杂的问题,但这些练习能帮助你打下坚实的基础。在准备蓝桥杯时,还应该关注算法题库,学习更多数据结构(如栈、队列、树、图等)、算法(如动态规划、贪心算法、搜索算法等),并进行大量的代码实践和调试。

  • 【java毕业设计】智慧社区远程办公平台(源代码+论文+PPT模板).zip

    zip里包含源码+论文+PPT,有java环境就可以运行起来 ,功能说明: 文档开篇阐述了随着计算机技术、通信技术和网络技术的快速发展,智慧社区门户网站的建设成为了可能,并被视为21世纪信息产业的主要发展方向之一 强调了网络信息管理技术、数字化处理技术和数字式信息资源建设在国际竞争中的重要性。 指出了智慧社区门户网站系统的编程语言为Java,数据库为MYSQL,并实现了新闻资讯、社区共享、在线影院等功能。 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。

  • 【飞行器】基于matlab线性控制器和广泛可视化四轴飞行器控制系统仿真【含Matlab源码 9910期】.zip

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

  • JSP企业人事管理系统(源代码+论文)(2024y1).7z

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于计算机科学与技术等相关专业,更为适合;

  • 【java毕业设计】智慧社区无障碍设施门户.zip

    有java环境就可以运行起来 ,zip里包含源码+论文+PPT, 系统设计与功能: 文档详细描述了系统的后台管理功能,包括系统管理模块、新闻资讯管理模块、公告管理模块、社区影院管理模块、会员上传下载管理模块以及留言管理模块。 系统管理模块:允许管理员重新设置密码,记录登录日志,确保系统安全。 新闻资讯管理模块:实现新闻资讯的添加、删除、修改,确保主页新闻部分始终显示最新的文章。 公告管理模块:类似于新闻资讯管理,但专注于主页公告的后台管理。 社区影院管理模块:管理所有视频的添加、删除、修改,包括影片名、导演、主演、片长等信息。 会员上传下载管理模块:审核与删除会员上传的文件。 留言管理模块:回复与删除所有留言,确保系统内的留言得到及时处理。 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea Maven包:Maven3.3及以上

  • 2024级涉外护理7班马天爱劳动实践总结1.docx

    2024级涉外护理7班马天爱劳动实践总结1.docx

  • JSP网上教学资源共享系统(源代码+论文)(2024r7).7z

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于计算机科学与技术等相关专业,更为适合;

  • LookupError.md

    LookupError

  • 四川采矿场领导带班下井管理制度.docx

    四川采矿场领导带班下井管理制度

  • Matlab实现MTF-CNN-Mutilhead-Attention基于马尔可夫转移场-卷积神经网络融合多头注意力多特征数据分类预测(含完整的程序,GUI设计和代码详解)

    内容概要:本文介绍了基于马尔可夫转移场(MTF)、卷积神经网络(CNN)和多头注意力机制的多特征数据分类预测模型。项目旨在处理多维度、多变量、复杂时序数据,通过融合这三种技术,有效提取时序数据中的非线性模式和长期依赖性,提升分类预测的准确性。文章详细描述了模型的构建、训练、评估和实际应用,包括数据预处理、模型架构设计、代码实现、系统架构设计、模型优化和部署等内容。 适合人群:具备一定编程基础,对深度学习和时间序列分析感兴趣的科研人员和工程师。 使用场景及目标:适用于金融预测、气象分析、智能制造、医疗健康等多个领域的多特征数据分类和预测任务。具体目标包括:提升多特征数据分类的精度,增强多维度数据处理能力,解决传统方法在大规模数据处理中的瓶颈,提供应用和实践价值。 其他说明:项目提供了一个完整的开发流程和详细的代码实现,适合用于学习和实际应用。

  • 基于wepy 商城(微店)微信小程序 全部资料+详细文档+源码+高分项目.zip

    【资源说明】 基于wepy 商城(微店)微信小程序 全部资料+详细文档+源码+高分项目.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

  • 深圳建设工程公司档案管理规定.docx

    深圳建设工程公司档案管理规定

  • 基于CNN算法的验证码识别系统资料齐全+高分项目+文档+源码.zip

    【资源说明】 基于CNN算法的验证码识别系统资料齐全+高分项目+文档+源码.zip 【备注】 1、该项目是个人高分项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

Global site tag (gtag.js) - Google Analytics