特別說明:要在個人隨筆後寫評論的小夥伴們請注意了,個人博客開啓了 MathJax 數學公式支持,MathJax 使用
$
標記數學公式的開始和結束。若是某條評論中出現了兩個$
,MathJax 會將兩個$
之間的內容按照數學公式進行排版,從而致使評論區格式混亂。若是你們的評論中用到了$
,可是又不是爲了使用數學公式,就請使用\$
轉義一下,謝謝。html
想從頭閱讀該系列嗎?下面是傳送門:python
不知道常常須要作科學計算的朋友們有沒有這樣的好奇:在 Linux 系統下使用什麼工具呢?說到科學計算,首先想到的確定是 Matlab,若是再說到符號計算,那就非 Mathematica 不可了。惋惜,以上兩款軟件都是商業軟件。雖然破解版滿天飛,可是這不符合開源世界的邏輯。在 Linux 系統下,也有很是不錯的科學計算工具,包括符號計算的也有。下面我就來隆重向你們推薦幾款。linux
這款軟件是 GNU 出品,在 GNU 的在線文檔網站上能夠下載到它的完整的幫助文檔,我喜歡 pdf 版,能夠一口氣從頭讀到尾,很舒服。從語法角度講,Octave 和 matlib 徹底兼容。下面是其運行效果圖:
算法
它也有 GUI 界面的包裝,那就是 QtOctave,以下圖:
編程
在 Ubuntu 下該軟件的安裝很是簡單,使用以下命令便可:數組
sudo apt-get install octave sudo apt-get install qtoctave
數值計算使用 Octave,那麼符號計算就少不了 Maxima 了。因爲符號計算中,數學公式的顯示也是很是重要的一環,因此我喜歡用它的 GUI 封裝 wxMaxima,該軟件使用以下命令安裝:瀏覽器
sudo apt-get install wxmaxima
下面是它的運行效果圖:
服務器
有了 GUI 的封裝,咱們的學習曲線都要簡單不少,由於它的功能都在它的菜單欄中體現出來了。Maxima 也自帶完善的文檔,以下圖:
多線程
符號運算不只能對各類數學公式進行運行、變形、化簡,也能夠直接對函數做圖,以下圖:
編程語言
可是以上介紹的都不是重點。下面的工具纔是我這篇隨筆的重量級嘉賓。它就是:
使用 python 進行科學計算最近幾年很火,主要得益於 python 語言和 Numpy、SciPy、pandas、matplotlib、SymPy 等庫。另一個大殺器就是 Jupyter notebook,它就是之前的 IPython notebook,如今的版本又進步了,更名叫 Jupyter notebook,能夠經過 pip 進行安裝。Jupyter notebook 能夠說是提供了在數學方面讀寫算加畫圖一條龍的服務了。
另外,寫 Python 最好的工具固然是 PyCharm 啦。若是隻是作科學計算,使用 Community 版本就足夠了,我並不建議你們用破解版。最後,雖然在 Ubuntu 中可使用 apt-get 安裝 Numpy、SciPy、pandas、matplotlib、SymPy 等庫,可是我以爲污染全局的 Python 環境很差,因此我首先考慮使用虛擬環境,而後在虛擬環境中使用 pip 安裝這些庫到局部環境中。在 PyCharm 中建立項目的時候,就能夠選擇使用虛擬環境,我就選擇 Python 自帶的 venv 就行了,以下圖:
直接打開 PyCharm 中的 Terminal,就能夠進入這個項目的虛擬環境,在這個 Terminal 中運行的命令,默認就在這個項目的虛擬環境中執行。咱們能夠在這個 Terminal 中運行pip install
命令安裝全部須要的庫。在 PyCharm 中使用pip install
命令時,有一個使人頭痛的問題,那就是從國外的源下載的速度太慢,咱們能夠替換成國內的源。我太懶,都是使用臨時替換,只須要添加-i 源地址
參數就能夠了。例如,要安裝 numpy,而且選擇從阿里雲下載,就使用pip install numpy -i https://mirrors.aliyun.com/pypi/simple/
命令。以下圖:
經常使用的國內源有:
新版 Ubuntu 要求使用 https 源,要注意。
使用pip install jupyter
就能夠安裝 Jupyter notebook 了,以下圖:
安裝完以後,連 PyCharm 中的 Python 控制檯的提示符都變了,從>>>
變成了In[1]:
。以下圖:
而後運行jupyter notebook
命令,就能夠啓動 Jupyter notebook 了。不錯,這是一個 B/S 應用,咱們啓動它時會在咱們的機器上創建一個簡單的服務器,而後自動打開一個瀏覽器訪問這個服務器,遠程訪問也行。下面是運行效果:
Jupyter notebook 中的內容是由一個一個的輸入區域組成的,稱爲 Cell。每個 Cell 除了能夠輸入代碼,還能夠輸入 Markdown,如上圖中所示。按 Shift+Enter 便可結束該區域的輸入,並執行和顯示效果。若是之後要從新編輯裏面的內容,雙擊該區域便可。Markdown 區域也是支持 MathJax 的哦。上面的輸入執行後,效果以下圖:
下面看看使用 NumPy 來進行數值計算和繪圖的效果:
在這裏,使用了 Jupyter notebook 的魔術命令%matplotlib inline
將繪圖結果直接顯示到頁面中。Jupyter notebook 還有好多魔術命令可用,好比,咱們可使用%timeit
測試某段代碼的性能。
使用 pandas 進行數據分析並繪圖的效果:
最後,看看使用 SymPy 進行符號計算的效果:
從上圖能夠看到,SymPy 的 latex 函數能夠把輸出的數學公式轉換成 LaTeX 代碼,不過該代碼有點問題,它裏面每一個反斜槓都變成了雙反斜槓。將該 LaTeX 代碼複製、修改後,輸入 Markdown 區域就能夠看到完美的數學公式了。以下圖:
咱們在 Jupyter notebook 中創建的筆記是能夠保存的,並且保存的是純文本的 JSON 格式,擴展名是.ipynb
,因此能夠很是方便地把它放到 GitHub 進行分享。從 Jupyter notebook 的幫助菜單能夠很方便地導航到 NumPy、SciPy、matplotlib、pandas、SymPy 的幫助文檔。在 matplotlib 的官網中,還專門有一個 gallary 頁面,裏面有各類圖表的縮略圖和代碼,對咱們的學習真的是頗有幫助哦。
最後,PyCharm 也支持咱們建立 Jupyter notebook 文件,PyCharm 能夠本身啓動一個 Jupyter notebook 服務器,也能夠鏈接到一個現有的 Jupyter notebook 服務器。其實從理論上講,這沒什麼難的,瀏覽器能作到的事,PyCharm 確定也能夠作到,只是在 PyCharm 中內嵌一個瀏覽器內核的事而已。可是,目前我不太建議在 PyCharm 中使用 Jupyter notebook,由於它的黑色主題看起來仍是不太舒服,並且對 Markdown 的支持很差。以下圖:
前面搞了很多工具論,下面再來討論一下編程語言。這段時間,我繼續徜徉在數值計算的世界。爲了普遍學習數值計算方面的知識,我抽空看了 Python 科學計算和數值分析方面的書,也仔細研讀了 Octave 的用戶手冊,甚至連古老的 Fortran、新興的 R 語言我都去逐一瞭解。對於數值計算的庫,我瞭解了一下 Boost 的 uBLAS,之前也用過 OpenCV,固然,瞭解最多的仍是 Python 中的 NumPy、SciPy 和 pandas。今天談的內容是我對適合作數值計算的編程語言的一些見解,主要是一些思路方面的東西,不評論具體語言的優劣。另外,我是想到哪兒寫到哪兒,若是有什麼不對的地方歡迎你們指正。
若是數值計算僅僅只是兩個標量之間的加減乘除,那就不須要我在這裏浪費口舌了。向量啊、矩陣啊、多維數組啊什麼,纔是數值計算真正的主角。因此,適合作數值計算的編程語言必須有一個好的方式表示數組,特別是多維數組。哪一種方式好呢?是這樣:
int a[m][n][k];
仍是這樣:
int a[m,n,k];
看似沒有什麼差異,可是若是你想獲取數組 a 的形狀呢?好比這樣:
? = a.shape();
或者再更進一步,想改變數組 a 的形狀呢?好比這樣:
a.reshape(?);
在上面的代碼中,「?」究竟應該用什麼代替呢?
若是讓我給出答案,我會說:要用元組。不少編程語言中都有元組的概念,好比 Python。元組就是用逗號隔開的幾個值,能夠加圓括號,也能夠不加。我以爲加上圓括號後可讀性更好。好比 (a,b)
是元組,(3,4,5)
也是元組。若是寫成 [3,4,5]
那就是數組了,在 Python 中,也稱之爲列表。不過 Python 的列表功能比數組要強大,由於數組只能保存同一種數據類型的值,而列表能夠保存任何對象。數組通常狀況下不能動態改變長度,而列表能夠。Octave 語言中使用 cell array 這個術語來表示能夠保存不一樣類型對象的容器。Octave 中的數組和矩陣是能夠動態改變長度的。C 語言的數組沒有動態改變長度這個功能,而若是使用 C++ 的話,則必須使用 vector<>
模板類。
我認爲,一個好的編程語言必需要有「元組」這個一個概念,必須可以用好大括號、中括號和小括號。在有沒有元組這個問題上,不少語言作得很差,C 語言沒有,C++ 也沒有,Java 沒有,C# 這個有不少新功能的語言也沒有,不要告訴我有 Tuple<>
模板類能夠用,那個真的沒有語言內置的元組功能好。在能不能用好大中小括號這個問題上,C 語言就作得很差。你看它無論是初始化數組,仍是初始化 struct,都是用大括號。而 Python 和 JSON 就作得很好嘛,初始化數組用中括號,初始化對象或字典的時候採用大括號。若是加上小括號表示元組,那就齊活兒了。
數值計算能夠針對標量、一維數組、二維數組以及n維數組進行。數組能夠以下組織,以下圖:
元組最大的用途就是能夠用來表示數組的形狀了。好比一維數組的形狀爲 (n,)
,請注意其中的逗號不能省略。二維數組的形狀 (m,n)
,三維數組的形狀 (m,n,k)
,依次類推。另外,元組能夠用來對數組中的元素進行索引。好比:
a = [ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16] ]; b = a[2,3,3];
元組還有一個很大的用途,那就是可讓一個函數返回多個值。C 語言在這個方面是作得比較醜陋的,若是一個函數要返回多個值,只能給這個函數傳指針或者多重指針做爲參數,C++ 能夠傳引用,C# 更加多此一舉,專門有一個out
關鍵字用來修飾函數的參數。微軟你真是的,你既然能想到out
,你就不能想到元組嗎?常見的例子,好比meshgrid()
函數能夠同時初始化兩個數組,peak()
函數能夠同時初始化三個數組。你看它們用元組多方便:
(xx, yy) = meshgrid(x, y); (xx, yy, zz) = peak();
另外,元組還能夠這樣用,好比交換兩個變量的值:
(a,b) = (b,a);
在數值計算中,數組的初始化也是很是重要的一環。若是像 C 語言這樣寫:
int a[100] = {1, 2, 3, 4, ... , 100};
估計不少人是要罵孃的。這樣寫:
for(int i=0; i<100; i++){ a[i] = i+1; }
也不優雅。我只是想初始化一個數組而已,怎麼就非得要寫一個循環呢?若是是二維數組呢,就得兩層循環,三維數組就得三層。真的是太鬧心了。
另外,如前所述,我也不喜歡在初始化數組的時候用大括號。我以爲中括號就是爲數組而生。好比這樣:
a = [1, 2, 3, 4];
這就是一個一維數組,可是若是這樣寫:
a = [ [1, 2, 3, 4] ];
就是一個行向量。若是寫成這樣:
a = [ [1], [2], [3], [4] ];
那麼這就是一個列向量,以下圖:
固然,上面的示例只有四個數字,這麼寫一寫無可厚非。若是是不少數字呢?或者不少維的數組呢?這時就必須得用到不少初始化函數了,並且這些初始化函數最好能接受元組做爲參數來決定數組的形狀。好比這樣:
a = xrange( 1, 60, (3,4,5) ); //用1到60的數字初始化一個3*4*5的數組 b = randn ( (3, 4, 5) ); //用隨機數初始化一個3*4*5的數組
其它的初始化函數還有 linspace()
、logspace()
、ones()
、zeros()
、eyes()
等等。這些函數還能夠配合 reshape()
使用,好比這樣:
c = linspace(0, 2*pi, 60).reshape(3, 4, 5);
在全部的這些初始化中,元組都是重要的組成部分。
其實,range 除了能夠是一個函數,還能夠更省點兒事,像這樣寫:
r = 0:10:2; //0,2,4,6,8,10 s = 11:0:-3; //11,8,5,2
在某些語言中,也把這個功能叫切片。其實就是:
的靈活運用,有標點符號能夠用固然不能浪費嘛。使用切片,只須要指定起始值、終止值和步長,就能夠得到一個數字序列。
可是,:
最大的用途並非用來對數組進行初始化,而是對數組進行索引。好比,a
是一個三維數組,能夠經過切片來獲取其中的一部分數據。見下面的代碼:
a = range(1, 60).reshape(3, 4, 5); // a是一個三維數組 b = a[1, 2:3, 1:4]; // b是一個二維數組,其值爲[ [12, 13, 14, 15], [17, 18, 19, 20]]
切片除了能夠指定起始值和終止值外,也能夠指定步長。固然,也能夠只用一個單獨的:
,表明取這一整個軸。關於軸的概念,能夠看我前面的圖片。見下面這樣的代碼:
a = range(1, 60).reshape(3, 4, 5); // a是一個三維數組 b = a[1, :, :]; // b的值爲二維數組[[1,2,3,4,5], [6,7,8,9,10], [11,12, 13, 14, 15], [16,17, 18, 19, 20]]
在對多維數組進行加減乘除的時候,若是使用傳統的像 C 這樣的語言,則避免不了要寫循環。好比要計算兩個多維數組的加法,不得不寫這樣的代碼:
m = 10; n = 20; k = 30; a = randn(m, n, k); //形狀爲(m, n, k)的三維數組,初始化爲隨機值 b = randn(m, n, k); //形狀爲(m, n, k)的三維數組,初始化爲隨機值 for(int i=0; i<m; i++){ for(int j=0; j<n; j++){ for(int p=0; p<k; p++){ c[i, j, p] = a[i, j, p] + b[i, j, p]; } } }
上面的代碼固然遠不以下面這樣的代碼簡潔:
C = A + B;
因此不寫循環基本上就成了全部數值計算語言的標準配置。Matlab 和 Octave 是這樣,NumPy 是這樣,R 語言也是這樣。C++ 也在追求這樣,由於 C++ 中有運算符重載的功能,因此能夠對矩陣類重載加減乘除運算符。可是 C++ 中運算符的基礎設施有缺陷,好比它沒有乘方運算符(冪運算符),在 Octave 和 NumPy 中,均可以這樣計算 $ x^y $:x**y。可是在 C++ 中,只有使用函數 power(x, y)
。不要想 ^
運算符,它是一個位運算符,因此取冪只有使用 **
了。另外,多維數組運算還有特例,好比二維數組之間加減乘除,既能夠是逐元素的加減乘除,也能夠是矩陣的加減乘除。向量計算也有特例,既能夠是逐元素加減乘除,也多是向量內積(點乘)。若是正好是長度爲 3 的向量,還能夠計算叉乘。這些運算符都須要從新定義,因此雖然 C++ 有重載運算符的機制,可是由於這些運算符徹底超越了 C++ 的基礎設施,因此 C++ 也沒有辦法寫得很優雅。
不寫循環還有一個優勢,那就是能夠對運算速度進行優化。優化是編譯器或解釋器的責任,寫數值計算程序的人能夠徹底不用費心。編譯器或解釋器可採起的優化方式有多是利用 SSE 等多媒體指令集,也多是發揮多核 CPU 的多線程優點,甚至是使用 GPGPU 計算都有可能。若是用戶非要寫成 C 語言那樣的循環,而他又不會內聯彙編或 OpenMP 的話,那麼就談不上什麼運算速度的優化了。
不寫循環,直接把兩個多維數組進行加減乘除固然省事。可是若是兩個數組的形狀不同呢?好比一個二維數組加一個行向量,或一個二維數組加一個列向量,甚至是數組加減乘除一個標量,會出現什麼狀況呢?
不用擔憂,在面向數值計算的語言中,通常都有「廣播」這樣一個特性。當兩個數組的形狀不同時,形狀比較小的那個每每能夠在長度爲 1 的維度上進行廣播。以下圖:
Fancy indexing,有的書上翻譯成花式索引,但我認爲叫奇異索引比較好。它就是指一個低維的數組,可使用高維的數組進行索引,最後獲得的結果是一個高維的數組。若是索引中含有切片,可能會獲得一個更高維度的數組做爲結果。
這個概念理解起來比較難。特別是再配合切片使用,更加增長其複雜性。所謂一圖勝千言,先看普通索引的狀況:
前面提到,對多維數組進行索引的時候須要用到元組,元組的長度等同於數組的維數。對於普通索引而言,元組的各個部分要麼是整數,要麼是切片。而對於奇異索引而言,索引元組的各個組成部分均可能是多維數組或者切片。若是是多維數組,則最後獲得的數組的形狀和索引數組的形狀相同,若是配合切片,則可能獲得更高維的數組。以下圖:
編程語言發展這麼多年,一直在進化,也一直在相互靠攏。對於一個編程語言來講,是應該面向過程仍是面向對象?是靜態類型仍是動態類型?這些都是值得思考的地方。可是在函數調用方面,有一些思想卻是能夠學習。
在 C 語言這樣比較古老的語言中,對於函數的參數來講,只有位置參數一種。也就是說,像一個函數傳遞參數的時候,只能正確的參數放到正確的位置,並且參數的個數必須和函數定義的相同。這是最原始的函數調用思想。
緊接着,在某些編程語言如 Java、C# 中,有了可選參數這個概念。可是可選參數要放到參數列表的最後面,並且必須提供默認值。當調用函數時若是指定了這個參數,則使用調用時指定的值,不然使用默認值。
可是我以爲適合數值計算的語言必須還得更進一步,提供關鍵字參數的功能。什麼是關鍵字參數呢?好比對數據進行繪圖的時候,須要指定線型、標籤、標題等各類屬性,能夠這樣調用函數:
plot(x, y, marker="*", color="r", linestyle="-", title="...", legend="...", xlabel="...", ylabel="...");
每個參數調用的時候均可以指定它的名字,這樣咱們就不用去死記各個參數的位置,是否是很方便呢?
對於一門編程語言而言,生態壞境很重要。在數值計算領域更是如此。由於不少數值計算的庫都是專業的人士寫給專業人士看的,好比物理專業的寫物理領域的算法,氣象專業的寫氣象專業的算法,因此不大可能有一個全面的官方的,像 C 或 C++ 這樣一個由 ANSI 定義的庫。
普遍接受開源社區的貢獻是一個比較好的辦法。Perl 是這樣,Python 也是這樣,新興的 R 語言也是這樣。Perl 有 CPAN,Python 有 PyPI,R 語言也有 CRAN。至於 Matlab,那更是有各類各樣的工具包。
OK,就寫這麼多吧,還有其它的一些什麼特點,我想到後再隨時更新此文。
另外,本文中全部的圖片都是在 Ubuntu 中使用 Inkscape 矢量圖軟件繪製而成。
我對此次寫的這個系列要求是很是高的:首先內容要有意義、夠充實,信息量要足夠豐富;其次是每個知識點要講透徹,不能模棱兩可含糊不清;最後是包含豐富的截圖,讓那些不想裝 Linux 系統的朋友們也能夠領略到 Linux 桌面的風采。若是個人努力獲得你們的承認,能夠掃下面的二維碼打賞一下:
該隨筆由京山遊俠在2018年11月16日發佈於博客園,引用請註明出處,轉載或出版請聯繫博主。QQ郵箱:1841079@qq.com