SLAM算法与工程实践——SLAM基本库的安装与使用(6):g2o优化库(4)构建g2o的边

SLAM算法与工程实践系列文章

下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此

SLAM算法与工程实践系列文章链接


下面是专栏地址:

SLAM算法与工程实践系列专栏


文章目录

  • SLAM算法与工程实践系列文章
    • SLAM算法与工程实践系列文章链接
    • SLAM算法与工程实践系列专栏
  • 前言
  • SLAM算法与工程实践——SLAM基本库的安装与使用(6):g2o优化库(4)
    • 初步认识图的边
    • 如何自定义边
    • 如何向图中添加边
      • 添加一元边
      • 添加二元边
    • 补充


前言

这个系列的文章是分享SLAM相关技术算法的学习和工程实践


SLAM算法与工程实践——SLAM基本库的安装与使用(6):g2o优化库(4)

初步认识图的边

3种类型——BaseUnaryEdge、BaseBinaryEdge和BaseMultiEdge,它们分别表示一元边、二元边和多元边。

在这里插入图片描述

通常是二元边为主

比如我们用边表示三维点投影到图像平面上的重投影误差,就可以设置如下输入参数。

BaseBinaryEdge<2,Vector2D,VertexSBAPointXYZ,VertexSE3Expmap>

BaseBinaryEdge类型的边是一个二元边。

第1个参数“2”是说测量值是二维的,测量值就是图像的二维像素坐标,对应测量值的类型是Vector2D,边连接的两个顶点分别是三维点 VertexSBAPointXYZ 和李群位姿 VertexSE3Expmap

常用的函数,成员变量

// 读/写函数,一般情况下不需要进行读/写操作,仅声明一下就可以
virtual bool read(std::istream& is);
virtual bool write(std::ostream& os) const;// 使用当前顶点的值计算的测量值与真实的测量值之间的误差
virtual void computeError ();//误差对优化变量的偏导数,也就是我们说的 Jacobian
virtual void linearizeoplus ();// 几个重要的成员变量和函数
_measurement		// 存储观测值
_error					// 存储计算的误差
_vertices[]			// 存储顶点信息setVertex(int,vertex)		// 定义顶点及其编号
setId(int)		// 定义边的编号
setMeasurement(type)		// 定义观测值
setInformation()		// 定义信息矩阵

如何自定义边

g2o中边的模板

//g2o中边的定义格式
class myEdge:public g2o:BaseBinaryEdge<errorDim,errorType,Vertex1Type,Vertex2Type>
{public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWmyEdge(){}// 读/写函数virtual bool read(istream& in){}virtual bool write(ostream& out) const {}//误差=测量值-估计值virtual void computeError() override{_error = _measurement - /*估计值*/;}//增量计算函数:误差对优化变量的偏导数virtual void linearizeOplus() override{_jacobianoplusxi(pos,pos)=something;_jocobianOplusxj(pos,pos)=something;}
}

曲线拟合中一元边的简单例子

//曲线拟合中一元边的简单例子
class CurveFittingEdge:public g2o::BaseUnaryEdge<1,double,CurveFittingVertex>
{public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWCurveFittingEdge(double x) : BaseUnaryEdge (), _x(x){}//计算曲线模型误差void computeError(){const CurveFittingVertex* v = static_cast<const CurveFittingVertex*>(vertices[0]);const Eigen::Vector3d abc = v->estimate();//曲线模型为a*×^2+b*x+c//误差=测量值-估计值_error(0,0) = _measurement - std:exp(abc(0,0)*_x*_x + abc(1,0)*x + 
abc(2,0));}//读/写函数virtual bool read(istream& in){}virtual bool write (ostream& out) const {}public:double _x;
};  

稍微复杂的例子,涉及3D-2D点的PP问题,也就是最小化重投影误差问题

