1. 程式人生 > >Eigen矩陣庫使用說明

Eigen矩陣庫使用說明

這是我在做專案時,給下一屆接手的人寫的一個關於Eigen庫的快速上手手冊,主要是針對於專案的應用來寫的。當時使用Eigen庫的目的是,將Matlab寫的,LPCC和MFCC兩種聲音識別演算法,十字形聲陣列的MUSIC定位演算法,和SVM分類器演算法,轉換成C++然後移植到到ARM處理器上(作業系統級上的並不是裸機)。而使用Eigen庫的原因就是,其能夠在編譯時進一步優化,而且只需匯入標頭檔案即可進行呼叫,而不像其他的一些庫需要安裝那麼麻煩。這篇使用說明是在2016年7月14日完成的。

由於我是第一次寫這個部落格,很多地方的圖片我不知道怎麼調整大小。原來的寫的說明手冊是在word上寫的,已經上傳到了CSDN上。

下面就是關於Eigen矩陣庫的使用說明。

準備工作

建議先把這個網頁上的東西看完,邊看邊輸入裡面的程式來看,撐死3個小時就完成了

下面以這個快速指導手冊為準來講解:

一、Modules and Header files

#include <Eigen/Dense>

這裡一般就包括了我們所需要的所有函式,如果還需要稀疏部分的話,那就使用

#include <Eigen/Eigen>

二、Array, matrix and vector types

1.        Eigen提供了兩種稠密矩陣:數學上的矩陣和向量,這兩種通過使用模板Matrix類來實現;通常對於1維或者2維的陣列通過模板Array

類來實現。這兩種類的不同點是主要在於API上:Array類提供了簡單的資料操作的介面。而Matrix類提供了線性代數操作的介面。一般來說我們轉演算法只用Matrix好了,以後是不是需要優化這裡先不管

兩個類的定義

Matrix

Array


因為我們主要需要用的是Matrix類,這一類中有很多的成員函式,可以參考

繼續講:


l  Scalar是型別,可以使float, double, bool, int, std::complex<float>

l  RowsAtCompileTime和ColsAtCompileTime是行數和列數,可以使動態的也可以是靜態的

l  Option是按列排列還是按行排列,預設是按列排列(這個引數就不用設了,這樣就跟

Matlab一樣都是按列排列)

2.        行數和列數是動態的還是靜態的還是一個動態一個靜態都隨便,下面給幾個例子:

這裡,Dynamic表示動態的意思。

3.        在大多數的情況下,我們可以用簡單的方式來定義Matrix變數(通過typedef)

這裡需要注意一下,向量一般都是指的是列向量(數學裡也差不多,沒發現所有的書上都喜歡定義列向量,然後轉置成行向量嘛 -_-)。

上面只是一部分的例子,下面我會列舉一下比較常用的例子:

4.        矩陣和陣列之間的轉換:

1)        array()的定義如下

就是將矩陣表示轉換成陣列表示

2)        matrix()的定義如下

就是將陣列表示轉換成矩陣表示

5.        Basicmatrix manipulation

下面每欄按照下圖的格式(總共4欄)

1)        建構函式

預設情況下,係數是未初始化的。1D objects表示1維的陣列或者向量。2D objects表示2維的矩陣(就是有行和列),陣列其實也是可以有二維的,上面沒有列出來(實際上對於我們來說也不需要)

2)        初始化

這裡要使用<<這個過載的運算子來進行賦值,在準備工作裡的也說過了。這裡需要說明的是,前面講過,矩陣是按列儲存的,所以在記憶體中的儲存形式應該是1,4,7,2,5,8,3,6,9.但是這並不意味著我們通過<<來進行賦值時,也要按照列來輸入,具體為什麼需要看一下<<的過載定義。而我們同樣可以跟Matlab一樣,通過m1(0),m1(1)……(注意下標要從0開始)來一個一個的取出元素,我們可以發現,確實是按照列來儲存的。具體的可以見http://eigen.tuxfamily.org/dox/group__TopicStorageOrders.html

3)        初始化

這裡,Identity是單位矩陣的意思。

4)        Runtimeinfo

這裡,rows()和cols()是獲取矩陣的行數和列數,size()是獲取矩陣的大小(也就是有多少個數據)。

l  innerSize()的定義如下

就是說,

向量的話(這裡用的是vector,說明是列向量)和size()一樣;

