Go语言实现深度学习的正向传播和反向传播

文章目录

  • 开发前言
  • 开发理论
  • 图解理论
  • 数据类型
  • 数学函数
  • 数据节点统一抽象
  • 变量数据节点
  • 常量数据节点
  • 单目运算封装
  • 双目运算封装
  • 算子节点统一抽象
  • 基础算子
  • 加法算子
  • 减法算子
  • 乘法算子
  • 除法算子
  • 指数算子
  • 对数算子
  • 正切算子
  • 正弦算子
  • 余弦算子
  • 数据流图
  • 正向传播
  • 反向传播
  • 运行示例
  • 开发总结

开发前言

正向传播是指从神经网络的输入层开始,通过逐层计算和传递,将输入数据一直传递到输出层。在每一层中,通过对输入数据进行加权求和并应用激活函数,得到该层的输出。这个过程可以看作是将输入数据在网络中前进(向前传播),直至得到模型的预测结果。

反向传播是指根据模型的预测结果和实际标签之间的差异,从输出层向输入层反向计算梯度,并利用梯度来更新网络参数。

这篇博客我将使用Go语言实现正向传播和反向传播,帮助你理解其底层的运转规律

项目代码使用纯粹的Go语言标准库实现,不借用任何其它第三方库。用轮子是生活,造轮子是信仰。

我是醉墨居士,我们现在开始吧🤗

开发理论

一个数学函数,由一系列数据和一系列运算方式构成,我们将数据对应为数据节点,将运算方式对应为算子节点,这样我们就可以将数学函数转化为由一系列数据节点和一系列算子节点组成的数据流图

正向传递数据流图,不断运算数据,就是正向传播的过程
反向传递数据流图,不断累加梯度,就是反向传播的过程

图解理论

我画了两张图来表示函数3 * pow(x, 2) + 2 * x + 1的正向传播和反向传播的过程

正向传播图解
forward

反向传播图解
backward

数据类型

data/type

package data

type Type interface {
    ~int | ~int32 | ~int64 |
    ~uint | ~uint32 | ~uint64 |
    ~float32 | ~float64
}

数学函数

math/math.go

package math

import (
	"dl/node"
	stmath "math"
)

func Pow[T node.Type](a, b T) T {
	return T(stmath.Pow(float64(a), float64(b)))
}

func Ln[T node.Type](a T) T {
	return T(stmath.Log(float64(a)))
}

func Tan[T node.Type](a T) T {
	return T(stmath.Tan(float64(a)))
}

func Sin[T node.Type](a T) T {
	return T(stmath.Sin(float64(a)))
}

func Cos[T node.Type](a T) T {
	return T(stmath.Cos(float64(a)))
}

数据节点统一抽象

fm/datanode.go

type DataNode[T data.Type] interface {
	Data()T
	SetData(T)
	Grad()T
	setGrad(T)
	preNode() CalNode[T]
	backNodes() *[]CalNode[T]
	fm()FlowMap[T]
	Add(DataNode[T]) DataNode[T]
	Sub(DataNode[T]) DataNode[T]
	Mul(DataNode[T]) DataNode[T]
	Div(DataNode[T]) DataNode[T]
	Pow(DataNode[T]) DataNode[T]
	Ln() DataNode[T]
	Tan() DataNode[T]
	Sin() DataNode[T]
	Cos() DataNode[T]
}

变量数据节点

package fm

import "dl/data"

type varDataNode[T data.Type] struct {
	data T
	grad T
	prenode CalNode[T]
	backnodes []CalNode[T]
	flowmap FlowMap[T]
}

func (n *varDataNode[T]) Data() T {
	return n.data
}

func (n *varDataNode[T]) SetData(i T) {
	n.data = i
}

func (n *varDataNode[T]) Grad() T {
	return n.grad
}

func (n *varDataNode[T]) setGrad(i T) {
	n.grad = i
}

func (n *varDataNode[T]) preNode() CalNode[T] {
	return n.prenode
}

func (n *varDataNode[T]) backNodes() *[]CalNode[T] {
	return &n.backnodes
}

func (n *varDataNode[T]) fm() FlowMap[T] {
	return n.flowmap
}