// g2o/types/sba/edge project xyz2uv.h,g2o/types/sba/edge project xyz2uv.cpp
// PnP问题中三维点投影到二维图像上二元边定义示例
class g2o_TYPES_SBA_API EdgeProjectXYZ2UV : public BaseBinaryEdge<2,Vector2,VertexPointXYZ,VertexSE3Expmap>
{public:EIGEN MAKE ALIGNED OPERATOR NEW;
EdgeprojectXYZ2UV();//读/写函数bool read(std:istream& is);bool write(std::ostream& os) const;//计算误差void computeError();//增量计算函数virtual void linearizeOplus();//相机参数CameraParameters* _cam;
};void EdgeProjectXYZ2UV::computeError()
{//将顶点中李群相机位姿记为v1const VertexSE3Expmap* v1 = static_cast<const VertexSE3Expmap*>(_vertices[1]);//将顶点中三维点记为v2const VertexPointXYZ* v2 = static_cast<const VertexPointXYZ*>(vertices[0]);
const CameraParameters*cam static cast<const CameraParameters*>
(parameter(0));//误差=测量值-估计值_error = measurement()- cam->cam_map(v1->estimate().map(v2->estimate()));
}// 增量计算函数:误差对优化变量的偏导数
void EdgeprojectXYZ2UV::linearizeOplus()
{VertexSE3Expmap* vj = static_cast<VertexSE3Expmap*>(vertices[1]);SE3Quat T(vj->estimate());VertexPointXYZ* vi = static_cast<VertexPointXYZ*>(vertices[0]);Vector3 xyz = vi->estimate();Vector3 xyz_trans = T.map(xyz);number_t x = xyz_trans[0];number_t y = xyz_trans[1];number_t z = xyz_trans[2];number_t z_2 = z * z;const CameraParameters* cam = static_cast<const CameraParameters*>(parameter(0));//重投影误差关于三维点的雅可比矩阵Eigen::Matrix<number_t,2,3,Eigen::ColMajor> tmp;tmp(0,0) = cam->focal_length;tmp(0,1) = 0:tmp(0,2) = -x / z * cam->focal_length;tmp(1,0) = 0;tmp(1,1) = cam->focal_length;tmp(1,2) = -y / z * cam->focal_length;_jacobianOplusXi = -1. / z * tmp * T.rotation().toRotationMatrix();//重投影误差关于相机位姿的雅可比矩阵_jacobianOplusXj(0,0) = x * y / z_2 * cam->focal_length;_jacobianOplusXj(0,1) = -(1 + (x * x / z_2)) * cam->focal_length;_jacobianOplusXj(0,2) = y / z * cam->focal_length;_jacobianOplusXj(0,3) = -1. / z * cam->focal_length;_jacobianOplusXj(0,4) = 0;_jacobianOplusXj(0,5) = x / z_2 * cam->focal_length;_jacobianOplusXj(1,0) = (1 + y * y / z_2) * cam->focal_length;_jacobianOplusXj(1,1) = -x * y / z_2 * cam->focal_length;_jacobianOplusXj(1,2) = -x / z * cam->focal_length;_jacobianOplusXj(1,3) = 0;_jacobianOplusXj(1,4) = -1./ z * cam->focal_length;_jacobianOplusXj(1,5) = y /z_2 * cam->focal_length;
}

其中有一些比较难理解的地方,我们分别解释。

首先是误差的计算:

//误差=测量值-估计值
_error = measurement()- cam->cam_map(v1->estimate().map(v2->estimate()));

这里的本质是误差 = 测量值 - 估计值。下面梳理一下思路。

我们先来看 cam_map 函数,它的功能是把相机坐标系下的三维点(输入)用内参转换为图像坐标(输出),具体定义如下。

// g2o/types/sba/types_six_dof_expmap.cpp
// cam_map函数定义
Vector2 CameraParameters::cam_map(const Vector3 & trans_xyz) const{Vector2 proj = project2d(trans_xyz);Vector2 res;res[0] = proj[0]*focal_length + principle_point[0];res[1] = proj[1]*focal_length + principle_point[1];return res;
}

然后看 map 函数,它的功能是把世界坐标系下的三维点转换到相机坐标系下,定义如下

// g2o/types/sim3/sim3.h
// map函数定义
Vector3 map (const Vector3& xyz) const
{return s*(r*xyz)+t;
}

因此,下面的代码就是用 v1 估计的位姿把 v2 代表的三维点转换到相机坐标系下。

