AlgoC++第九课:手写AutoGrad

news2024/11/27 8:41:50

目录

  • 手写AutoGrad
    • 前言
    • 1. 基本介绍
      • 1.1 计算图
      • 1.2 智能指针的引出
    • 2. 示例代码
      • 2.1 Scale
      • 2.2 Multiply
      • 2.3 Pow
    • 总结

手写AutoGrad

前言

手写AI推出的全新面向AI算法的C++课程 Algo C++,链接。记录下个人学习笔记,仅供自己参考。

本次课程主要是手写 AutoGrad 代码

课程大纲可看下面的思维导图

在这里插入图片描述

1. 基本介绍

1.1 计算图

自动微分中的计算图是一种数据结构,用于记录计算过程中变量之间的依赖关系,从而实现自动求导。计算图是由节点和边组成的有向无环图。每个节点代表一个变量或者操作,边代表变量之间的依赖关系。(from chatGPT)

计算图中的节点分为两类:变量节点和操作节点。变量节点代表输入的变量,操作节点代表计算过程中的操作。对于一个变量节点,我们需要记录该变量的取值以及其对应的导数值,对于一个操作节点,我们需要记录该操作的具体实现以及其所依赖的变量节点。下图是一个简单的计算图示例:

在这里插入图片描述

计算图中的边代表变量之间的依赖关系。对于一个变量节点,如果它是某个操作节点的输入,那么它与该操作节点之间就有一条边。对于一个操作节点,它的输入通常是多个变量节点,输出则是一个新的变量节点,因此它与多个变量节点之间都有边。

在前向计算中,我们从输入变量开始,按照计算图中的边进行计算,最终得到输出变量的值。在反向传播中,我们从输出变量开始,按照计算图中的边进行反向传播,计算每个变量对应的导数值。这里需要用到链式法则,将每个操作的导数值乘到其输入变量的导数值上。

在实现计算图时,我们通常采用面向对象的方法,将每个变量和操作都封装成一个类。对于变量节点,我们需要记录其取值和导数值;对于操作节点,我们需要记录其具体实现以及所依赖的变量节点。同时,我们需要提供一个接口,用于对变量和操作进行前向计算和反向传播。

综上所述,计算图是自动微分中非常重要的概念,它能够帮助我们自动推导变量之间的依赖关系,并自动计算每个变量的导数值。在实际应用中,我们通常使用现有的自动微分库,如 TensorFlow、PyTorch 等,来帮助我们快速地构建和计算计算图。

1.2 智能指针的引出

我们先来看一个简单的示例代码

#include <iostream>
#include <string.h>

class A{
public:
    A(){
        this->p = new int[100];
        this->p[0] = 555;
    }

    // 深拷贝的实现,对于实例对象的复制,复制一份p的副本
    A(const A& other){

        this->a = other.a;
        this->b = other.b;
        this->p = new int[100];
        memcpy(this->p, other.p, sizeof(int) * 100);
    }

    virtual ~A(){
        delete [] this->p;
    }

private:
    int a = 123;
    int b = 234
    int* p = nullptr;
}

int main(){
    A a;
    A b = a;
    cout << a.p << endl;
    cout << b.p << endl;
    return 0;
}

上述示例代码演示了对数据地址 p 的引用过程,我们实现了对 p 的深拷贝,在复制类对象的时候复制一份 p,但存在数据复制造成内存的浪费的问题,我们现在的需求有两个:

  • 1.无论 a 被复制多少份,它的数据指针 p 永远都是同一个(即数据引用),使得这个数据不至于有太多的副本
  • 2.我们希望能够正常释放内存,即 p 指针释放的时机

为此我们考虑二级指针和引用计数的思想,示例如下:

#include <iostream>
#include <string.h>

// 诉求并不是为了实现对p的深度copy,在复制类对象的时候复制一份p
// 是为了演示,对数据地址p的引用过程,即
// 1. 无论a被复制多少份,它的数据指针p永远都是同一个 (数据引用)
//    使得这个数据不至于有太多副本
// 2. 还能够正常释放内存
//    p指针的释放问题,也就是释放的时机

// 二级指针 + 引用计数
class Data{
public:
    Data(){
        this->p = new int[100];
        nref = 1;

        cout << "构造Data" << endl;
    }

    virtual ~Data(){

        cout << "析构Data" << endl;
        delete [] this->p;
    }

    int *p = nullptr;
    int nref = 0;   // 引用计数
}

class A{
public:
    A(){
        this->new Data();
    }