func (n *varDataNode[T]) Add(node DataNode[T]) DataNode[T] {
	return calTwo(newAdd[T](), n, node)
}

func (n *varDataNode[T]) Sub(node DataNode[T]) DataNode[T] {
	return calTwo(newSub[T](), n, node)
}

func (n *varDataNode[T]) Mul(node DataNode[T]) DataNode[T] {
	return calTwo(newMul[T](), n, node)
}

func (n *varDataNode[T]) Div(node DataNode[T]) DataNode[T] {
	return calTwo(newDiv[T](), n, node)
}

func (n *varDataNode[T]) Pow(node DataNode[T]) DataNode[T] {
	return calTwo(newPow[T](), n, node)
}

func (n *varDataNode[T]) Ln() DataNode[T] {
	return calOne(newLn[T](), n)
}

func (n *varDataNode[T]) Tan() DataNode[T] {
	return calOne(newTan[T](), n)
}

func (n *varDataNode[T]) Sin() DataNode[T] {
	return calOne(newSin[T](), n)
}

func (n *varDataNode[T]) Cos() DataNode[T] {
	return calOne(newCos[T](), n)
}

常量数据节点

type constDataNode[T data.Type] struct {
	data      T
	prenode   CalNode[T]
	backnodes []CalNode[T]
	flowmap   FlowMap[T]
}

func (n *constDataNode[T]) Data() T {
	return n.data
}

func (n *constDataNode[T]) SetData(i T) {
	n.data = i
}
func (n *constDataNode[T]) Grad() T {
	return 0
}

func (n *constDataNode[T]) setGrad(T) {}

func (n *constDataNode[T]) preNode() CalNode[T] {
	return n.prenode
}

func (n *constDataNode[T]) backNodes() *[]CalNode[T] {
	return &n.backnodes
}

func (n *constDataNode[T]) fm() FlowMap[T] {
	return n.flowmap
}

func (n *constDataNode[T]) Add(node DataNode[T]) DataNode[T] {
	return calTwo(newAdd[T](), n, node)
}

func (n *constDataNode[T]) Sub(node DataNode[T]) DataNode[T] {
	return calTwo(newSub[T](), n, node)
}

func (n *constDataNode[T]) Mul(node DataNode[T]) DataNode[T] {
	return calTwo(newMul[T](), n, node)
}

func (n *constDataNode[T]) Div(node DataNode[T]) DataNode[T] {
	return calTwo(newDiv[T](), n, node)
}

func (n *constDataNode[T]) Pow(node DataNode[T]) DataNode[T] {
	return calTwo(newPow[T](), n, node)
}

func (n *constDataNode[T]) Ln() DataNode[T] {
	return calOne(newLn[T](), n)
}

func (n *constDataNode[T]) Tan() DataNode[T] {
	return calOne(newTan[T](), n)
}

func (n *constDataNode[T]) Sin() DataNode[T] {
	return calOne(newSin[T](), n)
}

func (n *constDataNode[T]) Cos() DataNode[T] {
	return calOne(newCos[T](), n)
}

单目运算封装

func calOne[T data.Type](operation CalNode[T], a DataNode[T]) DataNode[T] {
	*a.fm().calnodes = append(*a.fm().calnodes, operation)
	*a.backNodes() = append(*a.backNodes(), operation)
	res := &varDataNode[T]{
		prenode: operation,
		flowmap: a.fm(),
	}
	*a.fm().datanodes = append(*a.fm().datanodes, res)
	operation.CalNode().PreNodes = []DataNode[T]{a}
	operation.CalNode().BackNode = res
	return res
}

双目运算封装

func calTwo[T data.Type] (operation CalNode[T], a, b DataNode[T]) DataNode[T] {
	if a.fm() != b.fm() {
		return nil
	}
	*a.fm().calnodes = append(*a.fm().calnodes, operation)
	*a.backNodes() = append(*a.backNodes(), operation)
	*b.backNodes() = append(*b.backNodes(), operation)
	res := &varDataNode[T]{
		prenode: operation,
		flowmap: a.fm(),
	}
	*a.fm().datanodes = append(*a.fm().datanodes, res)
	operation.CalNode().PreNodes = []DataNode[T]{a, b}
	operation.CalNode().BackNode = res
	return res
}