v1->estimate().map(v2->estimate())

linearizeOplus() 重投影误差关于相机位姿的雅可比矩阵为
∂ e ∂ δ ξ = [ f x X Y Z 2 − f x − f x X 2 Z 2 f x Y Z − f x Z 0 f x X Z 2 f y + f y Y 2 Z 2 − f y X Y Z 2 − f y X Z 0 − f y Z f y Y Z 2 ] \frac{\partial\boldsymbol{e}}{\partial\delta\boldsymbol{\xi}}=\begin{bmatrix}\frac{f_xXY}{Z^2}&-f_x-\frac{f_xX^2}{Z^2}&\frac{f_xY}{Z}&-\frac{f_x}{Z}&0&\frac{f_xX}{Z^2}\\\\f_y+\frac{f_yY^2}{Z^2}&-\frac{f_yXY}{Z^2}&-\frac{f_yX}{Z}&0&-\frac{f_y}{Z}&\frac{f_yY}{Z^2}\end{bmatrix} δξe= Z2fxXYfy+Z2fyY2fxZ2fxX2Z2fyXYZfxYZfyXZfx00ZfyZ2fxXZ2fyY

重投影误差关于三维点的雅可比矩阵为
∂ e ∂ P = − [ f x Z 0 − f x X Z 2 0 f y Z − f y Y Z 2 ] R \frac{\partial\boldsymbol{e}}{\partial\boldsymbol{P}}=-\begin{bmatrix}\frac{f_x}Z&0&-\frac{f_xX}{Z^2}\\\\0&\frac{f_y}Z&-\frac{f_yY}{Z^2}\end{bmatrix}\boldsymbol{R} Pe= Zfx00ZfyZ2fxXZ2fyY R
上述矩阵与函数 EdgeProjectXYZ2UV::computeError()中的实现是一一匹配的。

如何向图中添加边

添加一元边

先来看一元边的添加方法,仍然以曲线拟合的例子来说明。

添加一元边示例:曲线拟合

// 添加一元边示例:曲线拟合
for int i=0;i<N;i++)
{CurveFittingEdge* edge = new CurveFittingEdge(x_data[i])edge->setId(i);	//设置边的IDedge->setVertex(0,v);	//设置连接的顶点v,其编号为0edge->setMeasurement(y_data[i]);	//设置观测的数值edge->setInformation(Eigen::Matrix<double,1,1>::Identity()*1/(w_sigma * w_sigma)); 		//信息矩阵optimizer.addEdge(edge);		//将边添加到优化器
}

setMeasurement 函数输入的观测值具体指什么?

对于这个曲线拟合的例子来说,观测值就是实际观测到的数据。对于视觉SLAM来说,观测值通常就是我们观测到的特征点坐标。

添加二元边

添加二元边示例:PnP投影

// 添加二元边示例:PnP投影
// 顶点包括地图点和位姿
index = 1;
// points_2d是由二维图像特征点组成的向量
for (const Point2f p:points_2d)
{g2o::EdgeProjectXYZ2UV* edge = new g2o::EdgeProjectXYZ2UV();		// 设置边的IDedge->setId(index);// 设置边连接的第1个顶点:三维地图点edge->setVertex(0,dynamic_cast<g2o::VertexSBAPointXYZ*>(optimizer.vertex(index)));// 设置边连接的第2个顶点:位姿edge->setVertex(1,pose);// 设置观测:图像上的二维特征点坐标edge->setMeasurement(Eigen::Vector2d (p.x,p.y));// 设置信息矩阵edge->setInformation(Eigen::Matrix2d::Identity());// 将边添加到优化器中optimizer.addEdge(edge);//添加边的IDindex++;
}

这里的 setMeasurement 函数中的 p 来自由特征点组成的向量 points_2d,也就是特征点的图像坐标(x,y)

另外,setVertex 有两个,一个是 0 和 VertexSBAPointXYZ 类型的顶点,另一个是 1 和 pos。

这里的0和1是什么意思?能否互换呢?

这里的0和1分别指代顶点的ID,能不能互换可能需要查看顶点定义部分的代码。

setVertex在g2o中的定义。

