2019年10月31日 星期四

日元對台幣貶值

今天看到 YAHOO 這篇新聞 :

日圓又跌啦!換5萬賺約12碗拉麵

新聞說換 5 萬台幣比今年高峰時賺了 12 碗拉麵 (即 12000 日元), 換 10 萬就是 24 碗拉麵囉! 計算如下 :

台銀現金匯率 :

2019-08-13 : 0.3029
2019-10-31 : 0.2838

# 台銀營業時間牌告匯率
# 台銀日元近半年走勢圖




換 10000 元台幣差價 :

10000/0.2838 - 10000/0.3029=2384 元日幣

換 10 萬台幣差價=23840 元日幣

回漲到 0.3029 時換回台幣=23840*0.3029=7221 元台幣

7221/100000=7.22%

但查詢玉山日元走勢 (可查過去兩年), 發現在 2019-08 之前一波高點是 2016 年底, 亦即 2017 年初買的日元等了快兩年才來到新高點啊! 太久了 :

玉山銀行日元對台幣走勢





不過明年七月東奧, 日元或許會再漲吧? 不過如果以這個週期來看, 要抱著日元 1 年多才可能 7% 利得似乎有點久, 更何況外匯變化大, 除非要去日本旅遊需要, 還是抱台股好了.

關閉 Hostinger 自動扣款

三年前向英國虛擬主機業者 Hostinger 租用的空間與域名即將在 11/24/2019 到期, 前幾天收到通知說將於到期日自動從之前登記的信用卡扣款, 因已決定不再租用, 所以先登入系統取消自動更新功能 :





登入後在 Billing 頁籤會看到有兩筆新帳單, 把後面的 Auto Renew 扳到 OFF 位置就不會自動扣款了. 然後切到 Payment Method 頁籤將所登記的信用卡資料刪除即可.

舊用戶續約只打 75 折, 連同域名價格合計 106.2+11.9=118.1 英鎊, 以今日 39.338 匯率約合台幣 4646 元, 一年要 1549 元不算貴, 但主要是目前沒有在用 PHP 開發, 放著也是浪費, 而且域名價格也是越來越貴.

2019年10月30日 星期三

Python 學習筆記 : Numpy 測試 (一) : 建立陣列

最近因為看計算物理學的書時, 發現要用到 Numpy 與 Scipy 這兩個計算套件, 前兩章靠之前的初學還看得下去, 但後面就吃力了, 於是下定決心認真學習資料科學, 這也是機器學習的蹲馬步基本功, 資料科學必須練得紮實, 機器學習才能學得好. 計算物理學就等資料科學學完再來吧!

Python 資料科學基本上包含三部分 :
  1. Numpy
  2. Pandas
  3. Matplotlib
這三個套件都是免費的開源軟體, 與 MATLAB 有相似度極高之 API 介面與運算效能, 其中 Numpy 是資料科學的第一課, 是用 Python 進行高維陣列大量運算的主要工具.

Python 內建資料型態 list 屬於動態型態, 資料結構中含有指標群指向元素物件, 可儲存異質資料, 若使用 list 儲存同質性 (固定型態) 的數值資料, 在存取運算上速度太慢缺乏效率. 雖然 Python 3.3 之後內建了 array 模組, 但它只支援一維陣列且陣列運算函數不多, 故 Python 內建資料型態均不適合用來做高維度陣列運算, 參考 :

Python 學習筆記 : 陣列模組 array 測試

Jim HuguninTravis Oliphant (Anaconda 的創辦人與 Scipy 開發者) 分別在 1996 年與 2005 年相繼開發了 Python 的開源數學運算擴充函式庫 Numeric 與 Numarray, 後來於 2006 年結合發展成 Numpy 套件. Numpy 採用 BLASLAPACK 線性代數函式庫 API, 透過底層以 C 與 Fortran 撰寫的程式碼來提升高維向量與陣列運算效能, 其函數介面與功能與 MATLAB 非常類似, 參考 :

# 維基 : Numpy

Numpy 套件以 ndarray 物件為核心, 由資料本身與元資料 (metadata, 陣列維度與型別 dtype 等) 資訊構成, 其資料均為同質性 (homogeneous, 即元素的資料型別都一樣), 每一個元素在記憶體中都是緊密地連續配置且佔據相同的記憶體空間, 因此存取元素的效率比串列高; 而串列是容器 (container), 其元素散布在記憶體各處, 靠著指標來連結, 因此存取效率低. 此外, Numpy 提供了大量以 C 語言撰寫的 ufunc 函數 (universal function) 可進行高效運算.

Numpy 是第三方套件, 必須先安裝才能使用, 安裝指令如下 :

pip3 install numpy

如果要做科學計算, 則可以直接安裝 Scipy, 它會自動安裝 Numpy, 因為 Scipy 是以 Numpy 為基礎而建立的 :

pip3 install scipy

Numpy 的官方文件參考 :

https://numpy.org/devdocs/user/index.html
https://numpy.org/devdocs/reference/index.html
https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html

本篇測試範例參考了如下書籍 :

# Python 程式設計經典 (吳昱禎, 碁峰)
# Python 資料運算與分析實戰 (中久喜見司, 旗標)
# Python 資料科學與人工智慧 (陳允傑, 旗標)
# Python 資料科學學習手冊 (何敏煌譯, 歐萊里)
# Deep learning from scratch (齋藤康毅, 歐萊里)
# Python 在大數據科學計算上的最佳實作 (張若愚, 博碩)


一. 載入 Numpy 套件 :

Numpy 是第三方套件, 因此使用前須載入, 通常會取 np 當作別名 :

import numpy as np 

這樣就可以用別名 np 作為名稱空間呼叫 numpy 的函數, 例如查詢版本或用 help() 查詢 Numpy 使用說明 :


 numpy 查詢指令 說明
 np.__version__ 查詢 Numpy 版本
 np.info(np) 查詢 Numpy 訊息
 help(np) 查詢 Numpy 使用說明, 可查詢函數用法例如 np.arange
 np.lookfo('函數名') 查詢函數用法文件


>>> import numpy as np    
>>> np.__version__               #查詢 Numpy 版本
'1.16.0'


二. 建立陣列 :

陣列是一種資料結構, 用來儲存具有相同資料型態元素的串列 (list), 這些元素都是配置在連續的記憶體中. 一維的數值陣列稱為向量 (vector); 二維數值陣列稱為矩陣 (matrix); 三維以上的數值陣列則稱為張量 (tensor). 如以張量作為數值序列的一般化型態名稱, 向量可稱為一維張量; 矩陣是二維張量, 三維陣列則是三維張量.

最常用的陣列是二維的矩陣, EXCEL 試算表就是矩陣結構. 對於矩陣而言, 其維度 i*j 表示 "列*行", 意即此二維陣列元素有 i 列 j 行 (注意, 台灣習慣是橫列直行, 但強國人簡體書的習慣則是直列橫行), 在 Python 與 Numpy 中, 陣列的索引都是 0 起始的.

通常使用元組來表示陣列的形狀 (shape), 例如形狀為 (2, 3, 4) 的陣列表示這是一個三維陣列, 它有三個軸 (axis), 依序是軸 0, 軸 1, 與軸 3, 長度分別是 2, 3, 4.