矩陣的話,這個矩陣是按列儲存的,返回行數;這個矩陣是按行儲存的話,返回列數

l  outerSize()的定義如下

就是說,

向量的話,返回1;

矩陣的話,這個矩陣是按列儲存的,返回列數;這個矩陣是按行儲存的話,返回行數

l  innerStride()的定義如下

首先,先要說明白一個問題,假設是按列儲存的矩陣,那麼一列就是一個slice。

所以這個函式就是在一個slice中,兩個相鄰元素之間的指標增量(不應該說是地址增量,因為地址是要算上資料型別大小的)

l  outerStride()的定義如下

這個函式時兩個相鄰的slice之間的指標增量

l  data()的定義如下

返回向量或者矩陣的資料部分陣列的指標,可以用來進行陣列(正常意義下的)轉換成矩陣或者向量的操作

5)        Compile-timeinfo

這一部分暫時還沒什麼用,這裡就不展開了

6)        Resizing

l  resize函式的定義如下

上面可以看到有兩個不同的定義,分開來說:

上面這個是針對向量的,引數只有一個size。注意這句話:這個方法對於那些固定值不是1的區域性動態矩陣是沒有效果的。

同時這裡還需要注意一個地方,就是在例子中,w.resize(3);這句話中,w是一個固定尺寸的向量,這句話並沒有對這個向量的個數進行增加或者刪減,但是如果對個數進行增加或者減少時,就會出現錯誤,因為w的大小是固定的,而不是動態的。也就是固定大小的矩陣是不能使用resize()來修改矩陣的大小;

上面這個是對於矩陣的(其實也可以用於向量了,要明白的是,向量只是Matrix類中固定了其中一個引數為1的typedef)

注意第一句話:這個方法是針對於動態大小的矩陣,雖然在保證大小不變的前提下,可以使用它來對所有矩陣進行操作(但其實是沒意義的了)。

如果你只想改變其中的一個引數,可以使用resize(NoChange_t,Index), resize(Index, NoChange_t),具體的見下面這兩張圖

繼續:

如果當前矩陣的係數個數和重新分配大小後的rows* cols正好相等,那麼就沒有進行記憶體的分配並且當前的值也沒有改變。但是,其他情況下,包括收縮,所有的資料都進行了重新分配,並且以前的值都丟失了。

7)        Coeffaccess with range checking

8)        Coeffaccess without range checking

9)        Assignment/copy

這裡需要注意的是,使用“=”操作符操作動態矩陣時,如果左右邊的矩陣大小不等,則左邊的動態矩陣的大小會被修改為右邊的大小

6.        PredefinedMatrices

向量部分:

setConstant(value)是值都是value;

setRandom()是隨機值

setLinSpaced(size, low, high)向量的值是在[low,high]之間分佈,總共有size個數值

矩陣部分也是類似的,只是沒有了setLinSpaced(size, low, high)

這部分內容很簡單,這裡不進行展開了

7.        Mappingexternal arrays

這裡直接看例子就好了,需要注意的是按列儲存。

特別說明的是OuterStride<3>,根據前面提到過類似的(首字母大小寫不一樣),這個應該也是按照slice來操作,因為這個矩陣是2行3列按列儲存,所以slice大小是2,然後類似於分幀,幀長是2,幀移是3。(可以試試是不是可以通過這種方式來實現分幀加窗,這樣就可以不需要enframe函數了)

三、Arithmetic Operators

1.        Eigen提供+、-、一元操作符“-”、+=、-=

1)        +和-:兩矩陣相加(矩陣中對應元素相加/減,返回一個臨時矩陣)

2)        -表示對矩陣取負(矩陣中對應元素取負,返回一個臨時矩陣)

3)        +=或者-=表示(對應每個元素都做相應操作)

2.        矩陣還提供與標量(單一個數字)的乘除操作,表示每個元素都與該標量進行乘除操作

二元操作符*在A*a中表示矩陣A中的每隔元素都與數字a相乘,結果放在一個臨時矩陣中,矩陣的值不會改變。對於a*A、A/a、A*=a、A /=a也是一樣。

3.        矩陣的相乘,矩陣與向量的相乘也是使用操作符*,共有*和*=兩種操作符

4.        矩陣的轉置和共軛轉置