// g2o/core/hyper_graph.h
// set the ith vertex on the hyper-edge to the pointer supplied
void setVertex(size_t i,Vertex*v)
{assert(i<vertices.size() && "index out of bounds");_vertices[i]=v;
}

_vertices[i] 中的 i 对应的就是这里的 0 和 1。代码中的类型 g2o::EdgeProjectXYZ2UV 的定义如下。

class g2o_TYPES_SBA_API EdgeProjectXYZ2UV
{// ......// 相机位姿v1const VertexSE3Expmap* v1 = static_cast<const VertexSE3Expmap*>(_vertices[1]);// 三维点v2const VertexSBAPointXYZ* v2 = static_cast<const VertexSBAPointXYZ*> (_vertices[0]);// ......
}

vertices[0] 对应的是 VertexSBAPointXYZ 类型的顶点,也就是三维点。

vertices[1] 对应的是 VertexSE3Expmap 类型的顶点,也就是位姿pose。

因此,前面1对应的应该是pos,0对应的应该是三维点。所以,这个ID绝对不能互换

补充

static_castdynamic_cast 是C++中两种不同类型的类型转换操作符,它们在类型转换时的用途和行为有着显著的差异。

  1. static_cast

    • 用途static_cast 主要用于进行基本数据类型之间的转换(如 int 转 float)、类层次结构中基类和派生类指针或引用之间的转换(向上转型),以及具有转换构造函数或类型转换运算符的类之间的转换。

    • 行为static_cast 在编译时执行所有检查。如果转换是不合法的,编译器将报错。然而,它不进行运行时类型检查。这意味着,当你将派生类指针或引用向下转型为基类指针或引用时,static_cast 不会检查转换的安全性。

    • 例子

      float f = 3.5;
      int i = static_cast<int>(f); // 将 float 转换为 int
      
  2. dynamic_cast

    • 用途dynamic_cast 主要用于类层次结构中,尤其是进行向下转型(从基类指针或引用转换为派生类指针或引用)时。它被用于那些需要在运行时检查对象类型的情况。

    • 行为dynamic_cast 进行运行时类型检查,确保安全地进行向下转型。如果转换不合法或不安全,dynamic_cast 会返回空指针(对于指针类型)或抛出异常(对于引用类型)。

    • 例子

      class Base { /* ... */ };
      class Derived : public Base { /* ... */ };
      Base* b = new Derived();
      Derived* d = dynamic_cast<Derived*>(b); // 运行时检查
      

总结

  • static_cast 更适合那些在编译时就能确定安全性的转换,如基本数据类型转换或向上转型。
  • dynamic_cast 主要用于需要运行时类型检查的情况,特别是在向下转型时。
  • 使用 dynamic_cast 需要额外的运行时开销,因为它涉及到类型的运行时检查。而 static_cast 不涉及运行时检查,因此性能更好,但可能牺牲了安全性。

关于指针方面的 static_castdynamic_cast 的具体差异,可以从以下几个方面进行详细说明:

  1. 向上转型(Upcasting)

    • static_cast:

      • 安全地将派生类的指针或引用转换为基类的指针或引用。

      • 这种转换是安全的,因为派生类对象总是包含基类部分。

      • 示例:

        class Base {};
        class Derived : public Base {};
        Derived *d = new Derived();
        Base *b = static_cast<Base*>(d); // 安全的向上转型
        
    • dynamic_cast:

      • 也可以用于向上转型,但这通常没有必要,因为编译器会隐式进行这种转换。

      • 示例:

        Derived *d = new Derived();
        Base *b = dynamic_cast<Base*>(d); // 向上转型,但通常不必要
        
  2. 向下转型(Downcasting)

    • static_cast:

      • 可以将基类的指针或引用转换为派生类的指针或引用。

      • 这种转换不安全,因为没有运行时检查来确保转换的有效性。

      • 如果使用不当,可能会导致未定义行为。

      • 示例:

        Base *b = new Derived();
        Derived *d = static_cast<Derived*>(b); // 不安全的向下转型
        
    • dynamic_cast:

      • 安全地进行向下转型。

      • 在运行时检查对象是否真的是指定的派生类类型。

      • 如果转换不合法,对于指针类型返回空指针,对于引用类型抛出异常。

      • 需要基类中至少有一个虚函数(通常是虚析构函数)。

      • 示例:

        Base *b = new Derived();
        Derived *d = dynamic_cast<Derived*>(b); // 安全的向下转型
        if (d) {// 转换成功
        } else {// 转换失败
        }
        
  3. 性能

    • static_cast:
      • 由于没有运行时类型检查,性能较好。
    • dynamic_cast:
      • 需要运行时类型信息(RTTI),因此相比 static_cast 有一定的性能开销。
  4. 适用场景

    • 使用 static_cast 当你确定转换是安全的,并且了解你正在做的事情。
    • 使用 dynamic_cast 当你需要在运行时检查类型安全性,特别是在你不确定对象是否为某个派生类类型的时候。

