第 12 章 - 完整案例 RLab

在我還是學生的時候,那時候我們還不知道甚麼是網路,因此只能在電腦上寫自己的程式,幾乎所有的程式都是從頭到尾自己寫,除了使用《系統預設的函式庫》以外,就沒有別的資源可以用了。

但是、這30年來,程式語言的長相雖然並沒有非常大的改變,不過網路資源卻有了很大的改變。

原本由微軟主導的開發環境,逐漸變成《微軟、蘋果、Google》三強鼎立的局面。

除了這些商業公司之外,更重要的是開放原始碼的蓬勃發展,像是本書所採用的 node.js 就是個開放原始碼的系統。

現在、只要你會善用《開放原始碼》與《各種套件》,那麼很少需要從頭打造整個系統了。

於是、程式人的角色,往往從《手工業》變成了《組裝業》,我們愈來愈常使用外部的各種套件與模組,自己寫的程式往往只是將這些模組《黏合再一起形成我們所想要系統》而已!

在本章中,筆者希望透過自己創建 rlab 這個《科學計算套件》的過程,分享一下參與這種《程式組裝業》所需要具備的能力!

在科學計算領域,商用的軟體目前以 matlab 為主流,而開放原始碼軟體則有 R 、Octave 與 Python 的 (numpy, scipy) 等軟體。

不過、截至目前為止,我仍然沒找到以javascript語言為主的完整《科學計算》套件,因此我決定自己打造一個。

科學計算軟體通常必須支援《矩陣+機率統計》等功能,因此我們將使用 numeric.js 作為矩陣功能的主要模組, 用 jstat.js 作為機率統計功能的主要模組,然後再移植包裝一些較小的模組,組合建構出 rlab 這個科學計算套件,以下就是我們建構套件的過程。

矩陣套件 numeric.js

在我所用過的 javascript 矩陣套件當中,numeric.js 是最完整的一個,因此 rlab 科學計算套件的建構,將會以 numeric.js 作為矩陣部分的核心。

以下是 numeric.js 套件的 npm 網址:

雖然 npm 套件管理器中已經包含了 numeric.js 套件,但是很多套件都有自己的官方網站, numeric.js 也不例外,以下是 numeric.js 的官網。

我們可以透過 npm install numeric 這個指令,安裝這個套件,然後寫個小程式 numericTest.js 來測試一下這個套件。

檔案: numericTest.js

var numeric = require("numeric");

function print(name, m) {
  console.log("===================="+name+"=======================");
  console.log(numeric.prettyPrint(m));
}

var A = [[1,2,3],[3,2,2],[4,3,5]];
print("A", A);

var iA = numeric.inv(A);
print("iA", iA);

var AiA = numeric.dot(A, iA);
print("A*iA", AiA);

安裝與執行結果:

D:\jsbook>npm install numeric
D:\jsbook
`-- [email protected]

npm WARN enoent ENOENT: no such file or directory, open 'D:\jsbook\package.json'

npm WARN jsbook No description
npm WARN jsbook No repository field.
npm WARN jsbook No README data
npm WARN jsbook No license field.

D:\jsbook>node numericTest.js
====================A=======================
[[          1,          2,          3],
 [          3,          2,          2],
 [          4,          3,          5]]
====================iA=======================
[[    -0.5714,     0.1429,     0.2857],
 [          1,          1,         -1],
 [    -0.1429,    -0.7143,     0.5714]]
====================A*iA=======================
[[          1,          0,          0],
 [ -1.665e-16,          1,  -2.22e-16],
 [ -4.441e-16,  4.441e-16,          1]]

您可以看到上述程式中,我們用 numeric.inv(A) 這行指令輕鬆地就計算出了反矩陣,這種函數如果要自己寫,其實會很花時間的,但是用了套件之後,我們輕輕鬆鬆的就可以做完這些《矩陣運算》的動作。

當然、不只這些,numeric.js 套件其實還蠻完整的,您可以參考下列網址中的內容,進一步瞭解這個套件到底提供了哪些函數?

統計套件 jstat.js

雖然 numeric.js 套件在矩陣上很好用,但是不支援機率統計的功能,因此我們找到了 jstat.js 這個套件,其 npm 網址如下:

您可以從中找出此專案在 github 上的連結,網址如下:

該專案有自己的說明文件,位於下列網址:

透過這個說明文件,我們可以瞭解這個套件該如何使用。

jstat 裡面雖然也支援了一些《向量矩陣》之類的功能,但是這方面的能力並不夠強大,所以我們在《向量矩陣》方面還是會採用 numeric.js ,但是在《機率分布》還有《統計檢定》方面則會使用 jstat.js 套件的內容。

以下是 jstat 這兩個部份的說明文件:

現在、我們已經具有足夠的資源,可以開始建構我們的 rlab 科學計算套件了!

科學計算套件 -- RLab.js

我們所建構好的 rlab 專案,也同樣放上了 npm 和 github,其網址如下:

您只要用下列指令就可以安裝 rlab 套件。

npm install rlab

安裝好之後,您可以寫些小程式來測試看看。

首先是機率部分的測試,以下是程式碼

檔案:probabilityEx.js

var R = require("rlab");
var dice = R.steps(1,6);
log("sample(1:6, 10)", R.samples(dice, 10));
log("runif(10,0,1)=", R.runif(10, 0, 1).str());
log("rnorm(10,5,1)=", R.rnorm(10, 5, 1).str());
log("dnorm(5,5,1)=", R.dnorm(5, 5, 1));
log("pnorm(5,5,1)=", R.pnorm(5, 5, 1));
log("qnorm(0.5,5,1)=", R.qnorm(0.5, 5, 1));
log("rbinom(10, 5, 0.5)=", R.rbinom(10,5,0.5));
log("dbinom(4, 5, 0.5)=", R.dbinom(4,5,0.5));
log("dbinom(5, 5, 0.5)=", R.dbinom(5,5,0.5));
log("pbinom(4, 5, 0.5)=", R.pbinom(4,5,0.5));
log("qbinom(0.9, 5, 0.5)=", R.qbinom(0.9,5,0.5));

執行結果 :

$ node probabilityEx.js
sample(1:6, 10) [ 3, 5, 3, 2, 3, 3, 1, 2, 4, 3 ]
runif(10,0,1)= [0.9119,0.5899,0.6839,0.1350,0.6894,0.9512,0.8186,0.5826,0.4279,0
.5125]
rnorm(10,5,1)= [5.8961,5.4312,6.0002,5.3623,5.5281,4.4413,6.2144,5.7173,5.3111,1
.3146]
dnorm(5,5,1)= 0.3989422804014327
pnorm(5,5,1)= 0.5
qnorm(0.5,5,1)= 5
rbinom(10, 5, 0.5)= [ 2, 1, 2, 2, 4, 4, 1, 4, 3, 2 ]
dbinom(4, 5, 0.5)= 0.15625
dbinom(5, 5, 0.5)= 0.03125
pbinom(4, 5, 0.5)= 0.96875
qbinom(0.9, 5, 0.5)= 4

接著測試一下統計功能,以下是程式碼:

檔案 : statisticsEx.js

var R = require("rlab");
var v = [1,3,5];
log("v.max()=", v.max());
log("v.min()=", v.min());
log("v.sum()=", v.sum());
log("v.normalize()=", v.normalize());
log("v.normalize().sum()=", v.normalize().sum());
log("v.product()=", v.product());
log("v.mean()=", v.mean());
log("v.range()=", v.range());
log("v.median()=", v.median());
log("v.variance()=", v.variance());
log("v.sd()=", v.sd(), " sd^2=", v.sd()*v.sd());
log("v.cov(v)=", v.cov(v), "v.cor(v)=", v.cor(v));
log("factorial(5)=", R.factorial(5));

執行結果:

$ node statisticsEx.js
v.max()= 5
v.min()= 1
v.sum()= 9
v.normalize()= [ 0.1111111111111111, 0.3333333333333333, 0.5555555555555556 ]
v.normalize().sum()= 1
v.product()= 15
v.mean()= 1
v.range()= 4
v.median()= 3
v.variance()= 2.6666666666666665
v.sd()= 1.632993161855452  sd^2= 2.6666666666666665
v.cov(v)= 4 v.cor(v)= 1
factorial(5)= 120

然後測試《檢定功能》,以下是程式碼

檔案 : testEx.js

var R = require("rlab");
var v = [1,3,5];

var x = R.rnorm(10, 0, 0.1);
log("x=", x.str());
log("x.sort()=", x.sort().str());

var t1=R.ttest({x:x, mu:0});
R.report(t1);

執行結果 :

$ node testEx.js
x= [-0.1405,0.0495,-0.1850,0.0824,0.0687,-0.0854,-0.1049,-0.1171,0.0947,-0.1592]

x.sort()= [-0.0854,-0.1049,-0.1171,-0.1405,-0.1592,-0.1850,0.0495,0.0687,0.0824,
0.0947]
=========== report ==========
name    : ttest(X)
h       : H0:mu=0
alpha   : 0.0500
op      : =
pvalue  : 0.0003
ci      : [-0.2599,-0.1101]
df      : 9.0000
mean    : -0.1850
sd      : 0.1047

然後在測試矩陣功能,以下是程式碼

檔案 : matrixEx.js

var M = require("rlab").M;
var v = [1,2,3];
log("v.sin()=", v.sin());
log("v.norm2()=", v.norm2());
log("v.norm2Squared()=", v.norm2Squared());

var A = [[1,2,3],[4,5,6],[7,3,9]];
var AiA = A.inv().dot(A);
log("AiA=\n", AiA.strM());
log("AiA.tr()=\n", AiA.tr().strM());
log("A=\n", A.str());
log("A.mul(0.1)=\n", A.mul(0.1).strM());
log("A.row(1)=", A.row(1));
log("A.col(1)=", A.col(1));
log("A.sumM()=", A.sumM());
log("A.rowSum()=", A.rowSum());
log("A.colSum()=", A.colSum());
log("A.mean(row)=", A.rowMean().str());
log("A.mean(col)=", A.colMean().str());

var D = M.diag(v);
log("D=", D);

var Eλ = M.eigR(A);
var E = Eλ.E, λ=Eλ.lambda;
log("E*[λ]*E-1=", E.dot(λ.diag()).dot(E.inv()).strM());

矩陣功能的執行結果 :

$ node matrixEx.js
v.sin()= [ 0.8414709848078965, 0.9092974268256817, 0.1411200080598672 ]
v.norm2()= 3.7416573867739413
v.norm2Squared()= 14
AiA=
 [[          1,   1.11e-16,  -1.11e-16],
 [          0,          1,  4.441e-16],
 [ -3.331e-16, -3.331e-16,          1]]
AiA.tr()=
 [[          1,          0, -3.331e-16],
 [   1.11e-16,          1, -3.331e-16],
 [  -1.11e-16,  4.441e-16,          1]]
A=
 [[1.0000,2.0000,3.0000],[4.0000,5.0000,6.0000],[7.0000,3.0000,9.0000]]
A.mul(0.1)=
 [[        0.1,        0.2,        0.3],
 [        0.4,        0.5,        0.6],
 [        0.7,        0.3,        0.9]]
A.row(1)= [ 4, 5, 6 ]
A.col(1)= [ 2, 5, 3 ]
A.sumM()= 40
A.rowSum(2)= [ 6, 15, 19 ]
A.colSum(2)= [ 12, 10, 18 ]
A.mean(row)= [2.0000,5.0000,6.3333]
A.mean(col)= [4.0000,3.3333,6.0000]
D= [ [ 1, 0, 0 ], [ 0, 2, 0 ], [ 0, 0, 3 ] ]
E*[λ]*E-1= [[          1,          2,          3],
 [          4,          5,          6],
 [          7,          3,          9]]

最後我們還自己加上了微分和積分的函數,測試程式如下:

檔案 : differentialEx.js

var R = require("rlab");

var d = R.D.d, i=R.D.i, sin=R.sin, PI = R.PI, x2=(x)=>x*x;

log('d(x^2,2)=', d(x2, 2));
log('d(sin(x/4),pi/4)=', d(sin, PI/4));
log('i(x^2,0,1)=', i(x2,0,1));
log('i(sin(x),0,pi/2)=', i(sin,0,PI/2));

微積分部分的執行結果 :

D:\Dropbox\github\rlab\example>node differentialEx.js
d(x^2,2)= 4.000999999999699
d(sin(x/4),pi/4)= 0.7067531099743674
i(x^2,0,1)= 0.33283350000000095
i(sin(x),0,pi/2)= 0.9997035898637557

至於這樣的模組是如何上傳到 npm 與 github 的,請大家參考以下這篇投影片!

結語

到目前為止,我們已經介紹完 javascript + node.js 的語法、函式庫、模組、檔案、資料庫、網路等等功能。

當您學會這些部分,您就已經學會 javascript 語言與 node.js 平台了。

不過、node.js 主要是用來寫網站伺服端 server 的,而 javascript 則是瀏覽器裏唯一的語言,因此是前端網頁開發的主力。兩者合併就可以成為強大的網站開發工具。

但是要學習網站開發,還需要像 HTML, CSS, jQuery, Koa 等方面的知識,我們將會在本書的續集當中,介紹如何用 node.js 開發網站的這個主題。

如果你想學習網站設計,那麼就請閱讀本書的續集 - 《專門為中學生寫的 JavaScript 程式書 - 網站設計篇》 吧!

習題

  1. 請開始邁向偉大的航道,寫一個《自己真正想寫》的 node.js 專案吧!

results matching ""

    No results matching ""