算子节点统一抽象

fm/calnode.go

type CalNode[T data.Type] interface {
	CalNode() *BaseCalNode[T]
	Forward()
	Backward()
}

基础算子

type BaseCalNode[T data.Type] struct {
	PreNodes []DataNode[T]
	BackNode DataNode[T]
}

加法算子

type AddNode[T data.Type] BaseCalNode[T]

func newAdd[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*AddNode[T])(basenode)
}

func (n *AddNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *AddNode[T]) Forward() {
	n.BackNode.SetData(n.CalNode().PreNodes[0].Data() + n.CalNode().PreNodes[1].Data())
}

func (n *AddNode[T]) Backward() {
	// selfgrad + backgrad
	grad0 := n.PreNodes[0].Grad() + n.BackNode.Grad()
	// selfgrad + backgrad
	grad1 := n.PreNodes[1].Grad() + n.BackNode.Grad()

	n.PreNodes[0].setGrad(grad0)
	n.PreNodes[1].setGrad(grad1)
}

减法算子

type SubNode[T data.Type] BaseCalNode[T]

func newSub[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*SubNode[T])(basenode)
}

func (n *SubNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *SubNode[T]) Forward() {
	n.BackNode.SetData(n.CalNode().PreNodes[0].Data() - n.CalNode().PreNodes[1].Data())
}

func (n *SubNode[T]) Backward() {
	// selfgrad + backgrad
	grad0 := n.PreNodes[0].Grad() + n.BackNode.Grad()
	// selfgrad - backgrad
	grad1 := n.PreNodes[1].Grad() - n.BackNode.Grad()

	n.PreNodes[0].setGrad(grad0)
	n.PreNodes[1].setGrad(grad1)
}

乘法算子

type MulNode[T data.Type] BaseCalNode[T]

func newMul[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*MulNode[T])(basenode)
}

func (n *MulNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *MulNode[T]) Forward() {
	n.BackNode.SetData(n.CalNode().PreNodes[0].Data() * n.CalNode().PreNodes[1].Data())
}

func (n *MulNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	b := n.PreNodes[1].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad + (backgrad * b)
	grad0 := n.PreNodes[0].Grad() + (backgrad * b)
	// selfgrad + (backgrad * a)
	grad1 := n.PreNodes[1].Grad() + (backgrad * a)

	n.PreNodes[0].setGrad(grad0)
	n.PreNodes[1].setGrad(grad1)
}

除法算子

type DivNode[T data.Type] BaseCalNode[T]

func newDiv[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*DivNode[T])(basenode)
}

func (n *DivNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *DivNode[T]) Forward() {
	n.BackNode.SetData(n.CalNode().PreNodes[0].Data() / n.CalNode().PreNodes[1].Data())
}

func (n *DivNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	b := n.PreNodes[1].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad + (backgrad / b)
	grad0 := n.PreNodes[0].Grad() + (backgrad / b)
	// selfgrad - (backgrad * a / pow(b, 2))
	grad1 := n.PreNodes[1].Grad() - (backgrad * a / math.Pow(b, 2))

	n.PreNodes[0].setGrad(grad0)
	n.PreNodes[1].setGrad(grad1)
}

指数算子

type PowNode[T data.Type] BaseCalNode[T]

func newPow[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*PowNode[T])(basenode)
}

func (n *PowNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *PowNode[T]) Forward() {
	n.BackNode.SetData(math.Pow(n.CalNode().PreNodes[0].Data(), n.CalNode().PreNodes[1].Data()))
}

func (n *PowNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	b := n.PreNodes[1].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad + (backgrad * b * pow(a, b-1))
	grad0 := n.PreNodes[0].Grad() + (backgrad * b * math.Pow(a, b-1))
	// selfgrad + (backgrad * pow(a, b) * ln(a))
	grad1 := n.PreNodes[1].Grad() + (backgrad * math.Pow(a, b) * math.Ln(a))

	n.PreNodes[0].setGrad(grad0)
	n.PreNodes[1].setGrad(grad1)
}

对数算子

type LnNode[T data.Type] BaseCalNode[T]

func newLn[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*LnNode[T])(basenode)
}