    A(const A& other){
        *this = other;        
        this->p->nref ++;
    }

    virtual ~A(){
        // 当最后一个引用实例释放时,删除p指针
        // 只要存在其它引用,释放都是不合适的
        // 问题在于,怎么判断目前执行的析构函数是最后一个引用呢?
        // 引用计数
        delete [] this->p;
        if(this->p->nref == 1){
            delete this->p;
        }else{
            this->p->nref --;
        }
    }

    int a = 123;
    int b = 234
    Data* p = nullptr;
}

int main(){
    A* a = new A();
    A b = *a;

    delete a;
    // cout << a.p << endl;
    cout << b.p << endl;
    return 0;
}

上述示例代码在原基础上引入了二级指针和引用计数来对数据的引用,避免了深度复制时生成多副本的问题,并解决了对象的内存释放的问题。具体分析如下:

  • Data 类:将数据成员 p 单独抽象出来,并添加引用计数 nref 作为计数器,用于记录有多少个对象指向同一块数据空间,方便释放内存
  • A 类:将 Data 对象的指针作为 A 类的成员 p,实现对 Data 对象的引用。同时,在 A 类的拷贝构造函数中,对 Data 对象指针进行拷贝并引用计数加一。在 A 类的析构函数中,通过引用计数来判断是否释放 Data 对象的内存空间,如果当前对象是最后一个引用,释放 Data 对象,否则减少引用计数。
  • 在 main 函数中,通过 new 创建了一个 A 类型的对象,并将其指针 a 赋值给 b。通过 delete 删除 a 指针时,并不会影响 b 中 Data 对象的内存空间,因为其引用计数不为0.

由此我们引出 shared_ptr 共享智能指针,它可以帮助我们完成引用计数操作避免数据副本拷贝,自动实现内存的引用关系,而不需要我们自己去实现。下面是利用 shared_ptr 的示例代码:

#include <iostream>
#include <string.h>
#include <memory>

class Data{
public:
    Data(){
        this->p = new int[100];
        nref = 1;

        cout << "构造Data" << endl;
    }

    virtual ~Data(){

        cout << "析构Data" << endl;
        delete [] this->p;
    }

    int *p = nullptr;
}

class A{
public:
    A(){
        this->pdata.reset(new Data());
    }

    int a = 123;
    int b = 234
    shared_ptr<Data> pdata;
}

int main(){

    // shared_ptr
    A* a = new A();
    A b = *a;

    delete a;
    // cout << a.p << endl;
    cout << b.pdata.get() << endl;
    return 0;
}

在上面的示例代码中我们使用了智能指针 shared_ptr 对内存的管理。在 A 类里面只需要一个指向 Data 类型对象的 shared_ptr 智能指针,并通过 reset 函数进行初始化。需要注意的是,通过 shared_ptr 管理内存,不再需要显式地调用 delete 操作符,shared_ptr 析构时会自动释放所持有地内存,避免了内存泄漏的问题。关于更多细节请查看共享智能指针

2. 示例代码

自动微分简单版本的示例代码

2.1 Scale

自动微分中标量 Scale 实现的示例代码

#include <iostream>
#include <memory>
#include <cmath>

using namespace std;

// 储存表达式所需要的相关数据
class Container{
public:
    
    // 返回这个表达式具体是什么类型,或者说什么名称
    virtual const char* type() = 0;

    // 具体的forward的实现过程
    virtual float forward() = 0;

    // 具体的backward的实现
    virtual void backward(float gradient) = 0;

};

// 表达式类
// 1. 其实所有的操作都可以认为是表达式
//      a. 标量x,可以认为是标量表达式
//      b. 任意的算子,比如加减乘除,都可以抽象为表达式
class Expression{
public:
    
    // 对该表达式进行前向推理,并得到推理后的结果
    float forward(){
        return container_->forward();
    }

    // 对该表达式反向推理,计算每一个节点对应的导数
    void backward(){
        return container_->backward(1.0f);
    }

    // 为了储存表达式中的数据,所以需要引入二级指针,表示表达式所储存的具体实现
    // 具体实现在这里
    shared_ptr<Container> container_;

};

// 标量
class ScalarContainer : public Container{
public:
    ScalarContainer(float value){
        value_ = value;
    }

    virtual const char* type() override{
        return "Scalar";
    }

    virtual float forward() override{
        return value_;
    }

    virtual void backward(float gradient) override{
        gradient_ += gradient;
    }

    float value_    = 0;
    float gradient_ = 0;
};