Numpy 提供如下常用函數可用來建立陣列, 這些函數都是 Numpy 的全域方法 (即類別方法), 呼叫這些函數都會傳回一個 numpy.ndarray 陣列物件 :


 np 建立陣列方法 說明
 array(串列或元組) 傳入串列或元組來建立陣列, 傳回 numpy.ndarray 物件
 arange(e) 建立元素值為 0, 1, 2 ... (b-1) 的陣列
 arange(b, e) 建立元素值為 b, b+1, b+2 ... (e-1) 的陣列
 arange(b, e, s) 建立元素值為 b, b+s, b+2s ... (e-1) 的陣列 (步階為 s)
 linspace(b, e) 建立 50 個等差數列元素之陣列 (含 e), 公差為 (e-b)/49
 linspace(b, e, n) 建立 n 個等差數列元素之陣列 (含 e), 公差為 (e-b)/(n-1) 
 logspace(b, e) 建立 50 個基底為 10 等比數列元素之陣列 (含 e)
 logspace(b, e, n) 建立 n 個基底為 10 等比數列元素之陣列 (含 e)
 logspace(b, e, n, base=k) 建立 n 個基底為 k 等比數列元素之陣列 (含 e)
 zeros(n) 建立 n 個元素的一維零陣列 (全部元素為 0)
 zeros((i, j)) 建立 i*j 維 (i 列 j 行) 的零陣列 (全部元素為 0)
 ones(n) 建立 n  個 元素的一維壹陣列 (全部元素為 1)
 ones((i, j)) 建立 i*j 維 (i 列 j 行) 的壹陣列 (全部元素為 1)
 full(n, k) 建立 n 個元素的一維陣列 (全部元素為 k)
 full((i, j), k) 建立 i*j 維 (i 列 j 行) 的陣列 (全部元素為 k)
 zeros_like(a) 建立與範本陣列 a 結構一樣的零陣列 (全部元素為 0)
 ones_like(a) 建立與範本陣列 a 結構一樣的壹陣列 (全部元素為 1)
 identity(n) 建立 n*n 維 (n 列 n 行) 的對角矩陣 (對角元素為 1, 其餘為 0)
 eye(n) 建立 n*n 維 (n 列 n 行) 的對角矩陣 (對角元素為 1, 其餘為 0)
 eye(n, k) 建立 n*n 維 (n 列 n 行) 的對角矩陣 (第 k 條對角元素為 1, 其餘為 0)
 diag(v, k) v=二維傳回 v 指定對角線向量; v=一維建立以 v 為對角線之方陣
 empty(n) 建立 n 個元素的未初始化一維陣列 
 empty((i, j)) 建立 i*j 維 (i 列 j 行) 的未初始化陣列
 random.rand() 傳回 0~1 之間的一個隨機浮點數 (純量)
 random.rand(n) 建立 n 個元素的一維隨機陣列 (均勻分布隨機浮點數)
 random.rand(i, j) 建立 i*j 維 (i 列 j 行) 的隨機陣列 (均勻分布隨機浮點數)
 random.randint(L, H, size) 建立 [L, H] 間形狀為 size (元組) 的隨機陣列 (常態分佈隨機整數)
 random.randn(n) 建立 n 個元素的一維常態分布隨機陣列 (常態分佈隨機浮點數)
 random.random((i, j)) 同 rand() 建立 i*j 維 (i 列 j 行) 的隨機陣列 (均勻分布隨機浮點數)
 fromfunction(func, shape) 依據指定之外形  shape 執行函數 func 建立陣列


其中 array(), arange(), linspace(), 用來建立特定元素值的陣列; random.rand() 用來建立不特定 (隨機) 陣列, 其餘函數則用來建立預設內容之陣列.

上表中的函數都有一個備選參數 dtype 可用來指定元素之數值資料型態, 每一個函數有個別之預設值 :


 dtype 說明
 bool_ 布林值 (byte)
 int_ 預設之整數型態 (int32 或 int64)
 intc C 語言之 int (int32 或 int64)
 intp 做指標用之整數 (int32 或 int64)
 int8 有號 8 位元整數 (值 -128 至 127)
 int16 有號 16 位元整數 (值 -32768 至 32767)
 int32 有號 32 位元整數 (值 -2147483648 至 2147483647)
 int64 有號 64 位元整數 (值 -9223372036854775808 至 9223372036854775807)
 uint8 無號 8 位元整數 (值 0 至 255)
 uint16 無號 16 位元整數 (值 0 至 65535)
 uint32 無號 32 位元整數 (值 0 至 429497295)
 uint64 無號 64 位元整數 (值 0 至 18446744073709551615)
 float_ 同 float64
 float16 16 位元浮點數 (半精度), 含符號位元 1, 指數位元 5, 小數 10 位元
 float32 32 位元浮點數 (單精度), 含符號位元 1, 指數位元 8, 小數 23 位元
 float64 64 位元浮點數 (倍精度), 含符號位元 1, 指數位元 11, 小數 52 位元
 complex_ 同 complex128
 complex64 64 位元複數
 complext128 128 位元複數


dtype 是上面每一個建立陣列的函數都有的備選參數, 其設定方式如下 :
  1. dtype=float64
  2. dtype='float64'
  3. dtype=np.float64
numpy.ndarray 物件具有如下常用屬性 :


 ndarray 物件常用屬性 說明
 dtype 陣列元素之資料型態, 例如 int32/int64/float64 等
 shape 陣列的形狀, 型態為 tuple, 例如 (2, 3, 4)
 ndim 陣列的維度 (正整數)
 size 陣列的元素總數
 itemsize 每個元素所佔之 bytes 數
 nbytes 整個陣列物件所佔之 bytes 數=itemsize*size


查詢形狀除了可用物件的 shape 屬性外, 也可以呼叫 numpy.shape(a) 函數, 只要將陣列當參數傳進去即可.

陣列的形狀可以呼叫物件方法 reshape() 並傳入一個元組來改變, 它會傳回一個新陣列參考, 但此新陣列與原陣列元素共用記憶體空間, 新陣列只是陣列外觀不同而已, 元素仍然是儲存在原來的記憶體位址不變, 因此更改新陣列的元素值連帶舊陣列元素也會改變.


1. 從元組或串列字面值建立陣列 :   

呼叫 np.array() 可建立一維或多維陣列, 其介面如下 :

numpy.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)

可見至少必須傳入一個物件參數, 通常是元組或串列字面值, array() 會將其轉換成 numpy.ndarray 物件, 例如 :

>>> a=np.array([1,2,3])      #將串列轉成一維陣列
>>> type(a)
<class 'numpy.ndarray'>              #陣列維 numpy.ndarray 型別
>>> print(a)                          #顯示陣列 a 內容
array([1, 2, 3]) 
>>> b=np.array((1,2,3))      #將元組轉成一維陣列
>>> type(b)
<class 'numpy.ndarray'>   
>>> print(b)                         #顯示陣列 b 內容
array([1, 2, 3])
>>> a == b                            #依序比較陣列元素之值
array([ True,  True,  True])   
>>> c=np.array([[1,2,3],[4,5,6]])    #將串列轉成二維陣列
>>> type(c)                         
<class 'numpy.ndarray'>   
>>> print(c)                                      #顯示陣列 b 內容
array([[1, 2, 3],
       [4, 5, 6]])
>>> np.array(range(10))                 #呼叫 range() 產生串列
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.array(range(1,10))              #呼叫 range() 產生串列
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.array([range(i, i+3) for i in (2, 4, 6)])    
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])

接著檢視 ndarray 物件的屬性 :

>>> a=np.array([1,2,3])    #一維整數陣列
>>> a.dtype                        #元素型態為 int32
dtype('int32')
>>> a.shape                        #形狀為 1*3
(3,)
>>> np.shape(a)                 #查詢形狀也可呼叫 numpy.shape()
(3,)
>>> a.ndim                         #維度為一維
1
>>> a.size                            #有 3 個元素
3
>>> a.itemsize                     #每個元素佔 4 bytes (4*8=32)
4
>>> a.nbytes                        #整個陣列佔 3*4=12 bytes
12

呼叫陣列物件的 reshape() 方法並傳入一個元組會傳回指定形狀之陣列, 但元素與原陣列共用, 兩者只是外觀不同而已 :

