这一部分主要是最后的Problem比较重要。

带条件的代价函数ConditionedCostFunction

这个类使用户可以在使用封装好的代价函数的同时,对残差值加入一定的条件限制。举个例子,现在已经有一个代价函数可以产生N个值,但是用户希望的总代价,不是这N个值的简单的平方和。比如对某个特定残差值项赋予一定的系数来改变其在总残差值中的权重。具体代码如下:

//  my_cost_function 生成N个残差值。
CostFunction* my_cost_function = ...
CHECK_EQ(N, my_cost_function->num_residuals());
vector<CostFunction*> conditioners;//  Make N 1x1 cost functions (1 parameter, 1 residual)
//  将其构造成N个1x1的代价函数。
CostFunction* f_1 = ...
conditioners.push_back(f_1);
CostFunction* f_N = ...
conditioners.push_back(f_N);ConditionedCostFunction* ccf =new ConditionedCostFunction(my_cost_function, conditioners);

现在ccf中的residual[i](i=0…N-1)会被传递到对应的第i个conditioner中。

ccf_residual[i]=f_i(my_cost_function_residual[i])

然后雅可比矩阵的计算相会相应的被调整。

没看明白条件(权重)到底如何给定。


GradientChecker

该类将代价函数返回的雅可比矩阵与使用有限差分估计的导数进行比较。 它的作用是是作为单元测试的工具,给你比求解器选项中的check_gradients更细致的控制选项。强制执行的条件是:

i,j:JijJijmaxij(JijJij)<r∀i,j:Jij−Jij′maxij(Jij−Jij′)<r


JijJij
是代价函数计算出的雅可比矩阵再乘以本地参数化的雅可比矩阵。 JijJij′
是有限差分法计算出的雅可比矩阵也乘以本地参数化的雅可比矩阵。 rr
是相对精度。用法如下:

//  my_cost_function takes two parameter blocks. The first has a local
//  parameterization associated with it.
CostFunction* my_cost_function = ...
LocalParameterization* my_parameterization = ...
NumericDiffOptions numeric_diff_options;std::vector<LocalParameterization*> local_parameterizations;
local_parameterizations.push_back(my_parameterization);
local_parameterizations.push_back(NULL);std::vector parameter1;
std::vector parameter2;
// Fill parameter 1 & 2 with test data...std::vector<double*> parameter_blocks;
parameter_blocks.push_back(parameter1.data());
parameter_blocks.push_back(parameter2.data());GradientChecker gradient_checker(my_cost_function,local_parameterizations, numeric_diff_options);
GradientCheckResults results;
if (!gradient_checker.Probe(parameter_blocks.data(), 1e-9, &results) {LOG(ERROR) << "An error has occurred:\n" << results.error_log;
}

NormalPriorr

class NormalPrior: public CostFunction {public:// Check that the number of rows in the vector b are the same as the// number of columns in the matrix A, crash otherwise.NormalPrior(const Matrix& A, const Vector& b);virtual bool Evaluate(double const* const* parameters,double* residuals,double** jacobians) const;};

这个类实现了一个如下形式的代价函数:

cost(x)=||A(xb)||2
c


o


s


t


(


x


)


=



|




|



A


(


x





b


)



|





|



2


矩阵 AA
和向量b
b


是固定的。 xx
是变量。如果用户对实现一个如下形式的代价函数感兴趣

cost(x)=(xμ)TS1(xμ)
c


o


s


t


(


x


)


=


(


x





μ



)


T




S






1




(


x





μ


)


其中, μμ
是向量而 SS
是一个协方差矩阵,那么A=S1/2
A


=



S






1



/



2



,即 AA
是协方差的逆的平方根,也被称为刚度矩阵。然而对A
A


的形状是没有限制的。如果协方差 SS
秩亏它是矩形的,那么A
A


可以是矩形的。

A matrix is said to have full rank if its rank is either equal to its number of columns or to its number of rows (or to both). A matrix that does not have full rank is said to be rank deficient.
一个矩阵的秩等于行数或者列数,那么这个矩阵是满秩矩阵,否则就称这个矩阵亏秩。


损失函数LossFunction

对于最小二乘问题有时候会遇到一些异常的测量值,这时候应用损失函数来减小这些异常值的影响就显得十分重要了。

下面考虑一个运动问题的求解。未知数是三维空间点和相机参数,测量值是空间点再相机成像平面上重投影的坐标。例如,我们希望用参数未知的移动的相机来观察并模拟大街上消防栓和汽车的几何形状。我们关心的唯一点是消防栓的顶尖尖端。图像处理算法可以侦测到几乎每一帧图像中的消防栓的尖端(除了某一帧将汽车的车尾灯当成了消防栓的尖端),并且将测量结果输入到Ceres。如果我们不把那个错误识别结果提出,那么错误的值将使最终结果大大偏离最优解,从而引起较大误差。

一个好的损失函数可以减小异常残值对总代价的影响力,再上面的例子中,它仅仅使异常外围点的影响力减小,不会对最终结果产生过度影响。

class LossFunction {public:virtual void Evaluate(double s, double out[3]) const = 0;
};

损失函数的关键部分是LossFunction::Evaluate()方法,

out=[ρ(s),ρ(s),ρ′′(s)]out=[ρ(s),ρ′(s),ρ″(s)]。


通常某一项残值对代价函数的影响由 12ρ(s)12ρ(s)
给出,其中 s=|fi|2s=|fi|2
。注意调用这个方法时,如果 ss
是负值会导致错误,并且函数不会对这种情况做出处理。合理地ρ
ρ


一般满足以下条件:

ρ(0)ρ(0)ρ(s)ρ′′(s)=0=1<1in the outlier region<0in the outlier regionρ(0)=0ρ′(0)=1ρ′(s)<1in the outlier regionρ″(s)<0in the outlier region


由此该函数模仿了小残差平方成本。

so that they mimic the squared cost for small residuals.最后这一段没看懂。

缩放

实例Instances

Ceres包含了大量已经定义好的损失函数。为了简化我们只介绍他们的非放缩的版本(unscaled versions)。下图图形化的描绘了他们的形状。

class TrivialLoss

ρ(s)=sρ(s)=s

class HuberLoss

ρ(s)={s2s1s1s>1ρ(s)={ss≤12s−1s>1

class SoftLOneLoss

ρ(s)=2(1+s1)ρ(s)=2(1+s−1)

class CauchyLoss

ρ(s)=log(1+s)ρ(s)=log⁡(1+s)

class ArctanLoss

ρ(s)=arctan(s)ρ(s)=arctan⁡(s)

class TolerantLoss

ρ(s,a,b)=blog(1+e(sa)/b)blog(1+ea/b)ρ(s,a,b)=blog⁡(1+e(s−a)/b)−blog⁡(1+e−a/b)

class ComposedLoss
给定两个损失函数fg。那么最终的损失函数是h(s) = f(g(s))

class ComposedLoss : public LossFunction {public:explicit ComposedLoss(const LossFunction* f,Ownership ownership_f,const LossFunction* g,Ownership ownership_g);
};
ρ(s,a,b)=blog(1+e(sa)/b)blog(1+ea/b)ρ(s,a,b)=blog⁡(1+e(s−a)/b)−blog⁡(1+e−a/b)

class ScaledLosss
有时候你可能希望对强化器(robustifier,貌似是指的损失函数)的输出进行放缩。比如,你可能想要对不同的误差赋予不同的权重。这里我们给定一个损失函数ρ(s)ρ(s)
和一个放缩参数aa
ScaledLoss实现了函数a \rho(s)

因为我们把Null损失函数视为恒等损失函数,ρ=
ρ


=


`Null`是一个有效的输入值,并且会将输入放缩aa
倍后输出。这个函数为放缩残差块提供了一个很简单的方法。

class LossFunctionWrapper

有时在优化问题建立之后,我们希望改变损失函数的规模。 例如对具有异常值的数据进行计算的时候,通过大规模的开始-优化问题-然后减小规模的方式可以提高收敛性。这样可以比仅使用具有小规模的损失函数获得更好的收敛过程。

这个模板化的类允许用户实现一个优化问题后规模可变的损失函数,

Problem problem;// Add parameter blocksCostFunction* cost_function =new AutoDiffCostFunction < UW_Camera_Mapper, 2, 9, 3>(new UW_Camera_Mapper(feature_x, feature_y));LossFunctionWrapper* loss_function(new HuberLoss(1.0), TAKE_OWNERSHIP);
problem.AddResidualBlock(cost_function, loss_function, parameters);Solver::Options options;
Solver::Summary summary;
Solve(options, &problem, &summary);loss_function->Reset(new HuberLoss(1.0), TAKE_OWNERSHIP);
Solve(options, &problem, &summary);
理论基础Theory

下面让我们考虑一个最简单情况。

minx12ρ(f2(x))

min


x




1


2



ρ


(



f


2



(


x


)


)


那么,一个强化处理之后的梯度和其Gauss-Newton Hessian分别如下:

g(x)H(x)=ρJ(x)f(x)=J(x)(ρ+2ρ′′f(x)f(x))J(x)g(x)=ρ′J⊤(x)f(x)H(x)=J⊤(x)(ρ′+2ρ″f(x)f⊤(x))J(x)


其中,含有 f(x)f(x)
的二阶导数的项被省略了。注意如果 ρ′′f(x)f(x)+12ρ<0ρ″f(x)⊤f(x)+12ρ′<0
H(x)H(x)
是欠定义的。如果不是这种情况,那就可以给残差和雅可比矩阵重新赋以权重,从而获得为强化的Gauss-Newton step提供相应的线性最小二乘法问题。

its possible to re-weight the residual and the Jacobian matrix such that the corresponding linear least squares problem for the robustified Gauss-Newton step.这句翻译的不好。

αα
是下列方程的根:

12α2αρ′′ρf(x)2=0.12α2−α−ρ″ρ′‖f(x)‖2=0.


那么定义放缩后的残差值和雅可比矩阵为:

f~(x)J~(x)=ρ1αf(x)=ρ(1αf(x)f(x)f(x)2)J(x)f~(x)=ρ′1−αf(x)J~(x)=ρ′(1−αf(x)f⊤(x)‖f(x)‖2)J(x)


在这种情况下 2ρ′′f(x)2+ρ02ρ″‖f(x)‖2+ρ′≲0
。我们限定 α1ϵα≤1−ϵ

更多相关内容可以参考B. Triggs, P. F. Mclauchlan, R. I. Hartley & A. W. Fitzgibbon, Bundle Adjustment: A Modern Synthesis, Proceedings of the International Workshop on Vision Algorithms: Theory and Practice, pp. 298-372, 1999.

通过这一简单的放缩,人们就可以对强化后的非线性最小二乘法问题使用任何基于非线性最小二乘算法的雅可比行列式了。

这一部分看不太明白。


本地参数化LocalParameterization

class LocalParameterization {public:virtual ~LocalParameterization() {}virtual bool Plus(const double* x,const double* delta,double* x_plus_delta) const = 0;virtual bool ComputeJacobian(const double* x, double* jacobian) const = 0;virtual bool MultiplyByJacobian(const double* x,const int num_rows,const double* global_matrix,double* local_matrix) const;virtual int GlobalSize() const = 0;virtual int LocalSize() const = 0;
};

这一段看不懂,不理解,翻译了也没意义。原文如下:
Sometimes the parameters x can overparameterize a problem. In that case it is desirable to choose a parameterization to remove the null directions of the cost. More generally, if x lies on a manifold of a smaller dimension than the ambient space that it is embedded in, then it is numerically and computationally more effective to optimize it using a parameterization that lives in the tangent space of that manifold at each point.

For example, a sphere in three dimensions is a two dimensional manifold, embedded in a three dimensional space. At each point on the sphere, the plane tangent to it defines a two dimensional tangent space. For a cost function defined on this sphere, given a point x, moving in the direction normal to the sphere at that point is not useful. Thus a better way to parameterize a point on a sphere is to optimize over two dimensional vector Δx in the tangent space at the point on the sphere point and then “move” to the point x+Δx, where the move operation involves projecting back onto the sphere. Doing so removes a redundant dimension from the optimization, making it numerically more robust and efficient.

More generally we can define a function

x=(x,Δx),x′=⊞(x,Δx),


where x′ has the same size as x, and Δx is of size less than or equal to x. The function
, generalizes the definition of vector addition. Thus it satisfies the identity

(x,0)=x,x.⊞(x,0)=x,∀x.


Instances of LocalParameterization implement the
operation and its derivative with respect to Δx at Δx=0.


AutoDiffLocalParameterization

TODO


Problem的的构建

Problem保持了非线性最小二乘问题的强化的边界。要创建最小二乘问题,可以使用Problem::AddResidualBlock()Problem::AddParameterBlock()。例如,下面这个Problem包含了三个参数块,维度分别为3,4,5。同时有两个残差块,维度分别是2和6。

double x1[] = { 1.0, 2.0, 3.0 };
double x2[] = { 1.0, 2.0, 3.0, 5.0 };
double x3[] = { 1.0, 2.0, 3.0, 6.0, 7.0 };Problem problem;
problem.AddResidualBlock(new MyUnaryCostFunction(...), x1);
problem.AddResidualBlock(new MyBinaryCostFunction(...), x2, x3);

Problem::AddResidualBlock(),顾名思义,就是把残差块加入到Problem当中。它添加了一个CostFunction,一个LossFunction(非必要)并且把CostFunction链接到一系列的参数块上。

代价函数包含了关于期望的参数块大小的信息。该函数检查他们是否与parameter_blocks一致。如果不匹配,程序将中止。loss_function可以是Null,这种情况下这一项的代价就是残差的平方。

用户可以选择使用Problem::AddParameterBlock)显式添加参数块。 这会调用额外的正确性检查。然而,Problem :: AddResidualBlock()其实也可以隐含地添加参数块(如果它们不存在),因此显式调用Problem::AddParameterBlock()并非必要。作为选项,用户可以把LocalParameterization对象和参数块连接起来。重复调用的相同参数会被忽略。重复地用相同的双精度类型指针但用不同的大小的结果会导致未定义的行为。

Repeated calls with the same double pointer but a different size results in undefined behavior.

用户可以使用Problem::SetParameterBlockConstant()把任何参数块设为常数,并且使用SetParameterBlockVariable()来撤销这一操作。

实际上,您可以将任意数量的参数块设置为常量。并且Ceres足够聪明,可以根据可自由更改的参数块来确定您构建的问题的哪一部分取决于参数块,并且把时间用于解决它。 例如,如果您构造了一个有100万个参数块和200万个残余块的问题,但是将除了一个参数块之外的所有参数块都设为常量,并且只有10个残余块依赖于这个非常量参数块。如果你之前已经定义了另一个参数块和10个残留块的问题, 那么,Ceres花在解决这两个问题上的时间相同。

所有权Ownership
Problem默认掌握cost_functionloss_functionlocal_parameterization指针的所有权。这些对象在Problem的整个生命周期都保持活动状态。如果用户希望控制这些对象的销毁行为,那么他们可以通过在Problem :: Options中设置相应的选项来实现。

注意,虽然Problem掌握这cost_functionloss_function的所有权。它不排除用户在另一个残留块中重新使用它们。析构函数只管销毁一次每个cost_functionloss_function指针,而不管有多少个残留块仍然引用它们。


Problem的方法


ResidualBlockId Problem::AddResidualBlock(CostFunction *cost_function, LossFunction *loss_function, const vector<double *> parameter_blocks)
ResidualBlockId Problem::AddResidualBlock(CostFunction *cost_function, LossFunction *loss_function, double *x0, double *x1, ...)

为整个代价函数添加一个残差块。代价函数包含了关于期望的参数块大小的信息。该函数检查他们是否与parameter_blocks一致。如果不匹配,程序将中止。loss_function可以是Null,这种情况下这一项的代价就是残差的平方。参数块有可能整体的以vector<double*>的形式传递,或者分开用double *指针传递。

用户可以选择使用Problem :: AddParameterBlock()显式添加参数块。 这会调用额外的正确性检查。然而,Problem :: AddResidualBlock()其实也可以隐含地添加参数块(如果它们不存在),因此显式调用Problem :: AddParameterBlock()并非必要。作为选项,用户可以把LocalParameterization对象和参数块连接起来。这样可以避免重复调用相同参数。重复地用相同的双精度类型指针但用不同的大小的结果会导致未定义的行为。

注意,虽然Problem掌握这cost_functionloss_function的所有权。它不排除用户在另一个残留块中重新使用它们。析构函数只管销毁一次每个cost_functionloss_function指针,而不管有多少个残留块仍然引用它们。

这个函数非常重要也非常基础,在前面介绍过,所以很多内容是重复的。

用法案例:

double x1[] = {1.0, 2.0, 3.0};
double x2[] = {1.0, 2.0, 5.0, 6.0};
double x3[] = {3.0, 6.0, 2.0, 5.0, 1.0};
vector<double*> v1;
v1.push_back(x1);
vector<double*> v2;
v2.push_back(x2);
v2.push_back(x1);Problem problem;problem.AddResidualBlock(new MyUnaryCostFunction(...), NULL, x1);
problem.AddResidualBlock(new MyBinaryCostFunction(...), NULL, x2, x1);
problem.AddResidualBlock(new MyUnaryCostFunction(...), NULL, v1);
problem.AddResidualBlock(new MyBinaryCostFunction(...), NULL, v2);

void Problem::AddParameterBlock(double *values, int size, LocalParameterization *local_parameterization)
void Problem::AddParameterBlock(double *values, int size)

把参数块及其各个维度添加到Problem当中。重复调用的相同参数会被忽略。重复地用相同的双精度类型指针但用不同的大小的结果会导致未定义的行为。


void Problem::RemoveResidualBlock(ResidualBlockId residual_block)

把一个残差块从问题中移除。残差块所依赖的参数不会被移除。针对这个残差块的代价函数和损失函数不会立即被移除,但是直到Problem本身被删除不会运行。如果Problem::Options::enable_fast_removalTrue,那么移除非常迅速。否则,移除一个残差块将引起对整个问题的完整扫描,以便确认该残差块有意义。
注意:移除残差或参数块将会破坏隐式排序,从解算器返回的雅可比矩阵或残差可能变得不可解读。 如果你的运算依赖于雅可比矩阵,不要使用移除残差块! 这可能会在未来的版本中发生变化。


void Problem::RemoveParameterBlock(double *values)

把一个参数块从问题中移除。参数块的参数化(如果有的话)会保存到整个问题的删除。依赖于这些参数的残差块也会被移除,如同RemoveResidualBlock()一样。如果Problem::Options::enable_fast_removalTrue,那么移除非常迅速。否则,移除一个残差块将引起对整个问题的完整扫描,以便确认该残差块有意义。
注意:移除残差或参数块将会破坏隐式排序,从解算器返回的雅可比矩阵或残差可能变得不可解读。 如果你的运算依赖于雅可比矩阵,不要使用移除残差块! 这可能会在未来的版本中发生变化。


void Problem::SetParameterBlockConstant(double *values)

令指定的参数块在整个优化计算过程中保持常数。


void Problem::SetParameterBlockVariable(double *values)

允许指定的参数块在优化计算过程中发生改变。


void Problem::SetParameterization(double *values, LocalParameterization *local_parameterization)

为其中一个参数块设置本地参数设置。默认Problem拥有local_parameterization的所有权。可以为多个参数设置相同的参数; 析构函数注意只删除一次本地参数化。 本地参数设置每个参数只能设置一次,一旦设置就不能更改。


LocalParameterization *Problem::GetParameterization(double *values) const

获取与此参数块关联的本地参数化对象。 如果没有关联的参数化对象,则返回NULL


void Problem::SetParameterLowerBound(double *values, int index, double lower_bound)

为参数块*values中位置为index的参数设置下限。 默认情况下下限是−∞


void Problem::SetParameterUpperBound(double *values, int index, double upper_bound)

为参数块*values中位置为index的参数设置上限。 默认情况下上限是


int Problem::NumParameterBlocks() const

返回Problem中参数块的个数。永远等于parameter_blocks().size()parameter_block_sizes().size()


int Problem::NumParameters() const

返回参数向量的大小,包含所有参数块内参数个数的总和。


int Problem::NumResidualBlocks() const

返回Problem中残差块的数量,永远等于residual_blocks().size()


iint Problem::NumResiduals() const

返回残差向量的大小,包含所有残差块内残差个数的总和。


int Problem::ParameterBlockSize(const double *values) const

返回某参数块*values的大小,即其内参数个数。


int Problem::ParameterBlockLocalSize(const double *values) const

返回某本地参数化的参数块*values的大小,即其内参数个数。如果这个参数块没有相关的本地参数化,那么ParameterBlockLocalSize=ParameterBlockSize


bool Problem::HasParameterBlock(const double *values) const

返回某参数块*values是否属于某Problem。


void Problem::GetParameterBlocks(vector<double *> *parameter_blocks) const

把目前属于某Problem的参数块指针赋给函数参数*parameter_blocks。执行之后parameter_block.size() = NumParameterBlocks


void Problem::GetResidualBlocks(vector<ResidualBlockId> *residual_blocks) const

把目前属于某Problem的残差块指针赋给函数参数*residual_blocks。执行之后residual_blocks.size() = NumResidualBlocks


void Problem::GetParameterBlocksForResidualBlock(const ResidualBlockId residual_block, vector<double *> *parameter_blocks) const

获得给定残差块依赖的所有参数块。


void Problem::GetResidualBlocksForParameterBlock(const double *values, vector<ResidualBlockId> *residual_blocks) const

获得所有依赖于给定参数块的残差块。


const CostFunction *GetCostFunctionForResidualBlock(const ResidualBlockId residual_block) const

获得给定残差块的代价函数CostFunction


const LossFunction *GetLossFunctionForResidualBlock(const ResidualBlockId residual_block) const

获得给定残差块的损失函数LossFunction


bool Problem::Evaluate(const Problem::EvaluateOptions &options, double *cost, vector<double> *residuals, vector<double> *gradient, CRSMatrix *jacobian)

评估计算这个Problem。任何的输出都可能是Null。残差块和参数块受到Problem::EvaluateOptions的控制。

注意:
评估计算将会使用创建这个Problem时的参数块指针所指向的存储地址的值。例如:

Problem problem;
double x = 1;
problem.Add(new MyCostFunction, NULL, &x);double cost = 0.0;
problem.Evaluate(Problem::EvaluateOptions(), &cost, NULL, NULL, NULL);

代价计算时使用x = 1。如果你希望计算时x = 2,那么:

x = 2;
problem.Evaluate(Problem::EvaluateOptions(), &cost, NULL, NULL, NULL);

我的理解就是参数块的赋值,要在调用Evaluate函数之前完成。

注意2:
如果没有进行本地参数化,那么梯度向量的大小就是所有参数块的大小的总和。如果有本地参数化,那么将会把LocakSize设给梯度向量。

注意3:
这个函数不能在问题计算过程中调用。


class Problem::EvaluateOptions

用于控制Problem::Evaluate()的选项结构。

  • vector<double *> Problem::EvaluateOptions::parameter_blocks
    参与计算的参数块的集合。这个向量决定了参数块在梯度向量或者雅可比矩阵(列方向)中出现的顺序。如果为空,那么则默认其包含所有参数块。一般来说,参数块的排序取决于他们被add到Problem中的顺序以及用户是否认为移除了某些参数块。
    注意:这个向量不应该指向除了已经加入Problem的参数块外任何新的位置,否则可能会有不好的后果。

  • vector<ResidualBlockId> Problem::EvaluateOptions::residual_blocks
    参与计算的残差块的集合。这个向量决定了残差块的计算次序,以及雅可比矩阵(行方向)中出现的顺序。如果为空,那么则默认其包含所有残差块。

  • bool Problem::EvaluateOptions::apply_loss_function
    如果令其为false,那么即使残差块中包含了损失函数,那么损失函数也不会发挥作用,即直接输出代价函数。这个选项主要用于分析解的质量。
  • int Problem::EvaluateOptions::num_threads
    使用的线程数。要求安装OpenMP模块。

rotation.h

这一部分主要介绍了一系列Ceres提供的用于计算空间旋转的模板函数,可以应用到自动微分法。这一块我没有用到,所以就省略了。


Cubic Interpolation

在很多优化问题中涉及到表格形式的函数,比如在图片中。评估这类方程及其导数需要进行插值。虽然已经有很多库实现了差值运算。但是它们相对于Ceres自动微分法的兼容性很差,往往使用起来十分困难。所以Ceres提供了一维和二维的插值算法来解决这一问题。具体的可以参考官方指南这一部分的介绍以及Pascal Getreuer.的文章Linear Methods for Image Interpolation

Ceres Solver 官方教程学习笔记(十二)——非线性最小二乘法建模Modeling Non-linear Least Squares (下)相关推荐

  1. python基础教程学习笔记十二

    图形用户界面 Tkinter Wxpython Pythonwin Java swing PyGTK pyQt 第五章 数据库支持 一python数据库api 1 全局变量 Apilevel  版本 ...

  2. Python语言入门这一篇就够了-学习笔记(十二万字)

    Python语言入门这一篇就够了-学习笔记(十二万字) 友情提示:先关注收藏,再查看,12万字保姆级 Python语言从入门到精通教程. 文章目录 Python语言入门这一篇就够了-学习笔记(十二万字 ...

  3. 吴恩达《机器学习》学习笔记十二——机器学习系统

    吴恩达<机器学习>学习笔记十二--机器学习系统 一.设计机器学习系统的思想 1.快速实现+绘制学习曲线--寻找重点优化的方向 2.误差分析 3.数值估计 二.偏斜类问题(类别不均衡) 三. ...

  4. ROS学习笔记十二:使用roswtf

    ROS学习笔记十二:使用roswtf 在使用ROS过程中,roswtf工具可以为我们提供ROS系统是否正常工作的检查作用. 注意:在进行下列操作之前,请确保roscore没有运行. 检查ROS是否安装 ...

  5. 无敌python爬虫教程学习笔记(二)

    系列文章目录 无敌python爬虫教程学习笔记(一) 无敌python爬虫教程学习笔记(二) 无敌python爬虫教程学习笔记(三) 无敌python爬虫教程学习笔记(四) 手刃一个小爬虫 系列文章目 ...

  6. 【从零开始的大数据学习】Flink官方教程学习笔记(一)

    Flink官方教程学习笔记 学习资源 基础Scala语法 Scala数据结构专题 声明变量 代码块 函数(function) 方法(methods) Traits (接口) class(类) tupl ...

  7. Polyworks脚本开发学习笔记(十二)-输出和读取文本文件

    Polyworks脚本开发学习笔记(十二)-输出和读取文本文件 Polyworks作为一个测量工具,将测量的数据方便的导出到文本文件则是一项必须的功能.在DATA_FILE这个命令下提供了很多子命令用 ...

  8. OpenCV学习笔记(十二)——图像分割与提取

    在图像处理的过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来.例如,在视频监控中,观测到的是固定背景下的视频内容,而我们对背景本身并无兴趣,感兴趣的是背景中出现的车辆.行人或者其他对象. ...

  9. 【现代机器人学】学习笔记十二:轮式移动机器人

    目录 轮式机器人类型 全向轮式机器人 建模 单个全向轮是怎么运动的 多个全向轮是如何带动底盘运动的 运动规划和反馈控制 非完整约束轮式移动机器人 建模 独轮车 差速驱动机器人 车型机器人 非完整移动机 ...

最新文章

  1. 人工智能专业就业前景如何?
  2. 铃木dl250参数_铃木DL250,铃木GSX250,铃木GW250重量多少?哪款最值得买?
  3. mUrlPrefixes内entry的population逻辑
  4. java 阻塞锁_Java实现锁、公平锁、读写锁、信号量、阻塞队列、线程池等常用并发工具...
  5. 云开发听说过没? Compilr 屌爆的在线开发工具 -_-#
  6. iPhone 12 5G更耗电?续航时间较4G妥妥地缩短不少
  7. 提取字符串中字母数字方法
  8. 简单的小愿望,就这么难实现
  9. 【U+】U+通用财务数据库测试失败,无法保存。
  10. 【新手指南】App原型设计:如何快速实现这6种交互效果?
  11. win的反义词_英语中最常见 反义词、近义词、同义词及词形转换。欢迎大家收藏...
  12. 模块定义图(BDD)
  13. PTA 1072 开学寄语
  14. AD7760数据采集系统设计 [FPGA逻辑设计]
  15. 微信步数日历打卡小程序
  16. springmvc全局异常处理
  17. 童年记忆中的优良环境
  18. LPMS-IMU姿态解算
  19. 在MSRA学习项目管理
  20. (jsp一)概述及服务器配置

热门文章

  1. 笔记本电脑查询已连接WIFI密码
  2. matlab算地形坡度,自然地形坡度分析、坡向分析的作用
  3. 徐玉玉案有感——安全测试有风险,且行且珍惜
  4. 排名不好如何保研外校(ee跨计算机、人工智能 中科大保研经验分享)
  5. 整车下线流程(EOL)测试解决方案介绍
  6. 加快发展职业教育 让每个人都有人生出彩机会
  7. ESP32使用双cpu同时工作测试-arduino开发环境
  8. 【PC用户请留步】流氓软件卸载不了,卸载后有残留,这个软件帮你搞定!
  9. win10 uwp 手把手教你使用 asp dotnet core 做 cs 程序
  10. 中国计算机学科建设,CCF杭州浙婺信息大讲堂:计算机学科建设与学术前沿