可以通過成員函式transpose()、conjugate()和 adjoint()來完成矩陣的轉置,共軛矩陣,和共軛轉置矩陣,注意這些函式返回操作後的結果,而不會對原矩陣的元素進行直接操作,如果要讓原矩陣的進行轉換,則需要使用響應的InPlace函式,例如:transposeInPlace() 、 adjointInPlace() 之類。

adjoint()的定義如下(注意看警告部分)

5.        點乘和內積

dot()的定義如下

注意,只能由於向量,而且當是複數向量時,計算的是(a^H)*(a)

至於value()的定義如下,但是有什麼作用現在還不知道

6.        外積

7.        範數和歸一化

1)        norm()的定義如下

對於向量,使用的是L2範數,也就是向量本身點積的開方

對於矩陣,使用的是Frobenius範數

2)        normalized()的定義如下:

這個是針對於向量的。用處就是向量的每個數除以這個向量的範數

3)        normalize()的定義如下:

作用和normalized()是一樣的,區別在於,normalized()是產生一個臨時矩陣,而這個是直接對原矩陣進行變換。

4)        squaredNorm()的定義如下

和前面的norm()的區別就是,在norm()的結果上做了一個平方

8.        叉積

cross()的定義如下

至於叉積和外積有什麼區別,自己去找,懶得解釋了

四、Coefficient-wise & Array operators

1.        矩陣和向量的係數操作

1)        cwiseMin()的定義如下

就是輸出相同位置的兩個矩陣中較小的係數所組成的矩陣

2)        cwiseMax()的定義如下

跟上面的一樣,只是較小換成了較大

3)        cwiseAbs2()的定義如下

就是求各個係數絕對值的平方

4)        cwiseAbs()的定義如下

就是求各個係數的絕對值

5)        cwiseSqrt()的定義如下

就是求各個係數的開方

6)        cwiseProduct()的定義如下

就是輸出相同位置的兩個矩陣中各個係數的乘積所組成的矩陣

7)        cwiseQuotient()的定義如下

比如v.cwiseQuotient(w),那麼就是v中的各個係數分別除以w中的相對應位置的係數的結果

2.        陣列操作

這一部分一般不是重點,所以這裡就不詳細展開了

上面要注意一個地方,就是Matrix類是沒有<><=>===!=這些操作的,如果想進行這些操作,可以通過array()來轉換成陣列表示

五、Reduction

Eigen提供了很多的降維方法,比如minCoeff(), maxCoeff() , sum() , prod() , trace() , norm() ,  squaredNorm() , all() , any()。所有的這些降維操作都可以對矩陣的係數,column-wise,row-wise進行。

1.        先說明一下VectorwiseOp,下面是這個的說明

這是一個偽表示式,提供了局部降維操作,有兩個引數:

ExpressionType:區域性降維的目標型別

Direction:表示進行的方向(垂直或者水平)

他是DenseBase::colwise()和DenseBase::rowwise()的返回型別,而且大部分情況下只有這兩種方式

1)        colwise()的定義如下

其實也就是可以理解為取出每一列。但是,這裡要注意,colwise()是把每一列變成了一個塊,然後這些塊組成了一個行向量,所以這時候你是不能將這些塊進行輸出或者對矩陣中的某一個元素進行賦值的。

2)        rowwise()的定義如下

其實也就是可以理解為取出每一行,需要注意的地方和上面是一樣的

2.        下面對每一個降維操作進行講解

1)        minCoeff()的定義如下

這是第一種定義,可以對矩陣和向量操作,直接返回最小值

這是第二種定義,可以對矩陣和向量操作,返回最小值,同時將最小值所在的行數和列數儲存到呼叫時輸入的兩個地址指向的記憶體中

這是第三種定義,只能對向量操作,返回最小值,同時將最小值所在的位置儲存到呼叫時輸入的地址指向的記憶體中

例子:


和minCoeff()一樣

3)        sum()的定義如下

返回所有係數的和

返回所有係數的積

5)        trace()的定義如下

返回矩陣的跡,不一定要是方陣

6)        norm() ,squaredNorm()前面已經講過

7)        all()的定義如下

就如字面意思一樣,要所有的係數都滿足條件才行

8)        any()的定義如下

就如字面意思一樣,只要有一個係數滿足條件就行

例子

六、Sub-matrices

1.        矩陣的行和列的讀寫訪問

1)        col()的定義如下

就是取出矩陣中的第i列,要注意和cols()的區別

2)        row()的定義如下