>>> a=np.array(range(12))   #建立 1*12 的一維陣列
>>> print(a)   
[ 0  1  2  3  4  5  6  7  8  9 10 11]
>>> b=a.reshape((3, 4))         #改變形狀為 3*4
>>> print(b)   
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> b[0][0]=1                          #改變 b 的第 0 元素
>>> print(b)                             #b[0][0] 變 1 了
[[ 1  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> print(a)                              #a[0][0] 也變 1 了
[ 1  1  2  3  4  5  6  7  8  9 10 11]

注意, 傳入 array() 的串列或元組結構必須一致 (即元素長度必須相同), 否則會變成結構性陣列, 例如 :

>>> a1=np.array([[1,2,3],[4,5,6]])   
>>> print(a1) 
[[1 2 3]
 [4 5 6]]
>>> a2=np.array([[1,2,3],[4,5]]) 
>>> print(a2)   
[list([1, 2, 3]) list([4, 5])] 

上面 a2 陣列的第二個串列少了 1 個元素, 因此 Numpy 會以串列物件儲存, 而不是全部轉成陣列.


2. 建立等差數列之陣列 :     

呼叫 arange() 與 linspace() 這兩個函數都可建立以等差數列為元素的一維陣列, 兩者的差別是 linspace() 預設有包含結束值, 而 arange() 則不包含; 其次是傳入三個參數時 arange(b, e, s) 第三個參數 s 是步階值 (即公差), 而 linspace(b, e, n) 的第三參數 n 則是指元素個數.

函數 arrange() 是 Python 內建函數 range() 的 Numpy 陣列版, 它可傳入 1~3 個參數, 當只有一個參數時 arange(e) 會建立一維陣列, 其元素為 0~e (但不包含 e) 步階為 1 的等差數列 (即公差=1), 介面如下 :

numpy.arange([start, ]stop, [step, ]dtype=None)

例如 :

>>> np.arange(10)            #建立 [0, 10) 步階為 1 的一維陣列
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

如果傳入兩個參數, arange(b, e) 會建立一維陣列, 其元素為 b~e  (但不包含 e) 步階為 1 的等差數列, 例如 :

>>> np.arange(2, 10)         #建立 [2, 10) 步階為 1 的一維陣列
array([2, 3, 4, 5, 6, 7, 8, 9])

如果傳入三個參數, arange(b, e, s) 會建立一維陣列, 其元素為 b~e 但不包含 e, 步階為 s 的等差數列, 例如 :

>>>  np.arange(1, 10, 2)    #建立 [1, 10) 步階為 2 的一維陣列
array([1, 3, 5, 7, 9])

三個參數也可以是浮點數, 例如 :

>>> np.arange(1.2, 5.6, 0.8) 
array([1.2, 2. , 2.8, 3.6, 4.4, 5.2])
>>> np.arange(1.2, 2.4, 0.2)   
array([1.2, 1.4, 1.6, 1.8, 2. , 2.2])              #不含結束值 2.4

函數 linspace() 也可以建立元素是等差數列的一維陣列, 它至少需傳入兩個參數, 分別為開始值與結束值, linspace(b, e) 會建立一個 50 個元素的一維陣列, 其元素為 b~e (包含 e) 步階或公差為 (e-b)/49 的等差數列, 介面如下 :

numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0)

例如 :

>>> np.linspace(0, 10)       #建立 [0, 10] 步階為 10/49 的一維陣列
array([ 0.        ,  0.20408163,  0.40816327,  0.6122449 ,  0.81632653,
        1.02040816,  1.2244898 ,  1.42857143,  1.63265306,  1.83673469,
        2.04081633,  2.24489796,  2.44897959,  2.65306122,  2.85714286,
        3.06122449,  3.26530612,  3.46938776,  3.67346939,  3.87755102,
        4.08163265,  4.28571429,  4.48979592,  4.69387755,  4.89795918,
        5.10204082,  5.30612245,  5.51020408,  5.71428571,  5.91836735,
        6.12244898,  6.32653061,  6.53061224,  6.73469388,  6.93877551,
        7.14285714,  7.34693878,  7.55102041,  7.75510204,  7.95918367,
        8.16326531,  8.36734694,  8.57142857,  8.7755102 ,  8.97959184,
        9.18367347,  9.3877551 ,  9.59183673,  9.79591837, 10.        ])

因為預設是 50 個元素, 中間有 49 段區間, 故步階值為 (10-0)/49=0.20408163.

如果傳入三個參數 linspace(b, e, n), 則第三參數 n 為元素個數, 它會將此 b~e 區段均分以產生元素, 因此步階值或公差為 (e-b)/(n-1), 例如 :

>>> np.linspace(0, 10, 9)      #[0~10] 分 8 段, 步階=10/8=1.25
array([ 0.  ,  1.25,  2.5 ,  3.75,  5.  ,  6.25,  7.5 ,  8.75, 10.  ])
>>> np.linspace(0, 10, 10)    #[0~10] 分 9 段, 步階=10/9=1.1111
array([ 0.        ,  1.11111111,  2.22222222,  3.33333333,  4.44444444,
        5.55555556,  6.66666667,  7.77777778,  8.88888889, 10.        ])
>>> np.linspace(0, 10, 11)    #[0~10] 分 10 段, 步階=10/10=1
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])


3. 建立等比數列之陣列 :

呼叫 numpy.logspace() 可建立元素為等比數列的陣列, 其介面如下 :

numpy.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, axis=0)[source]

可見 logspace() 至少須傳入兩個參數 (整數, 浮點數, 或複數均可), 注意, 這是指數值, 不是數列的首項與末項, 預設基底為 10, 等比數列起始值為 base**start, 結束值為 base**stop, 預設會建立 50 個元素的等比數列陣列 (含結束值). 例如 np(0, 2) 會建立 [0, 100] 之間 50 個等比數列元素之陣列 :

>>> np.logspace(0, 2)     #建立 0~100 間 50 個等比數列元素之陣列
array([  1.        ,   1.09854114,   1.20679264,   1.32571137,
         1.45634848,   1.59985872,   1.75751062,   1.93069773,
         2.12095089,   2.32995181,   2.55954792,   2.8117687 ,
         3.0888436 ,   3.39322177,   3.72759372,   4.09491506,
         4.49843267,   4.94171336,   5.42867544,   5.96362332,
         6.55128557,   7.19685673,   7.90604321,   8.68511374,
         9.54095476,  10.48113134,  11.51395399,  12.64855217,
        13.89495494,  15.26417967,  16.76832937,  18.42069969,
        20.23589648,  22.22996483,  24.42053095,  26.82695795,
        29.47051703,  32.37457543,  35.56480306,  39.06939937,
        42.9193426 ,  47.14866363,  51.79474679,  56.89866029,
        62.50551925,  68.6648845 ,  75.43120063,  82.86427729,
        91.0298178 , 100.])

可見 np.logspace(0, 2) 的公比為 1.09854114, 依據等比數列公式 :


np.logspace(0, 2) 的首項為 10**0=1, 末項為 10**2=100, 因此公比為末項除以首項後再開 49 次方根, 即 (100/1)**(1/49)=1.09854114.

可傳入第三參數 num 指定元素個數, 例如 :

>>> np.logspace(0, 2, 10)   #建立 [1, 100] 10 個元素等比陣列
array([  1.        ,   1.66810054,   2.7825594 ,   4.64158883,
         7.74263683,  12.91549665,  21.5443469 ,  35.93813664,
        59.94842503, 100.        ])

首項與末項同樣是 1 與 100, 但元素為 10 個, 故公比要開 9 次方為 (100/1)**(1/9)=1.66810054.

可傳入 base 參數指定基底, 例如 :

>>> np.logspace(0, 2, 10, base=2)    #建立基底=10 的等比陣列
array([1.        , 1.16652904, 1.36079   , 1.58740105, 1.85174942,
       2.16011948, 2.5198421 , 2.93946898, 3.42897593, 4.])

因基底為 2, 因此首項為 2**0=1, 末項=2**2=4, 所以公比為 (4/1)**(1/9)=1.16652904.


4. 建立預設內容陣列 : 

呼叫 zeros(), ones(), full(), zeros_like(), ones_like(), eye(), diag(), identity() 等函數可建立預設內容之陣列, 例如零陣列, 壹陣列, 與對角陣列等.


(1). 零陣列 : 

zeros() 必須傳入一個參數, 當傳入一個正整數時, zeros(n) 表示要建立有 n 個元素的一維零陣列 (所有元素為 0, 若 n=0 建立空陣列); 而傳入一個元組時 zeros((i, j)) 表示要建立一個 i 列 j 行的零陣列, 例如 :

>>> np.zeros(5)                      #建立 5 個元素的一維零陣列 
array([0., 0., 0., 0., 0.])
>>> np.zeros(5, dtype=int)    #指定元素資料型態為 int
array([0, 0, 0, 0, 0])
>>> np.zeros((2, 3))               #建立 i 列 j 行的零陣列
array([[0., 0., 0.],
       [0., 0., 0.]])
>>> np.zeros(0)                      #建立空陣列
array([], dtype=float64)

zeros_like() 可依據傳入之陣列結構, 建立與其維度相同之零陣列, 例如 :

>>> a=np.array([[1, 2 , 3], [4, 5 ,6]])    #建立一個 2*3 陣列
>>> print(a)   
[[1 2 3]
 [4 5 6]]
>>> b=np.zeros_like(a)          #依據 a 陣列結構建立零陣列
>>> print(b)       
[[0 0 0]
 [0 0 0]]
>>> b.dtype            #zeros_like() 建立之陣列預設型態為整數
dtype('int32')

可見 zeros() 建立元素之預設型態為 float 浮點數, 而 zeros_like() 則是整數.


(2). 壹陣列 :

ones() 與 zeros() 一樣必須傳入一個參數, 當傳入一個正整數時, ones(n) 表示要建立有 n 個元素的一維壹陣列 (所有元素為 1, 若 n=0 建立空陣列); 而傳入一個元組時 ones((i, j)) 表示要建立一個 i 列 j 行的壹陣列, 例如 :

>>> np.ones(5)                        #建立 5 個元素的一維壹陣列
array([1., 1., 1., 1., 1.])
>>> np.ones(5, dtype=int)      #指定元素資料型態為 int
array([1, 1, 1, 1, 1])
>>> np.ones((2, 3))                  #建立 i 列 j 行的壹陣列
array([[1., 1., 1.],
       [1., 1., 1.]])
>>> np.ones(0)                        #建立空陣列
array([], dtype=float64)

ones_like() 可依據傳入之陣列結構, 建立與其維度相同之壹陣列, 例如 :

>>> a=np.array([[1, 2 , 3], [4, 5 ,6]])    #建立一個 2*3 陣列
>>> print(a)     
[[1 2 3]
 [4 5 6]]
>>> b=np.ones_like(a)            #依據 a 陣列結構建立壹陣列
>>> print(b) 
[[1 1 1]
 [1 1 1]]
>>> b.dtype            #ones_like() 建立之陣列預設型態為整數
dtype('int32')


