程式設計與線性代數
先來了解線性代數是什麼東東?
在大學數學學科中,線性代數是最為抽象的一門課,從初等數學到線性代數的思維跨度比微積分和概率統計要大得多。很多人學過以後一直停留在知其然不知 其所以然的階段,若干年之後接觸圖形程式設計或機器學習等領域才發現線性代數的應用無處不在,但又苦於不能很好地理解和掌握。的確,多數人很容易理解初等數學 的各種概念,函式、方程、數列一切都那麼的自然,但是一進入線性代數的世界就好像來到了另一個陌生的世界,在各種奇怪的符號和運算裡迷失了。
我在初接觸線性代數的時候簡直感覺這是一門天外飛仙的學科,一個疑問在我腦子裡浮現出來:線性代數到底是一種客觀的自然規律還是人為的設計?
如果看到這個問題,你的反應是“這還用問,數學當然是客觀的自然規律了”,我一點兒都不覺得奇怪,我自己也曾這樣認為。從中學的初等數學和初等物理 一路走來,很少人去懷疑一門數學學科是不是自然規律,當我學習微積分、概率統計時也從來沒有懷疑過,唯獨線性代數讓我產生了懷疑,因為它的各種符號和運算 規則太抽象太奇怪,完全對應不到生活經驗。所以,我還真要感謝線性代數,它引發了我去思考一門數學學科的本質。其實,不止是學生,包括很多數學老師都不清 楚線性代數到底是什麼、有什麼用,不僅國內如此,在國外也是這樣,國內的孟巖寫過《理解矩陣》,國外的Sheldon Axler教授寫過《線性代數應該這樣學》,但都還沒有從根本上講清楚線性代數的來龍去脈。對於我自己來講,讀大學的時候沒有學懂線性代數,反而是後來從程式設計的角度理解了它。很多人說數學好可以幫助程式設計,我恰好反過來了,對程式的理解幫助了我理解數學。
本文的目標讀者是程式設計師,下面我就帶各位做一次程式設計師線上性代數世界的深度歷險!既然是程式設計師,在進入線性代數的領域之前,我們不妨先從考察一番程式世界,請思考這樣一個問題:
計算機裡面有彙編、C/C++、Java、Python等通用語言,還有Makefile、CSS、SQL等DSL,這些語言是一種客觀的自然規律還是人為的設計呢?
為什麼要問這樣一個看起來很蠢的問題呢?因為它的答案顯而易見,大家對天天使用的程式語言的認識一定勝過抽象的線性代數,很顯然程式語言雖然包含了 內在的邏輯,但它們本質上都是人為的設計。所有程式語言的共同性在於:建立了一套模型,定義了一套語法,並將每種語法對映到特定的語義。程式設計師和語言實現 者之間遵守語言契約:程式設計師保證程式碼符合語言的語法,編譯器/直譯器保證程式碼執行的結果符合語法相應的語義。比如,C++規定用new A()語法在堆上構造物件A,你這樣寫了C++就必須保證相應的執行效果,在堆上分配記憶體並呼叫A的建構函式,否則就是編譯器違背語言契約。
從應用的角度,我們能不能把線性代數視為一門程式語言呢?答案是肯定的,我們可以用語言契約作為標準來試試。假設你有一個影象,你想把它旋轉60 度,再沿x軸方向拉伸2倍;線性代數告訴你,“行!你按我的語法構造一個矩陣,再按矩陣乘法規則去乘你的影象,我保證結果就是你想要的”。
實際上,線性代數和SQL這樣的DSL非常相似,下面來作一些類比:
模型和語義:SQL是在低階語言之上建立了關係模型,核心語義是關係和關係運算;線性代數在初等數學之上建立了向量模型,核心語義是向量和線性變換
語法:SQL為每種語義定義了相應的語法,如select, where, join等;線性代數也定義了向量、矩陣、矩陣乘法等語義概念相應的語法
編譯/解釋:SQL可以被編譯/解釋為C語言;線性代數相關概念和運算規則可以由初等數學知識來解釋
實現:我們可以在MySQL、Oracle等關聯式資料庫上進行SQL程式設計;我們也可以在MATLAB、Mathematica等數學軟體上進行線性代數程式設計
所以,從應用的角度看,線性代數是一種人為設計的領域特定語言(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 = abs(1/2 * cross_product((x2 - x1, y2 - y1), (x3 - x1, y3 - y1)))
注:abs表示取絕對值,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元一次方程組問題也可以轉化為矩陣和向量乘法形式更容易地解決。這個例子是想說明,凡是滿足 線性關係的系統都是向量模型的用武之地,我們往往可以把它轉化為線性代數得到簡潔高效的解決方案。
總之,向量模型是整個線性代數的核心,向量的概念、性質、關係、變換是掌握和運用線性代數的重點,線性代數視為一門特定領域的程式語言,在初等數學基礎上建立了向量模型,定義了一套語法和語義,符合程式語言的語言契約。
END
∑編輯 | Gemini
來源 | 今日頭條
微信公眾號“演算法數學之美”,由演算法與數學之美團隊打造的另一個公眾號,歡迎大家掃碼關注!
更多精彩:
演算法數學之美微信公眾號歡迎賜稿
稿件涉及數學、物理、演算法、計算機、程式設計等相關領域,經採用我們將奉上稿酬。
投稿郵箱:math_alg@163.com
相關文章
- 線性代數
- 程式設計師的線性代數教程!Jupyter 程式碼和視訊可能更適合你程式設計師
- 線性代數學習
- 線性代數基礎
- 線性代數相關
- 線性代數--矩陣矩陣
- 線性代數中的線性方程組方法
- MATLAB版線性代數-線性方程組1Matlab
- 【scipy 基礎】--線性代數
- 如何入門線性代數?這裡有一份Python線性代數講義Python
- 線性代數--二次型
- 現代c++與模板超程式設計C++程式設計
- 線性代數常用基本知識整理
- 線性代數本質第10節
- Wincc 7.5SP2下VBA程式設計批次設定變數線性標定程式設計變數
- 《線性代數的本質》筆記10筆記
- 《線性代數的本質》筆記(09)筆記
- datawhale_Day4_task09_線性代數
- [譯] JavaScript 線性代數:使用 ThreeJS 製作線性變換動畫JavaScriptJS動畫
- [譯] 用 React 製作線性代數教程示例:網格與箭頭React
- Shell程式設計規範與變數程式設計變數
- JavaScript函數語言程式設計之pointfree與宣告式程式設計JavaScript函數程式設計
- 01 shell程式設計規範與變數程式設計變數
- 程式設計思想之冪等性 | 程式設計之道程式設計
- 數學與Python有機結合及統計學、微積分、線性代數相關資源、圖形軟體Python
- GAMES101系列筆記一 圖形學概述與線性代數入門GAM筆記
- 《線性代數的本質》筆記(01-03)筆記
- 高等代數理論基礎22:線性相關性
- “花書”的佐餐,你的線性代數筆記筆記
- 高等代數 第三章 線性空間
- NumPy之:多維陣列中的線性代數陣列
- 併發程式設計系列之Lock鎖可重入性與公平性程式設計
- 註釋之重——程式設計師與程式碼可維護性程式設計師
- 數學與程式設計:“機率論”總結程式設計
- shell程式設計02——變數定義與使用程式設計變數
- Socket程式設計-長連線與短連線,心跳(keep-alive)程式設計Keep-Alive
- Java併發程式設計-解決可見性與有序性問題Java程式設計
- 現代程式設計 homework-03程式設計