class Scalar : public Expression{
public:
    Scalar(flaot value){
        container_.reset(new ScalarContainer(value));    // 智能指针初始化方法
    }
};

int main(){

    // 1. 实现计算图的统计
    // 2. 实现过程,应该跟四则运算类似,跟普通写表达式类似
    // 3. 实现forward前向计算和backward反向求导
	Scale a(3.0f);
    cout << a.forward() << endl;
    
    return 0;
}

上述示例代码实现了自动微分中的标量,其中定义了 ContainerExpression 两个类,分别用于储存表达式所需的相关数据和定义表达式。在 Container 中,包含了类型定义、前向计算和反向求导的虚函数,其中 ScalarContainer 是用于储存标量的数据。在 Expression 中,定义了前向计算和反向求导的成员函数,并利用二级智能指针 container_ 引用 Container 类的实例。在 Scale 中,重载了 Expression 的构造函数,并初始化了 container_ 为一个 ScalarContainer 类的实例。

2.2 Multiply

自动微分中乘法 Multiply 实现的示例代码

#include <iostream>
#include <memory>
#include <cmath>

using namespace std;

// 储存表达式所需要的相关数据
class Container{
public:
    
    // 返回这个表达式具体是什么类型,或者说什么名称
    virtual const char* type() = 0;

    // 具体的forward的实现过程
    virtual float forward() = 0;

    // 具体的backward的实现
    virtual void backward(float gradient) = 0;

};

// 表达式类
// 1. 其实所有的操作都可以认为是表达式
//      a. 标量x,可以认为是标量表达式
//      b. 任意的算子,比如加减乘除,都可以抽象为表达式
class Expression{
public:
    
    // 对该表达式进行前向推理,并得到推理后的结果
    float forward(){
        return container_->forward();
    }

    // 对该表达式反向推理,计算每一个节点对应的导数
    void backward(){
        return container_->backward(1.0f);
    }

    // 为了储存表达式中的数据,所以需要引入二级指针,表示表达式所储存的具体实现
    // 具体实现在这里
    shared_ptr<Container> container_;

};

// 乘法
class MultiplyContainer : public Container{
public:
    MultiplyContainer(const Expression& left, const Expression& right){
        left_value_  = left.container_;
        right_value_ = right.container_;
    }

    virtual const char* type() override{
        return "Multiply";
    }

    virtual float forward() override{
        return left_value_->forward() * right_value_->forward();
    }

    virtual void backward(float gradient) override{
        left_value_->backward(gradient * right_value_->forward());
        right_value_->backward(gradient * left_value_->forward());
    }

    shared_ptr<Container> left_value_;
    shared_ptr<Container> right_value_;
    
};

class Multiply : public Expression{
public:
    Multiply(const Expression& left, const Expression& right){
        container_.reset(new MultiplyContainer(left, right));
    }
};

// 重载
Expression operator*(float left, const Expression& right){
    return Multiply(Scalar(left), right);
}

Expression operator*(const Expression& left, float right){
    return Multiply(left, Scalar(right));
}

Expression operator*(const Expression& left, const Expression& right){
    return Multiply(left, right);
}

int main(){

    // 1. 实现计算图的统计
    // 2. 实现过程,应该跟四则运算类似,跟普通写表达式类似
    // 3. 实现forward前向计算和backward反向求导

    Scalar a(3.0f);
    // auto exp = Multiply(a, Scalar(5.0f));
    auto exp = 5.0f * a * 2.0f;
    auto f   = exp * 10 * a;
    cout << exp.forward() << endl;
    exp.backward();

    cout << a.gradient() << endl;
    return 0;
}

上述示例代码实现了自动微分中的乘法,实现了 MultiplyContainer 类,该类继承自 Container 类。它有两个操作数即 left_value_ 和 right_value_,分别是左右操作数的值,构造函数需要传入左右操作数的 Expression 对象,用于后续的前向推理和反向推理。Multiply 类继承自 Expression 类,它包含 MultiplyContainer 类对象的智能指针,用于储存乘法表达式的实现。

此外,示例代码还实现了乘法运算的重载,用于简化表达式的书写。

2.3 Pow

自动微分中平方 Pow 实现的示例代码

#include <iostream>
#include <memory>
#include <cmath>

using namespace std;

// 储存表达式所需要的相关数据
class Container{
public:
    
    // 返回这个表达式具体是什么类型,或者说什么名称
    virtual const char* type() = 0;

    // 具体的forward的实现过程
    virtual float forward() = 0;

