原文出處:http://www.cnblogs.com/weidagang2046/p/linear-algebra-from-programming-perspective.htmlhtml
在大學數學學科中,線性代數是最爲抽象的一門課,從初等數學到線性代數的思惟跨度比微積分和機率統計要大得多。不少人學過之後一直停留在知其然不知其因此然的階段,若干年以後接觸圖形編程或機器學習等領域才發現線性代數的應用無處不在,但又苦於不能很好地理解和掌握。的確,多數人很容易理解初等數學的各類概念,函數、方程、數列一切都那麼的天然,可是一進入線性代數的世界就好像來到了另外一個陌生的世界,在各類奇怪的符號和運算裏迷失了。程序員
我在初接觸線性代數的時候簡直感受這是一門天外飛仙的學科,一個疑問在我腦子裏浮現出來:算法
線性代數究竟是一種客觀的天然規律仍是人爲的設計?
若是看到這個問題,你的反應是「這還用問,數學固然是客觀的天然規律了」,我一點兒都不以爲奇怪,我本身也曾這樣認爲。從中學的初等數學和初等物理一路走來,不多人去懷疑一門數學學科是否是天然規律,當我學習微積分、機率統計時也歷來沒有懷疑過,惟獨線性代數讓我產生了懷疑,由於它的各類符號和運算規則太抽象太奇怪,徹底對應不到生活經驗。因此,我還真要感謝線性代數,它引起了我去思考一門數學學科的本質。其實,不止是學生,包括不少數學老師都不清楚線性代數究竟是什麼、有什麼用,不只國內如此,在國外也是這樣,國內的孟巖寫過《理解矩陣》,國外的Sheldon Axler教授寫過《線性代數應該這樣學》,但都尚未從根本上講清楚線性代數的前因後果。對於我本身來說,讀大學的時候沒有學懂線性代數,反而是後來從編程的角度理解了它。不少人說數學好能夠幫助編程,我剛好反過來了,對程序的理解幫助了我理解數學。數據庫
本文的目標讀者是程序員,下面我就帶各位作一次程序員在線性代數世界的深度歷險!既然是程序員,在進入線性代數的領域以前,咱們不妨先從考察一番程序世界,請思考這樣一個問題:編程
計算機裏面有彙編、C/C++、Java、Python等通用語言,還有Makefile、CSS、SQL等DSL,這些語言是一種客觀的天然規律仍是人爲的設計呢?
爲何要問這樣一個看起來很蠢的問題呢?由於它的答案顯而易見,你們對每天使用的程序語言的認識必定賽過抽象的線性代數,很顯然程序語言雖然包含了內在的邏輯,但它們本質上都是人爲的設計。全部程序語言的共同性在於:創建了一套模型,定義了一套語法,並將每種語法映射到特定的語義。程序員和語言實現者之間遵照語言契約:程序員保證代碼符合語言的語法,編譯器/解釋器保證代碼執行的結果符合語法相應的語義。好比,C++規定用new A()語法在堆上構造對象A,你這樣寫了C++就必須保證相應的執行效果,在堆上分配內存並調用A的構造函數,不然就是編譯器違背語言契約。閉包
從應用的角度,咱們能不能把線性代數視爲一門程序語言呢?答案是確定的,咱們能夠用語言契約做爲標準來試試。假設你有一個圖像,你想把它旋轉60度,再沿x軸方向拉伸2倍;線性代數告訴你,「行!你按個人語法構造一個矩陣,再按矩陣乘法規則去乘你的圖像,我保證結果就是你想要的」。實際上,線性代數和SQL這樣的DSL很是類似,下面來做一些類比:app
因此,從應用的角度看,線性代數是一種人爲設計的領域特定語言(DSL),它創建了一套模型並經過符號系統完成語法和語義的映射。實際上,向量、矩陣、運算規則的語法和語義都是人爲的設計,這和一門語言中的各類概念性質相同,它是一種創造,可是前提是必須知足語言契約。框架
可能有人對把線性代數當成一門DSL不放心,我給你一個矩陣,你就把個人圖形旋轉了60度沿x軸拉伸了2倍,我總感受不踏實啊,我都不知道你「底層」是怎麼作!其實,這就像有的程序員用高級語言不踏實,以爲底層纔是程序的本質,總是想知道這句話編譯成彙編是什麼樣?那個操做又分配了多少內存?別人在Shell裏直接敲一個wget命令就能取下一個網頁,他非要用C語言花幾十分鐘來寫一堆代碼才踏實。其實,所謂底層和上層只是一種習慣性的說法,並非誰比誰更本質。程序的編譯和解釋本質上是不一樣模型間的語義映射,一般狀況下是高級語言映射爲低級語言,可是徹底也能夠把方向反過來。Fabrice Bellard用JavaScript寫了一個虛擬機,把Linux跑在JavaScript虛擬機上,這就是把機器模型往JavaScript模型上映射。機器學習
創建新模型確定依賴於現有的模型,但這是建模的手段而不是目的,任何一種新模型的目的都爲了更簡單地分析和解決某一類問題。線性代數在創建的時候,它的各類概念和運算規則依賴於初等數學的知識,可是一旦創建起來這層抽象模型以後,咱們就應該習慣於直接利用高層次的抽象模型去分析和解決問題。說到線性代數是爲了比初等數學更容易地分析和解決問題,下面咱們經過一個例子來實際感覺一下它的好處:函數
給定三角形的頂點(x1, y1)
,(x2, y2)
,(x3, y3)
,求三角形的面積。
初等數學中三角形面積最著名的計算公式是area = 1/2 * base * height
,當三角形有一條邊剛好在座標軸上時咱們就很容易算出它的面積。可是,假如一樣一個三角形咱們把座標軸旋轉一下,讓它的邊不在座標軸上,怎麼辦?咱們還能獲得它的底和高嗎?答案確定是能夠的,可是就明顯複雜了,並且還要分不少種狀況去分別討論。
相反,若是咱們用線性代數知識來解決這個問題就很是輕鬆。在線性代數中兩個向量a,b的叉積(Cross Product)是一個向量,其方向與a,b垂直,其大小等於a,b構成的平行四邊形的面積:
咱們能夠把三角形的邊視爲向量,因此三角形的面積等於兩個邊向量的叉積向量的長度除以二:
area = 1/2 * length(cross_product((x2 - x1, y2 - y1), (x3 - x1, y3 - y1)))
注:length表示取向量長度,cross_product表示兩個向量的叉積。
這樣一個在初等數學裏面有點兒小難的問題在線性代數中瞬間搞定!可能有人會說,你直接基於叉積來作,固然簡單了,可是叉積自己不是也挺複雜的嗎?你把它展開試試看呢?是的,模型的做用就是把一部分複雜性隱藏到模型中,使得模型的使用者能夠更加簡單地解決問題。曾經有人質疑C++太複雜,C++之父Bjarne Stroustrup這樣回答:
Complexity will go somewhere: if not the language then the application code.
在特定環境下,問題的複雜性是由其本質決定的,C++把一部分的複雜性歸入了語言和標準庫,目的是使得應用程序更爲簡單。固然,並不是全部場合C++都使得問題更加簡單,可是從原理上講,C++的複雜性是有道理的。除了C++,Java、SQL、CSS等各類語言和框架莫不如是,想象一下,若是不使用數據庫,動不動就本身去作數據存儲和管理是多麼複雜啊!這樣咱們就不難理解爲何線性代數要定義叉積這樣奇怪的運算了,它和C++把不少經常使用的算法和容器歸入STL是同一道理。一樣的,甚至你還能夠在線性代數中定義本身想要的運算拿來複用。因此,數學一點兒不死板,它和程序同樣是活活潑潑的,你理解了它的前因後果就能駕馭自如。說到這裏,咱們就順便回答一個很常見的疑惑:
線性代數的點積、叉積還有矩陣運算都很奇怪,爲何要定義這些運算呢?它們的定義又爲何是這個樣子呢?
其實,和程序複用同樣,線性代數定義點積、叉積和矩陣運算是由於它們的應用很是廣,有很大的複用價值,能夠做爲咱們分析和解決問題的基礎。好比,不少問題都涉及到一個向量到另外一個向量的投影或是求兩個向量的夾角,那麼就會考慮專門定義點積(Dot Product)這個運算:
點積概念的提出屬於設計,有發揮創造的餘地;一旦設計定了,具體公式就不能隨意發揮了,必須符合邏輯,保證它映射到初等數學模型的正確性。這就像一門高級語言能夠定義不少概念,什麼高階函數、閉包等等,可是它必須保證映射到底層實現時在執行產生的效果符合其定義的規範。
上面說了,線性代數是一種高層次抽象模型,咱們能夠採用學習一門程序語言的方法去學習它的語法和語義,可是這一認識不僅針對線性代數,它是對每一門數學學科通用的,可能有人會有疑問
微積分、機率論也是高層次抽象,那麼線性代數這種高層次抽象的特色在哪裏呢?
這就問到了根本上,線性代數的核心:向量模型。咱們在初等數學中學習的座標系屬於笛卡爾所提出的解析模型,這個模型頗有用,但同時也有很大的缺點。座標系是人爲加上的虛擬參考系,可是咱們要解決的問題,好比求面積,圖形旋轉、拉伸等應用都是和座標系無關的,創建一個虛擬的座標系每每無助於解決問題,剛纔三角形面積的例子就是這樣。
向量模型很好地克服瞭解析模型的缺點,若是說解析模型表明了某種「絕對性」的世界觀,那麼向量模型就表明了某種「相對性」的世界觀,我推薦把向量模型和解析模型看做對立的兩種模型。
向量模型中定義了向量和標量的概念。向量具備大小和方向,知足線性組合法則;標量是隻有大小沒有方向的量(注:標量的另外一種更深入的定義是在座標變換中保持不變的量)。向量模型的優勢之一是其座標系無關性,也就是相對性,它在定義向量和運算規則的時候從一開始就拋開了座標系的束縛,無論你座標軸怎麼旋轉,我都能適應,向量的線性組合、內積、叉積、線性變換等等運算所有都是座標系無關的。注意,所謂座標系無關性不是說就沒有座標系了,仍是有的,剛纔三角形例子的頂點就是用座標表示的,只是在解決問題的時候不一樣的座標系不會構成影響。用一個比喻,Java號稱平臺無關,不是說Java就是空中樓閣,而是說你用Java編程時底層是Linux仍是Windows每每對你沒有影響。
向量模型有什麼好處呢?除了剛纔三角形面積問題是一個例子,下面我再舉一個幾何的例子:
給定三維座標系中的一點(x0, y0, z0)
和一個平面a*x + b*y + c*z + d = 0
,求點到平面的垂直距離?
這個問題若是是要從解析幾何的角度去解決幾乎複雜到無法下手,除非是平面剛好是過座標軸的特殊狀況,可是若是從向量模型考慮就很簡單:根據平面方程,平面的法向量(Normal Vector)是v=(a, b, c)
,設從平面上任意一點(x, y, z)
到(x0, y0, z0)
的向量爲w
,那麼經過內積dot_product(w, v)
算出w
到v
的投影向量p
,其大小就是(x0, y0, z0)
到平面a*x + b*y + c*z + d = 0
的垂直距離。這裏用到了向量模型的基本概念:法向量,投影向量,內積,整個問題解決過程簡潔明快。
下面再給你們留一道類似的練習題(熟悉機器學習的朋友可能會發現這是線性代數在線性分類中的應用):
給定n維空間中的兩點(a1, a2, ... an)
,(b1, b2, ... bn)
和一個超平面c1*x1 + c2*x2 ... + cn*xn + d = 0
,請判斷兩點在超平面的同側或異側?
離開向量,下面咱們要請出線性代數的另外一個主角:矩陣(Matrix)。
線性代數定義了矩陣和向量、矩陣和矩陣的乘法,運算規則很複雜,用來作什麼也不清楚,不少初學者都不能很好地理解,能夠說矩陣是學好線性代數的攔路虎。遇到複雜的東西,每每須要先避免一頭陷入細節,先從總體上把握它。其實,從程序的角度看,不管形式多麼奇怪,它無非是一種語法,語法必然對應了語義,因此理解矩陣的重點在於理解其語義。矩陣的語義不止一種,在不一樣的環境中有不一樣的語義,在同一環境中也能夠有不一樣的解讀,最多見的包括:1)表示一個線性變換;2)表示列向量或行向量的集合;3)表示子矩陣的集合。
矩陣做爲一個總體對應的是線性變換語義:用矩陣A
乘以一個向量v
獲得w
,矩陣A
就表明了v
到w
的線性變換。好比,若是想要把向量v0
按逆時針方向旋轉60度獲得v'
,只須要用旋轉變換矩陣(Rotation Matrix)去乘v0
就能夠了。
除了旋轉變換,拉伸變換也是一種常見的變換,好比,咱們能夠經過一個拉伸矩陣把向量沿x軸拉伸2倍(請試着本身給出拉伸矩陣的形式)。更重要的是,矩陣乘法有一個很好的性質:知足結合率,這就意味着能夠對線性變換進行疊加。舉個例子,咱們能夠把「沿逆時針旋轉60度」的矩陣M和「沿x軸拉伸2倍」的矩陣N相乘,獲得一個新矩陣T來表明「沿逆時針旋轉60度並沿x軸拉伸2倍」。這是否是很像咱們Shell中把多個命令經過管道進行疊加呢?
上面重點介紹了向量模型的座標系無關性,除此以外,向量模型的另外一優勢是它能描述線性關係,下面咱們來看一個熟悉的Fibonacci數列的例子:
Fibonacci數列定義爲:f(n) = f(n-1) + f(n-2), f(0) = 0, f(1) = 1
;問題:輸入n,請給出求f(n)
的時間複雜度不超過O(logn)的算法。
首先,咱們構造兩個向量v1=(f(n+1), f(n))
和v2=(f(n+2), f(n+1))
,根據Fibonacci數列性質,咱們能夠獲得從v1
到v2
的遞推變換矩陣:
並進一步獲得:
這樣就把線性遞推問題轉化爲了矩陣的n次冪經典問題,在O(log n)時間複雜度內解決。除了線性遞推數列,初等數學中著名的n元一次方程組問題也能夠轉化爲矩陣和向量乘法形式更容易地解決。這個例子是想說明,凡是知足線性關係的系統都是向量模型的用武之地,咱們每每能夠把它轉化爲線性代數獲得簡潔高效的解決方案。
本文提出了一種觀點:從應用的角度,咱們能夠把線性代數視爲一門特定領域的程序語言。線性代數在初等數學基礎上創建了向量模型,定義了一套語法和語義,符合程序語言的語言契約。向量模型具備座標系無關性和線性性,它是整個線性代數的核心,是解決線性空間問題的最佳模型。向量的概念、性質、關係、變換是掌握和運用線性代數的重點。