就是取出矩陣中的第i行,要注意和rows()的區別

3)        swap()的定義如下

就是交換,注意是交換,兩個矩陣都會發生變化

2.        向量子塊的讀寫訪問

3.        矩陣子塊的讀寫訪問

1)        block()的定義如下

注意,這個函式返回的是一個動態矩陣

2)        block<rows,cols>(i,j)的定義如下

注意,這個函式返回的是一個靜態矩陣

3)        topLeftCorner()的定義如下

取左上角的矩陣,返回的是一個動態矩陣

返回一個靜態矩陣

這個其實和第一個是差不多的,區別就是可以選擇一個引數靜態的而另外一個是動態的

4)        topRightCorner()是右上角,bottomLeftCorner()是左下角,bottomRightCorner()是右下角,具體的定義都和topLeftCorner()差不多

5)        topRows()的定義如下

就是從第一行開始,取n行,返回的是一個動態矩陣

注意,這裡當N時固定數值時,n必須等於N,或者不寫,這時候返回的是一個靜態矩陣

當N是Dynamic時,n需要寫好,表示取幾行,返回的是一個行數是動態,列數是靜態的矩陣

6)        bottomRows()是從底部行取,leftCols()是從左邊列取, rightCols()是從右邊列取,具體的定義都和 topRows()差不多

七、Miscellaneous operations

1.        Reverse

向量,矩陣的行或者列或者整個矩陣都可以倒置

1)        reverse()的定義如下

就是資料倒置過來

2)        reverseInPlace()的定義如下

這個跟前面的那個作用是一樣的,區別是直接在原矩陣上進行操作,這個定義裡面也講了這樣做的意義。

2.        Replicate

注意,這個例子中的第一行的兩個定義是錯誤的

replicate()的定義如下

這是第一個,返回的是一個動態矩陣

這是第二個,返回的是一個靜態矩陣

這兩個的作用都是將矩陣或者向量看成是一塊,然後按照引數進行復制

這是第三個,和前兩個的區別是,這個只有一個引數,就是將向量進行復制,列向量就複製成多列(橫向複製),可以向定義中的例子裡的一樣,使用v.rowwise().replicate(5),也可以使用v.rowwise().replicate<5>()。但是這裡要注意,一定要使用rowwise(),這就是為什麼之前的例子的第一行是錯誤的原因

這是第4個,和上面那個的區別就是這是對矩陣進行操作,同樣先通過colwise()將每一列變成了一個塊,這些塊組成了行向量(這樣就跟上面類似了),然後進行復制(縱向複製);或者通過rowwise()將每一行變成一個塊,這些塊組成了列向量,然後進行復制(橫向複製)。可以向定義中的例子裡的一樣,使用m.colwise().replicate<3>(),也可以使用m.colwise().replicate (3)。

八、Diagonal, Triangular, and Self-adjoint matrices

1.        Diagonalmatrices

1)        asDiagonal()的定義如下

這個函式是針對向量的,是將向量的係數作為對角矩陣對角線上的值

2)        DiagonalMatrix類的定義如下

_Scalar:是係數型別

SizeAtCompileTime:矩陣的維數,可以是Dynamic

MaxSizeAtCompileTime:這個引數是最優的或者預設和SizeAtCompileTime一樣。大部分情況下不用理他。

注意例子中使用了一個向量來對對角矩陣進行賦值。

3)        diagonal()的定義如下

這個就是取出主對角線上的係數,並不一定要求矩陣為方陣

這個就是以主對角線為基準0,當引數為正數,對角線向上移,當引數為負數時,對角線向下移。

4)        最後一個例子只是簡單的運算,這裡不展開,主要說一個求逆函式,這個函式是很重要的,但是前面一直沒有提到

inverse()的定義如下

2.        Triangularviews和Symmetric/selfadjoint views

這兩部分有些麻煩,就不展開了,等以後用到的話再提

九、Fast Fourier Transform module

FFT的實現並不是Eigen中自帶的庫,而是採用外部的模組,即unsupported module。

所以我們需要首先包含這個檔案,即#include <unsupported/Eigen/FFT>。

這個模組提供了FFT,通過一個可配置後端實現。

預設的實現方式是基於kissfft的,這是一個小的,免費的,可靠有效的

還有兩種實現後端:

十、特徵分解

很麻煩,懶得講了

十一、奇異值分解