    // 具体的backward的实现
    virtual void backward(float gradient) = 0;

};

// 表达式类
// 1. 其实所有的操作都可以认为是表达式
//      a. 标量x,可以认为是标量表达式
//      b. 任意的算子,比如加减乘除,都可以抽象为表达式
class Expression{
public:
    
    // 对该表达式进行前向推理,并得到推理后的结果
    float forward(){
        return container_->forward();
    }

    // 对该表达式反向推理,计算每一个节点对应的导数
    void backward(){
        return container_->backward(1.0f);
    }

    // 为了储存表达式中的数据,所以需要引入二级指针,表示表达式所储存的具体实现
    // 具体实现在这里
    shared_ptr<Container> container_;

};

// 标量
class ScalarContainer : public Container{
public:
    ScalarContainer(float value){
        value_ = value;
    }

    virtual const char* type() override{
        return "Scalar";
    }

    virtual float forward() override{
        return value_;
    }

    virtual void backward(float gradient) override{
        gradient_ += gradient;
    }

    float value_    = 0;
    float gradient_ = 0;
};

class Scalar : public Expression{
public:
    Scalar(float value){
        container_.reset(new ScalarContainer(value));    // 智能指针初始化方法
    }

    float gradient() const{
        return dynamic_pointer_cast<ScalarContainer>(container_)->gradient_;
        // return ((ScalarContainer*)container_.get())->gradient_;
    }
};


// 乘法
class MultiplyContainer : public Container{
public:
    MultiplyContainer(const Expression& left, const Expression& right){
        left_value_  = left.container_;
        right_value_ = right.container_;
    }

    virtual const char* type() override{
        return "Multiply";
    }

    virtual float forward() override{
        return left_value_->forward() * right_value_->forward();
    }

    virtual void backward(float gradient) override{
        left_value_->backward(gradient * right_value_->forward());
        right_value_->backward(gradient * left_value_->forward());
    }

    shared_ptr<Container> left_value_;
    shared_ptr<Container> right_value_;
    
};

class Multiply : public Expression{
public:
    Multiply(const Expression& left, const Expression& right){
        container_.reset(new MultiplyContainer(left, right));
    }
};

Expression operator*(float left, const Expression& right){
    return Multiply(Scalar(left), right);
}

Expression operator*(const Expression& left, float right){
    return Multiply(left, Scalar(right));
}

Expression operator*(const Expression& left, const Expression& right){
    return Multiply(left, right);
}

// 减法
class SubContainer : public Container{
public:
    SubContainer(const Expression& left, const Expression& right){
        left_value_  = left.container_;
        right_value_ = right.container_;
    }

    virtual const char* type() override{
        return "Sub";
    }

    virtual float forward() override{
        return left_value_->forward() - right_value_->forward();
    }

    virtual void backward(float gradient) override{
        left_value_->backward(gradient);
        right_value_->backward(-gradient);
    }

    shared_ptr<Container> left_value_;
    shared_ptr<Container> right_value_;
    
};

class Sub : public Expression{
public:
    Sub(const Expression& left, const Expression& right){
        container_.reset(new SubContainer(left, right));
    }
};

Expression operator-(const Expression& left, float right){
    return Sub(left, Scalar(right));
}

// power
class PowerContainer : public Container{
public:
    PowerContainer(const Expression& x, float y){
        x_  = x.container_;
        y_ = y;
    }

    virtual const char* type() override{
        return "Power";
    }

    virtual float forward() override{
        return std::pow(x_->forward(), y_);
    }

    virtual void backward(float gradient) override{
        x_->backward(gradient * (y_ * std::pow(x_->forward(), y_ - 1)));
    }

    shared_ptr<Container> x_;
    float y_ = 0;    
};

class Power : public Expression{
public:
    Power(const Expression& x, float y){
        container_.reset(new PowerContainer(x, y));
    }
};

namespace op{

  Expression power(const Expression& x, float y){
        return Power(x, y);
  }  
};

int main(){

    // 1. 实现计算图的统计
    // 2. 实现过程,应该跟四则运算类似,跟普通写表达式类似
    // 3. 实现forward前向计算和backward反向求导

    // 需要求解的值
    float x = 3.0f;

    // 中间变量
    float t = x / 2.0f;

    // 定义loss
    float loss = 0.5 * std::pow(t * t - x, 2.0f);

    // 定义最少容忍的误差
    // 三种停止条件
    // 1. loss低于一定值
    // 2. t的改变量低于一定值
    // 3. 迭代次数满足条件
    float eps = 1e-5;

    // 迭代步长,其实就是所谓的学习率
    float lr = 0.01;

    while(loss > eps){

        // float dt = (t * t - x) * 2 - t;
        Scalar st(t);
        auto sl = 0.5 * op::power(st * st - x, 2.0f);
        s1.backward();

        float dt = st.gradient();
        t = t - lr * dt;
        
        loss = 0.5 * std::pow(t * t - x, 2.0f);
        
        std::printf("Loss: %.5f, t = %.5f, sqrt(x) = %.5f\n", loss, t, std::sqrt(x));
    }

    return 0;
}