func (n *LnNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *LnNode[T]) Forward() {
	n.BackNode.SetData(math.Ln(n.CalNode().PreNodes[0].Data()))
}

func (n *LnNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad + (backgrad / a)
	grad0 := n.PreNodes[0].Grad() + (backgrad / a)

	n.PreNodes[0].setGrad(grad0)
}

正切算子

type TanNode[T data.Type] BaseCalNode[T]

func newTan[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*TanNode[T])(basenode)
}

func (n *TanNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *TanNode[T]) Forward() {
	n.BackNode.SetData(math.Tan(n.CalNode().PreNodes[0].Data()))
}

func (n *TanNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad + (backgrad / pow(cos(a), 2))
	grad0 := n.PreNodes[0].Grad() + (backgrad / math.Pow(math.Cos(a), 2))

	n.PreNodes[0].setGrad(grad0)
}

正弦算子

type SinNode[T data.Type] BaseCalNode[T]

func newSin[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*SinNode[T])(basenode)
}

func (n *SinNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *SinNode[T]) Forward() {
	n.BackNode.SetData(math.Sin(n.CalNode().PreNodes[0].Data()))
}

func (n *SinNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad + (backgrad * cos(a))
	grad0 := n.PreNodes[0].Grad() + (backgrad * math.Cos(a))

	n.PreNodes[0].setGrad(grad0)
}

余弦算子

type CosNode[T data.Type] BaseCalNode[T]

func newCos[T data.Type]() CalNode[T] {
	basenode := &BaseCalNode[T]{}
	return (*CosNode[T])(basenode)
}

func (n *CosNode[T]) CalNode() *BaseCalNode[T] {
	return (*BaseCalNode[T])(n)
}

func (n *CosNode[T]) Forward() {
	n.BackNode.SetData(math.Cos(n.CalNode().PreNodes[0].Data()))
}

func (n *CosNode[T]) Backward() {
	a := n.PreNodes[0].Data()
	backgrad := n.BackNode.Grad()

	// selfgrad - (backgrad * sin(a))
	grad0 := n.PreNodes[0].Grad() - (backgrad * math.Sin(a))

	n.PreNodes[0].setGrad(grad0)
}

数据流图

type FlowMap[T data.Type] struct {
	calnodes  *[]CalNode[T]
	datanodes *[]DataNode[T]
}

func NewFlowMap[T data.Type]() *FlowMap[T] {
	calnodes := make([]CalNode[T], 0)
	datanods := make([]DataNode[T], 0)

	return &FlowMap[T]{
		calnodes:  &calnodes,
		datanodes: &datanods,
	}
}

func (m FlowMap[T]) NewData() DataNode[T] {
	node := &varDataNode[T]{
		backnodes: make([]CalNode[T], 0),
		flowmap:   m,
	}
	*m.datanodes = append(*m.datanodes, node)
	return node
}

func (m FlowMap[T]) NewConstData(i T) DataNode[T] {
	return &constDataNode[T]{
		backnodes: make([]CalNode[T], 0),
		data:      i,
		flowmap:   m,
	}
}

正向传播

func (m FlowMap[T]) Forward() {
	n := len(*m.calnodes)
	for i := 0; i < n; i++ {
		(*m.calnodes)[i].Forward()
	}
}

反向传播

func (m FlowMap[T]) Backward() {
	for i := len(*m.datanodes) - 1; i >= 0; i-- {
		(*m.datanodes)[i].setGrad(0)
	}

	n := len(*m.calnodes)-1
	(*m.calnodes)[n].CalNode().BackNode.setGrad(1)

	for i := n; i >= 0; i-- {
		(*m.calnodes)[i].Backward()
	}
}

运行示例

我们的运行示例使用理论图解的那个例子: 3 * pow(x, 2) + 2 * x + 1