总之,选择这两者之间的适当转换取决于你的具体需求,以及你对类型安全和性能的考量。在实际编程中,正确使用类型转换对于保证程序的正确性和稳定性至关重要。

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

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

相关文章

在MacOS上Qt配置OpenCV并进行测试

目录 一.Qt环境准备 二.在Qt项目中加载Opencv库并编写代码测试 1.使用Opencv加载图片 &#xff08;1&#xff09;在Qt中创建一个新项目 &#xff08;2&#xff09;在.pro文件中链接OpenCV库 &#xff08;3&#xff09;添加新资源文件 &#xff08;4&#xff09;在mainw…

Vue 3 Composition API:让组件开发更高效、灵活(上)

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

图解二叉树的Morris(莫里斯)遍历

二叉树的Morris(莫里斯)遍历 本文参考链接&#xff1a;https://leetcode.cn/problems/binary-tree-preorder-traversal/submissions/490846864/ 文章目录 二叉树的Morris(莫里斯)遍历模板代码前序遍历中序遍历后序遍历 Morris 遍历使用二叉树节点中大量指向 null 的指针&…

编程规范:长函数的思考

在工作&#xff0c;我们应该都不想看到非常的长函数。对于一个运行5年左右的项目&#xff0c;极有可能出现这种情况。由于长函数的长、if/else嵌套&#xff0c;导致代码的可读性非常差&#xff0c;这对于项目的维护和开发带来了极大的困难。所以我们应该避免写长函数&#xff0…

人工智能_机器学习070_SVM支持向量机_软间隔及优化_硬间隔_衡量间隔软度_引入松弛变量_理解隔离参数---人工智能工作笔记0110

我们继续说,之前说的C是什么意思? 我们在这个软间隔优化中就可以引出C 可以看到之前我们讨论的问题,都是基于样本点的,完全的线性可分的问题,我们称为硬间隔 可以看到这种,一分就可以,分开,简单分割就可以分开的数据,我们称之为硬间隔 但是可以看到上面这种情况,无论怎么分,都…

第1课 配置FFmpeg+OpenCV开发环境

本教程所对应的SDK下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88657539 本课对应源文件下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88657528 一、配置开发环境 1.下载FFmpegOpenCV开发所用的SDK压缩包&#xff0…

分享70个Java源码总有一个是你想要的

分享70个Java源码总有一个是你想要的 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1s8ZVYHb5B1GgXMlpG-6-Iw?pwd6666 提取码&#xff1a;6666 项目名称 admin、cms、console 等多…

构建创新学习体验:企业培训系统技术深度解析

企业培训系统在现代企业中发挥着越来越重要的作用&#xff0c;它不仅仅是传统培训的延伸&#xff0c;更是技术创新的结晶。本文将深入探讨企业培训系统的关键技术特点&#xff0c;并通过一些简单的代码示例&#xff0c;展示如何在实际项目中应用这些技术。 1. 前端技术&#…

Redis基础-Redis概念及常见命令

1.nosql数据库 NoSQL数据库是一种提供了非关系型数据存储的数据库系统&#xff0c;与传统的关系型数据库&#xff08;如SQL数据库&#xff09;不同。NoSQL数据库的特点是灵活性高&#xff0c;能够处理结构化、半结构化或非结构化数据。它们通常用于大数据和实时Web应用。NoSQL数…