上述示例代码实现了自动微分中的平方。Power 类实现了 Pow 函数的前向计算和反向传播,它继承自 Expression。整个代码实现了基本的自动微分框架,并在此基础上实现了一个求解平方根的例子。

总结

在本次课程中引入了计算图的概念,通过计算图我们可以实现自动微分过程。并实现了一个简单且基础的自动微分示例代码,包括 Scale 标量、Multiply 乘法以及 Power 平方三个部分的内容,并完成了一个求解平方根的例子。实际上自动微分还可以针对向量甚至矩阵,但这样一来复杂度就高了,杜老师有提供相应的示例代码供大家学习。😂

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

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

相关文章

逆向动态调试工具简介

常用逆向工具简介&#xff1a; 二进制尽管属于底层知识&#xff0c;但是还是离不开一些相应工具的使用&#xff0c;今天简单介绍一下常用的逆向工具OD以及他的替代品x96dbg&#xff0c;这种工具网上很多&#xff0c;也可以加群找老满&#xff08;184979281&#xff09;&#x…

java实现乘法的方法

我们都知道&#xff0c;乘法运算的核心思想就是两个数相乘&#xff0c;如果能将乘法运算转化成一个加数的运算&#xff0c;那么这个问题就很容易解决。比如我们要实现23的乘法&#xff0c;首先需要定义两个变量&#xff1a;2和3。我们将这两个变量定义为一个变量&#xff1a;2x…

如何利用Mybatis-Plus自动生成代码(超详细注解)

1、简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 特性 无侵入&#xff1a;只做增强不做改变&#xff0c;引入…

一例感染型病毒样本的分析

这个样本是会释放两个dll和一个驱动模块&#xff0c;通过感染USB设备中exe文件传播&#xff0c;会向C&C下载PE执行&#xff0c;通过rookit关闭常用的杀软&#xff0c;是一例典型的感染型病毒&#xff0c;有一定的学习价值。 原始样本 样本的基本信息 Verified: Unsigned …

免费无需魔法会语音聊天的ChatGPT

今天发现了一个很好的ChatGPT&#xff0c;可以语音聊天&#xff0c;而且免费无需魔法 角色目前包括夏洛克、雷电影等等&#xff0c;对话的声调完全模拟了原角色&#xff01; 目前只有英文和日语两种对话&#xff0c;我们可以文字输入或者语音输入&#xff0c;中文即可&#xff…

泰克Tektronix DPO5204B混合信号示波器

特征 带宽&#xff1a;2 GHz输入通道&#xff1a;4采样率&#xff1a;1 或 2 个通道上为 5 GS/s、10 GS/s记录长度&#xff1a;所有 4 个通道 25M&#xff0c;50M&#xff1a;1 或 2 个通道上升时间&#xff1a;175 皮秒MultiView zoom™ 记录长度高达 250 兆点>250,000 wf…

M序列测量幅频特性

M序列 M 序列是一种伪随机序列&#xff0c;具有很好的伪噪声特性&#xff0c;常用于信道噪声测试和保密通信。不过 M 序列还有一个用途&#xff0c;也就是本文所介绍的——通过 M 序列测量频率响应。在讨论这个问题之前&#xff0c;我们先介绍 M 序列的特征与生成方法。 M 序列…

活力二八:CRM助力销售管理再现“浓缩”新活力

活力28、沙市日化&#xff01; 央视段子手朱广权再次喊出这句口号时&#xff0c;迅速激活了人们心中对于曾经“日化一哥”的记忆。 作为市场占率曾超 70% 的家清品牌&#xff0c;活力二八业务始于1950年&#xff0c;前身为沙市油脂化工厂&#xff0c;伴随中国改革开放大潮&…

第十一章_SpringBoot集成Redis

总体概述 redisTemplate-jedis-lettuce-redission之间的的联系 1、redisTemplate是基于某个具体实现的再封装&#xff0c;比如说springBoot1.x时&#xff0c;具体实现是jedis&#xff1b;而到了springBoot2.x时&#xff0c;具体实现变成了lettuce。封装的好处就是隐藏了具体的…