(4). 填滿陣列 :

呼叫 numpy.full() 可建立元素全為指定值的填滿陣列, 其介面為 :

numpy.full(shape, fill_value, dtype=None, order='C')

第一個參數為陣列外形, 可傳入一個正整數, 元組或串列; 第二個參數是要填入的元素值. 當第一參數是正整數時, full(n, k) 會建立一個具有 n 個元素值為 k 的一維陣列; 當第一參數是元組或串列時, full(shape, k) 會依據外形 shape 建立元素值為 k 的高維陣列, 例如 :

>>> np.full(5, 3.14159)         #用 pi 填滿一維陣列
array([3.14159, 3.14159, 3.14159, 3.14159, 3.14159])
>>> np.full((2, 3), 3.14159)   #用 pi 填滿二維陣列
array([[3.14159, 3.14159, 3.14159],
       [3.14159, 3.14159, 3.14159]])
>>> np.full([2, 3], 3.14159)   #用 pi 填滿二維陣列   
array([[3.14159, 3.14159, 3.14159],
       [3.14159, 3.14159, 3.14159]])
>>> np.full([2, 3, 4], 9)          #用 9 填滿三維陣列
array([[[9, 9, 9, 9],
        [9, 9, 9, 9],
        [9, 9, 9, 9]],
       [[9, 9, 9, 9],
        [9, 9, 9, 9],
        [9, 9, 9, 9]]])


(5). 對角陣列 (矩陣) :

呼叫 identity() 會建立一個中央對角線元素值為 1 的對角方陣, 介面如下 :

numpy.identity(n, dtype=None)

至少須傳入一個正整數參數以建立一個 n*n 方陣 :

>>> np.identity(4)          #建立 4*4 對角方陣 (浮點數)
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> np.identity(4, dtype=int)      #指定型態為整數
array([[1, 0, 0, 0],
       [0, 1, 0, 0],
       [0, 0, 1, 0],
       [0, 0, 0, 1]])

呼叫 eye() 可建立一個指定對角線元素值為 1.0 (預設為中央對角線), 其餘元素為 0 的對角矩陣 (元素值預設為浮點數), 介面如下 :

numpy.eye(n, m=None, k=0, dtype=np.float, order='C')

第一參數 n 為矩陣的列數, 第二參數 m 為矩陣的行數, 第三參數 k 為元素值為 1 之對角線編號, 預設 0 為中央正對角線, k 為正整數時, 由中央往右上角第 k 條對角線元素全部為 1, 其餘為 0; k 為負整數時, 由中央往左下角第 k 條對角線元素全部為 1, 其餘為 0, 例如 :

