From 53387b5e6e978edbd510c0cd8094d629846bdfc0 Mon Sep 17 00:00:00 2001 From: Hui Zhang Date: Thu, 27 May 2021 20:24:04 +0800 Subject: [PATCH 1/3] add crf, eigen --- doc/images/prefix_beam_search.pdf | Bin 0 -> 79173 bytes doc/src/crf.md | 29 + doc/src/decoding.md | 5 + doc/src/eigen.md | 2050 +++++++++++++++++++++++++++++ doc/src/tts_text_frontend.md | 215 +++ 5 files changed, 2299 insertions(+) create mode 100644 doc/images/prefix_beam_search.pdf create mode 100644 doc/src/crf.md create mode 100644 doc/src/eigen.md create mode 100644 doc/src/tts_text_frontend.md diff --git a/doc/images/prefix_beam_search.pdf b/doc/images/prefix_beam_search.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f28a7b6ecb236d191b42be905a9966c8ba61ac07 GIT binary patch literal 79173 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 Q0WMBnOnQ1rWhu=61BQZaU;qFB literal 0 HcmV?d00001 diff --git a/doc/src/crf.md b/doc/src/crf.md new file mode 100644 index 00000000..faff2ead --- /dev/null +++ b/doc/src/crf.md @@ -0,0 +1,29 @@ +# CRF([Conditional Random Fields](http://blog.echen.me/2012/01/03/introduction-to-conditional-random-fields/)) + +## Repos + +* https://github.com/kmkurn/pytorch-crf +* https://github.com/allenai/allennlp +* https://github.com/mtreviso/linear-chain-crf + +## Reference + +* [Overview of Conditional Random Fields](https://medium.com/ml2vec/overview-of-conditional-random-fields-68a2a20fa541) +* [Introduction to Conditional Random Fields](http://blog.echen.me/2012/01/03/introduction-to-conditional-random-fields/)] +* [Viterbi algorithm](https://en.wikipedia.org/wiki/Viterbi_algorithm) +* [Conditional Random Field Tutorial in PyTorch](https://towardsdatascience.com/conditional-random-field-tutorial-in-pytorch-ca0d04499463) +* [Implementing a linear-chain Conditional Random Field (CRF) in PyTorch](https://towardsdatascience.com/implementing-a-linear-chain-conditional-random-field-crf-in-pytorch-16b0b9c4b4ea) + +* .https://homepages.inf.ed.ac.uk/csutton/publications/crftutv2.pdf +* [Implementing a linear-chain Conditional Random Field (CRF) in PyTorch](https://towardsdatascience.com/implementing-a-linear-chain-conditional-random-field-crf-in-pytorch-16b0b9c4b4ea) + + + +* http://www.cs.columbia.edu/~mcollins/ + +* [Tagging Problems, and Hidden Markov Models](http://www.cs.columbia.edu/~mcollins/hmms-spring2013.pdf) +* http://www.cs.columbia.edu/~mcollins/crf.pdf +* [The Forward-Backward Algorithm](http://www.cs.columbia.edu/~mcollins/fb.pdf) + +* [The Naive Bayes Model, Maximum-Likelihood Estimation, and the EM Algorithm](http://www.cs.columbia.edu/~mcollins/em.pdf) + diff --git a/doc/src/decoding.md b/doc/src/decoding.md index 347a4098..6eb77dd3 100644 --- a/doc/src/decoding.md +++ b/doc/src/decoding.md @@ -3,3 +3,8 @@ ## Reference * [时间戳和N-Best](https://mp.weixin.qq.com/s?__biz=MzU2NjUwMTgxOQ==&mid=2247483956&idx=1&sn=80ce595238d84155d50f08c0d52267d3&chksm=fcaacae0cbdd43f62b1da60c8e8671a9e0bb2aeee94f58751839b03a1c45b9a3889b96705080&scene=21#wechat_redirect) + +* [Viterbi algorithm](https://en.wikipedia.org/wiki/Viterbi_algorithm) + +* [如何通俗地讲解 viterbi 算法](https://www.zhihu.com/question/20136144) + diff --git a/doc/src/eigen.md b/doc/src/eigen.md new file mode 100644 index 00000000..143c8f6a --- /dev/null +++ b/doc/src/eigen.md @@ -0,0 +1,2050 @@ +# Eigen + +http://eigen.tuxfamily.org/dox/index.html + +## 简介 + +Eigen是C++中可以用来调用并进行矩阵计算的一个库,简单了说它就是一个c++版本的matlab包。 + +## 安装 + +下载eigen:http://eigen.tuxfamily.org/index.php?title=Main_Page#Download + +Eigen只包含头文件,因此它不需要实现编译,只需要你include到你的项目,指定好Eigen的头文件路径,编译项目即可。而且跨平台,当然这是必须的。 + +**方案一** + +下载后,解压得到文件夹中,Eigen子文件夹便是我们需要的全部;如果你想使用Eigen暂不支持的特性,可以使用unsupported子文件夹。可以把Eigen/unsupported复制到任何你需要的地方。 + +**方案二** + +安装改包,其实就是把Eigen/unsupported的内容复制到“/usr/local/include/eigen3”下。在解压的文件夹下,新建build_dir,执行。 + +``` + cd build_dir + cmake ../ + make install +``` + +详见INSTALL文件即可。 + +## 模块和头文件 + +Eigen库被分为一个Core模块和其他一些模块,每个模块有一些相应的头文件。 为了便于引用,Dense模块整合了一系列模块;Eigen模块整合了所有模块。一般情况下,`include` 就够了。 + +| Module | Header file | Contents | +| ----------- | --------------------------- | ------------------------------------------------------ | +| Core | #include | Matrix和Array类,基础的线性代数运算和数组操作 | +| Geometry | #include | 旋转、平移、缩放、2维和3维的各种变换 | +| LU | #include | 求逆,行列式,LU分解 | +| Cholesky | #include | LLT和LDLT Cholesky分解 | +| Householder | #include | 豪斯霍尔德变换,用于线性代数运算 | +| SVD | #include | SVD分解 | +| QR | #include | QR分解 | +| Eigenvalues | #include | 特征值,特征向量分解 | +| Sparse | #include | 稀疏矩阵的存储和一些基本的线性运算 | +| 稠密矩阵 | #include | 包含了Core/Geometry/LU/Cholesky/SVD/QR/Eigenvalues模块 | +| 矩阵 | #include | 包括Dense和Sparse(整合库) | + +## 一个简单的例子 + +``` +#include +#include +using Eigen::MatrixXd; +int main() +{ + MatrixXd m(2,2); + m(0,0) = 3; + m(1,0) = 2.5; + m(0,1) = -1; + m(1,1) = m(1,0) + m(0,1); + std::cout << m << std::endl; +} +``` + +编译并执行:`g++ main.cpp -I /usr/local/include/eigen3/ -o maincpp` + +``` + 3 -1 +2.5 1.5 +``` + +Eigen头文件定义了许多类型,所有的类型都在Eigen的命名空间内。MatrixXd代表的是任意大小(X*X)的矩阵,并且每个元素为double类型。 + +## 例2: 矩阵和向量 + +再看另一个例子 + +``` +#include +#include +using namespace Eigen; +using namespace std; +int main() +{ + MatrixXd m = MatrixXd::Random(3,3); + m = (m + MatrixXd::Constant(3,3,1.2)) * 50; + cout << "m =" << endl << m << endl; + VectorXd v(3); + v << 1, 2, 3; + cout << "m * v =" << endl << m * v << endl; +} +``` + +输出为: + +``` +m = + 94 89.8 43.5 +49.4 101 86.8 +88.3 29.8 37.8 +m * v = +404 +512 +261 +``` + +程序中定义了一个任意大小的矩阵,并用3`*`3的随机阵初始化。`MatrixXd::Constant`创建一个3*3的常量矩阵。 + +VectorXd表示列向量,并用*逗号初始化语法*来初始化。 + +在看同样功能的代码 + +``` +#include +#include +using namespace Eigen; +using namespace std; +int main() +{ + Matrix3d m = Matrix3d::Random(); + m = (m + Matrix3d::Constant(1.2)) * 50; + cout << "m =" << endl << m << endl; + Vector3d v(1,2,3); + + cout << "m * v =" << endl << m * v << endl; +} +``` + +MatrixXd表示是任意尺寸的矩阵,Matrix3d直接指定了3*3的大小。Vector3d也被直接初始化为[1,2,3]'的列向量。 + +使用固定大小的矩阵或向量有两个好处:编译更快;指定大小可以进行更为严格的检查。当然使用太多类别(Matrix3d、Matrix4d、Matrix5d...)会增加编译时间和可执行文件大小,原则建议使用4及以内的。 + +## Matrix类 + +在Eigen,所有的矩阵和向量都是**Matrix**模板类的对象,Vector只是一种特殊的矩阵(一行或者一列)。 + +Matrix有6个模板参数,主要使用前三个参数,剩下的有默认值。 + +``` +Matrix +``` + +Scalar是表示元素的类型,RowsAtCompileTime为矩阵的行,ColsAtCompileTime为矩阵的列。 + +库中提供了一些类型便于使用,比如: + +``` +typedef Matrix Matrix4f; +``` + +## Vectors向量 + +列向量 + +``` +typedef Matrix Vector3f; +``` + +行向量 + +``` +typedef Matrix RowVector2i; +``` + +## Dynamic + +Eigen不只限于已知大小(编译阶段)的矩阵,有些矩阵的尺寸是运行时确定的,于是引入了一个特殊的标识符:Dynamic + +``` +typedef Matrix MatrixXd; +typedef Matrix VectorXi; +Matrix +``` + +## 构造函数 + +默认的构造函数不执行任何空间分配,也不初始化矩阵的元素。 + +``` +Matrix3f a; +MatrixXf b; +``` + +这里,a是一个3*3的矩阵,分配了float[9]的空间,但未初始化内部元素;b是一个动态大小的矩阵,定义是未分配空间(0*0)。 + +指定大小的矩阵,只是分配相应大小的空间,未初始化元素。 + +``` +MatrixXf a(10,15); +VectorXf b(30); +``` + +这里,a是一个10*15的动态大小的矩阵,分配了空间但未初始化元素;b是一个30大小的向量,同样分配空间未初始化元素。 + +为了对固定大小和动态大小的矩阵提供统一的API,对指定大小的Matrix传递sizes也是合法的(传递也被忽略)。 + +``` +Matrix3f a(3,3); +``` + +可以用构造函数提供4以内尺寸的vector的初始化。 + +``` +Vector2d a(5.0, 6.0); +Vector3d b(5.0, 6.0, 7.0); +Vector4d c(5.0, 6.0, 7.0, 8.0); +``` + +## 获取元素 + +通过中括号获取元素,对于矩阵是:(行,列);对于向量,只是传递它的索引,以0为起始。 + +``` +#include +#include +using namespace Eigen; +int main() +{ + MatrixXd m(2,2); + m(0,0) = 3; + m(1,0) = 2.5; + m(0,1) = -1; + m(1,1) = m(1,0) + m(0,1); + std::cout << "Here is the matrix m:\n" << m << std::endl; + VectorXd v(2); + v(0) = 4; + v(1) = v(0) - 1; + std::cout << "Here is the vector v:\n" << v << std::endl; +} +``` + +输出 + +``` +Here is the matrix m: + 3 -1 +2.5 1.5 +Here is the vector v: +4 +3 +``` + +m(index)也可以用于获取矩阵元素,但取决于matrix的存储顺序,默认是按列存储的,当然也可以改为按行。 + +[]操作符可以用于向量元素的获取,但是不能用于matrix,因为C++中[]不能传递超过一个参数。 + +## 逗号初始化 + +``` +Matrix3f m; +m << 1, 2, 3, + 4, 5, 6, + 7, 8, 9; +std::cout << m; +``` + +## resizing + +matrix的大小可以通过rows()、cols()、size()获取,resize()可以重新调整动态matrix的大小。 + +``` +#include +#include +using namespace Eigen; +int main() +{ + MatrixXd m(2,5); + m.resize(4,3); + std::cout << "The matrix m is of size " + << m.rows() << "x" << m.cols() << std::endl; + std::cout << "It has " << m.size() << " coefficients" << std::endl; + VectorXd v(2); + v.resize(5); + std::cout << "The vector v is of size " << v.size() << std::endl; + std::cout << "As a matrix, v is of size " + << v.rows() << "x" << v.cols() << std::endl; +} +``` + +输出: + +``` +The matrix m is of size 4x3 +It has 12 coefficients +The vector v is of size 5 +As a matrix, v is of size 5x1 +``` + +如果matrix的实际大小不改变,resize函数不做任何操作。resize操作会执行析构函数:元素的值会被改变,如果不想改变执行 conservativeResize()。 + +为了统一API,所有的操作可用于指定大小的matrix,当然,实际中它不会改变大小。尝试去改变一个固定大小的matrix到一个不同的值,会出发警告失败。只有如下是合法的。 + +``` +#include +#include +using namespace Eigen; +int main() +{ + Matrix4d m; + m.resize(4,4); // no operation + std::cout << "The matrix m is of size " + << m.rows() << "x" << m.cols() << std::endl; +} +``` + +## assignment 和 resizing + +assignment(分配)是复制一个矩阵到另外一个,操作符=。Eigen会自动resize左变量大小等于右变量大小,比如: + +``` +MatrixXf a(2,2); +std::cout << "a is of size " << a.rows() << "x" << a.cols() << std::endl; +MatrixXf b(3,3); +a = b; +std::cout << "a is now of size " << a.rows() << "x" << a.cols() << std::endl; + +a is of size 2x2 +a is now of size 3x3 +``` + +当然,如果左边量是固定大小的,上面的resizing是不允许的。 + +## 固定尺寸 vs 动态尺寸 + +实际中,应该使用固定尺寸还是动态尺寸,简单的答案是:小的尺寸用固定的,大的尺寸用动态的。使用固定尺寸可以避免动态内存的开辟,固定尺寸只是一个普通数组。 + +``` +Matrix4f mymatrix;` 等价于 `float mymatrix[16]; +MatrixXf mymatrix(rows,columns);` 等价于 `float *mymatrix = new float[rows*columns]; +``` + +使用固定尺寸(<=4*4)需要编译前知道矩阵大小,而且对于足够大的尺寸,如大于32,固定尺寸的收益可以忽略不计,而且可能导致栈崩溃。而且基于环境,Eigen会对动态尺寸做优化(类似于std::vector) + +## 其他模板参数 + +上面只讨论了前三个参数,完整的模板参数如下: + +``` +Matrix +``` + +Options是一个比特标志位,这里,我们只介绍一种RowMajor,它表明matrix使用按行存储,默认是按列存储。`Matrix` + +MaxRowsAtCompileTime和MaxColsAtCompileTime表示在编译阶段矩阵的上限。主要是避免动态内存分配,使用数组。 + +``` +Matrix` 等价于 `float [12] +``` + +## 一些方便的定义 + +Eigen定义了一些类型 + +- MatrixNt = Matrix 特殊地有 MatrxXi = Matrix +- VectorNt = Matrix 比如 Vector2f = Matrix +- RowVectorNt = Matrix 比如 RowVector3d = Matrix + +N可以是2,3,4或X(Dynamic) + +t可以是i(int)、f(float)、d(double)、cf(complex)、cd(complex)等。 + +# 矩阵和向量的运算 + +提供一些概述和细节:关于矩阵、向量以及标量的运算。 + +## 介绍 + +Eigen提供了matrix/vector的运算操作,既包括重载了c++的算术运算符+/-/*,也引入了一些特殊的运算比如点乘dot、叉乘cross等。 + +对于Matrix类(matrix和vectors)这些操作只支持线性代数运算,比如:matrix1*matrix2表示矩阵的乘机,vetor+scalar是不允许的。如果你想执行非线性代数操作,请看下一篇(暂时放下)。 + +## 加减 + +左右两侧变量具有相同的尺寸(行和列),并且元素类型相同(Eigen不自动转化类型)操作包括: + +- 二元运算 + 如a+b +- 二元运算 - 如a-b +- 一元运算 - 如-a +- 复合运算 += 如a+=b +- 复合运算 -= 如a-=b + +``` +#include +#include +using namespace Eigen; +int main() +{ + Matrix2d a; + a << 1, 2, + 3, 4; + MatrixXd b(2,2); + b << 2, 3, + 1, 4; + std::cout << "a + b =\n" << a + b << std::endl; + std::cout << "a - b =\n" << a - b << std::endl; + std::cout << "Doing a += b;" << std::endl; + a += b; + std::cout << "Now a =\n" << a << std::endl; + Vector3d v(1,2,3); + Vector3d w(1,0,0); + std::cout << "-v + w - v =\n" << -v + w - v << std::endl; +} +``` + +输出: + +``` +a + b = +3 5 +4 8 +a - b = +-1 -1 + 2 0 +Doing a += b; +Now a = +3 5 +4 8 +-v + w - v = +-1 +-4 +-6 +``` + +## 标量乘法和除法 + +乘/除标量是非常简单的,如下: + +- 二元运算 * 如matrix*scalar +- 二元运算 * 如scalar*matrix +- 二元运算 / 如matrix/scalar +- 复合运算 *= 如matrix*=scalar +- 复合运算 /= 如matrix/=scalar + +``` +#include +#include +using namespace Eigen; +int main() +{ + Matrix2d a; + a << 1, 2, + 3, 4; + Vector3d v(1,2,3); + std::cout << "a * 2.5 =\n" << a * 2.5 << std::endl; + std::cout << "0.1 * v =\n" << 0.1 * v << std::endl; + std::cout << "Doing v *= 2;" << std::endl; + v *= 2; + std::cout << "Now v =\n" << v << std::endl; +} +``` + +结果 + +``` +a * 2.5 = +2.5 5 +7.5 10 +0.1 * v = +0.1 +0.2 +0.3 +Doing v *= 2; +Now v = +2 +4 +6 +``` + +## 表达式模板 + +这里简单介绍,在高级主题中会详细解释。在Eigen中,线性运算比如+不会对变量自身做任何操作,会返回一个“表达式对象”来描述被执行的计算。当整个表达式被评估完(一般是遇到=号),实际的操作才执行。 + +这样做主要是为了优化,比如 + +``` +VectorXf a(50), b(50), c(50), d(50); +... +a = 3*b + 4*c + 5*d; +``` + +Eigen会编译这段代码最终遍历一次即可运算完成。 + +``` +for(int i = 0; i < 50; ++i) + a[i] = 3*b[i] + 4*c[i] + 5*d[i]; +``` + +因此,我们不必要担心大的线性表达式的运算效率。 + +## 转置和共轭 + +![img](https://images2015.cnblogs.com/blog/532915/201701/532915-20170124234218331-958991885.png) 表示transpose转置 + +![img](https://images2015.cnblogs.com/blog/532915/201701/532915-20170124234219644-1435768692.png) 表示conjugate共轭 + +![img](https://images2015.cnblogs.com/blog/532915/201701/532915-20170124234220831-1674600657.png) 表示adjoint(共轭转置) 伴随矩阵 + +``` +MatrixXcf a = MatrixXcf::Random(2,2); +cout << "Here is the matrix a\n" << a << endl; +cout << "Here is the matrix a^T\n" << a.transpose() << endl; +cout << "Here is the conjugate of a\n" << a.conjugate() << endl; +cout << "Here is the matrix a^*\n" << a.adjoint() << endl; +``` + +输出 + +``` +Here is the matrix a + (-0.211,0.68) (-0.605,0.823) + (0.597,0.566) (0.536,-0.33) +Here is the matrix a^T + (-0.211,0.68) (0.597,0.566) +(-0.605,0.823) (0.536,-0.33) +Here is the conjugate of a + (-0.211,-0.68) (-0.605,-0.823) + (0.597,-0.566) (0.536,0.33) +Here is the matrix a^* + (-0.211,-0.68) (0.597,-0.566) +(-0.605,-0.823) (0.536,0.33) +``` + +对于实数矩阵,conjugate不执行任何操作,adjoint等价于transpose。 + +transpose和adjoint会简单的返回一个代理对象并不对本省做转置。如果执行 `b=a.transpose()` ,a不变,转置结果被赋值给b。如果执行 `a=a.transpose()` Eigen在转置结束之前结果会开始写入a,所以a的最终结果不一定等于a的转置。 + +``` +Matrix2i a; a << 1, 2, 3, 4; +cout << "Here is the matrix a:\n" << a << endl; +a = a.transpose(); // !!! do NOT do this !!! +cout << "and the result of the aliasing effect:\n" << a << endl; + +Here is the matrix a: +1 2 +3 4 +and the result of the aliasing effect: +1 2 +2 4 +``` + +这被称为“别名问题”。在debug模式,当assertions打开的情况加,这种常见陷阱可以被自动检测到。 + +对 `a=a.transpose()` 这种操作,可以执行in-palce转置。类似还有adjointInPlace。 + +``` +MatrixXf a(2,3); a << 1, 2, 3, 4, 5, 6; +cout << "Here is the initial matrix a:\n" << a << endl; +a.transposeInPlace(); +cout << "and after being transposed:\n" << a << endl; + +Here is the initial matrix a: +1 2 3 +4 5 6 +and after being transposed: +1 4 +2 5 +3 6 +``` + +## 矩阵-矩阵的乘法和矩阵-向量的乘法 + +向量也是一种矩阵,实质都是矩阵-矩阵的乘法。 + +- 二元运算 *如a*b +- 复合运算 *=如a*=b + +``` +#include +#include +using namespace Eigen; +int main() +{ + Matrix2d mat; + mat << 1, 2, + 3, 4; + Vector2d u(-1,1), v(2,0); + std::cout << "Here is mat*mat:\n" << mat*mat << std::endl; + std::cout << "Here is mat*u:\n" << mat*u << std::endl; + std::cout << "Here is u^T*mat:\n" << u.transpose()*mat << std::endl; + std::cout << "Here is u^T*v:\n" << u.transpose()*v << std::endl; + std::cout << "Here is u*v^T:\n" << u*v.transpose() << std::endl; + std::cout << "Let's multiply mat by itself" << std::endl; + mat = mat*mat; + std::cout << "Now mat is mat:\n" << mat << std::endl; +} +``` + +输出 + +``` +Here is mat*mat: + 7 10 +15 22 +Here is mat*u: +1 +1 +Here is u^T*mat: +2 2 +Here is u^T*v: +-2 +Here is u*v^T: +-2 -0 + 2 0 +Let's multiply mat by itself +Now mat is mat: + 7 10 +15 22 +``` + +`m=m*m`并不会导致别名问题,Eigen在这里做了特殊处理,引入了临时变量。实质将编译为: + +``` +tmp = m*m +m = tmp +``` + +如果你确定矩阵乘法是安全的(并没有别名问题),你可以使用noalias()函数来避免临时变量 `c.noalias() += a*b` 。 + +## 点运算和叉运算 + +dot()执行点积,cross()执行叉积,点运算得到1*1的矩阵。当然,点运算也可以用u.adjoint()*v来代替。 + +``` +#include +#include +using namespace Eigen; +using namespace std; +int main() +{ + Vector3d v(1,2,3); + Vector3d w(0,1,2); + cout << "Dot product: " << v.dot(w) << endl; + double dp = v.adjoint()*w; // automatic conversion of the inner product to a scalar + cout << "Dot product via a matrix product: " << dp << endl; + cout << "Cross product:\n" << v.cross(w) << endl; +} +``` + +输出 + +``` +Dot product: 8 +Dot product via a matrix product: 8 +Cross product: + 1 +-2 + 1 +``` + +注意:点积只对三维vector有效。对于复数,Eigen的点积是第一个变量共轭和第二个变量的线性积。 + +## 基础的归约操作 + +Eigen提供了而一些归约函数:sum()、prod()、maxCoeff()和minCoeff(),他们对所有元素进行操作。 + +``` +#include +#include +using namespace std; +int main() +{ + Eigen::Matrix2d mat; + mat << 1, 2, + 3, 4; + cout << "Here is mat.sum(): " << mat.sum() << endl; + cout << "Here is mat.prod(): " << mat.prod() << endl; + cout << "Here is mat.mean(): " << mat.mean() << endl; + cout << "Here is mat.minCoeff(): " << mat.minCoeff() << endl; + cout << "Here is mat.maxCoeff(): " << mat.maxCoeff() << endl; + cout << "Here is mat.trace(): " << mat.trace() << endl; +} +``` + +输出 + +``` +Here is mat.sum(): 10 +Here is mat.prod(): 24 +Here is mat.mean(): 2.5 +Here is mat.minCoeff(): 1 +Here is mat.maxCoeff(): 4 +Here is mat.trace(): 5 +``` + +trace表示矩阵的迹,对角元素的和等价于 `a.diagonal().sum()` 。 + +minCoeff和maxCoeff函数也可以返回结果元素的位置信息。 + +``` +Matrix3f m = Matrix3f::Random(); + std::ptrdiff_t i, j; + float minOfM = m.minCoeff(&i,&j); + cout << "Here is the matrix m:\n" << m << endl; + cout << "Its minimum coefficient (" << minOfM + << ") is at position (" << i << "," << j << ")\n\n"; + RowVector4i v = RowVector4i::Random(); + int maxOfV = v.maxCoeff(&i); + cout << "Here is the vector v: " << v << endl; + cout << "Its maximum coefficient (" << maxOfV + << ") is at position " << i << endl; +``` + +输出 + +``` +Here is the matrix m: + 0.68 0.597 -0.33 +-0.211 0.823 0.536 + 0.566 -0.605 -0.444 +Its minimum coefficient (-0.605) is at position (2,1) + +Here is the vector v: 1 0 3 -3 +Its maximum coefficient (3) is at position 2 +``` + +## 操作的有效性 + +Eigen会检测执行操作的有效性,在编译阶段Eigen会检测它们,错误信息是繁冗的,但错误信息会大写字母突出,比如: + +``` +Matrix3f m; +Vector4f v; +v = m*v; // Compile-time error: YOU_MIXED_MATRICES_OF_DIFFERENT_SIZES +``` + +当然动态尺寸的错误要在运行时发现,如果在debug模式,assertions会触发后,程序将崩溃。 + +``` +MatrixXf m(3,3); +VectorXf v(4); +v = m * v; // Run-time assertion failure here: "invalid matrix product" +``` + +# Array类和元素级操作 + +## 为什么使用Array + +相对于Matrix提供的线性代数运算,Array类提供了更为一般的数组功能。Array类为元素级的操作提供了有效途径,比如点加(每个元素加值)或两个数据相应元素的点乘。 + +## Array + +Array是个类模板(类似于Matrx),前三个参数是必须指定的,后三个是可选的,这点和Matrix是相同的。 + +``` +Array +``` + +Eigen也提供的一些常用类定义,Array是同时支持一维和二维的(Matrix二维,Vector一维)。 + +| Type | Tyoedef | +| ----------------------------- | -------- | +| Array | ArrayXf | +| Array | Array3f | +| Array | ArrayXXd | +| Array | Array33d | + +## 获取元素 + +读写操作重载于matrix, `<<` 可以用于初始化array或打印。 + +``` +#include +#include +using namespace Eigen; +using namespace std; +int main() +{ + ArrayXXf m(2,2); + + // assign some values coefficient by coefficient + m(0,0) = 1.0; m(0,1) = 2.0; + m(1,0) = 3.0; m(1,1) = m(0,1) + m(1,0); + + // print values to standard output + cout << m << endl << endl; + + // using the comma-initializer is also allowed + m << 1.0,2.0, + 3.0,4.0; + + // print values to standard output + cout << m << endl; +} +``` + +## 加法和减法 + +和matrix类似,要求array的尺寸一致。同时支持`array+/-scalar`的操作! + +``` +#include +#include +using namespace Eigen; +using namespace std; +int main() +{ + ArrayXXf a(3,3); + ArrayXXf b(3,3); + a << 1,2,3, + 4,5,6, + 7,8,9; + b << 1,2,3, + 1,2,3, + 1,2,3; + + // Adding two arrays + cout << "a + b = " << endl << a + b << endl << endl; + // Subtracting a scalar from an array + cout << "a - 2 = " << endl << a - 2 << endl; +} +``` + +输出 + +``` +a + b = + 2 4 6 + 5 7 9 + 8 10 12 + +a - 2 = +-1 0 1 + 2 3 4 + 5 6 7 +``` + +## 乘法 + +支持array*scalar(类似于matrix),但是当执行array*array时,执行的是相应元素的乘积,因此两个array必须具有相同的尺寸。 + +``` +int main() +{ + ArrayXXf a(2,2); + ArrayXXf b(2,2); + a << 1,2, + 3,4; + b << 5,6, + 7,8; + cout << "a * b = " << endl << a * b << endl; +} + +a * b = + 5 12 +21 32 +``` + +## 其他元素级操作 + +| Function | function | +| -------- | ------------------------- | +| abs | 绝对值 | +| sqrt | 平方根 | +| min(.) | 两个array相应元素的最小值 | + +``` +int main() +{ + ArrayXf a = ArrayXf::Random(5); + a *= 2; + cout << "a =" << endl + << a << endl; + cout << "a.abs() =" << endl + << a.abs() << endl; + cout << "a.abs().sqrt() =" << endl + << a.abs().sqrt() << endl; + cout << "a.min(a.abs().sqrt()) =" << endl + << a.min(a.abs().sqrt()) << endl; +} +``` + +## array和matrix之间的转换 + +当需要线性代数类操作时,请使用Matrix;但需要元素级操作时,需要使用Array。这样就需要提供两者的转化方法。 + +Matrix提供了.array()函数将它们转化为Array对象。 + +Array提供了.matrix()函数将它们转化为Matrix对象。 + +在Eigen,在表达式中混合Matrix和Array操作是被禁止的,但是可以将array表达式结果赋值为matrix。 + +另外,Matrix提供了cwiseProduct函数也实现了点乘。 + +``` +#include +#include +using namespace Eigen; +using namespace std; +int main() +{ + MatrixXf m(2,2); + MatrixXf n(2,2); + MatrixXf result(2,2); + m << 1,2, + 3,4; + n << 5,6, + 7,8; + result = m * n; + cout << "-- Matrix m*n: --" << endl << result << endl << endl; + result = m.array() * n.array(); + cout << "-- Array m*n: --" << endl << result << endl << endl; + result = m.cwiseProduct(n); + cout << "-- With cwiseProduct: --" << endl << result << endl << endl; + result = m.array() + 4; + cout << "-- Array m + 4: --" << endl << result << endl << endl; +} +``` + +输出 + +``` +-- Matrix m*n: -- +19 22 +43 50 + +-- Array m*n: -- + 5 12 +21 32 + +-- With cwiseProduct: -- + 5 12 +21 32 + +-- Array m + 4: -- +5 6 +7 8 +``` + +类似, `array1.matrix() * array2.matrix()` 将执行矩阵乘法。 + +# 块操作 + +块是matrix或array中的矩形子部分。 + +## 使用块 + +函数.block(),有两种形式 + +| operation | 构建一个动态尺寸的block | 构建一个固定尺寸的block | +| -------------------- | ----------------------- | ----------------------- | +| 起点(i,j)块大小(p,q) | .block(i,j,p,q) | .block< p,q >(i,j) | + +Eigen中,索引从0开始。 + +两个版本都可以用于固定尺寸和动态尺寸的matrix/array。功能是等价的,只是固定尺寸的版本在block较小时速度更快一些。 + +``` +int main() +{ + Eigen::MatrixXf m(4,4); + m << 1, 2, 3, 4, + 5, 6, 7, 8, + 9,10,11,12, + 13,14,15,16; + cout << "Block in the middle" << endl; + cout << m.block<2,2>(1,1) << endl << endl; + for (int i = 1; i <= 3; ++i) + { + cout << "Block of size " << i << "x" << i << endl; + cout << m.block(0,0,i,i) << endl << endl; + } +} +``` + +输出 + +``` +Block in the middle + 6 7 +10 11 + +Block of size 1x1 +1 + +Block of size 2x2 +1 2 +5 6 + +Block of size 3x3 + 1 2 3 + 5 6 7 + 9 10 11 +``` + +作为左值 + +``` +int main() +{ + Array22f m; + m << 1,2, + 3,4; + Array44f a = Array44f::Constant(0.6); + cout << "Here is the array a:" << endl << a << endl << endl; + a.block<2,2>(1,1) = m; + cout << "Here is now a with m copied into its central 2x2 block:" << endl << a << endl << endl; + a.block(0,0,2,3) = a.block(2,1,2,3); + cout << "Here is now a with bottom-right 2x3 block copied into top-left 2x2 block:" << endl << a << endl << endl; +} +``` + +输出 + +``` +Here is the array a: +0.6 0.6 0.6 0.6 +0.6 0.6 0.6 0.6 +0.6 0.6 0.6 0.6 +0.6 0.6 0.6 0.6 + +Here is now a with m copied into its central 2x2 block: +0.6 0.6 0.6 0.6 +0.6 1 2 0.6 +0.6 3 4 0.6 +0.6 0.6 0.6 0.6 + +Here is now a with bottom-right 2x3 block copied into top-left 2x2 block: + 3 4 0.6 0.6 +0.6 0.6 0.6 0.6 +0.6 3 4 0.6 +0.6 0.6 0.6 0.6 +``` + +## 行和列 + +| Operation | Method | +| --------- | ------------- | +| ith row | matrix.row(i) | +| jth colum | matrix.col(j) | + +``` +int main() +{ + Eigen::MatrixXf m(3,3); + m << 1,2,3, + 4,5,6, + 7,8,9; + cout << "Here is the matrix m:" << endl << m << endl; + cout << "2nd Row: " << m.row(1) << endl; + m.col(2) += 3 * m.col(0); + cout << "After adding 3 times the first column into the third column, the matrix m is:\n"; + cout << m << endl; +} +``` + +输出 + +``` +Here is the matrix m: +1 2 3 +4 5 6 +7 8 9 +2nd Row: 4 5 6 +After adding 3 times the first column into the third column, the matrix m is: + 1 2 6 + 4 5 18 + 7 8 30 +``` + +## 角相关操作 + +| operation | dynamic-size block | fixed-size block | +| ---------- | ------------------------------ | ---------------------------------- | +| 左上角p\*q | matrix.topLeftCorner(p,q); | matrix.topLeftCorner< p,q >(); | +| 左下角p\*q | matrix.bottomLeftCorner(p,q); | matrix.bottomLeftCorner< p,q >(); | +| 右上角p\*q | matrix.topRightCorner(p,q); | matrix.topRightCorner< p,q >(); | +| 右下角p\*q | matrix.bottomRightCorner(p,q); | matrix.bottomRightCorner< p,q >(); | +| 前q行 | matrix.topRows(q); | matrix.topRows< q >(); | +| 后q行 | matrix.bottomRows(q); | matrix.bottomRows< q >(); | +| 左p列 | matrix.leftCols(p); | matrix.leftCols< p >(); | +| 右p列 | matrix.rightCols(p); | matrix.rightCols< p >(); | + +``` +int main() +{ + Eigen::Matrix4f m; + m << 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10,11,12, + 13,14,15,16; + cout << "m.leftCols(2) =" << endl << m.leftCols(2) << endl << endl; + cout << "m.bottomRows<2>() =" << endl << m.bottomRows<2>() << endl << endl; + m.topLeftCorner(1,3) = m.bottomRightCorner(3,1).transpose(); + cout << "After assignment, m = " << endl << m << endl; +} +``` + +输出 + +``` +m.leftCols(2) = + 1 2 + 5 6 + 9 10 +13 14 + +m.bottomRows<2>() = + 9 10 11 12 +13 14 15 16 + +After assignment, m = + 8 12 16 4 + 5 6 7 8 + 9 10 11 12 +13 14 15 16 +``` + +## vectors的块操作 + +| operation | dynamic-size block | fixed-size block | +| -------------- | -------------------- | ----------------------- | +| 前n个 | vector.head(n); | vector.head< n >(); | +| 后n个 | vector.tail(n); | vector.tail< n >(); | +| i起始的n个元素 | vector.segment(i,n); | vector.segment< n >(i); | + +# 高级初始化方法 + +本篇介绍几种高级的矩阵初始化方法,重点介绍逗号初始化和特殊矩阵(单位阵、零阵)。 + +## 逗号初始化 + +Eigen提供了逗号操作符允许我们方便地为矩阵/向量/数组中的元素赋值。顺序是从左上到右下:自左到右,从上至下。对象的尺寸需要事先指定,初始化的参数也应该和要操作的元素数目一致。 + +``` +Matrix3f m; +m << 1, 2, 3, + 4, 5, 6, + 7, 8, 9; +std::cout << m; +``` + +初始化列表不仅可以是数值也可以是vectors或matrix。 + +``` +RowVectorXd vec1(3); +vec1 << 1, 2, 3; +std::cout << "vec1 = " << vec1 << std::endl; +RowVectorXd vec2(4); +vec2 << 1, 4, 9, 16; +std::cout << "vec2 = " << vec2 << std::endl; +RowVectorXd joined(7); +joined << vec1, vec2; +std::cout << "joined = " << joined << std::endl; +``` + +输出 + +``` +vec1 = 1 2 3 +vec2 = 1 4 9 16 +joined = 1 2 3 1 4 9 16 +``` + +也可以使用块结构。 + +``` +MatrixXf matA(2, 2); +matA << 1, 2, 3, 4; +MatrixXf matB(4, 4); +matB << matA, matA/10, matA/10, matA; +std::cout << matB << std::endl; +``` + +输出 + +``` + 1 2 0.1 0.2 + 3 4 0.3 0.4 +0.1 0.2 1 2 +0.3 0.4 3 4 +``` + +同时逗号初始化方式也可以用来为块表达式赋值。 + +``` +Matrix3f m; +m.row(0) << 1, 2, 3; +m.block(1,0,2,2) << 4, 5, 7, 8; +m.col(2).tail(2) << 6, 9; +std::cout << m; + +1 2 3 +4 5 6 +7 8 9 +``` + +## 特殊的矩阵和向量 + +零阵:类的静态成员函数Zero(),有三种定义形式。 + +``` +std::cout << "A fixed-size array:\n"; +Array33f a1 = Array33f::Zero(); +std::cout << a1 << "\n\n"; +std::cout << "A one-dimensional dynamic-size array:\n"; +ArrayXf a2 = ArrayXf::Zero(3); +std::cout << a2 << "\n\n"; +std::cout << "A two-dimensional dynamic-size array:\n"; +ArrayXXf a3 = ArrayXXf::Zero(3, 4); +std::cout << a3 << "\n"; +``` + +输出 + +``` +A fixed-size array: +0 0 0 +0 0 0 +0 0 0 + +A one-dimensional dynamic-size array: +0 +0 +0 + +A two-dimensional dynamic-size array: +0 0 0 0 +0 0 0 0 +0 0 0 0 +``` + +类似地,还有常量矩阵:Constant([rows],[cols],value),Random()随机矩阵。 + +单位阵Identity()方法只能使用与Matrix不使用Array,因为单位阵是个线性代数概念。 + +LinSpaced(size, low, high)可以从low到high等间距的size长度的序列,适用于vector和一维数组。 + +``` +ArrayXXf table(10, 4); +table.col(0) = ArrayXf::LinSpaced(10, 0, 90); +table.col(1) = M_PI / 180 * table.col(0); +table.col(2) = table.col(1).sin(); +table.col(3) = table.col(1).cos(); +std::cout << " Degrees Radians Sine Cosine\n"; +std::cout << table << std::endl; +``` + +输出 + +``` + Degrees Radians Sine Cosine + 0 0 0 1 + 10 0.175 0.174 0.985 + 20 0.349 0.342 0.94 + 30 0.524 0.5 0.866 + 40 0.698 0.643 0.766 + 50 0.873 0.766 0.643 + 60 1.05 0.866 0.5 + 70 1.22 0.94 0.342 + 80 1.4 0.985 0.174 + 90 1.57 1 -4.37e-08 +``` + +### 功能函数 + +Eigen也提供可同样功能的函数:setZero(), MatrixBase::setIdentity()和 DenseBase::setLinSpaced()。 + +``` +const int size = 6; +MatrixXd mat1(size, size); +mat1.topLeftCorner(size/2, size/2) = MatrixXd::Zero(size/2, size/2); +mat1.topRightCorner(size/2, size/2) = MatrixXd::Identity(size/2, size/2); +mat1.bottomLeftCorner(size/2, size/2) = MatrixXd::Identity(size/2, size/2); +mat1.bottomRightCorner(size/2, size/2) = MatrixXd::Zero(size/2, size/2); +std::cout << mat1 << std::endl << std::endl; +MatrixXd mat2(size, size); +mat2.topLeftCorner(size/2, size/2).setZero(); +mat2.topRightCorner(size/2, size/2).setIdentity(); +mat2.bottomLeftCorner(size/2, size/2).setIdentity(); +mat2.bottomRightCorner(size/2, size/2).setZero(); +std::cout << mat2 << std::endl << std::endl; +MatrixXd mat3(size, size); +mat3 << MatrixXd::Zero(size/2, size/2), MatrixXd::Identity(size/2, size/2), + MatrixXd::Identity(size/2, size/2), MatrixXd::Zero(size/2, size/2); +std::cout << mat3 << std::endl; +``` + +输出均为 + +``` +0 0 0 1 0 0 +0 0 0 0 1 0 +0 0 0 0 0 1 +1 0 0 0 0 0 +0 1 0 0 0 0 +0 0 1 0 0 0 +``` + +三种赋值(初始化)的方式逗号初始化、特殊阵的静态方法和功能函数setXxx()。 + +## 表达式变量 + +上面的静态方法如 Zero()、Constant()并不是直接返回一个矩阵或数组,实际上它们返回的是是‘expression object’,只是临时被使用/被用于优化。 + +``` +m = (m + MatrixXd::Constant(3,3,1.2)) * 50; +``` + +`MatrixXf::Constant(3,3,1.2)`构建的是一个3*3的矩阵表达式(临时变量)。 + +逗号初始化的方式也可以构建这种临时变量,这是为了获取真正的矩阵需要调用finished()函数: + +``` +MatrixXf mat = MatrixXf::Random(2, 3); +std::cout << mat << std::endl << std::endl; +mat = (MatrixXf(2,2) << 0, 1, 1, 0).finished() * mat; +std::cout << mat << std::endl; +``` + +输出 + +``` + 0.68 0.566 0.823 +-0.211 0.597 -0.605 + +-0.211 0.597 -0.605 + 0.68 0.566 0.823 +``` + +# 归约、迭代器和广播 + +## 归约 + +在Eigen中,有些函数可以统计matrix/array的某类特征,返回一个标量。 + +``` +int main() +{ + Eigen::Matrix2d mat; + mat << 1, 2, + 3, 4; + cout << "Here is mat.sum(): " << mat.sum() << endl; + cout << "Here is mat.prod(): " << mat.prod() << endl; + cout << "Here is mat.mean(): " << mat.mean() << endl; + cout << "Here is mat.minCoeff(): " << mat.minCoeff() << endl; + cout << "Here is mat.maxCoeff(): " << mat.maxCoeff() << endl; + cout << "Here is mat.trace(): " << mat.trace() << endl; +} +``` + +### 范数计算 + +L2范数 squareNorm(),等价于计算vector的自身点积,norm()返回squareNorm的开方根。 + +这些操作应用于matrix,norm() 会返回Frobenius或Hilbert-Schmidt范数。 + +如果你想使用其他Lp范数,可以使用lpNorm< p >()方法。p可以取Infinity,表示L∞范数。 + +``` +int main() +{ + VectorXf v(2); + MatrixXf m(2,2), n(2,2); + + v << -1, + 2; + + m << 1,-2, + -3,4; + cout << "v.squaredNorm() = " << v.squaredNorm() << endl; + cout << "v.norm() = " << v.norm() << endl; + cout << "v.lpNorm<1>() = " << v.lpNorm<1>() << endl; + cout << "v.lpNorm() = " << v.lpNorm() << endl; + cout << endl; + cout << "m.squaredNorm() = " << m.squaredNorm() << endl; + cout << "m.norm() = " << m.norm() << endl; + cout << "m.lpNorm<1>() = " << m.lpNorm<1>() << endl; + cout << "m.lpNorm() = " << m.lpNorm() << endl; +} +``` + +输出 + +``` +v.squaredNorm() = 5 +v.norm() = 2.23607 +v.lpNorm<1>() = 3 +v.lpNorm() = 2 + +m.squaredNorm() = 30 +m.norm() = 5.47723 +m.lpNorm<1>() = 10 +m.lpNorm() = 4 +``` + +**Operator norm**: 1-norm和∞-norm可以通过其他方式得到。 + +``` +int main() +{ + MatrixXf m(2,2); + m << 1,-2, + -3,4; + cout << "1-norm(m) = " << m.cwiseAbs().colwise().sum().maxCoeff() + << " == " << m.colwise().lpNorm<1>().maxCoeff() << endl; + cout << "infty-norm(m) = " << m.cwiseAbs().rowwise().sum().maxCoeff() + << " == " << m.rowwise().lpNorm<1>().maxCoeff() << endl; +} + +1-norm(m) = 6 == 6 +infty-norm(m) = 7 == 7 +``` + +### 布尔归约 + +all()=true matrix/array中的所有算术是true any()=true matrix/array中至少有一个元素是true count() 返回为true元素的数目 + +``` +#include +#include +using namespace std; +using namespace Eigen; +int main() +{ + ArrayXXf a(2,2); + + a << 1,2, + 3,4; + cout << "(a > 0).all() = " << (a > 0).all() << endl; + cout << "(a > 0).any() = " << (a > 0).any() << endl; + cout << "(a > 0).count() = " << (a > 0).count() << endl; + cout << endl; + cout << "(a > 2).all() = " << (a > 2).all() << endl; + cout << "(a > 2).any() = " << (a > 2).any() << endl; + cout << "(a > 2).count() = " << (a > 2).count() << endl; +} +``` + +输出 + +``` +(a > 0).all() = 1 +(a > 0).any() = 1 +(a > 0).count() = 4 + +(a > 2).all() = 0 +(a > 2).any() = 1 +(a > 2).count() = 2 +``` + +## 迭代器(遍历) + +当我们想获取某元素在Matrix或Array中的位置的时候,迭代器是必须的。常用的有:minCoeff和maxCoeff。 + +``` +int main() +{ + Eigen::MatrixXf m(2,2); + + m << 1, 2, + 3, 4; + //get location of maximum + MatrixXf::Index maxRow, maxCol; + float max = m.maxCoeff(&maxRow, &maxCol); + //get location of minimum + MatrixXf::Index minRow, minCol; + float min = m.minCoeff(&minRow, &minCol); + cout << "Max: " << max << ", at: " << + maxRow << "," << maxCol << endl; + cout << "Min: " << min << ", at: " << + minRow << "," << minCol << endl; +} + +Max: 4, at: 1,1 +Min: 1, at: 0,0 +``` + +## 部分归约 + +Eigen中支持对Matrx或Array的行/行进行归约操作。部分归约可以使用colwise()/rowwise()函数。 + +``` +int main() +{ + Eigen::MatrixXf mat(2,4); + mat << 1, 2, 6, 9, + 3, 1, 7, 2; + + std::cout << "Column's maximum: " << std::endl + << mat.colwise().maxCoeff() << std::endl; +} + +Column's maximum: +3 2 7 9 +``` + +类似,针对行也可以,只是返回的是列向量而已。 + +``` +int main() +{ + Eigen::MatrixXf mat(2,4); + mat << 1, 2, 6, 9, + 3, 1, 7, 2; + + std::cout << "Row's maximum: " << std::endl + << mat.rowwise().maxCoeff() << std::endl; +} + +Row's maximum: +9 +7 +``` + +## 结合部分归约和其他操作 + +例子:寻找和最大的列向量。 + +``` +int main() +{ + MatrixXf mat(2,4); + mat << 1, 2, 6, 9, + 3, 1, 7, 2; + + MatrixXf::Index maxIndex; + float maxNorm = mat.colwise().sum().maxCoeff(&maxIndex); + + std::cout << "Maximum sum at position " << maxIndex << std::endl; + std::cout << "The corresponding vector is: " << std::endl; + std::cout << mat.col( maxIndex ) << std::endl; + std::cout << "And its sum is is: " << maxNorm << std::endl; +} +``` + +输出 + +``` +Maximum sum at position 2 +The corresponding vector is: +6 +7 +And its sum is is: 13 +``` + +## 广播 + +广播是针对vector的,将vector沿行/列重复构建一个matrix,便于后期运算。 + +``` +int main() +{ + Eigen::MatrixXf mat(2,4); + Eigen::VectorXf v(2); + + mat << 1, 2, 6, 9, + 3, 1, 7, 2; + + v << 0, + 1; + + //add v to each column of m + mat.colwise() += v; + + std::cout << "Broadcasting result: " << std::endl; + std::cout << mat << std::endl; +} +``` + +输出 + +``` +Broadcasting result: +1 2 6 9 +4 2 8 3 +``` + +注意:对Array类型,*=,/=和/这些操作可以进行行/列级的操作,但不使用与Matrix,因为会与矩阵乘混淆。 + +## 结合广播和其他操作 + +示例:计算矩阵中哪列与目标向量距离最近。 + +``` +int main() +{ + Eigen::MatrixXf m(2,4); + Eigen::VectorXf v(2); + + m << 1, 23, 6, 9, + 3, 11, 7, 2; + + v << 2, + 3; + MatrixXf::Index index; + // find nearest neighbour + (m.colwise() - v).colwise().squaredNorm().minCoeff(&index); + cout << "Nearest neighbour is column " << index << ":" << endl; + cout << m.col(index) << endl; +} +``` + +输出 + +``` +Nearest neighbour is column 0: +1 +3 +``` + +# 原生缓存的接口:Map类 + +这篇将解释Eigen如何与原生raw C/C++ 数组混合编程。 + +## 简介 + +Eigen中定义了一系列的vector和matrix,相比copy数据,更一般的方式是复用数据的内存,将它们转变为Eigen类型。Map类很好地实现了这个功能。 + +## Map类型 + +Map的定义 + +``` +Map > +``` + +默认情况下,Mao只需要一个模板参数。 + +为了构建Map变量,我们需要其余的两个信息:一个指向元素数组的指针,Matrix/vector的尺寸。定义一个float类型的矩阵: `Map mf(pf,rows,columns);` pf是一个数组指针float *。 + +固定尺寸的整形vector声明: `Map mi(pi);` + +注意:Map没有默认的构造函数,你需要传递一个指针来初始化对象。 + +Mat是灵活地足够去容纳多种不同的数据表示,其他的两个模板参数: + +``` +Map +``` + +MapOptions标识指针是否是对齐的(Aligned),默认是Unaligned。 + +StrideType表示内存数组的组织方式:行列的步长。 + +``` +int array[8]; +for(int i = 0; i < 8; ++i) array[i] = i; +cout << "Column-major:\n" << Map >(array) << endl; +cout << "Row-major:\n" << Map >(array) << endl; +cout << "Row-major using stride:\n" << + Map, Unaligned, Stride<1,4> >(array) << endl; +``` + +输出 + +``` +Column-major: +0 2 4 6 +1 3 5 7 +Row-major: +0 1 2 3 +4 5 6 7 +Row-major using stride: +0 1 2 3 +4 5 6 7 +``` + +## 使用Map变量 + +可以像Eigen的其他类型一样来使用Map类型。 + +``` +typedef Matrix MatrixType; +typedef Map MapType; +typedef Map MapTypeConst; // a read-only map +const int n_dims = 5; + +MatrixType m1(n_dims), m2(n_dims); +m1.setRandom(); +m2.setRandom(); +float *p = &m2(0); // get the address storing the data for m2 +MapType m2map(p,m2.size()); // m2map shares data with m2 +MapTypeConst m2mapconst(p,m2.size()); // a read-only accessor for m2 +cout << "m1: " << m1 << endl; +cout << "m2: " << m2 << endl; +cout << "Squared euclidean distance: " << (m1-m2).squaredNorm() << endl; +cout << "Squared euclidean distance, using map: " << + (m1-m2map).squaredNorm() << endl; +m2map(3) = 7; // this will change m2, since they share the same array +cout << "Updated m2: " << m2 << endl; +cout << "m2 coefficient 2, constant accessor: " << m2mapconst(2) << endl; +/* m2mapconst(2) = 5; */ // this yields a compile-time error +``` + +输出 + +``` +m1: 0.68 -0.211 0.566 0.597 0.823 +m2: -0.605 -0.33 0.536 -0.444 0.108 +Squared euclidean distance: 3.26 +Squared euclidean distance, using map: 3.26 +Updated m2: -0.605 -0.33 0.536 7 0.108 +m2 coefficient 2, constant accessor: 0.536 +``` + +Eigen提供的函数都兼容Map对象。 + +## 改变mapped数组 + +Map对象声明后,可以通过C++的placement new语法来改变Map的数组。 + +``` +int data[] = {1,2,3,4,5,6,7,8,9}; +Map v(data,4); +cout << "The mapped vector v is: " << v << "\n"; +new (&v) Map(data+4,5); +cout << "Now v is: " << v << "\n"; + +The mapped vector v is: 1 2 3 4 +Now v is: 5 6 7 8 9 +``` + +Eigen并没有为matrix提供直接的Reshape和Slicing的API,但是这些特性可以通过Map类来实现。 + +## Reshape + +reshape操作是改变matrix的尺寸大小但保持元素不变。采用的方法是创建一个不同“视图” Map。 + +``` +MatrixXf M1(3,3); // Column-major storage +M1 << 1, 2, 3, + 4, 5, 6, + 7, 8, 9; +Map v1(M1.data(), M1.size()); +cout << "v1:" << endl << v1 << endl; +Matrix M2(M1); +Map v2(M2.data(), M2.size()); +cout << "v2:" << endl << v2 << endl; +``` + +输出 + +``` +v1: +1 4 7 2 5 8 3 6 9 +v2: +1 2 3 4 5 6 7 8 9 +``` + +reshape 2*6的矩阵到 6*2 + +``` +MatrixXf M1(2,6); // Column-major storage +M1 << 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12; +Map M2(M1.data(), 6,2); +cout << "M2:" << endl << M2 << endl; +``` + +输出 + +``` +M2: + 1 4 + 7 10 + 2 5 + 8 11 + 3 6 + 9 12 +``` + +## Slicing + +也是通过Map实现的,比如:每p个元素获取一个。 + +``` +RowVectorXf v = RowVectorXf::LinSpaced(20,0,19); +cout << "Input:" << endl << v << endl; +Map > v2(v.data(), v.size()/2); +cout << "Even:" << v2 << endl; +``` + +输出 + +``` +Input: + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +Even: 0 2 4 6 8 10 12 14 16 18 +``` + +## 混淆 + +在Eigen中,当变量同时出现在左值和右值,赋值操作可能会带来混淆问题。这一篇将解释什么是混淆,什么时候是有害的,怎么使用做。 + +## 例子 + +``` +MatrixXi mat(3,3); +mat << 1, 2, 3, 4, 5, 6, 7, 8, 9; +cout << "Here is the matrix mat:\n" << mat << endl; +// This assignment shows the aliasing problem +mat.bottomRightCorner(2,2) = mat.topLeftCorner(2,2); +cout << "After the assignment, mat = \n" << mat << endl; +``` + +输出 + +``` +Here is the matrix mat: +1 2 3 +4 5 6 +7 8 9 +After the assignment, mat = +1 2 3 +4 1 2 +7 4 1 +``` + +在 `mat.bottomRightCorner(2,2) = mat.topLeftCorner(2,2);` 赋值中展示了混淆。 + +mat(1,1) 在bottomRightCorner(2,2)和topLeftCorner(2,2)都存在。赋值结果中mat(2,2)本应该赋予操作前mat(1,1)的值=5。但是,最终程序结果mat(2,2)=1。原因是Eigen使用了lazy evaluation(懒惰评估),上面等价于 + +``` +mat(1,1) = mat(0,0); +mat(1,2) = mat(0,1); +mat(2,1) = mat(1,0); +mat(2,2) = mat(1,1); +``` + +下面会解释如何通过eval()来解决这个问题。 + +混淆还会在缩小矩阵时出现,比如 `vec = vec.head(n)` 和 `mat = mat.block(i,j,r,c)`。 + +一般来说,混淆在编译阶段很难被检测到。比如第一个例子,如果mat再大一些可能就不会出现混淆了。但是Eigen可以在运行时检测某些混淆,如前面讲的例子。 + +``` +Matrix2i a; a << 1, 2, 3, 4; +cout << "Here is the matrix a:\n" << a << endl; +a = a.transpose(); // !!! do NOT do this !!! +cout << "and the result of the aliasing effect:\n" << a << endl; +Here is the matrix a: +1 2 +3 4 +and the result of the aliasing effect: +1 2 +2 4 +``` + +我们可以通过EIGEN_NO_DEBUG宏,在编译时关闭运行时的断言。 + +## 解决混淆问题 + +Eigen需要把右值赋值为一个临时matrix/array,然后再将临时值赋值给左值,便可以解决混淆。eval()函数实现了这个功能。 + +``` +MatrixXi mat(3,3); +mat << 1, 2, 3, 4, 5, 6, 7, 8, 9; +cout << "Here is the matrix mat:\n" << mat << endl; +// The eval() solves the aliasing problem +mat.bottomRightCorner(2,2) = mat.topLeftCorner(2,2).eval(); +cout << "After the assignment, mat = \n" << mat << endl; +``` + +输出 + +``` +Here is the matrix mat: +1 2 3 +4 5 6 +7 8 9 +After the assignment, mat = +1 2 3 +4 1 2 +7 4 5 +``` + +同样: `a = a.transpose().eval();` ,当然我们最好使用 transposeInPlace()。如果存在xxxInPlace函数,推荐使用这类函数,它们更加清晰地标明了你在做什么。提供的这类函数: + +| Origin | In-place | +| ----------------------- | ------------------------------ | +| MatrixBase::adjoint() | MatrixBase::adjointInPlace() | +| DenseBase::reverse() | DenseBase::reverseInPlace() | +| LDLT::solve() | LDLT::solveInPlace() | +| LLT::solve() | LLT::solveInPlace() | +| TriangularView::solve() | TriangularView::solveInPlace() | +| DenseBase::transpose() | DenseBase::transposeInPlace() | + +而针对`vec = vec.head(n)`这种情况,推荐使用`conservativeResize()`。 + +## 混淆和component级的操作。 + +组件级是指整体的操作,比如matrix加法、scalar乘、array乘等,这类操作是安全的,不会出现混淆。 + +``` +MatrixXf mat(2,2); +mat << 1, 2, 4, 7; +cout << "Here is the matrix mat:\n" << mat << endl << endl; +mat = 2 * mat; +cout << "After 'mat = 2 * mat', mat = \n" << mat << endl << endl; +mat = mat - MatrixXf::Identity(2,2); +cout << "After the subtraction, it becomes\n" << mat << endl << endl; +ArrayXXf arr = mat; +arr = arr.square(); +cout << "After squaring, it becomes\n" << arr << endl << endl; +``` + +输出 + +``` +Here is the matrix mat: +1 2 +4 7 + +After 'mat = 2 * mat', mat = + 2 4 + 8 14 + +After the subtraction, it becomes + 1 4 + 8 13 + +After squaring, it becomes + 1 16 + 64 169 +``` + +## 混淆和矩阵的乘法 + +在Eigen中,矩阵的乘法一般都会出现混淆。除非是方阵(实质是元素级的乘)。 + +``` +MatrixXf matA(2,2); +matA << 2, 0, 0, 2; +matA = matA * matA; +cout << matA; + +4 0 +0 4 +``` + +其他的操作,Eigen默认都是存在混淆的。所以Eigen对矩阵乘法自动引入了临时变量,对的`matA=matA*matA`这是必须的,但是对`matB=matA*matA`这样便是不必要的了。我们可以使用noalias()函数来声明这里没有混淆,matA*matA的结果可以直接赋值为matB。 + +``` +matB.noalias() = matA * matA; +``` + +从Eigen3.3开始,如果目标矩阵resize且结果不直接赋值给目标矩阵,默认不存在混淆。 + +``` +MatrixXf A(2,2), B(3,2); +B << 2, 0, 0, 3, 1, 1; +A << 2, 0, 0, -2; +A = (B * A).cwiseAbs();//cwiseAbs()不直接赋给目标 +//A = (B * A).eval().cwiseAbs() +cout << A; +``` + +当然,对于任何混淆问题,都可以通过`matA=(matB*matA).eval()` 来解决。 + +## 总结 + +当相同的矩阵或array在等式左右都出现时,很容易出现混淆。 + +1. compnent级别的操作不用考虑混淆。 +2. 矩阵相乘,Eigen默认会解决混淆问题,如果你确定不会出现混淆,可以使用noalias()来提效。 +3. 混淆出现时,可以用eval()和xxxInPlace()函数解决。 + +## 存储顺序 + +对于矩阵和二维数组有两种存储方式,列优先和行优先。 + +假设矩阵: + +![img](https://images2015.cnblogs.com/blog/532915/201701/532915-20170125204938597-130252060.png) + +按行优先存储,内存中形式如下: + +``` +8 2 2 9 9 1 4 4 3 5 4 5 +``` + +列优先,内存格式: + +``` +8 9 3 2 1 5 2 4 4 9 4 5 +Matrix Acolmajor; +Acolmajor << 8, 2, 2, 9, + 9, 1, 4, 4, + 3, 5, 4, 5; +cout << "The matrix A:" << endl; +cout << Acolmajor << endl << endl; +cout << "In memory (column-major):" << endl; +for (int i = 0; i < Acolmajor.size(); i++) + cout << *(Acolmajor.data() + i) << " "; +cout << endl << endl; +Matrix Arowmajor = Acolmajor; +cout << "In memory (row-major):" << endl; +for (int i = 0; i < Arowmajor.size(); i++) + cout << *(Arowmajor.data() + i) << " "; +cout << endl; +``` + +输出 + +``` +The matrix A: +8 2 2 9 +9 1 4 4 +3 5 4 5 + +In memory (column-major): +8 9 3 2 1 5 2 4 4 9 4 5 + +In memory (row-major): +8 2 2 9 9 1 4 4 3 5 4 5 +``` + +PlainObjectBase::data()函数可以返回矩阵中第一个元素的内存位置。 + +## 存储顺序及选择 + +Matrix类模板中可以设定存储的方向,RowMajor表示行优先,ColMajor表示列优先。默认是列优先。 + +如何选择存储方式呢? + +1. 如果要和其他库合作开发,为了转化方便,可以选择同样的存储方式。 +2. 应用中涉及大量行遍历操作,应该选择行优先,寻址更快。反之亦然。 +3. 默认是列优先,而且大多库都是按照这个顺序的,默认的不失为较好的。 + +## 总结 + +本来想春节前任务比较少,翻译完所有的Eigen系列的。但是我的目的是为了使用google的非线性优化库[ceres](http://ceres-solver.org/installation.html#getting-the-source-code),介绍了这些基本知识也够用了,如果遇到不清楚的函数可以直接到Eigen的官网查询。 + +c + + + +这个系列很简单,只是入门。有更深理解了再续写。 + +## Reference + +* https://www.cnblogs.com/houkai/p/6347408.html +* https://www.cnblogs.com/houkai/p/6347648.html +* https://www.cnblogs.com/houkai/p/6348044.html +* https://www.cnblogs.com/houkai/p/6349970.html +* https://www.cnblogs.com/houkai/p/6349974.html +* https://www.cnblogs.com/houkai/p/6351358.html +* https://www.cnblogs.com/houkai/p/6351609.html +* https://www.cnblogs.com/houkai/p/6349981.html +* https://www.cnblogs.com/houkai/p/6349988.html +* https://www.cnblogs.com/houkai/p/6349990.html +* https://www.cnblogs.com/houkai/p/6349991.html \ No newline at end of file diff --git a/doc/src/tts_text_frontend.md b/doc/src/tts_text_frontend.md new file mode 100644 index 00000000..e942469e --- /dev/null +++ b/doc/src/tts_text_frontend.md @@ -0,0 +1,215 @@ +# Text Front End + + + +## Text Segmentation + +There are various libraries including some of the most popular ones like NLTK, Spacy, Stanford CoreNLP that that provide excellent, easy to use functions for sentence segmentation. + +* https://github.com/bminixhofer/nnsplit +* [DeepSegment](https://github.com/notAI-tech/deepsegment) [blog](http://bpraneeth.com/projects/deepsegment) [1](https://praneethbedapudi.medium.com/deepcorrection-1-sentence-segmentation-of-unpunctuated-text-a1dbc0db4e98) [2](https://praneethbedapudi.medium.com/deepcorrection2-automatic-punctuation-restoration-ac4a837d92d9) [3](https://praneethbedapudi.medium.com/deepcorrection-3-spell-correction-and-simple-grammar-correction-d033a52bc11d) [4](https://praneethbedapudi.medium.com/deepsegment-2-0-multilingual-text-segmentation-with-vector-alignment-fd76ce62194f) + + + +## Text Normalization(文本正则) + +The **basic preprocessing steps** that occur in English NLP, including data cleaning, stemming/lemmatization, tokenization and stop words. **not all of these steps are necessary for Chinese text data!** + +### Lexicon Normalization + +There’s a concept similar to stems in this language, and they’re called Radicals. **Radicals are basically the building blocks of Chinese characters.** All Chinese characters are made up of a finite number of components which are put together in different orders and combinations. Radicals are usually the leftmost part of the character. There are around 200 radicals in Chinese, and they are used to index and categorize characters. + +Therefore, procedures like stemming and lemmatization are not useful for Chinese text data because seperating the radicals would **change the word’s meaning entirely**. + +### Tokenization + +**Tokenizing breaks up text data into shorter pre-set strings**, which help build context and meaning for the machine learning model. + +These “tags” label the part of speech. There are 24 part of speech tags and 4 proper name category labels in the `**jieba**` package’s existing dictionary. + + + +### Stop Words + +In NLP, **stop words are “meaningless” words** that make the data too noisy or ambiguous. + +Instead of manually removing them, you could import the `**stopwordsiso**` package for a full list of Chinese stop words. More information can be found [here](https://pypi.org/project/stopwordsiso/). And with this, we can easily create code to filter out any stop words in large text data. + +```python +!pip install stopwordsiso +import stopwordsiso +from stopwordsiso import stopwords +stopwords(["zh"]) # Chinese +``` + + + +文本正则化 文本正则化主要是讲非标准词(NSW)进行转化,比如: + +1. 数字、电话号码: 10086 -> 一千零八十六/幺零零八六 +2. 时间,比分: 23:20 -> 二十三点二十分/二十三比二十 +3. 分数、小数、百分比: 3/4 -> 四分之三,3.24 -> 三点一四, 15% -> 百分之十五 +4. 符号、单位: ¥ -> 元, kg -> 千克 +5. 网址、文件后缀: www. -> 三W点 + +其他转换: + +1. 简体和繁体转换:中国语言 -> 中國語言 +2. 半角和全角准换:, -> , + +### tools + +* https://github.com/google/re2 + +* https://github.com/speechio/chinese_text_normalization + +* [vinorm](https://github.com/NoahDrisort/vinorm) [cpp_verion](https://github.com/NoahDrisort/vinorm_cpp_version) + + Python package for text normalization, use for frontend of Text-to-speech Reseach + +* https://github.com/candlewill/CNTN + + This is a ChiNese Text Normalization (CNTN) tool for Text-to-speech system, which is based on [sparrowhawk](https://github.com/google/sparrowhawk). + +* [Simplified and Traditional Chinese Characters converter](https://github.com/berniey/hanziconv) + +* [Halfwidth and Fullwidth](https://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2) + +* https://github.com/BYVoid/OpenCC + + + +## Word Segmentation(分词) + +分词之所以重要可以通过这个例子来说明: +广州市长隆马戏欢迎你 -> 广州市 长隆 马戏 欢迎你 +如果没有分词错误会导致句意完全不正确: +广州 市长 隆马戏 欢迎你 + +分词常用方法分为最大前向匹配(基于字典)和基于CRF的分词方法。用CRF的方法相当于是把这个任务转换成了序列标注,相比于基于字典的方法好处是对于歧义或者未登录词有较强的识别能力,缺点是不能快速fix bug,并且性能略低于词典。 + + +中文分词的常见工具: +* https://github.com/lancopku/PKUSeg-python +* https://github.com/thunlp/THULAC-Python +* https://github.com/fxsjy/jieba +* CRF++ +* https://github.com/isnowfy/snownlp + +### MMSEG +* [MMSEG: A Word Identification System for Mandarin Chinese Text Based on Two Variants of the Maximum Matching Algorithm](http://technology.chtsai.org/mmseg/) +* [`中文分词`简单高效的MMSeg](https://www.cnblogs.com/en-heng/p/5872308.html) +* [mmseg分词算法及实现](https://blog.csdn.net/daniel_ustc/article/details/50488040) +* [Mmseg算法](https://www.jianshu.com/p/e4ae8d194487) +* [浅谈中文分词](http://www.isnowfy.com/introduction-to-chinese-segmentation/) + +* [pymmseg-cpp](https://github.com/pluskid/pymmseg-cpp.git) +* [ustcdane/mmseg](https://github.com/ustcdane/mmseg) +* [jkom-cloud/mmseg](https://github.com/jkom-cloud/mmseg) + + +### CScanner +* [CScanner - A Chinese Lexical Scanner](http://technology.chtsai.org/cscanner/) + + + +## Part of Speech(词性预测) + +词性解释 + +``` +n/名词 np/人名 ns/地名 ni/机构名 nz/其它专名 +m/数词 q/量词 mq/数量词 t/时间词 f/方位词 s/处所词 +v/动词 a/形容词 d/副词 h/前接成分 k/后接成分 +i/习语 j/简称 r/代词 c/连词 p/介词 u/助词 y/语气助词 +e/叹词 o/拟声词 g/语素 w/标点 x/其它 +``` + + + +## G2P(注音) + +注音是需要将词转换成对应的发音,对于中文是将其转换成拼音,比如 绿色->(lv4 se4) 这里的数字表示声调。 + +传统方法是使用字典,但是对于未登录词就很难解决。基于模型的方法是使用 [Phonetisaurus](https://github.com/AdolfVonKleist/Phonetisaurus)。 论文可以参考 - WFST-based Grapheme-to-Phoneme Conversion: Open Source Tools for Alignment, Model-Building and Decoding + +当然这个问题也可以看做是序列标注用CRF或者基于神经网络的模型都可以做。 基于神经网络工具: + +* https://github.com/kakaobrain/g2pM +* https://github.com/Kyubyong/g2p + + + + +## Prosody(韵律预测) + +ToBI(an abbreviation of tones and break indices) is a set of conventions for transcribing and annotating the prosody of speech. 中文主要关注break。 + +韵律等级结构: + +``` +音素 -> 音节 -> 韵律词(Prosody Word, PW) -> 韵律短语(prosody phrase, PPH) -> 语调短句(intonational phrase, IPH) -> 子句子 -> 主句子 -> 段落 -> 篇章 +LP -> LO -> L1(#1) -> L2(#2) -> L3(#3) -> L4(#4) -> L5 -> L6 -> L7 +``` + + +主要关注 PW, PPH, IPH + +| | 停顿时长 | 前后音高特征 | +| --- | ----------| --- | +| 韵律词边界 | 不停顿或从听感上察觉不到停顿 | 无 | +| 韵律短语边界 | 可以感知停顿,但无明显的静音段 | 音高不下倾或稍下倾,韵末不可做句末 | +| 语调短语边界 | 有较长停顿 | 音高下倾比较完全,韵末可以作为句末 | + +常用方法使用的是级联CRF,首先预测如果是PW,再继续预测是否是PPH,再预测是否是IPH + + + +论文: 2015 .Ding Et al. - Automatic Prosody Prediction For Chinese Speech Synthesis Using BLSTM-RNN and Embedding Features + + + +## Polyphone(多音字) + + + +## Linguistic Features(语言学特征) + + + +## 基于神经网络的前端文本分析模型 + +最近这两年基本都是基于 BERT,所以这里记录一下相关的论文: + +- g2p: 2019. Sevinj Et al. Transformer based Grapheme-to-Phoneme Conversion +- 分词: 2019 huang Et al. - Toward Fast and Accurate Neural Chinese Word Segmentation with Multi-Criteria Learning +- 韵律: 2020 Zhang Et al. - Chinese Prosodic Structure Prediction Based on a Pretrained Language Representation Model + +除此之外,BLSTM + CRF 也比较主流。 + + + + + +## 总结 + +总结一下,文本分析各个模块的方法: + +TN: 基于规则的方法 + +分词: 字典/CRF/BLSTM+CRF/BERT + +注音: ngram/CRF/BLSTM/seq2seq + +韵律: CRF/BLSTM + CRF/ BERT + + + +考虑到分词,注音,韵律都是基于序列标注任务,所以理论上来说可以通过一个模型搞定。 + + + +## Reference +* [Text Front End](https://slyne.github.io/%E5%85%AC%E5%BC%80%E8%AF%BE/2020/10/03/TTS1/) +* [Chinese Natural Language (Pre)processing: An Introduction](https://towardsdatascience.com/chinese-natural-language-pre-processing-an-introduction-995d16c2705f) +* [Beginner’s Guide to Sentiment Analysis for Simplified Chinese using SnowNLP](https://towardsdatascience.com/beginners-guide-to-sentiment-analysis-for-simplified-chinese-using-snownlp-ce88a8407efb) \ No newline at end of file From 230c7ac2aef2525db8f4bc9cd5bd7c0bcf82b60d Mon Sep 17 00:00:00 2001 From: Hui Zhang Date: Thu, 27 May 2021 20:24:49 +0800 Subject: [PATCH 2/3] fix eigen --- doc/src/eigen.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/src/eigen.md b/doc/src/eigen.md index 143c8f6a..f96d580f 100644 --- a/doc/src/eigen.md +++ b/doc/src/eigen.md @@ -2029,12 +2029,8 @@ Matrix类模板中可以设定存储的方向,RowMajor表示行优先,ColMaj 本来想春节前任务比较少,翻译完所有的Eigen系列的。但是我的目的是为了使用google的非线性优化库[ceres](http://ceres-solver.org/installation.html#getting-the-source-code),介绍了这些基本知识也够用了,如果遇到不清楚的函数可以直接到Eigen的官网查询。 -c - -这个系列很简单,只是入门。有更深理解了再续写。 - ## Reference * https://www.cnblogs.com/houkai/p/6347408.html From dc4b7e4c10bde935980fe98305d87f5836517f48 Mon Sep 17 00:00:00 2001 From: Hui Zhang Date: Fri, 28 May 2021 11:44:47 +0800 Subject: [PATCH 3/3] add algebra --- doc/src/eigen.md | 6 +++--- doc/src/linear_algebra.md | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 doc/src/linear_algebra.md diff --git a/doc/src/eigen.md b/doc/src/eigen.md index f96d580f..d2dd3282 100644 --- a/doc/src/eigen.md +++ b/doc/src/eigen.md @@ -2029,8 +2029,6 @@ Matrix类模板中可以设定存储的方向,RowMajor表示行优先,ColMaj 本来想春节前任务比较少,翻译完所有的Eigen系列的。但是我的目的是为了使用google的非线性优化库[ceres](http://ceres-solver.org/installation.html#getting-the-source-code),介绍了这些基本知识也够用了,如果遇到不清楚的函数可以直接到Eigen的官网查询。 - - ## Reference * https://www.cnblogs.com/houkai/p/6347408.html @@ -2043,4 +2041,6 @@ Matrix类模板中可以设定存储的方向,RowMajor表示行优先,ColMaj * https://www.cnblogs.com/houkai/p/6349981.html * https://www.cnblogs.com/houkai/p/6349988.html * https://www.cnblogs.com/houkai/p/6349990.html -* https://www.cnblogs.com/houkai/p/6349991.html \ No newline at end of file +* https://www.cnblogs.com/houkai/p/6349991.html +* [**Eigen Cheat sheet**](https://gist.github.com/gocarlos/c91237b02c120c6319612e42fa196d77#file-eigen-cheat-sheet) + diff --git a/doc/src/linear_algebra.md b/doc/src/linear_algebra.md new file mode 100644 index 00000000..634d2010 --- /dev/null +++ b/doc/src/linear_algebra.md @@ -0,0 +1,14 @@ +# 线性代数 + +* https://www.3blue1brown.com/ + +* https://www.zhihu.com/column/c_1068883024023834624 + +### 矩阵分解 + +* [LU分解](https://zhuanlan.zhihu.com/p/54943042) +* [QR分解](https://zhuanlan.zhihu.com/p/54957185) +* SVD分解 +* PCA分解 +* NMF分解 +