大家都在用的视频音频提取器,免费用!

随着互联网的日益普及&#xff0c;人们可以通过多种方式获取和分享媒体内容&#xff0c;例如通过社交媒体、视频共享网站等。但是&#xff0c;在处理媒体文件时&#xff0c;提取其中的音频或视频仍然是一个挑战。这就是为什么越来越多的人都在使用免费的视频音频提取器。 这些…

Node框架 【Koa】介绍、安装以及使用

文章目录 &#x1f31f;前言&#x1f31f;介绍&#x1f31f;koa优势&#x1f31f;洋葱模型&#x1f31f;安装&#x1f31f;具体步骤&#xff1a;&#x1f31f;创建项目目录&#x1f31f;初始化项目&#x1f31f;进入目录,安装koa &#x1f31f;使用&#x1f31f;案例&#x1f3…

C++STL详解(十一)-- 位图(bitset)

文章目录 位图的介绍位图的引入位图的概念位图的应用 位图的使用位图的定义位图的成员函数位图运算符的使用 位图的模拟实现成员函数构造函数set reset testflip,size,countnone,any,all 位图应用题扩展位图模拟实现代码 位图的介绍 位图的引入 有一道面试题: 给40亿个不重复…

QFIELD-GIS工具 定位功能使用方法

一、 简介 定位是一款GIS APP重要功能&#xff0c;可以帮助我们快速在地图上找到现在所处的位置。结合地图我们就可以快速了解我们所处环境的情况。同时可以利用APP的信息标注采集功能采集当前位置的信息到数据库中。 下面我们来介绍【QFIELD-GIS】如何进行GPS定位、如何…

平衡二叉树的实现(包含旋转)

平衡二叉树是子啊二叉排序树的基础上建立的&#xff0c;他的概念就是这棵树中的任意节点的平衡因子都必须要大于1或是小于-1。平衡因子就是这个节点的左子树高度减右子树高度所得到的差。那么&#xff0c;它有什么优点呢&#xff1f;为什要在二叉排序树的基础上来建立这个平衡二…

语音芯片排行榜,为何唯创知音WT588F语音芯片如此受欢迎

随着智能家居、智能玩具、智能机器人等领域的快速发展&#xff0c;语音芯片逐渐成为智能硬件的重要组成部分。在众多语音芯片中&#xff0c;唯创知音WT588F语音芯片备受关注&#xff0c;成为市场上备受欢迎的产品。那么&#xff0c;WT588F语音芯片具备哪些功能&#xff0c;为何…

您的云,您做主:Google Distributed Cloud Hosted 全面可用

近日&#xff0c;谷歌宣布Google 分布式云(GDC) 托管的全面可用性来扩展该产品组合&#xff0c;它支持具有最严格要求的客户的需求&#xff0c;包括机密、受限和绝密数据。 GDC Hosted 包括部署、操作、扩展和保护完整托管云所需的硬件、软件、本地控制平面和操作工具。此外&…

【高危】MySQL Server拒绝服务漏洞(CVE-2023-21912)

漏洞描述 MySQL是Oracle开源的关系型数据库管理系统。 MySQL Server 受影响版本存在拒绝服务漏洞&#xff0c;攻击者者无需身份验证可发送连接数据包导致MySQL Server 崩溃拒绝服务。官方未公布相关细节&#xff0c;可能由于对客户端设置字符集的处理不当&#xff0c;当客户端…

关于倾斜摄影超大场景的三维模型轻量化中的数据质量优化方法浅析

关于倾斜摄影超大场景的三维模型轻量化中的数据质量优化方法浅析 倾斜摄影超大场景的三维模型轻量化处理需要兼顾数据大小和渲染性能&#xff0c;同时保证模型的准确性和真实感。为了提高轻量化质量&#xff0c;可以从以下方面入手&#xff1a; 1、选择合适的轻量化算法和参数…

OpenCV实战5 车牌号识别

原文在这里&#xff0c;参考这个进行了改进 感觉学到了很多东西&#xff0c;便在这里作下笔记。 效果&#xff1a; 目录 一、知识点学习&#xff1a; 1. fstream 2. 形态学开操作与形态闭操作 2.1 第一个角度:消除较小的联通区域 vs 弥合较小的联通区域 2.2 第二个角度&…

【LeetCode】222.完全二叉树的节点数

1.问题 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位…