C++面试宝典第9题:找出第K大元素

题目 给定一个整数数组a,同时给定它的大小N和要找的K(1 <= K <= N),请根据快速排序的思路,找出数组中第K大的数(保证答案存在)。比如:数组a为[50, 23, 66, 18, 72],数组大小N为5,K为3,则第K大的数为50。 解析 这道题主要考察应聘者对于快速排序的理解,以及实…

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

SAE 2.0,让容器化应用开发更简单

作者&#xff1a;邵丹 云原生容器化应用托管模式的演变 云原生这个概念从提出&#xff0c;到壮大&#xff0c;再到今天的极大普及&#xff0c;始终处于一个不断演进和革新的过程中。云原生体系下应用的托管形态是随着企业应用架构在不断演进的。最早的应用大多是集中式、单体…

Bellman_Ford算法总结

知识概览 Bellman_Ford算法适合解决存在负权边的最短路问题&#xff0c;时间复杂度为O(nm)。在存在负权边的最短路问题中&#xff0c;Bellman_Ford算法的效率虽然不如SPFA算法&#xff0c;但是Bellman_Ford算法能解决SPFA算法不能解决的经过不超过k条边的最短路问题。 例题展示…

【c++、数据结构课设】哈夫曼树

时间过的真快&#xff0c;转眼之间一个学期即将结束&#xff0c;想必这个时候大家都在准备各科的课设作业&#xff0c;本期内容是我的数据结构课设&#xff0c;希望能给大家带来帮助&#xff0c;如果有任何不足或需要改进的地方&#xff0c;欢迎各位提出宝贵的意见。 屏幕录制2…

bean生命周期源码(三)

书接上文 文章目录 一、Bean的销毁逻辑1. 简介2. Bean销毁逻辑的注册3. Bean的销毁过程 一、Bean的销毁逻辑 1. 简介 前面我们已经分析完了Spring创建Bean的整个过程的源码&#xff0c;在创建bean的核心方法中doCreateBean这一个核心方法中&#xff0c;在方法的最后面有这么…

pycharm修改项目文件夹名称

目录 1 修改项目文件夹名称 2 修改代码中的项目名称 1 修改项目文件夹名称 选中项目文件夹&#xff0c;右键&#xff0c;选择refactor-rename。 选择rename project&#xff1a; 然后输入新的项目名称。 此时进入资源管理器&#xff0c;修改项目文件夹的名字&#xff0c;完成…

spring aop实际开发中怎么用,Spring Boot整合AOP,spring boot加spring mvc一起使用aop,项目中使用aop

前言&#xff1a;本文不介绍 AOP 的基本概念、动态代理方式实现 AOP&#xff0c;以及 Spring 框架去实现 AOP。本文重点介绍 Spring Boot 项目中如何使用 AOP&#xff0c;也就是实际项目开发中如何使用 AOP 去实现相关功能。 如果有需要了解 AOP 的概念、动态代理实现 AOP 的&…

Spark集群部署与架构

在大数据时代&#xff0c;处理海量数据需要分布式计算框架。Apache Spark作为一种强大的大数据处理工具&#xff0c;可以在集群中高效运行&#xff0c;处理数十TB甚至PB级别的数据。本文将介绍如何构建和管理Spark集群&#xff0c;以满足大规模数据处理的需求。 Spark集群架构…

LLM微调(四)| 微调Llama 2实现Text-to-SQL,并使用LlamaIndex在数据库上进行推理

Llama 2是开源LLM发展的一个巨大里程碑。最大模型及其经过微调的变体位居Hugging Face Open LLM排行榜&#xff08;https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard&#xff09;前列。多个基准测试表明&#xff0c;就性能而言&#xff0c;它正在接近GPT-3.5…

光耦继电器

光耦继电器(光电继电器) AQW282SX 282SZ 280SX 280SZ 284SX 284SZ 212S 212SX 21 2SZ 文章目录 光耦继电器(光电继电器)前言一、光耦继电器是什么二、光耦继电器的类型三、光电耦合器的应用总结前言 光耦继电器在工业控制、通讯、医疗设备、家电及汽车电子等领域得到广泛应…