// 3 * pow(x, 2) + 2 * x + 1
func main() {
	m := fm.NewFlowMap[float64]()
	x := m.NewData()
	three := m.NewConstData(3)
	two := m.NewConstData(2)
	one := m.NewConstData(1)
	res := three.Mul(x.Pow(two)).Add(two.Mul(x)).Add(one)

	x.SetData(2)
	m.Forward()
	m.Backward()
	// data = 3 * pow(2, 2) + 2 * 2 + 1 = 17
	// grad = 2 + 6 * x = 14
	fmt.Println("x=2 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad())

	x.SetData(3)
	m.Forward()
	m.Backward()
	// data = 3 * pow(3, 2) + 2 * 3 + 1 = 34
	// grad = 2 + 6 * x = 20
	fmt.Println("x=3 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad())

	x.SetData(4)
	m.Forward()
	m.Backward()
	// data = 3 * pow(4, 2) + 2 * 4 + 1 = 57
	// grad = 2 + 6 * x = 26
	fmt.Println("x=4 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad())
}

运行结果
result

开发总结

恭喜你,我们一起使用Go语言完成了深度学习的正向传播和反向传播,希望这个项目能让你有所收获😊

我的特色就是用最简单的方式帮助你学会最硬核的知识,一起加油吧❤️

我是醉墨居士,之前这个账号改了好几次名称,从此之后这个账号的名称大概率不会再变动😜

如果有什么错误,请你评论区或者私信我指出,让我们一起进步✌️

请你多多关注我,开发这个项目,并且整理总结,花费了很多的精力,博客热度越高,更新速度越快😎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

甄知黄建华:从“天赋平平”到IT行业“六边形战士”,探索出企业数智化转型的“强IT”之路

本期我们先抛开人物和主体不表&#xff0c;从大环境开始谈起。随着科技的快速发展和全球商业环境的不断变化&#xff0c;中国企业对灵活性、创新性、全球化和效率的需求是迫切的&#xff0c;进行数字化转型来支撑企业的业务变革、组织优化已是业界共识。如何根据企业的实际情况…

Hdoop学习笔记(HDP)-Part.17 安装Spark2

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

接口测试 —— Requests库介绍

1、Requests库 Requests库是用Python语言编写&#xff0c;基于urllib3模块&#xff0c;采用Apache2 Licensed开源协议的 HTTP 库。 虽然Python的标准库中urllib3模块已经包含了平常我们使用的大多数功能&#xff0c;但是它的 API使用起来让人感觉不太友好。而Requests库使用的…

揭秘原型链:探索 JavaScript 面向对象编程的核心(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

type-c充电器输出电压5V9V12V15V20V PD协议诱骗快充应用方案

Type-C接口的PD充电器&#xff08;如iPhone的20W充电器&#xff09;默认是没有电压输出的&#xff0c;想要让Type-C的充电器输出5V、9V、12V、15V、20V&#xff0c;只需要在产品上使用一颗快充取电芯片XSP08即可。 工作原理&#xff1a; 各类小家电产品如平板电脑、智能穿戴产…

申请Azure学生订阅——人工验证

一&#xff1a;联系客服进行人工验证 点击 Services Hub 填写资料申请人工验证 点击 Azure - Sign up 进行学生验证 二&#xff1a;与客服的邮件沟通的记录 ​​​​一、结果&#xff08;输入客服给的验证码后&#xff0c;笔者便得到了学生订阅&#xff09;&#xff1a; 二…

TypeScript编程语言学习,为学习HarmonyOS开发做准备

1. 编程语言 ArkTS是HarmonyOS优选的应用开发语言&#xff0c;它在TypeScript&#xff08;TS&#xff09;的基础上&#xff0c;匹配ArkUI扩展&#xff0c;扩展了声明式UI、状态管理等相应的能力。 JavaScript&#xff08;JS&#xff09;&#xff0c;使用在Web应用开发&#xf…

linux之buildroot(3)配置软件包

Linux之buildroot(3)配置软件包 Author&#xff1a;Onceday Date&#xff1a;2023年11月30日 漫漫长路&#xff0c;才刚刚开始… 全系列文章请查看专栏: buildroot编译框架_Once_day的博客-CSDN博客。 参考文档&#xff1a; Buildroot - Making Embedded Linux Easymdev.t…

C++——初始化列表

初始化列表&#xff1a;一一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员列表&#xff0c;每个“成员变量”后面跟一个放在括号中的初始值或表达式。 #include <iostream> using namespace std; class Date { public:Date(int year, int month, int day): _ye…

交换综合实验

目录 一、实验拓扑 二、实验要求 三、实验步骤 1、链路聚合&#xff08;配置Eth-trunk&#xff09; 2、配置vlan&#xff08;创建划分vlan&#xff0c;配置trunk干道&#xff09; 3、MSTP配置 4、VRRP配置 5、DHCP配置 6、vlan互通 7、NAT配置&#xff08;做ACL&#…

逻辑回归与正则化 逻辑回归、激活函数及其代价函数

逻辑回归、激活函数及其代价函数 线性回归的可行性 对分类算法&#xff0c;其输出结果y只有两种结果{0&#xff0c;1}&#xff0c;分别表示负类和正类&#xff0c;代表没有目标和有目标。 在这种情况下&#xff0c;如果用传统的方法以线性拟合 &#xff08; h θ ( x ) θ T…

scrapyd及gerapy的使用及docker-compse部署

一、scrapyd的介绍 scrapyd是一个用于部署和运行scrapy爬虫的程序&#xff0c;它允许你通过JSON API(也即是web api)来部署爬虫项目和控制爬虫运行&#xff0c;scrapyd是一个守护进程&#xff0c;监听爬虫的运行和请求&#xff0c;然后启动进程来执行它们 scrapyd的安装 scr…

opencv知识库:基于cv2.flip()函数对图像进行随机翻转(水平/垂直)

需求场景 欲对RGB格式的lena图像进行随机翻转&#xff0c;要求这些图像不翻转、水平翻转、垂直翻转的概率都为1/3。 功能代码 import cv2 import random# 读取并展示图像 img cv2.imread("lena.jpg") cv2.imshow(lena, img) cv2.waitKey(0)for i in range(6): #…

matlab操作方法(一)——向量及其操作

1.向量及其操作 matlab是英文Matrix Laboratory&#xff08;矩阵实验室&#xff09;的简称&#xff0c;是基于矩阵运算的操作环境。matlab中的所有数据都是以矩阵或多维数组的形式存储的。向量和标量是矩阵的两种特殊形式 向量是指单行或者单列的矩阵&#xff0c;它是构成矩阵…

QT 中 QDateTime::currentDateTime() 输出格式备查

基础 QDateTime::currentDateTime() //当前的日期和时间。 QDateTime::toString() //以特定的格式输出时间&#xff0c;格式 yyyy: 年份&#xff08;4位数&#xff09; MM: 月份&#xff08;两位数&#xff0c;07表示七月&#xff09; dd: 日期&#xff08;两位数&#xff0c…

代码浅析DLIO(四)---位姿更新

0. 简介 我们刚刚了解过DLIO的整个流程&#xff0c;我们发现相比于Point-LIO而言&#xff0c;这个方法更适合我们去学习理解&#xff0c;同时官方给出的结果来看DLIO的结果明显好于现在的主流方法&#xff0c;当然指的一提的是&#xff0c;这个DLIO是必须需要六轴IMU的&#x…

Redis--10--Pipeline

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Pipeline举例比较普通模式与 PipeLine 模式小结&#xff1a; Pipeline 前面我们已经说过&#xff0c;Redis客户端执行一条命令分为如下4个部分:1&#xff09;发送命…

基于Java SSM框架+Vue实现汉服文化平台网站项目【项目源码+论文说明】

基于java的SSM框架Vue实现汉服文化平台系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个汉服文化平台网站 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将…

【论文阅读】ICRA: An Intelligent Clustering Routing Approach for UAV Ad Hoc Networks

文章目录 论文基本信息摘要1.引言2.相关工作3.PROPOSED SCHEME4.实验和讨论5.总结补充 论文基本信息 《ICRA: An Intelligent Clustering Routing Approach for UAV Ad Hoc Networks》 《ICRA:无人机自组织网络的智能聚类路由方法》 Published in: IEEE Transactions on Inte…

Selenium自动化测试 —— 模拟鼠标键盘的操作事件

鼠标操作事件 在实际的web产品测试中&#xff0c;对于鼠标的操作&#xff0c;不单单只有click()&#xff0c;有时候还要用到右击、双击、拖动等操作&#xff0c;这些操作包含在ActionChains类中。 ActionChains类中鼠标操作常用方法&#xff1a; 首先导入ActionChains类&#…