>>> np.eye(4)            #建立 4*4 單位方陣
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> np.eye(4, k=1)   #建立 4*4 對角矩陣
array([[0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [0., 0., 0., 0.]])
>>> np.eye(4, k=-1)   #左下方第 1 條對角線填 1
array([[0., 0., 0., 0.],
       [1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.]])
>>> np.eye(4, k=2)     #右上方第 2 條對角線填 1
array([[0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.eye(4, k=3)     #右上方第 3 條對角線填 1
array([[0., 0., 0., 1.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.eye(4, k=4)    #右上方第 4 條對角線填 1 (超過了)
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.eye(4, 3)        #4*3 矩陣 (非方陣)
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.],
       [0., 0., 0.]])

可見當 k 超過對角線總數時會變成建立零陣列.

另外一個與對角陣列有關的函數是 diag(), 其介面如下 :

numpy.diag(v, k=0)

第一個函數是一維或二維陣列, 第二個參數與 eye() 中的 k 參數一樣, 中央對角線 k 為 0 (預設值), 往右上走之對角線依序為 1, 2, ... ; 往左下走之對角線依序為 -1, -2, ... 等等. 若第一參數 v 傳入一個二維陣列, 則以一維陣列形式將其第 k 的對角線元素傳回 (getter); 若 v 傳入一個一維陣列, 則以該陣列元素取代第 k 個對角線.

首先建立一個 5*5 方陣如下 :

>>> a=np.array(range(25))      #建立 [0, 1, 2, .... , 24] 一維陣列
>>> x=a.reshape((5,5))             #調整形狀建立 5*5 方陣
>>> print(x)     
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

將此二維陣列傳給 diag() 會傳回其第 k 的對角線元素 :

>>> np.diag(x)                          #傳回方陣 x 的中央對角線
array([ 0,  6, 12, 18, 24])                           #傳回值為一維陣列
>>> np.diag(x, k=1)                 #傳回方陣 x 的 k=1 對角線
array([ 1,  7, 13, 19])
>>> np.diag(x, k=-1)                #傳回方陣 x 的 k=-1 對角線
array([ 5, 11, 17, 23])

如果傳入一維陣列, 則會傳回一個指定對角線是該一維陣列的方陣, 例如 :

>>> np.diag([1, 2, 3, 4, 5])       #建立指定中央對角線之方陣
array([[1, 0, 0, 0, 0],
       [0, 2, 0, 0, 0],
       [0, 0, 3, 0, 0],
       [0, 0, 0, 4, 0],
       [0, 0, 0, 0, 5]])
>>> np.diag([1, 2, 3, 4, 5], k=1)      #k=1 右上對角線方陣
array([[0, 1, 0, 0, 0, 0],
       [0, 0, 2, 0, 0, 0],
       [0, 0, 0, 3, 0, 0],
       [0, 0, 0, 0, 4, 0],
       [0, 0, 0, 0, 0, 5],
       [0, 0, 0, 0, 0, 0]])
>>> np.diag([1, 2, 3, 4, 5], k=-1)    #k=-1 左下對角線方陣
array([[0, 0, 0, 0, 0, 0],
       [1, 0, 0, 0, 0, 0],
       [0, 2, 0, 0, 0, 0],
       [0, 0, 3, 0, 0, 0],
       [0, 0, 0, 4, 0, 0],
       [0, 0, 0, 0, 5, 0]])


(6). 空陣列 :

呼叫 empty() 會建立一個指定形狀的未初始化陣列, 其元素值為所配置記憶體內原本的值, 其介面如下 :

numpy.empty(shape, dtype=float, order='C')

第一參數為陣列外形, 可以是正整數, 或元素為正整數之元組或串列 (通常用元組), 例如 :

>>> np.empty(4)              #建立 4 個元素的未初始化一維陣列
array([1., 1., 1., 1.])
>>> np.empty((4,))          #建立 4 個元素的未初始化一維陣列
array([1., 1., 1., 1.])
>>> np.empty((2, 3))       #建立 2*3 未初始化二維陣列
array([[3.14159, 3.14159, 3.14159],
       [3.14159, 3.14159, 3.14159]])
>>> np.empty((2, 3, 4))   #建立 2*3*4 未初始化三維陣列
array([[[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]],
       [[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]]])

可見傳入 (4, ) 相當於傳入整數 4, 都是建立一維陣列.


5. 建立隨機內容之陣列 :

Numpy 底下的 random 模組提供了幾個方法可用來建立隨機陣列, 不過如果果要讓隨機數可預測, 可以透過呼叫 np.random.seed() 設定隨機種子, 這樣在同一隨機種子下重複呼叫時, 傳回的隨機數列值與順序是一樣的 :

np.random.seed(i)

傳入參數 i 須為正整數.


(1). rand() 與 random() 函數 :

此函數會傳回 [0, 1) 區間均勻分布隨機陣列, 介面如下 :

numpy.random.rand(d0, d1, ..., dn)

rand() 的參數必須是正整數, 都是可選的, 未傳入參數時會傳回一個 0~1 之間的均勻分布隨機浮點數 (純量); 傳入一個參數 n (須正整數) 時 random.rand(n) 會傳回 n 個元素的一維均勻分布隨機陣列, 這相當於 random.rand(1, n); 傳入兩個參數 (須正整數) 時 random.rand(i, j) 會傳回一個 i*j 維度的均勻分布隨機隨機陣列, 更多參數則產生更高維的陣列, random.rand(i, j, k) 會傳回三維陣列, 例如 :

>>> np.random.rand()       #傳回 0~1 間的隨機數 (float 純量)
0.19644093345544167
>>> type(np.random.rand())   #沒有傳入參數時傳回一個純量
<class 'float'>
>>> np.random.rand(5)      #傳回 1*5 一維隨機陣列
array([0.74998027, 0.78622133, 0.92885681, 0.18321391, 0.89496996])
>>> np.random.rand(3, 4)   #傳回 3*4 二維隨機陣列
array([[0.27912439, 0.73034461, 0.58826309, 0.59707398],
       [0.44257555, 0.01745328, 0.59587659, 0.68253437],
       [0.22404294, 0.19438719, 0.99749655, 0.49556471]])
>>> np.random.rand(2, 3, 4)   #傳回 2*3*4 三維隨機陣列
array([[[0.27131017, 0.40616805, 0.36128135, 0.75666407],
        [0.40549157, 0.41149331, 0.30765576, 0.39208383],
        [0.82069598, 0.60894839, 0.4239132 , 0.92491804]],
       [[0.38630791, 0.36035005, 0.56409681, 0.15552691],
        [0.74267144, 0.81580992, 0.8991233 , 0.34849928],
        [0.37512254, 0.56358772, 0.60626021, 0.11272833]]])

可見 random.rand() 的元素值都是介於 0~1 之間.

另外 random.random() 功能與 random.rand() 一樣, 差別只是 random.random() 之參數為表示陣列形狀的物件, 通常是元組, 其介面如下 :

numpy.random.randdom(shape)

例如 :

>>> np.random.random((2, 3, 4))     #傳回 2*3*4 三維隨機陣列
array([[[0.8636309 , 0.85336527, 0.17550813, 0.11426248],
        [0.84716226, 0.7543792 , 0.24974123, 0.67595818],
        [0.81296995, 0.5708457 , 0.35645387, 0.46249463]],
       [[0.86340479, 0.20525588, 0.86448148, 0.67061865],
        [0.78973599, 0.87887531, 0.85816169, 0.37024847],
        [0.06263618, 0.10129474, 0.7355295 , 0.30408336]]])

參考 :

np.random.rand vs np.random.random


(2). randn() 函數 :

此函數與 rand() 一樣會傳回 [0, 1) 區間指定形狀之隨機陣列, 不同之處為 randn() 的隨機數為常態分布 (結尾的 n 表示 normal distribution), 而 rand() 為均勻分布, 介面如下 :

numpy.random.rand(d0, d1, ..., dn)

例如 :

>>> np.random.randn()     #傳回 0~1 間的隨機數 (float 純量)
0.3856502495158207
>>> np.random.randn(5)     #傳回 1*5 一維隨機陣列
array([ 0.1719701 ,  0.30347725, -1.62440603, -0.2287934 ,  0.8188689 ])
>>> np.random.randn(3, 4)     #傳回 3*4 二維隨機陣列
array([[ 0.42688578,  0.39268864, -0.3210757 , -0.59786833],
       [ 0.62772359,  0.95096534,  0.47403366,  1.07871119],
       [-0.46120767,  0.02107573, -1.12275642, -0.44598128]])
>>> np.random.randn(2, 3, 4)     #傳回 2*3*4 三維隨機陣列
array([[[ 0.65388739, -1.57062685,  1.29388902, -0.10002825],
        [-1.34442224, -1.8373028 ,  0.68837754,  0.13592657],
        [-0.1309182 , -1.41535012,  0.2141069 ,  1.30175303]],
       [[ 1.83388743,  0.18719303, -1.58136535,  0.69444909],
        [-1.20114114, -1.32826911,  0.16630603, -1.48900367],
        [ 0.77561497,  0.18556015,  0.12806819, -0.70700624]]])


(3). randint() 函數 :

顧名思義, 此函數名結尾的 int 表示它會建立一個隨機整數陣列, 介面如下 :

numpy.random.randint(low, high=None, size=None, dtype='l')

可見 randint() 至少必須傳入一個參數 low, 表示傳回的隨機數最小值, 第二參數 high 為傳回的隨機數最大值 (不含), 第三參數為陣列維度, 可用元組或串列指定 (通常用元組), 例如 size=(2, 3) 表示建立 2*3 維度陣列, 預設傳回值型態為整數. 資料型態預設為  32 位元整數 'I', 也可以用其它整數型態例如 64 位元整數 'i8', 但不可指定為非整數型態.

總之, 此函數會傳回一個元素值大於等於 low, 小於 high, 維度為 size 的均勻分布隨機數, 但實際測試發現, 當只傳入一個參數時, 它會被視為是 high 而不是 low, 例如 randint(4) 會傳回 [0, 4) 間隨機數, 必須同時傳入兩個參數時才會被解讀為前一個 low, 後一個 high, 例如 :

>>> np.random.randint(4)            #傳回小於 4 的隨機數
2
>>> np.random.randint(low=4)    #傳回小於 4 的隨機數
1
>>> np.random.randint(1, 4)        #傳回 [1, 4) 間的隨機數
3
>>> np.random.randint(2, 31)       #傳回 [2, 31) 間的隨機數
21
>>> np.random.randint(2,10, [2,3])   #傳回 [2, 10) 間的隨機陣列
array([[5, 4, 8],
       [2, 3, 9]])

參考 :

为什么你用不好Numpy的random函数?


6. 呼叫函數建立陣列 :

呼叫 numpy.fromfunction() 可透過函數呼叫來建立陣列, 介面如下 :

numpy.fromfunction(function, shape, **kwargs)

第一參數為建立陣列要呼叫的函數 (可用 lamda), 第二參數為輸入陣列的外形 (也是輸出陣列的外形), 通常是正整數元組 (tuple). 注意, shape 並非傳入函數 function() 之參數, 傳入函數的參數是符合此外形的座標陣列 (coordinate array), 例如 shape=(2, 2) 時, 傳入參數為 [[0, 0], [1, 1]] 與 [[0, 1], [0, 1]] 這兩個陣列, 例如 :

>>> def func(i, j):         #呼叫的函數傳回兩陣列加 1 之乘積
...     return (i+1)*(j+1) 
...
>>> np.fromfunction(func, (2, 2))   
array([[1., 2.],
       [2., 4.]])

此處傳入函數 func(i, j) 的參數 i, j 分別為 [[0, 0], [1, 1]] 與 [[0, 1], [0, 1]] 這兩個陣列, 所以傳回 i+1=[[1, 1], [2, 2]] 與 j+1=[[1, 2], [1, 2]] 的陣列乘積為 [[1, 2], [2, 4]], 此可由下列陣列乘積印證 :

>>> i=np.array([[1, 1], [2, 2]])     #二維陣列
>>> j=np.array([[1, 2], [1, 2]])     #二維陣列
>>> i*j 
array([[1, 2],
       [2, 4]])

與上面呼叫 fromfunction() 結果是一樣的. 如果傳入外形 shape=(9, 9) 就可以建立九九乘法表陣列, 例如 :

>>> def func(i, j): 
...     return (i+1)*(j+1) 
...
>>> np.fromfunction(func, (9, 9))   
array([[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.],
       [ 2.,  4.,  6.,  8., 10., 12., 14., 16., 18.],
       [ 3.,  6.,  9., 12., 15., 18., 21., 24., 27.],
       [ 4.,  8., 12., 16., 20., 24., 28., 32., 36.],
       [ 5., 10., 15., 20., 25., 30., 35., 40., 45.],
       [ 6., 12., 18., 24., 30., 36., 42., 48., 54.],
       [ 7., 14., 21., 28., 35., 42., 49., 56., 63.],
       [ 8., 16., 24., 32., 40., 48., 56., 64., 72.],
       [ 9., 18., 27., 36., 45., 54., 63., 72., 81.]]

參考 :

Numpy里的fromfunction()的问题?
# Python資料分析(三)NumPy 

2019年10月27日 星期日

2019 年第 43 周記事

今日下午繼續上周日未完的整地種菜工程, 紅龍果樹旁的雜草剷除後經一周曝曬已成乾草, 鋤出約一畦榻榻米大的地方, 鬆完土打一層基肥後灑上細細的杏菜種子, 然後淋水濕潤, 或許兩天就會發芽了. 接著看到檸檬枝枒亂長, 容易被勾到刺傷, 索性找來園藝大剪做個徹底修剪, 下次花期來時可能結更多果實. 修完檸檬換芒果樹, 路邊的龍眼, 蓮霧, 波羅蜜全部慘遭我的毒手, 然後到樓上攀著屋簷砍那些高至二樓的龍眼與蓮霧枝枒, 弄完後覺得整個清爽多了. 

週三 0056 除息, 我用參考價 27,21 掛了老半天根本買不到, 最後用市價買在 27.54 元.

本周學習研究轉到資料科學了, 從 Numpy 著手. 

市圖還書 1 本 : 拒當 AI 時代的局外人

此書是前 104 人力銀行行銷總監邱文仁寫的, 她十餘年前在台灣行銷公關界甚為活躍, 是 104 人力銀行篳路藍縷時期的老員工, "被離職" 後成為西進大軍到強國闖天下. 此書對我來說其實算不上好書, 但對文科的人來說也許還算是可看之書. 摘要如下 :
  1. 以醫療機器人實施手術萬一失敗將引發責任歸屬問題. 
  2. IBM 的華生大廚 Chef Watson 透過學習 3 萬 5 千種以上食譜, 能自行開發出新食譜.
  3. 未來人類最好的工作模式是藉助人工智慧與機器人減少錯誤增進效率.
  4. Slash 斜槓青年一詞出自紐約時報專欄作家 Albert 的書籍 "雙重職業", 描述現代年輕人希望擁有多重職業與身分的多元生活.
  5. 一件事做久了就會養成新的能力, 天天做, 就成為專業.
  6. 擁有知識與創造力, 即可橫跨產業. 
我個人認為以下工作不僅不會被 AI 取代, 還能從 AI 獲得幫助 :
  1. 法醫 :
    屍檢與解剖 AI 機器人無法取代人類, 但 AI 資訊提供倒是可以幫法醫很多忙. 
  2. 醫師與護理師 :
    照顧病人, 打針給藥, CPR 急救等等醫療行為 AI 與機器人做不來, 倒是 AI 資訊系統可以提升醫師診斷的精確性. 
  3. 保母 :
    AI 機器人沒辦法顧小孩, 但 AI 監控系統倒是可以幫忙保母監看嬰幼兒狀況. 
  4. 司法官與律師 :
    法庭攻防實務還是需要人類才行, AI 頂多可在法條梳理與判例蒐集等方面提供即時協助.
另外, 作者在書中引述了許多李開復關於 AI 的觀點, 我對此君實在不完全苟同. 例如作者引述李開復說法, AI 機器人沒有情感不懂愛, 那是現在吧? 誰知道以後會怎樣, 搞不好幾年後具備意識引擎的 AI 機器人就問世了. 對於真正的科學人而言, 嘴巴裡面很難蹦出 "不可能".

李開復曾說台灣在 AI 方面已經落後強國太多了, 應該發展 AI 無法企及的能力, 這點沒問題, 但 AI 基礎就不用做了嗎? 我覺得這就好比說忙到沒時間吃中餐, 那現在閒下來也就別吃了, 想辦法看看能不能忍到晚餐一起吃那般可笑. 我看到陳良基, 唐鳳, 陳昇瑋與杜奕瑾等人對台灣 AI 發展的努力, 李開復的貢獻應該是趨近於零吧 (除了在台灣賣書之外)!

市圖還書 1 本 : 雙動能投資

本周開始要來清理借書, 希望每周至少要看完一本 :
  1. 雙動能投資 :高報酬低風險策略
這本書已借甚久, 因為晦澀難懂, 稱不上是好書, 不過有些資料還是值得記一下.
  1. 完美效率市場 (EMH : Efficient Market Hypothesis, EMH) 是不存在的, 也沒有所謂的投資人絕對理性, 許多例子可證明價格會長時間偏離價值, 因此存在許多套利機會. 人們的情緒性與非理性行為會造成價格以某種可預測方式系統性地偏離基本面價值 (這種異常現象稱為動能), 因此市場說不定是可以被擊敗的. 
  2. 動能就是投資持續維持其績效的趨勢. 第一個以投資術語解釋動能者是古典經濟學家大衛李嘉圖 (David Ricardo), 他說 : 迅速認賠, 讓你的獲利持續發展. 李佛摩說 : 大錢不是來自於個股的波動, 而是整個市場與其上漲趨勢, 買進永遠不嫌股價高; 賣出永遠不嫌股價低. 順勢操作就是動能投資的一種形式. 
  3. 尼采說 : 對真理而言, 信念比謊言更危險. 
  4. H.M. Gartley 認為應該計算動能應該衡量股價速度, 亦即股價漲跌相對於大盤指數的百分率 (相對強度價格動能, relative strength=momentum). 
  5. 股票市場如同人生的許多其他層面, 強者恆強, 弱者恆弱, 買進強勢股, 賣出弱勢股, 在統計分析上有較高獲利機率. 
  6. 有華爾街之獅美譽的 Jack Dreyfus 說 : 如果看到一部電梯正在往上走, 你最好打賭它會繼續往上而不是往下. 他只買進些從健全底部向上突破創新高的股票. 以 CANSLIM 選股法則聞名的 William O'Neil 也是以買強賣弱進行動能投資. 
  7. 要注意的是, 相對強度動能投資雖然能夠提升報酬績效, 但無助於降低價格波動與最大帳戶淨值耗損, 比起非動能的買進持有策略具有較高操作風險. 
  8. AQR 資本管理公司的動能基金選擇過去 12 個月 (不含最近一個月) 表現最好的前 1/3 股票, 每季進行調整. 
  9. 經濟學家 Joan Robinson 說 : 學習經濟學的目的並非針對經濟問題尋求可用的答案, 而是學習如何不被經濟學家欺騙
  10. Jeremy Siegel 認為 : 股票市場報酬不只超過所有其他金融資產, 在考慮通膨後其報酬也遠較債券安全且可預期. 相較於債券, 國庫券, 與商品如黃金來說, 股票的長期累積報酬顯然較優. 
  11. 巴菲特說 : 投資很容易, 但不簡單 (Investing is simple, but not easy).
  12. 投資人績效不彰的主因是, 他們情緒很容易受到干擾, 時效決策拿捏不當, 經常在發生嚴重虧損之後才賣出, 等到行情上漲時卻往往沒有做投資. 
  13. 投資人過度強調分散投資, 反而造成績效不彰與不必要的費用. 投資要成功, 只需要透過雙動能. 謹慎挑選低成本股票與固定收益的指數型基金即可. 
  14. 價格的時間序列是所有動能的根本, 所有的動能都屬於順勢操作. 
  15. Paul Tudor Jones 說 : 交易的最重要法則在於防禦而不是攻擊. 
  16. 雙動能投資 :
    (1). 相對動能 : 挑選過去 12 個月表現最佳的資產.
    (2). 絕對動能 : 評估所挑選的資產在過去一年呈現正報酬或負報酬, 正報酬就買進該資產; 否則表示趨勢向下, 改為投資於短中期固定收益資產, 直到反轉向上. 
  17. 美國市場雙動能投資 : 以 S&P500 作為絕對動能訊號, 若 S&P500 大於 ACWI 不含美國與美國國庫券就買進 S&P500, 否則比較 ACWI 與國庫券, 若 ACWI 不含美國大於國庫券就買進 ACWI 不含美國, 否則買進整體債券, 每個月重複此程序. 這套方法只採用美國股票, 非美國股票, 以及整體債券. 

2019年10月26日 星期六

matplotlib 不支援 jpg 輸出問題

今天二哥去學校跟同學討論物理實驗報告, 中午 Line 給我下列程式出現 "MemoryError", 問我是資料太多讓記憶體爆掉了嗎?

import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize

def f_1(x, A, B):
    return A*x + B 

plt.figure(figsize=(7,5),dpi=80)
x=[0.070*9.807,0.080*9.807,0.090*9.807,0.100*9.807,0.110*9.807]
y=[1/(0.68115*0.68115),1/(0.76904*0.76904),1/(0.85885*0.85885),1/(0.93795*0.93795),1/(0.99073*0.99073)]

plt.style.use("ggplot")
plt.xlabel("Centripetal force(N)", fontweight = "bold")
plt.ylabel("square of Average period(s*s)", fontweight = "bold")
plt.title("Centripetal force and square of Average period",fontweight = "bold")       
plt.scatter(x, y, c = "m", s = 50,)
A2,B2=optimize.curve_fit(f_1, x, y)[0]
x2 = np.arange(0.070*9.807,0.110*9.807,0.000001)
y2 = A2*x2 + B2
plt.plot(x2, y2,"--")   
plt.savefig("666.jpg")
plt.show()
plt.close()

我想就這點資料而已, Numpy 哪有可能輕易就掛掉? 晚上回到鄉下實際測試, 果然執行失敗, 但原因不是 MemoryError, 而是出現 "'jpg' is not surported" 錯誤, 建議用 png 格式 :

D:\test>python test.py
Traceback (most recent call last):
  File "test.py", line 21, in <module>
    plt.savefig("666.jpg")
  File "C:\Python36\lib\site-packages\matplotlib\pyplot.py", line 701, in savefig
    res = fig.savefig(*args, **kwargs)
  File "C:\Python36\lib\site-packages\matplotlib\figure.py", line 1834, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "C:\Python36\lib\site-packages\matplotlib\backend_bases.py", line 2170, in print_figure
    canvas = self._get_output_canvas(format)
  File "C:\Python36\lib\site-packages\matplotlib\backend_bases.py", line 2113, in _get_output_canvas
    '%s.' % (format, ', '.join(formats)))
ValueError: Format "jpg" is not supported.
Supported formats: eps, pdf, pgf, png, ps, raw, rgba, svg, svgz.

我改成 png 後果然就順利跑出圖形了, 二哥遇到 MemoryError 可能是電腦跑太多東西, 重開機應該就 OK 了 :




我查了 matplotlib 不能匯出 jpg 的問題, 原來只要安裝 pillow 套件就可以解決了, 參考 :

https://stackoverflow.com/questions/8827016/matplotlib-savefig-in-jpeg-format

安裝指令如下 :

pip3 install pillow

然後再重新執行上面的程式就可以順利輸出 jpg 檔了.

老實說 matplotlib 我也只是三腳貓而已, 還沒正式學過, 看來我的資料科學要加速學習才行, 不然很快就會被問倒.

看完監獄醫生

昨天終於看完 16 集的韓劇 "監獄醫生", 羅以載醫生終於將本部長李在俊送入監牢, 先趁會面時以針筒攻擊李在俊, 注射了誘發杭廷頓舞蹈症的藥物, 又故意讓李在俊反擊時以拆信刀刺入自己肺部不致命部位, 讓鄭義植檢察官得以用殺人現行犯逮捕李在俊, 送進自己任職的西首爾監獄, 藉此機會套出他給李在煥打的第二針藥劑為氯化鉀, 使李在煥恢復意識, 並在董事會中指認李在俊為殺人犯.

此劇是 KBS 令人驚豔的作品, 從後面花絮可以看出製作團隊在編劇, 攝影, 醫學手術擬真等方面的用心, 讓 KBS 這個無線台也能端出與有線電視如 OCN 那樣電影級別的戲劇. 我想很多人也是看這部戲才知道杭廷頓舞蹈症這個遺傳性疾病吧? 我問過同事真的都沒人聽說過.

花絮也介紹了劇中出現過幾次韓素錦醫生施展過肩摔的練習畫面, 當演員還真的是不容易. 以前在校時我也是柔道社的一員, 看了之後就激起我的武術魂, 我最喜歡的就是過肩摔了, 因為個子矮的人用這招最有效, 大部分的人是右撇子, 所以左摔常常可出其不意絆倒對手.

前幾天也看了 "檢法男女2" 後半部, 沒有大咖也能拍出這樣的作品也還不錯, 只是目前在忙著學 Numpy, 等有空再來看吧.

2019年10月24日 星期四

好書 : 白話微積分(3版)

最近在測試雙曲函數時找到一本微積分的好書 :

白話微積分(3版) (五南, 卓永鴻)




作者卓永鴻台大歷史系, 台大數學研究所畢, 歷史系直上數學碩士, 這經歷也太奇特了, 跟他的老師楊維哲教授讀完醫學院在重讀數學系不遑相讓. 作者長期擔任台大學生的微積分輔導, 對於微積分教學經驗豐富, 相關文章發布於其個人網站 :

http://calcgospel.in/

2019年10月21日 星期一

Python 學習筆記 : 數學模組 math 與 cmath 測試

在學習 Python 第三方套件 Numpy 之前我想先對 Pyhon 內建的 math 模組進行較完整的測試, 因為對於非向量運算來說, 使用 math 模組提供的函數就綽綽有餘了, 而且運算效能比較好. 不過 math 模組只能處理實數運算, Python 提供另一個內建模組 cmath 專門用來處理複數運算.

本系列測試文章索引參考 :

Python 學習筆記索引

math 與 cmath 模組的說明文件參考 :

https://docs.python.org/3/library/math.html
https://docs.python.org/3/library/cmath.html


1. 匯入 math 模組 : 

使用 math 模組須先匯入 :

import math

這樣呼叫 math 的方法時必須前綴模組名稱 math, 例如 math.sin(1). 也可以取一個較短的別名 (alias) :

import math as m 

這樣就可以用別名 m.sin() 來呼叫函數了.

用 dir() 檢視 math 模組的成員 (屬性與方法) :

>>> import math           #math 模組只能處理實數
>>> dir(math)     
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']

用 dir() 檢視 cmath 模組的成員 (屬性與方法) :

>>> import cmath         #cmath 模組可處理複數
>>> dir(cmath) 
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'cos', 'cosh', 'e', 'exp', 'inf', 'infj', 'isclose', 'isfinite', 'isinf', 'isnan', 'log', 'log10', 'nan', 'nanj', 'phase', 'pi', 'polar', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau']

可見大部分函數均與 math 模組一樣, 常數 e, pi, tau, nan, inf 都是相同的實數, 但 cmath 多了虛數的 infj 與 nanj.


2. math 模組中的數學常數 : 

math 模組中定義了如下五個常數 (屬性) :


 math 常數 說明
 e 自然指數=2.718281828459045
 pi 圓周率=3.141592653589793
 tau 圓周率的兩倍=2*pi
 nan 非數值=float('nan')  
 inf 正無限大之浮點數=float('inf')


其中 nan 與 inf 也可以用 float('nan') 與 float('inf') 產生, 並有對應的方法 isnan() 與 isinf() 用來檢測傳入參數是否為 nan 與 inf.

例如 :

>>> import math   
>>> math.e                                   #自然指數
2.718281828459045
>>> math.pi                                 #圓周率
3.141592653589793
>>> math.nan                              #非數值
nan
>>> type(math.nan)                    #nan 為 float 型態
<class 'float'>
>>> float('NAN') 
nan
>>> math.isnan(float('nan'))      #檢查是否為 nan
True
>>> math.inf 
inf
>>> type(math.inf)                      #inf 為 float 型態
<class 'float'>
>>> float('INF')   
inf
>>> math.isinf(float('inf'))         #檢查是否為 inf
True
>>> math.isnan(123)                  #檢查是否為 nan
False
>>> math.isinf(123)                    #檢查是否為 inf
False
>>> math.isnan('abc')                #不可傳入字串
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
TypeError: must be real number, not str


3. math 模組的函數 :

math 模組提供了三角, 指數, 對數, 反雙曲等超越函數如下表 :


 math 方法 說明
 sqrt(x) 傳回 x 的平方根
 pow(x, y) 傳回 x 的 y 次方
 exp(x) 傳回 x 的自然指數
 expm1(x) 傳回 x 的自然指數-1 (在 x 接近 0 時仍有精確值)
 log(x [, b]) 傳回 x 以 b 為基底的對數 (預設 b=e 自然對數)
 log10(x) 傳回 x 的常用對數 (以 10 為底數)
 degrees(x) 傳回弧度 x 的角度 (degree)
 radians(x) 傳回角度 x 的弧度 (radian)
 dist(p, q) 傳回兩個座標點 p, q 的歐幾里得距離 (畢式定理斜邊)
 hypot(coor) 傳回座標序列 coor 的歐幾里得距離
 sin(x) 傳回 x 的正弦值
 cos(x) 傳回 x 的餘弦值
 tan(x) 傳回 x 的正切值
 asin(x) 傳回 x 的反正弦值 (sin 的反函數)
 acos(x) 傳回 x 的反餘弦值 (cos 的反函數)
 atan(x) 傳回 x 的反正切值 (tan 的反函數)
 atan2(y, x) 傳回 y/x 的反正切值 (tan 的反函數)=atan(y/x)
 sinh(x) 傳回 x 的雙曲正弦值
 cosh(x) 傳回 x 的雙曲餘弦值
 tanh(x) 傳回 x 的雙曲正切值
 asinh(x) 傳回 x 的反雙曲正弦值=log(x+sqrt(x**2+1))
 acosh(x) 傳回 x 的反雙曲餘弦值=log(x+sqrt(x**2-1))
 atanh(x) 傳回 x 的反雙曲正切值=1/2*log((1+x)/(1-x))
 fabs(x) 傳回 x 的絕對值 (或稱模數, modulus)
 floor(x) 傳回浮點數 x 的向下取整數 (即小於 x 之最大整數)
 ceil(x) 傳回浮點數 x 的向上取整數 (即大於 x 之最小整數)
 trunc(x) 傳回浮點數 x 的整數部分 (捨去小數)
 modf(x) 傳回浮點數 x 的 (小數, 整數) 元組
 factorial(x) 傳回 x 階乘 (x!, x=整數)
 gcd(x, y) 傳回整數 x, y 之最大公因數
 comb(n, k) 傳回 n 取 k 的組合數 (不依序不重複)
 perm(n, k) 傳回 n 取 k 的組合數 (依序不重複)
 modf(x, y) 傳回 x/y 之精確餘數 (浮點數 float)
 fsum(iter) 傳回可迭代數值 iter 之精確總和
 isclose(x, y) 若 a, b 值很接近傳回 True (預設差小於 1e-9)
 isfinite(x) 若 x 不是 nan 或 inf 傳回 True, 否則 False
 isnan(x) 若 x 為 nan 傳回 True, 否則 False
 isinf(x) 若 x 為 inf 傳回 True, 否則 False


例如 :

#==========數值函數測試============
>>> import math
>>> math.fabs(-1.23)      #絕對值
1.23
>>> math.floor(1.5)        #向下取整數
1
>>> math.floor(-1.5)       #向下取整數
-2
>>> math.ceil(1.5)           #向上取整數
2
>>> math.ceil(-1.5)          #向上取整數
-1
>>> math.trunc(3.14159)    #捨去小數部分
3
>>> math.modf(3.14159)     #傳回浮點數之 (小數, 整數) 元組
(0.14158999999999988, 3.0)
>>> math.fmod(10, 3)          #傳回除法餘數 (浮點數)
1.0
>>> 10 % 3                            #傳回除法餘數 (整數)
1
>>> math.fsum([1, 2, 3])      #計算可迭代物件之元素和
6.0
>>> math.fsum((1, 2, 3))      #計算可迭代物件之元素和
6.0
>>> math.fsum(range(1, 11))      #計算可迭代物件之元素和
55.0

要注意 fmod() 與 modf() 的不同, fmod() 是取餘數, 表示 float modulus 之意; 而 modf() 用 Python 的取餘數運算子 % 得到的餘數為整數, 而用 math.fmod() 得到的是浮點數.

階乘函數 factorial() 在數學上 n 階乘表示為 n!=n*(n-1)*(n-2) ... *2*1, 但 0!=1!=1 :

#==========階乘函數============
>>> import math
>>> math.factorial(0)                   
1
>>> math.factorial(1) 
1
>>> math.factorial(2) 
2
>>> math.factorial(3) 
6
>>> math.factorial(10) 
3628800

math 官方文件中有排列函數 math.perm() 與組合函數 math.comb(), 但在 dir(math) 中卻沒有, 實際測試也確實不支援 :

>>> math.perm(8,3) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'math' has no attribute 'perm'
>>> math.comb(8,3) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'math' has no attribute 'comb'

這只好用 factorial() 來自行定義了, 排列函數 perm(n, k) 是指在 n 個不同元素中取出 k 個做排列, 總共有 n!/(n-k)! 排法 (注意, 排列是有順序的) :

def perm(n, k):
    if (n<0 or k<0):
        return None
    else:
        return math.factorial(n)/math.factorial(n-k)

組合函數 comb(n, k) 是指在 n 個不同元素中取出 k 個, 總共有 n!/[k!(n-k)!] 個取法 (注意, 組合是沒有順序的) :

def comb(n, k):
    if (n<0 or k<0):
        return None
    else:
        return math.factorial(n)/(math.factorial(k)*math.factorial(n-k))

例如 :

>>> import math   
>>> def perm(n, k): 
...     if (n<0 or k<0):   
...         return None 
...     else: 
...         return math.factorial(n)/math.factorial(n-k)   
...
>>> def comb(n, k): 
...     if (n<0 or k<0):   
...         return None   
...     else:   
...         return math.factorial(n)/(math.factorial(k)*math.factorial(n-k))   
...
>>> perm(8,3)        #8 取 3 做排列
336.0
>>> comb(49,6)      #49 取 6 做組合
13983816.0


#==========指數與對數函數=============
>>> import math
>>> math.sqrt(2)              #開平方
1.4142135623730951
>>> math.pow(2, 3)          #次方
8.0
>>> math.exp(1)               #自然指數
2.718281828459045
>>> math.expm1(1)          #自然指數少 1
1.718281828459045
>>> math.e                        #自然指數
2.718281828459045
>>> math.log(1)                #1 的對數為 0
0.0
>>> math.log(math.e)      #自然指數的對數為 1
1.0
>>> math.log10(1)            #1 的對數為 0
0.0
>>> math.log10(10)          #常用對數
1.0
>>> math.log(8, 2)            #8 以 2 為基底的對數=3, 因 2**3=8
3.0
>>> math.log(49, 7)          #49 以 7 為基底的對數=2, 因 7**2=49
2.0

注意, math 模組只能處理實數, 複數需用 cmath 處理, 例如 math.sqrt(-1) 會出現 "domain error" 錯誤訊息, 但 cmath.sqrt(-1) 則會傳回 1j, 例如 :

>>> math.sqrt(-1)           #math 無法處理 -1 開根號
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error   
>>> cmath.sqrt(-1)         #cmath 可處理 -1 開根號
1j 

#==========角度與弧度轉換============
>>> import math
>>> math.degrees(math.pi)          #弧度轉角度 pi=180
180.0
>>> math.degrees(math.tau)        #弧度轉角度 2pi=360
360.0
>>> math.degrees(math.pi/2)       #弧度轉角度 pi/2=90
90.0
>>> math.degrees(math.pi/4)       #弧度轉角度 pi/4=45
45.0
>>> math.degrees(1.234)               #弧度轉角度
70.70299191914359
>>> math.radians(70.703)             #角度轉弧度
1.234000141037551
>>> math.radians(30)                    #角度轉弧度
0.5235987755982988
>>> math.radians(45)                    #角度轉弧度
0.7853981633974483
>>> math.radians(60)                    #角度轉弧度
1.0471975511965976
>>> math.radians(90)                    #角度轉弧度
1.5707963267948966
>>> math.radians(180)                  #角度轉弧度 (pi)
3.141592653589793
>>> math.radians(360)                  #角度轉弧度 (2pi)
6.283185307179586

三角函數是圓函數, 一共有六個, 但 math 模組只提供了三個 : sin(), cos(), 與 tan(), 因為其他三個是這三個的倒數, 即 cot=1/tan, sec=1/cos, csc=1/sin, 其反函數也是如此. 參考 :

# 維基 : 三角函數


#==========三角函數============
>>> import math
>>> math.sin(math.pi/6) 
0.49999999999999994
>>> math.sin(math.pi/2)   
1.0
>>> math.sin(math.pi)   
1.2246467991473532e-16
>>> math.asin(1)   
1.5707963267948966
>>> math.asin(0)   
0.0

雙曲函數起源於解決達文西的懸鍊曲線問題 (例如吊橋與架高電纜以前被認為是拋物線, 事實上是雙曲線), 也應用在非歐幾何與相對論等. 雙曲函數跟三角函數長得很像, 函數名稱多了一個 h, 但其實與三角函數無關, 不過這兩個無關的函數在尤拉公式出現後卻統一在複變數理論下. 三角函數與雙曲函數在複平面上形狀是一樣的, 三角函數的週期是 2*pi, 而雙曲函數則是 2*pi*i.

雙曲函數定義如下 (定義域為實數) :




其實只要定義 sinh(), cosh(), 與 tanh() 即可, 另外三個只是倒數而已. 雖然雙曲函數雖然與三角函數無關, 但卻有類似三角函數之特性, 因為其公式除了正負號略為不同外均可套用, 而且在虛數圓角上與三角函數具有如下關係 :


參考 :

# 維基 : 雙曲函數
雙曲函數及反三角函數
雙曲函數的基本數性質
4980N031的學習歷程檔案
# 雙曲函數
可能是最好的讲解双曲函数的文章
python弧度制轉換 三角函數 反三角函數 雙曲 反雙曲


#==========雙曲函數============
>>> import math
>>> math.sinh(1)   
1.1752011936438014
>>> math.cosh(1) 
1.5430806348152437
>>> math.tanh(1) 
0.7615941559557649
>>> math.asinh(1.1752011936438014)    #反雙曲正弦
1.0
>>> math.acosh(1.5430806348152437)    #反雙曲餘弦
1.0
>>> math.atanh(0.7615941559557649)    #反雙曲正切
0.9999999999999999


4. cmath 模組的函數 :

cmath 模組的運算元是複數, 常數增加了複數的 infj 與 nanj, 方法則大部分與 math 相同, 不過沒有階乘函數 (因為複數沒有階乘), 但增加了如下三個與複數座標轉換有關的方法, 方便在卡式座標與極座標之間轉換 :


 cmath 方法 說明
 polar(x) 傳回複數 x 的極座標表示法元組 (r, p), r=長度, p=角度
 rect(r, p) 傳回極座標 (r, p) 的複數
 phase(x) 傳回複數 x 的弧度 (radian)


#==========cmath 測試============
>>> import math
>>> import cmath
>>> print(cmath.infj) 
infj
>>> print(cmath.nanj) 
nanj
>>> type(cmath.nanj)                               #複數類型
<class 'complex'>
>>> type(cmath.nan) 
<class 'float'>
>>> type(cmath.infj)                                 #複數類型
<class 'complex'>
>>> type(cmath.inf)     
<class 'float'>
>>> cmath.polar(1+1j)                              #複數轉極座標
(1.4142135623730951, 0.7853981633974483)
>>> cmath.rect(1.4142135623730951, 0.7853981633974483)   
(1.0000000000000002+1.0000000000000002j)         #極座標轉直角坐標
>>> cmath.phase(1+1j)                              #傳回複數弧度
0.7853981633974483
>>> math.degrees(cmath.phase(1+1j))     #弧度轉角度
45.0
>>> cmath.sinh(1+1j) 
(0.6349639147847361+1.2984575814159773j) 
>>> cmath.cosh(1+1j) 
(0.8337300251311491+0.9888977057628651j) 
>>> cmath.tanh(1+1j) 
(1.0839233273386946+0.2717525853195118j) 

我覺得 cmath.polar() 與 cmath.rect() 在學習交流電分析時蠻好用的.