Ubuntu設定自動化安全性更新

  • install

    1
    sudo apt install unattended-upgrades
  • Verify

    1
    systemctl status unattended-upgrades
  • config

    1
    sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
  • Blacklist
    Unattended-Upgrade::Package-Blacklist

  • Email Notifications
    Unattended-Upgrade::Mail example@email.com;

Unattended-Upgrade::MailOnlyOnError “true”;

  • Enable Automatic Upgrades

    1
    sudo nano /etc/apt/apt.conf.d/20auto-upgrades
  • Testing Automatic Upgrades

    1
    sudo unattended-upgrades --dry-run --debug
  • check log

    1
    /var/log/unattended-upgrades/unattended-upgrades.log

參考:
https://phoenixnap.com/kb/automatic-security-updates-ubuntu

windows docker設定

docker image搬離開系統槽

https://stackoverflow.com/questions/62441307/how-can-i-change-the-location-of-docker-images-when-using-docker-desktop-on-wsl2

  1. 關閉wsl2

    1
    wsl --shutdown
  2. 匯出docke檔案

    1
    wsl --export docker-desktop-data "D:\Docker\wsl\data\docker-desktop-data.tar"
  3. unregister

    1
    wsl --unregister docker-desktop-data
  4. 匯入

    1
    wsl --import docker-desktop-data "D:\Docker\wsl\data" "D:\Docker\wsl\data\docker-desktop-data.tar" --version 2

wsl2 volume

https://stackoverflow.com/questions/61083772/where-are-docker-volumes-located-when-running-wsl-using-docker-desktop

Windows使用SOCKS5-proxy上網

簡介

這裡將介紹一個只需要使用SSH連線到一台可以連到對網網路的主機,就可以讓防火牆內的主機上網的方法
圖片說明

設定putty

https://www.simplified.guide/putty/create-socks-proxy

或是用ssh指令連接到可以上網的電腦

1
ssh -D 4444 -q -C -N user@ma.ttias.be

windows 設定SOCKS5 proxy

https://blog.gtwang.org/linux/ssh-tunnel-socks-proxy-forwarding-tutorial/

新增憑證

https://learn.microsoft.com/zh-tw/biztalk/adapters-and-accelerators/accelerator-swift/adding-certificates-to-the-certificates-store-on-the-client

windows下設定git proxy command

https://serverfault.com/questions/956613/windows-10-ssh-proxycommand-posix-spawn-no-such-file-or-directory

Windows設定自動重啟服務

開啟工作排程器

image info

分別設定停止服務和啟動服務兩個工作

  • 使用最高權限執行工作並且使用者不須登入就執行
    image info

  • 分別設定停止服務和啟動服務兩個工作
    在動作設定中,選擇開啟程式,並且在程式或指令中輸入net,並且在參數中輸入stop "服務名稱"start "服務名稱"

image info
image info

libcurl上傳圖片

快速產生libcurl程式碼

首先先用curl指令發送request,確認可以發送成功後可以用指令自動產生程式碼,在這裡我們將上傳一張圖片到http server,利用curl讀取電腦的圖片並且上傳

1
curl -X POST "http://localhost:8000/upload" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "uploadedFile=@a.jpg;type=image/jpeg" -F "EventTime=2022-01-01"

利用curl的--libcurl選項就可以快速產生c語言的程式碼,程式碼會被儲存到code.c

1
curl -X POST "http://localhost:8000/upload" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "uploadedFile=@a.jpg;type=image/jpeg" -F "EventTime=2022-01-01" --libcurl code.c

產生的程式碼如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/********* Sample code generated by the curl command line tool **********
* All curl_easy_setopt() options are documented at:
* https://curl.haxx.se/libcurl/c/curl_easy_setopt.html
************************************************************************/
#include <curl/curl.h>

int main(int argc, char *argv[])
{
CURLcode ret;
CURL *hnd;
curl_mime *mime1;
curl_mimepart *part1;
struct curl_slist *slist1;

mime1 = NULL;
slist1 = NULL;
slist1 = curl_slist_append(slist1, "accept: application/json");
slist1 = curl_slist_append(slist1, "Content-Type: multipart/form-data");

hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
curl_easy_setopt(hnd, CURLOPT_URL, "http://localhost:8000/upload");
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
mime1 = curl_mime_init(hnd);
part1 = curl_mime_addpart(mime1);
curl_mime_filedata(part1, "a.jpg");
curl_mime_name(part1, "uploadedFile");
curl_mime_type(part1, "image/jpeg");
part1 = curl_mime_addpart(mime1);
curl_mime_data(part1, "2022-01-01", CURL_ZERO_TERMINATED);
curl_mime_name(part1, "EventTime");
curl_easy_setopt(hnd, CURLOPT_MIMEPOST, mime1);
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1);
curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.68.0");
curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
curl_easy_setopt(hnd, CURLOPT_SSH_KNOWNHOSTS, "/home/ai_server/.ssh/known_hosts");
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L);
curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);

/* Here is a list of options the curl code used that cannot get generated
as source easily. You may select to either not use them or implement
them yourself.

CURLOPT_WRITEDATA set to a objectpointer
CURLOPT_INTERLEAVEDATA set to a objectpointer
CURLOPT_WRITEFUNCTION set to a functionpointer
CURLOPT_READDATA set to a objectpointer
CURLOPT_READFUNCTION set to a functionpointer
CURLOPT_SEEKDATA set to a objectpointer
CURLOPT_SEEKFUNCTION set to a functionpointer
CURLOPT_ERRORBUFFER set to a objectpointer
CURLOPT_STDERR set to a objectpointer
CURLOPT_HEADERFUNCTION set to a functionpointer
CURLOPT_HEADERDATA set to a objectpointer

*/

ret = curl_easy_perform(hnd);

curl_easy_cleanup(hnd);
hnd = NULL;
curl_mime_free(mime1);
mime1 = NULL;
curl_slist_free_all(slist1);
slist1 = NULL;

return (int)ret;
}
/**** End of sample code ****/

curl_mime_filedata改成curl_mime_data

由於我的目的是將在記憶體中已經被encode好的圖片直接上傳,所以要用curl_mime_data
直接上傳記憶體內的內容。
注意除了用curl_mime_data放入資料以外,還必須自己手動把Content-Disposition補上filename。
另外記得curl_mime_data不可以用CURL_ZERO_TERMINATED而是要實際算出圖片的資料長度。
可以參考官方的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
curl_mime *mime;
curl_mimepart *part;

/* create a mime handle */
mime = curl_mime_init(easy);

/* add a part */
part = curl_mime_addpart(mime);

/* send image data from memory */
curl_mime_data(part, imagebuf, imagebuf_len);

/* set a file name to make it look like a file upload */
curl_mime_filename(part, "image.png");

/* set name */
curl_mime_name(part, "data");

讀binary file

直接將圖片讀進記憶體內不要做任何處理
範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void ReadFile(char *name)
{
FILE *file;
char *buffer;
unsigned long fileLen;

//Open file
file = fopen(name, "rb");
if (!file)
{
fprintf(stderr, "Unable to open file %s", name);
return;
}

//Get file length
fseek(file, 0, SEEK_END);
fileLen=ftell(file);
fseek(file, 0, SEEK_SET);

//Allocate memory
buffer=(char *)malloc(fileLen+1);
if (!buffer)
{
fprintf(stderr, "Memory error!");
fclose(file);
return;
}

//Read file contents into buffer
fread(buffer, fileLen, 1, file);
fclose(file);

//Do what ever with buffer

free(buffer);
}

termshark

再除錯的過程中,最好可以直接看一看自己的封包長什麼樣子,對於沒有螢幕的Ubuntu Server,可以安裝termshark,它可以做到跟wireshark差不多的事情

完整範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/********* Sample code generated by the curl command line tool **********
* All curl_easy_setopt() options are documented at:
* https://curl.haxx.se/libcurl/c/curl_easy_setopt.html
************************************************************************/
#include <curl/curl.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
CURLcode ret;
CURL *hnd;
curl_mime *mime1;
curl_mimepart *part1;
struct curl_slist *slist1;

mime1 = NULL;
slist1 = NULL;
slist1 = curl_slist_append(slist1, "accept: application/json");
slist1 = curl_slist_append(slist1, "Content-Type: multipart/form-data");

hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);


curl_easy_setopt(hnd, CURLOPT_URL, "http://localhost:8000/upload");


curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
mime1 = curl_mime_init(hnd);
part1 = curl_mime_addpart(mime1);


// https://www.w3schools.blog/c-read-binary-file
FILE *file;
const char *buffer;
unsigned long fileLen;

//Open file
file = fopen("image.jpg", "rb");
if (!file)
{
fprintf(stderr, "Unable to open file");
return;
}

//Get file length
fseek(file, 0, SEEK_END);
fileLen=ftell(file);
fseek(file, 0, SEEK_SET);

//Allocate memory
buffer=(char *)malloc(fileLen+1);
if (!buffer)
{
fprintf(stderr, "Memory error!");
fclose(file);
return;
}

//Read file contents into buffer
fread(buffer, fileLen, 1, file);
fclose(file);

/* add data to the part */
curl_mime_data(part1, buffer, fileLen);

/* set a file name to make it look like a file upload */
curl_mime_filename(part1, "image.jpg");

free(buffer);


curl_mime_name(part1, "uploadedFile");
curl_mime_type(part1, "image/jpeg");
part1 = curl_mime_addpart(mime1);
curl_mime_data(part1, "2022-02-02", CURL_ZERO_TERMINATED);
curl_mime_name(part1, "EventTime");
curl_easy_setopt(hnd, CURLOPT_MIMEPOST, mime1);
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1);
curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.68.0");
curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L);
curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);


ret = curl_easy_perform(hnd);

curl_easy_cleanup(hnd);
hnd = NULL;
curl_mime_free(mime1);
mime1 = NULL;
curl_slist_free_all(slist1);
slist1 = NULL;

return (int)ret;
}
/**** End of sample code ****/

參考:

https://daniel.haxx.se/blog/2022/09/12/convert-a-curl-cmdline-to-libcurl-source-code/

https://curl.se/libcurl/c/curl_mime_filename.html

https://geekscripts.guru/termshark-terminal-ui-for-tshark/

Computer Vision Models Learning and Inference整理

CH3 常見的probability distributions

要利用第二章操作probabilities的規則,為了使用這些規則,我們需要定義一些機率分布。選擇機率分布的依據是取決於我們正在建模的數據x的領域。

當我們在使用模型去擬合資料的時候,我們需要知道我們對擬合的不確定性有多大,而這個不確定性被表示為”對擬合模型的參數的機率分布”。也就是說每當一個模型在擬合資料的時候,都存在一個與之相關聯的參數的第二個機率分布。

例如 Dirichlet 是用於categorical distribution的參數的模型。在這種狀況下 Dirichlet的參數會被稱為超參數(hyperparameters)。更一般地說,超參數確定了原始分布參數的機率分布的形狀。

Distribution Domain Parameters modeled by
Bernoulli x ∈ {0, 1} beta
categorical x ∈ {1, 2, . . . , K} Dirichlet
univariate normal x ∈ R normal inverse gamma
multivariate normal x ∈ Rk normal inverse Wishart

|Distribution | Domain | Parameters modeled by |
Bernoulli | x ∈ {0, 1} | beta
categorical | x ∈ {1, 2, . . . , K} | Dirichlet
univariate normal | x ∈ R | normal inverse gamma
multivariate normal | x ∈ Rk | normal inverse Wishart

CH3.1 Bernoulli distribution

Bernoulli distribution是一個離散分布用於模擬二元試驗。他用於描述只有兩中輸出結果的狀況,$x \in {0, 1}$分別代表是(success)、否(failure)。在機器視覺中,Bernoulli distribution可以用來模擬資料。例如,它可以描述一個像素具有大於或小於128的強度值的概率。或者是用來描述世界的狀態。例如,它可以描述圖像中臉部存在或不存在的概率。

Bernoulli distribution只有一個parameter $\lambda \in [0, 1]$,用來描述觀察到success的機率。Bernoulli distribution的機率質量函數如下
$$Pr(x = 0) = 1 - \lambda$$
$$Pr(x = 1) = \lambda$$

我們可以用另一種表達方式,將0或1帶入就可以得到上面其中一條式子。
$$Pr(x) = \lambda^x(1-\lambda)^{1-x}$$
或是另一種等價的表達方式
$$Pr(x) = Bern_x[\lambda] $$

CH3.2 Beta distribution

Beta distribution是一個連續分布,他是定義在單變量$\lambda$上的連續分布,$\lambda \in [0, 1]$。它適用於表示伯努利分布參數$\lambda$的不確定性。

beta distribution 有兩個parameter$\alpha, \beta \in [0, \infty]$,兩個parameter均為正數並且影響distribution的形狀。以數學表達如下
$$Pr(\lambda) = \frac{\Gamma[\alpha + \beta]}{\Gamma[\alpha]\Gamma[\beta]}\lambda^{\alpha-1}(1-\lambda)^{\beta-1}$$

式子中的$\Gamma[]$代表gamma function,定義為
$$\Gamma[z] = \int_0^{\infty}t^{z-1}e^{-t}dt$$
,他與階乘密切相關,因此對於正數的積分$\Gamma[z] = (z - 1)!$而且$\Gamma[z+1] = z\Gamma[z]$ 。

beta distribution還有更簡單的表達式
$$Pr(\lambda) = Beta_{\lambda}[\alpha, \beta]$$

CH3.3 Catagorical distribution

Catagorical distribution是一個離散分布,他用來決定K個可能結果之一的概率。因此Bernoulli distribution是Catagorical distribution的一種特例,也就是只有兩種可能結果的Catagorical distribution。在機器視覺中也經常出現多個離散值取一個的情況,例如依照照片可能是{car,motorbike,van,truck}的其中一個。
對於有K種結果的Catagorical distribution,Catagorical distribution會一個$K \times 1$的參數的向量$\lambda = [\lambda_1, \lambda_2 … , \lambda_K]$其中$\lambda_K \in [0, 1]$而且$\sum^K_{k=1}\lambda_k = 1$Catagorical distributiond可以寫成

$$Pr(x = k) = \lambda_k$$

更簡短的可以寫成
$$ Pr(x) = Cat_x[\lambda]$$

或者,我們可以將數據看作取值$x \in {e_1, e_2, …,e_K}$,其中$e_k$是第k個單位向量;除了第k個元素為1之外,$e_k$的所有元素都為零。寫成式子如下

$$Pr(x=e_k) = \prod^K_{j=1}\lambda_k^{x_j} = \lambda_k$$

其中$x_j$是$x$的第j個元素。

CH3.4 Dirichlet distribution

Dirichlet distribution 是一個定義在K個連續值$\lambda_1 … \lambda_K$上的分佈,其中$\lambda_k \in [0, 1]$而且$\sum^K_{k=1}\lambda_k = 1$。因此他很適合用來作為定義Catagorical distribution的參數分布。

在$K$個維度上的Dirichlet distribution有$K$個parameter $\alpha_1 … \alpha_K$,每一個都可以是正數或是零。parameters之間的相對值決定了 expected values $E[\lambda_1] … E[\lambda_K]$。parameters的絕對值決定了Dirichlet distribution的集中度。他的機率密度函數如下
$$Pr(\lambda_{1…L}) = \frac{\Gamma[\sum^K_{k=1}\alpha_k]}{\prod^K_{k=1}\Gamma[\alpha_k]}\prod^K_{k=1}\lambda_k^{\alpha_k-1}$$

更簡短的寫法
$$Pr(\lambda_{1…K}) = Dir_{\lambda_{1…K}}[\alpha_{1…K}]$$

如同Bernoulli distribution是Catagorical distribution的特例,所以Beta distribution是Dirichlet distribution的特例,也就是$K=2$的Beta distribution。

3.5 Univariate normal distribution

Univariate normal distribution 或是 Gaussian distribution 是一個定義在實數上$x \in [-\infty, \infty]$的連續分佈。
在計算機視覺中,常常忽略像素強度被量化的事實,並使用連續的正態分佈模型來建模。
Normal distribution有兩個parameter,平均數$\mu$和變異數$\sigma^2$,平均數$\mu$決定了分佈的中心的位置,變異數$\sigma^2$決定了分佈的寬度。Normal distribution的機率密度函數如下
$$Pr(x|) = \frac{1}{\sqrt{2\pi\sigma^2}}exp{-\frac{1}{2\sigma^2}(x-\mu)^2}$$

或是更簡單的寫法
$$Pr(x) = Norm_x[\mu, \sigma^2]$$

3.6 Normal-scaled inverse gamma distribution

Normal-scaled inverse gamma distribution是一個定義在一對實數$\mu, \sigma$上的連續分佈,他的機率密度函數如下,其中$\mu$可以為正為負,但是$\sigma$必須為正。
Normal-scaled inverse gamma distribution有四個parameter,$\alpha, \beta, \gamma, \delta$,其中$\alpha, \beta, \gamma$必須為正,而$\delta$可以為正為負。機率密度函數如下:
$$Pr(\mu, \sigma^2) = \frac{\sqrt{\gamma}}{\sigma\sqrt{2\pi}}\frac{\beta^\alpha}{\Gamma[\alpha]}(\frac{1}{\sigma^2})^{\alpha + 1}exp[-\frac{2\beta + \gamma(\delta - \mu)^2}{2\sigma^2}]$$
或者是寫成
$$Pr(\mu, \sigma^2) = NormInvGam_{\mu, \sigma^2}[\alpha, \beta, \gamma, \delta]$$

3.7 Multivariate normal distribution

multivariate normal 也就是 D-dimensional Gaussian distribution,他的維度可以表示成D個元素 $x_1, … x_D$每個維度都是連續並且介於$[-\infty, \infty]$。而univariate normal distribution是multivariate normal distribution的特例,也就是$D=1$。
在機器視覺中,多變量正態分佈可以用來模擬圖像區域內 D 個像素的強度的聯合分佈。

世界的狀態也可以用這個分佈來描述。例如,多變量正態分佈可以描述場景中物體的三維位置(x、y、z)的聯合不確定性。

multivariate normal distribution有兩個parameter,平均數向量$\mu$ 以及covariance $\Sigma$。其中$\mu$是一個$D \times 1$的向量來描述分布的平均值。而covariance $\Sigma$是一個$D \times D$的正定矩陣,因此對於任何實數向量$z$而言,$z^T\Sigma z$都是正數。機率密度函式如下
$$Pr(x) = \frac{1}{\sqrt{(2\pi)^D|\Sigma|}}exp[-\frac{1}{2}(x-\mu)^T\Sigma^{-1}(x-\mu)]$$

或是更簡短一點
$$Pr(x) = Norm_x[\mu, \Sigma]$$

3.8 Normal inverse Wishart distribution

normal inverse Wishart distribution是一個定義在$D \times 1$的向量 $\mu和$D \times D$的矩陣 \Sigma$上的連續分佈。他適合用來描述 multivariate normal distribution 的參數的不確定性。normal inverse Wishart distribution有四個參數,$\alpha, \Psi, \gamma, \delta$,其中$\alpha$和$\gamma$為正純量,$\delta$為$D \times 1$的向量,$\Psi$為$D \times D$的正定矩陣。機率密度函數如下
$$\operatorname{Pr}(\boldsymbol{\mu}, \boldsymbol{\Sigma})=\frac{\gamma^{D / 2}|\boldsymbol{\Psi}|^{\alpha / 2} \exp \left[-0.5\left(\operatorname{Tr}\left[\boldsymbol{\Psi} \boldsymbol{\Sigma}^{-1}\right]+\gamma(\boldsymbol{\mu}-\boldsymbol{\delta})^T \boldsymbol{\Sigma}^{-1}(\boldsymbol{\mu}-\boldsymbol{\delta})\right)\right]}{2^{\alpha D / 2}(2 \pi)^{D / 2}|\boldsymbol{\Sigma}|^{(\alpha+D+2) / 2} \Gamma_D[\alpha / 2]}$$
其中$\Gamma_D[\bullet]$是multivariate gamma function and而$\operatorname{Tr}[\Psi]$回傳矩陣的迹(trace)。更簡單的寫法如下
$$Pr(\mu, \Sigma) = NormInvWish_{\mu, \Sigma}[\alpha, \Psi, \gamma, \delta]$$
很難將normal inverse Wishart distribution分佈可視化,但很容易抽樣並檢查樣本:每個樣本是一個常態分佈的平均值和協方差。

3.9 Conjugacy

在前面幾節有提到有些distribution可以呈現另一個distribution參數的機率分布。因為這些分布之間都有Conjugacy的關係。例如beta distribution conjugate Bernoulli distribution。Dirichlet conjugate categorical。當我們將兩個有Conjugacy的distribution相乘,結果將與共軛具有相同形式的新分佈成正比。例如
$$ Bern_x[\lambda]\times Beta_\lambda[\alpha, \beta] = \kappa(x, \alpha, \beta)\times Beta_\lambda[~{\alpha}, ~{\beta}]$$
在這裡$\kappa$是一個對於所關心的變量$\lambda$來說是常數的縮放因子。
conjugate重要的原因是因為在學習(擬合分佈)和評估模型(評估新數據在擬合分佈下的概率)的過程中,我們需要對分佈進行乘法運算。共軛關係意味著這些乘積都可以以封閉形式整潔地計算得出。

CH4 擬和機率模型

這章節的目標是將機率模型擬和到資料$${x_i}^I_{i=1}$$,這個過程稱為學習learning,目標是找到模型的參數組$$\theta$$。此外我們也學習如何用學習後的模型對新的數據$$x^*$$計算出他的機率,這過程evaluating the predictive distribution。
在這章我們探討三種方法

  1. maximum likelihood
  2. maximum a posteriori
  3. Bayesian approach

maximum likelihood

maximum likelihood目標是要找到一組參數$$\hat{\theta}$$使得資料$${x_i}^I_{i=1}$$出現的機率最大化。而計算likelihood function 在單一個資料點$$x_i$$,也就是$$Pr(x_i|\theta)$$,我們只需在$$x_i$$處評估概率密度函數。
假設每一個資料點都是從分布中獨立被取出的,那麼所有資料點的機率$$Pr(x_{i…I}|{\theta})$$就是所有單獨資料點代入likelihood function的乘積。因此要maximum likelihood可以寫成

$$\hat{\theta} = argmax_{\theta}[Pr(x_{i…I}|{\theta})] =argmax_{\theta} [\prod_{i=1}^I Pr(x_i|\theta)]$$

式子中的$$argmax_{\theta}f[\theta]$$是指找到一組參數組$${\theta}$$,讓$$argmax_{\theta}f[\theta]$$最大化。

要計算新資料$$x^*$$的predictive distribution,只需將新資料和我們找到的參數組帶入likelihood function,計算出機率即可。

Maximum Posteriori

在Maximum Posteriori(MAP)擬合中,我們引入有關參數$$\theta$$的先驗信息。由於我們可能對可能的參數值有所了解。例如,在時間序列中,時間 t 的參數值可以告訴我們在時間 t + 1 可能的值,於是將此信息將被編碼在先驗分佈中。
如同它的名稱,Maximum Posteriori方法將會找到一組參數組$$\hat{\theta}$$,使得Posteriori probability $$Pr(\theta|x_{i…I})$$最大化。

$$\hat{\theta} = argmax_{\theta}[Pr(\theta|x_{i…I})]$$

$$ =argmax_{\theta} [\frac{Pr(x_{i…I}|\theta)Pr(\theta)}{Pr(x_{i…I})}]$$

$$ =argmax_{\theta} [\frac{\prod_{i=1}^I Pr(x_i|\theta)Pr(\theta)}{Pr(x_{i…I})}]$$

在這裡第一行可以由貝葉斯定理推得第二行。另外由於我們找的是參數組$$\theta$$的最大值,所以分母的常數$$Pr(x_{i…I})$$是可以忽略的。因此我們可以將式子簡化成

$$\hat{\theta} = argmax_{\theta} [\prod_{i=1}^I Pr(x_i|\theta)Pr(\theta)]$$

我們可以發現他其實跟maximum likelihood只差了一項先驗分佈$$Pr(\theta)$$,所以maximum likelihood其實是maximum posteriori的一種特例,也就是maximum likelihood是$$Pr(\theta)$$是一個常數的情況。

Bayesian Approach

Bayesian Approach裡,我們不再把參數$$\theta$$當作一個常數,而是承認一件顯而易見的事實,參數組$$\theta$$可能不是唯一的。因此我們嘗試利用貝葉思定裡計算$$Pr(\theta|x_{i…I})$$,也就是在資料$$x_{i…I}$$出現的情況下,參數組$$\theta$$的機率分佈。

$$Pr(\theta|x_{i…I}) = \frac{\prod_{i=1}^I Pr(x_i|\theta)Pr(\theta)}{Pr(x_{i…I})}$$

而要驗證Bayesian Approach會比較複雜一點,因為跟前面不一樣我們沒有一個固定的參數組$$\theta$$可以帶入並且計算出機率,在這裡我們必須可能模型的機率分布。因此我們用以下方法計算。

$$Pr(x^*|x_{i…I}) = \int Pr(x^*|\theta)Pr(\theta|x_{i…I})d\theta$$

這條式子可以用以下方式解讀:
$Pr(x^*|\theta)$ 是對於給定的參數組 $$\theta$$,$$x^*$$出現的機率,因此這個積分可以視為使用不同的參數$$\theta$$所做出預測的加權總和,其中權重是由參數的posterior probability distribution $$Pr(\theta|x_{i…I})$$ 來決定的(代表我們對不同參數正確性的信心程度)。

統一三種方法的 predictive density calculations

如果我們將maximum likelihood, maximum posteriori 的參數的機率分布視為一種特例,也就是maximum likelihood, maximum posteriori參數分布全部集中在$$\hat{\theta}$$。正式的說法就是參數分布為一個以$$\hat{\theta}$$為中心的delta function。一個delta function $$\delta[z]$$是一個函式他的積分為1,而且在除了中心點z以外的任何地方都是0。我們將剛才predictive density帶入delta function,可以得到以下結果。

$$Pr(x^*|x_{i…I}) = \int Pr(x^*|\theta)\delta[\theta - \hat{\theta}]d\theta$$

$$= Pr(x^*|\hat{\theta})$$

ch4.4範例一

下面範例我們考慮擬和一個univariate normal model到一組數據$${x_i}^I_{i=1}$$。
首先univariate normal model的probability density function為

$$Pr(x|\mu,\sigma^2) = Norm_x[\mu,\sigma^2] = \frac{1}{\sqrt{2\pi\sigma^2}}exp[-\frac{(x-\mu)^2}{2\sigma^2}]$$

他有兩個參數平均$$\mu$$和變異數$$\sigma^2$$,首先我們從一個平均為1,變異數為1的normal distribution中取出$$I$$個數據$$x_{1…I}$$,我們的目標是利用前面的三種方法擬和抽取出來的數據集。

方法一Maximum likelihood estimation

對於觀測到的數據$Pr(x_{1…I}|\mu,\sigma^2)$$來說,參數 $${\mu,\sigma^2}$$ 的概率(likelihood) Pr(x_1…I |µ, σ^2) 是通過對每個數據點分別評估概率密度函數,然後取乘積得到的。

$$Pr(x_{1…I}|\mu,\sigma^2) = \prod_{i=1}^I Pr(x_i|\mu,\sigma^2)$$

$$= \prod_{i=1}^I Norm_{x_i}[\mu,\sigma^2]$$

$$= \frac{1}{\sqrt{2\pi\sigma^2}}exp[-\sum_{i=1}^I \frac{(x_i-\mu)^2}{2\sigma^2}]$$

很明顯的某些$${\mu,\sigma^2}$$參數組會使得likelihood比其他參數組還高。而且我們可以在二維平面視覺化各種參數組的likelihood,我們將以平均$$\mu$$和變異數$$\sigma^2$$為軸,而Maximum likelihood的解答就是在圖形的頂點(圖4.2)。也就是以下式子的解答。

$$\hat{\mu}, {\hat\sigma}^2 = argmax_{\mu,\sigma^2} [Pr(x_{1…I}|\mu,\sigma^2)]$$

理論上我們可以藉由微分$$Pr(x_{1…I}|\mu,\sigma^2)$$來求解,但是實際上$$Pr(x_{1…I}|\mu,\sigma^2)$$太複雜,因此我們可以將$$Pr(x_{1…I}|\mu,\sigma^2)$$取log,因為log是一個單調遞增函數,經過轉換後的$$Pr(x_{1…I}|\mu,\sigma^2)$$的最大值會在相同的地方。代數上,對數把各個數據點的可能性的乘積轉化為它們的總和,因此可以將每個數據點的貢獻分離出來。於是Maximum likelihood可以用以下方式計算。

$$\hat{\mu}, {\hat\sigma}^2 = argmax_{\mu,\sigma^2} [\sum_{i=1}^I log [Norm_{x_{i}}[\hat{\mu}, {\hat\sigma}^2]]]$$

$$= argmax_{\mu,\sigma^2} [-0.5Ilog[2\pi] - 0.5Ilog(\sigma^2) - 0.5\sum_{i=1}^I\frac{(x_i-\mu)^2}{\sigma^2}]$$

接著對對 log likelihood function L 進行$$\mu$$的偏微分。
$$\frac{\partial L}{\partial \mu} = \sum_{i=1}^I \frac{(x_i-\mu)}{\sigma^2}$$
$$= \frac{\sum_{i=1}^I x_i}{\sigma^2} - \frac{I\mu}{\sigma^2} = 0$$

整理後可以得到
$$\hat{\mu} = \frac{\sum_{i=1}^I x_i}{I}$$

利用類似的方利用類似的方法可以得到變異數$$\sigma^2$$的解答為
$$\hat{\sigma}^2 = \frac{\sum_{i=1}^I (x_i-\hat{\mu})^2}{I}$$

Least squares fitting

另外需要注意的是,很多文獻都是以最小二乘法來討論擬合的。我們使用maximum
likelihood來擬和正態分佈的平均參數$$\mu$$。將前面式子將$$\sigma^2$$視為常數可以得到
$$\hat{\mu} = argmax_{\mu,\sigma^2} [-0.5Ilog[2\pi] - 0.5Ilog(\sigma^2) - 0.5\sum_{i=1}^I\frac{(x_i-\mu)^2}{\sigma^2}]$$

$$= argmax_{\mu,\sigma^2} [-\sum_{i=1}^I(x_i-\mu)^2]$$

$$= argmax_{\mu,\sigma^2} [\sum_{i=1}^I(x_i-\mu)^2]$$

也就是說least squares fitting和使用maximum likelihood 估計常態分布的均值參數是等價的。

log likehood好處

likelihood function可以看到是許多乘法的結果,因此微分很不好算。
由於log函數是monotonic的關係,所以likelihood function經過log轉換後的最大值會在相同的地方,而且經過log轉換後乘法變成加法,因此微分變得容易許多。

https://bookdown.org/dereksonderegger/571/13-maximum-likelihood-estimation.html#likelihood-function

https://math.stackexchange.com/questions/3053131/why-are-the-local-extrema-of-a-log-transformed-function-equal-to-local-extrema-o

https://towardsdatascience.com/log-loss-function-math-explained-5b83cd8d9c83

方法二Maximum posteriori estimation

根據前面的定義,Maximum posteriori estimation的cost function為
$$\hat{\mu}, {\hat\sigma}^2 = argmax_{\mu,\sigma^2} [\prod_{i=1}^I Pr(x_i|\mu,\sigma^2)Pr(\mu,\sigma^2)]$$

$$= argmax_{\mu,\sigma^2} [\prod_{i=1}^I Norm_{x_i}[\mu,\sigma^2]NormInvGam_{\mu,\sigma^2}[\alpha,\beta,\gamma,\delta]]$$

在這裡我們選擇我們選擇了normal inverse gamma prior,其參數為α,β,γ,δ(圖4.4),因為它與normal distribution共軛。

在式子中的prior如下
$$Pr(\mu,\sigma^2) = \frac{\sqrt{\gamma}}{\sigma\sqrt{2\pi}}\frac{\beta^{\alpha}}{\Gamma(\alpha)}(\frac{1}{\sigma^2})^{\alpha + 1}exp[-\frac{2\beta + \gamma(\delta-\mu)^2}{2\sigma^2}]$$

而posterior distribution 與likelihood和prior的乘積成正比(見圖4.5),在與數據一致且先驗可信的區域具有最高的機率密度。

而跟maximum likelihood一樣我們利用把式子取log來計算最大值。式子如下
$$\hat{\mu}, {\hat\sigma}^2 = argmax_{\mu,\sigma^2} [\sum_{i=1}^I log [Norm_{x_{i}}[\mu, {\sigma}^2]] + log [NormInvGam_{\mu,\sigma^2}[\alpha,\beta,\gamma,\delta]]]$$

要找到MAP(maximum a posteriori)我們將式子拆成兩段並且分別對$$\mu$$和$$\sigma^2$$做偏微分。式子如下
$$\hat{\mu} = \frac{\sum_{i=1}^I x_i + \gamma\delta}{I + \gamma}$$

$$\hat{\sigma}^2 = \frac{\sum_{i=1}^I (x_i-\hat{\mu})^2 + 2\beta + \gamma(\delta-\hat{\mu})^2}{I + 3 + 2\alpha}$$

而平均數$\hat{\mu}$的解答可以進一步簡化
$$\hat{\mu} = \frac{I\bar{x} + \gamma\delta}{I + \gamma}$$

這式子是兩項的加權平均值,第一項是資料的平均$\bar{x}$並且以訓練樣本的數量$I$為權重,第二項是先驗分佈的參數$\delta$並且以先驗分佈的參數$\gamma$為權重

這裡給我們一些MAP(maximum a posteriori)的洞察。

  1. 當資料數量越多時,MAP的解會越接近資料平均(也就是ML(Maximum likelihood)的解)
  2. 當資料數量少一些的時候,MAP的解會在ML和prior的中間
  3. 當完全沒有資料的時候,MAP的解就是proir

方法三Bayesian estimation

在Bayesian estimation我們利用Bayesian定理計算參數的posterior distribution。
$$Pr(\mu, \sigma^2 | x_{1…I}) = \frac{\prod_{i=1}^I Pr(x_i|\mu,\sigma^2)Pr(\mu,\sigma^2)}{Pr(x_{1…I})}$$

$$= \frac{\prod_{i=1}^I Norm_{x_i}[\mu,\sigma^2]NormInvGam_{\mu,\sigma^2}[\alpha,\beta,\gamma,\delta]}{Pr(x_{1…I})}$$

$$= \frac{\kappa NormInvGam_{\mu,\sigma^2}[~\alpha,~\beta,~\gamma,~\delta]}{Pr(x_{1…I})}$$

在這裡likelihood和prior有共軛關係,而$\kappa$是 associated constant。Normal likelihood和normal inverse gamma prior的乘機產生出一個關於$\mu$$\sigma^2$的posterior distribution。其參數如下

$$~\alpha = \alpha + \frac{I}{2}$$

$$~\gamma = \gamma + I$$

$$~\delta = \frac{\gamma\delta + \sum_{i=1} x_i}{\gamma + I}$$

$$~\beta = \frac{\sum_{i=1} x_i^2}{2} + \beta + \frac{\gamma\delta^2}{2} - \frac{(\gamma\delta + \sum_{i=1} x_i)^2}{2(\gamma + I)}$$

需要注意的是,後驗分布(式4.20左側)必須是一個有效的概率分布,總和為一,因此共軛乘積中的常數 κ 和右側的分母必須完全抵消,才能得到:

$$Pr(\mu, \sigma^2 | x_{1…I}) = NormInvGam_{\mu,\sigma^2}[~\alpha,~\beta,~\gamma,~\delta]$$

現在我們可以看到conjugate prior的好處,我們保證可以得到關於參數的後驗分布的封閉形式表達式。

  • Predictive density
    跟maximum likelihood和MAP(maximum a posteriori)不一樣是,Bayesian estimation計算Predictive density的方式是我們計算每個可能參數集的預測值的加權平均值,其中加權由參數的後驗分布給出。

$$Pr(x_*|x_{1…I}) = \int \int Pr(x^*|\mu,\sigma^2)Pr(\mu,\sigma^2|x_{1…I})d\mu d\sigma^2$$

$$= \int \int Norm_{x^*}[\mu,\sigma^2]NormInvGam_{\mu,\sigma^2}[~\alpha,~\beta,~\gamma,~\delta]d\mu d\sigma^2$$

$$= \int \int \kappa(x^*, ~\alpha, ~\beta, ~\gamma, ~\delta)NormInvGam_{\mu,\sigma^2}[~\alpha,~\beta,~\gamma,~\delta]d\mu d\sigma^2$$

這裡我們又再次用到conjugate relation。積分包含一個與$\mu$和$\sigma^2$無關的常數乘以一個概率分布。將常數移到積分號外,可以得到:

$$Pr(x^*|x_{1…I}) = \kappa(x^*, ~\alpha, ~\beta, ~\gamma, ~\delta)\int \int NormInvGam_{\mu,\sigma^2}[~\alpha,~\beta,~\gamma,~\delta]d\mu d\sigma$$

因為pdf的積分為1,所以

$$= \kappa(x^*, ~\alpha, ~\beta, ~\gamma, ~\delta)$$

常數可以表示為:

$$\kappa(x^*, ~\alpha, ~\beta, ~\gamma, ~\delta) = \frac{1}{\sqrt{2\pi}}\frac{\sqrt{~\gamma}~\beta^{~\alpha}}{\sqrt{\v\gamma}\v\beta^{\v\alpha}}\frac{\Gamma(\v\alpha)}{\Gamma(~\alpha)}$$

其中

$$\v\alpha = ~\alpha + \frac{I}{2}$$

$$\v\gamma = ~\gamma + I$$

$$\v\beta = \frac{\sum_{i=1} x_i^2}{2} + ~\beta + \frac{~\gamma~\delta^2}{2} - \frac{(~\gamma~\delta + \sum_{i=1} x_i)^2}{2(~\gamma + I)}$$

在這裡我可以看到第二個使用conjugate prior的好處:可以計算積分,所以我們得到一個很好的封閉形式表達式來預測密度。

在有大量訓練數據的情況下,貝葉斯預測分布和最大事後概率(MAP)預測分布非常相似,但當數據量減少時,貝葉斯預測分布的尾部明顯更長。這是貝葉斯解決方案的典型特徵:它們在預測時更加中庸(不那麼確定)。在 MAP 的情況下,錯誤地承諾一個 µ 和 σ^2 的估計值導致我們對未來的預測過於自信。

ch4.5範例一

第二個範例我們考慮離散的資料${x_i}^I_{i=1}$其中$x_i \in {1, 2, …, 6}$。這種表達方式可以用來表達一個不均勻的骰子所出現的點數資料。我們將使用 categorical distribution來描述這些資料。
$$ Pr(x=k|\lambda_{1…K}) = \lambda_k $$

利用Maximum posteriori estimation和maximum a posteriori來推測六個參數${\lambda_k}^6_{k=1}$。而Bayesian方法則計算參數的probability distribution

4.5.1 Maximum Likelihood

為了找到Maximum Likelihood,我們要最大化每一個資料點的likelihood相乘的乘積

$$ \hat \lambda_{1…6} = argmax_{\lambda_{1…6}}[\prod^I_{i=1}Pr(x_i|\lambda_{1…6})] \qquad s.t. \sum_k \lambda_k = 1$$

$$ = argmax_{\lambda_{1…6}}[\prod^I_{i=1} Cat_{x_i}[\lambda_{1…6}]] \qquad s.t. \sum_k \lambda_k = 1$$

$$ = argmax_{\lambda_{1…6}}[\prod^6_{k=1}\lambda^{N_k}_k] \qquad s.t. \sum_k \lambda_k = 1$$

其中$N_k$代表在訓練資料中觀察到k的總次數。跟前一個例子一樣,利用log probability來協助尋找最大值。

$$ L = \sum^6_{k=1}N_klog[\lambda_k] + \nu(\sum^6_{k=1}\lambda_k - 1) $$
在這個式子中利用了 Lagrange multiplier $\nu$ 來達成$\sum^6_{k=1} \lambda_k = 1$的限制。接著我們對L對於$\lambda_k$和$\nu$微分並且將導數設為0,可以得到下式。
$$ \hat \lambda_k = \frac{N_k}{\sum^6_{m=1}N_m} $$
換句話說$\lambda_k$和觀察到k的次數成正比。

4.5.2 Maximum a posteriori

要找到 maximum a posteriori 首先要定義一個prior。我們選擇和categorical likelihood共軛的 Dirichlet distribution。式子如下。
$$ \hat \lambda_{1…6} = \underset{\lambda_{1…6}}{\mathrm{argmax}}[\prod^I_{i=1}Pr(x_i|\lambda_{1…6})Pr(\lambda_{1…6})]$$

$$ = \underset{\lambda_{1…6}}{\mathrm{argmax}}[\prod^I_{i=1}Cat_{xi}[\lambda_{1…6}]Dir_{\lambda_{1…6}}[\alpha_{1…6}]]$$

$$ = \underset{\lambda_{1…6}}{\mathrm{argmax}}[\prod^6_{k=1}\lambda_k^{N_k}\prod^6_{k=1}\lambda_k^{\alpha_k-1}]$$

$$ = \underset{\lambda_{1…6}}{\mathrm{argmax}}[\prod^6_{k=1}\lambda^{N_k + \alpha_k -1}_k] $$

接著和 maximum likelihood一樣利用 Lagrange multiplier來達成$\sum^6_{k=1} \lambda_k = 1$的限制。Maximum a posteriori推估參數的式子如下。
$$ \hat \lambda_k = \frac{N_k + \alpha_k -1}{\sum^6_{m=1}(N_m + \alpha_m - 1)}$$
其中$N_k$代表訓練資料中k出現的次數,這裡注意到如果$\alpha_k$全部設為1的時候,式子就變成跟 maximum likelihood的解一樣。

4.5.3 Bayesian Approach

在Bayesian approach中我們計算對於參數的posterior。
$$Pr(\lambda_1 … \lambda_6|x_{1…I}) = \frac{\prod^I_{i=1}Pr(x_i|\lambda_{1…6})Pr(\lambda_{1…6})}{Pr(x_{1…I})}$$
$$=\frac{\prod^I_{i=1}Cat_{x_i}[\lambda_{1…6}]Dir_{\lambda_{1…6}}[\alpha_{1…6}]}{Pr(x_{1…I})}$$
$$=\frac{\kappa(\alpha_{1…6}, x_{1…I})Dir_{\lambda_{1…6}}[~\alpha_{1…6}]}{Pr(x_{1…I})}$$
$$=Dir_{\lambda_{1…6}}[~\alpha_{1…6}]$$

其中$~\alpha_k=N_k+\alpha_k$。我們再次利用共軛關係,以產生具有與先驗分布相同形式的後驗分布。為了確保左邊的概率分布有效,常數κ必須再次與分母相抵消。

Predictive Density

Maximum Likelihood和Maximum a posteriori計算Predictive Density的方式就是把新的資料點代入求出來的參數。注意到如果prior是uniform(也就是$\alpha_{1…6}=1$),則MAP和ML會完全一樣,而預測結果會和觀察資料的頻率一樣。
對於Bayesian Approach,我們計算每個可能的參數集的預測的加權平均值,其中加權由參數的後驗分布給出。
$$Pr(x^*|x_{1…I})=\int Pr(x^*|\lambda_{1…6})Pr(\lambda_{1…6}|x_{1…I})d\lambda_{1…6}$$
$$=\int Cat_{x^*}[\lambda_{1…6}]Dir_{\lambda_{1…6}}[~\alpha_{1…6}]d\lambda_{1…6}$$
$$=\int \kappa(x^*, ~\alpha_{1…6})Dir_{\lambda_{1…6}}[\breve\alpha_{1…6}]d\lambda_{1…6}$$
$$=\kappa(x^*, ~\alpha_{1…6})$$

在這裡,我們再次利用共軛關係,得到一個常數乘以一個概率分布,而積分則簡單地等於該常數,因為概率分布的積分為一。
$$Pr(x^*=k|x_{1…I})=\kappa(x^*, ~\alpha_{1…6})=\frac{N_k+\alpha_k}{\sum^6_{j=1}(N_j+\alpha_j)}$$

再次強調貝葉斯預測密度比 ML/MAP解更不自信。特別是,儘管在訓練數據中從未觀察到$x^*=4$這個值,但它並未將觀察到該值的概率分配為零。這是合理的;僅僅因為在15次觀察中我們並未抽到4這個數字,並不意味著我們將永遠不會看到它。我們可能只是運氣不好。貝葉斯方法將這一點納入考慮,並給予這個類別一個小的概率。

CH5 The normal distribution

回顧第三章 multivariate normal distribution有兩個參數:平均$\mu$和變異數$\Sigma$。Proability density function為:
$$Pr(x) = \frac{1}{(2\pi)^{D/2}|\Sigma|^{1/2}}exp[-0.5(x - \mu)^T\Sigma^{-1}(x-\mu)]$$

或是簡短的
$$Pr(x)=Norm_x[\mu, \Sigma]$$

5.1 covariance矩陣的種類

covariance矩陣有三種類型

  1. spherical:對角元素全部都是同樣的正數,而且除了對角元素以外都是0
  2. diagonal:對角元素數字不一樣且均為正數,而且除了對角元素以外都是0
  3. full covariances:所有元素都不為0的正數,此外矩陣式對稱的也就是$\sigma_{12}^2=\sigma_{21}^2$

對於bivariate的情況spherical covariances產生出圓的圖形,Diagonal covariances產生出橢圓的圖形,而且橢圓的主軸和座標軸重疊,Full covariances產生出橢圓但是他的主軸方向可以是任意方向。
如果為 covariance 為 spherical 或 diagonal,則個別的變數均為獨立的,也就是
$$Pr(x_1, x_2) = Pr(x_1)Pr(x_2)$$

5.2 Decomposition of covariance

利用幾何的觀點可以Decomposition full covariances matric。想像一下有一個新的座標軸對齊full covariances所產生的橢圓圖形的兩個主軸,在這個新的座標軸上觀察這這個圖形,他的covariance matric就變成diagonal covariance matric。所以用座標轉換的觀點,我們可以得到新坐標軸上的diagonal covariance matric和原作標軸上full covariances matric的關係。其中R為座標旋轉矩陣。
$$\Sigma_{full}=R^T\Sigma’{diag}R$$
拆解過後,$\Sigma’
{diag}$裡面隱含了varience,也就是在新座標軸上圖形的寬度資訊,因此可以再利用eigen-decomposition得在空間中哪一個方向對圖形比較重要。

5.3 Linear transformations of variables

multivariate normal pdf 經過線性轉換後,依然是一個multivariate normal,而轉換後的multivariate normal他的mean和covarience會和轉換前的multivariate normal有關,也和線性轉換方程式的$y=Ax+b$的係數和常數有關。
這個關係讓我麼可以簡化抽樣normal distribitaion的過程。假設想要從mean為$\mu$和covarianc為$\Sigma$的normal distribitaion抽樣,首先我們可以先從一個standart normal distribution抽樣一個點(mean $\mu=0$, covariance $\Sigma=I$),接著套用Linear transformations $y=\Sigma^{1/2}x+\mu$

影音串流如何運作的

Live Streaming工作流程

首先Live Streaming起頭於將大量的影音檔壓縮以便傳送,我們利用encoder將原始影音檔用指定的codec(例如H.264)進行壓縮。經過壓縮後gigabytes大小的資料被縮小成megabytes大小。

經過壓縮的資料encoder會把資料放入media container,這動作稱為打包。media container的目的是為了讓其他人知道這些被壓縮的資料是用什麼codec壓縮的。media container另一個重要功能是紀錄用來同步聲音和影像的資訊。常見的media container格式有mp4。

被打包過後的資料透過特定的傳輸協議protocol在網路上傳送,常見的協議有RTMP、HLS。

參考:
https://pjchender.blogspot.com/2019/07/protocol-of-media-video-and-audio.html
https://yarslv.com/codecs-and-containers-explained/#what-is-a-container
https://www.wowza.com/blog/complete-guide-to-live-streaming

Data exploration for Object Detection

資料整體的品質

首先可以對整個資料集做一個初步的檢查,包含

  1. 瀏覽整份資料集
  2. 確認沒有嚴重有誤的照片(例如全黑的照片)
  3. 確認所有照片都能夠被電腦讀取(以免訓練到一半程式被中斷)

照片尺寸和深寬比

對於整份資料集,統計所有照片的深寬比和尺寸十分重要,這些將會影響anchor size 和 ratios。通常有三種情況

  1. 照片大小和深寬比都一樣:
    只需要決定縮放比例
  2. 照片大小和深寬比不同但差異不大,深寬比都介於0.7~1.5:
    可以non-destructive resize(也就是不改變深寬比的縮放) -> padding
  3. 照片大小和深寬比差異很大

參考:
https://neptune.ai/blog/data-exploration-for-image-segmentation-and-object-detection

makefile教學

Makefiles用途

用來決定大型程式需要被重新編譯的部分。

第一個範例

首先安裝make,並且將下面程式放到名稱為Makefile的檔案裡面。注意Makefile必須要用TAB來縮排而不是用空白鍵。

1
2
hello:
echo "Hello, World"

接下來在Makefile所在的資料夾位置下make指令

1
2
3
$ make
echo "Hello, World"
Hello, World

Makefile語法

Makefile是由許多的規則組合而成,每一條則看起來如下

1
2
3
4
targets: prerequisites
command
command
command
  • targets是檔案名稱,用空白建作為分隔。通常每個rule只有一個target
  • command是產生targets的一系列的步驟。command以Tab作為開頭,而不是空白鍵。
  • prerequisites也是檔案名稱,以空白鍵作為分隔,這些是指令開始製作target前必須存在的檔案,因此這些檔案也可以稱為dependencies

Make基礎元素

1
2
3
hello:
echo "Hello, World"
echo "This line will always print, because the file hello does not exist."

以這個範例來說,

  • 有一個名為hello的target
  • 這個target有兩個command
  • 這個target沒有prerequisites
    接下來我們執行make hello,由於hello檔不存在,所以下面的指令會被執行。如果hello檔存在,make就不會做任何事。
    特別注意在這裡hello同時代表target以及檔案,因為通常來說下面的command執行的目的就是要生成target。
    下面舉一個編譯c語言的例子,首先我們製作一個blah.c檔。
    1
    2
    // blah.c
    int main() { return 0; }
    接下來製作另一個Makefile
    1
    2
    blah:
    cc blah.c -o blah
    接下來執行make,因為我們每有在第一個參數指定目標target,所以第一個target會被執行。第一次執行的時blah檔會被生成,如果再執行一次就會出現make: 'blah' is up to date的訊息,因為blah檔已經存在了。但是這有一個問題,如果我們更動blah.c檔案,make並不會重新編譯!!
    要解決這個問題就必須要增加prerequisite。
    1
    2
    blah: blah.c
    cc blah.c -o blah
    我們增加了blah的prerequisite,這時候make就除了檢查blah有沒有存在以外,還會另外去檢查blah.c是不是比blah還新。這裡我們可以看到make是利用系統的時間戳來判定blah.c有沒有被修改過,因此如果修改blah.c之後又把blah.c時間戳改回修改前的時間,那make就會以為blah.c沒有被修改過。

Make clean

clean常用來清除make產生的檔案,但是他並不是make的關鍵字。可以用make clean清除make產生的檔案。我們可以在makefile中編寫clean要清除的檔案。注意clean這個target除非你用make clean指令,不然他不會被執行。

1
2
3
4
5
some_file: 
touch some_file

clean:
rm -f some_file

Makefile使用相對路徑

1
rootdir = $(realpath .)

https://stackoverflow.com/a/3342259

Variables

變數Variables只能夠是字串,並且用:=賦值。對make來說單引號跟雙引號並意義,make會把他當成字元來處理。因此賦值的時候不需要加引號。
下面範例使用Variables

1
2
3
4
5
6
7
8
9
10
11
12
files := file1 file2
some_file: $(files)
echo "Look at this variable: " $(files)
touch some_file

file1:
touch file1
file2:
touch file2

clean:
rm -f file1 file2 some_file

要引用變數可以用${}或是$()

1
2
3
4
5
6
7
8
x := dude

all:
echo $(x)
echo ${x}

# Bad practice, but works
echo $x

Targets

all target

可以用來依次產生所有需要的target,通常會放在第一個target的位置,如此一來只要下make指令就可以生成所有target。

1
2
3
4
5
6
7
8
9
10
11
all: one two three

one:
touch one
two:
touch two
three:
touch three

clean:
rm -f one two three

多個target

當一個rule有多個target的時候,底下的command就會針對每一個target都跑一次
$@就是一個有target名稱的automatic variable,下面範例就可以用$@看看現在command正在執行的是哪一個target

1
2
3
4
5
6
7
8
9
all: f1.o f2.o

f1.o f2.o:
echo $@
# Equivalent to:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o

Automatic Variables and Wildcards

* 萬用字元

在cmake中*%在cmake中都是萬用字元,但是它們代表的意義不一樣。*最好要包裝在wildcard 萬用字符函式中。否則可能會常陷入下面常見的陷阱。

  1. 陷阱: *不能直接在變量定義中使用
  2. 陷阱: 當*未匹配任何文件時,它會保持不變(除非在萬用字符函數(wildcard)中運行)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    thing_wrong := *.o # Don't do this! '*' will not get expanded
    thing_right := $(wildcard *.o)

    all: one two three four

    # Fails, because $(thing_wrong) is the string "*.o"
    one: $(thing_wrong)

    # Stays as *.o if there are no files that match this pattern :(
    two: *.o

    # Works as you would expect! In this case, it does nothing.
    three: $(thing_right)

    # Same as rule three
    four: $(wildcard *.o)

% 萬用字元

%非常有用,但由於它可以在各種情況下使用,因此有些令人困惑。

  1. 在“匹配”模式下使用時,它會在字符串中匹配一個或多個字符。此匹配稱為stem。
  2. 在“替換”模式下使用時,它會取出匹配的stem,並將其替換為一個字符串。
  3. 在規則定義和某些特定函數中最常用。

Automatic Variables

這裡有完整的Automatic Variables表,下面只介紹常用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
hey: one two
# Outputs "hey", since this is the target name
# 印出目前的target
echo $@

# Outputs all prerequisites newer than the target
# 印出所有比target新的prerequisite
echo $?

# Outputs all prerequisites
# 印出所有的prerequisite
echo $^

touch hey

one:
touch one

two:
touch two

clean:
rm -f hey one two

Rules

Make的隱藏規則

CC CXX CFLAGS CXXFLAGS LDFLAGS LDLIBS這些變數是make的隱藏規則。

  • CC : 編譯C語言的編譯器; 預設是 cc
  • CXX : 編譯C++語言的編譯器; 預設是 g++
  • CFLAGS : 給C語言編譯器的額外參數
  • CXXFLAGS : 給C++語言編譯器的額外參數
  • CPPFLAGS : 給C/C++編譯器的額外參數
  • LDFLAGS : 給連結器的額外參數

下面範例使用隱藏規則

1
2
3
4
5
6
7
8
9
10
11
12
CC = gcc # 使用gcc來編譯C語言
CFLAGS = -g # 給gcc的額外參數,開啟debug模式

# 隱藏規則 #1: blah會由C連接器產生(即使我們的command沒有呼叫C連接器)
# 隱藏規則 #2: blah.o 會由c編譯器產生,因為blah.c存在(即使我們的command沒有呼叫c編譯器)
blah: blah.o

blah.c:
echo "int main() { return 0; }" > blah.c

clean:
rm -f blah*

Static Pattern Rules

他的語法如下

1
2
targets...: target-pattern: prereq-patterns ...
commands

這個語法的意思是,如果有一個target符合target-pattern(利用% wildcard),且它的所有prerequisite都符合prereq-patterns,那麼就會執行commands
例如我們可以改寫下面makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
objects = foo.o bar.o all.o
all: $(objects)

# These files compile via implicit rules
foo.o: foo.c
bar.o: bar.c
all.o: all.c

all.c:
echo "int main() { return 0; }" > all.c

%.c:
touch $@

clean:
rm -f *.c *.o all

改寫後如下,可以看到,我們把foo.o bar.o all.o的規則都合併成一個規則$(objects): %.o: %.c。首先foo.o符合%.o,且它的所有prerequisite都符合%.c,因此會執行%.o: %.c的規則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
objects = foo.o bar.o all.o
all: $(objects)

# These files compile via implicit rules
# Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
$(objects): %.o: %.c

all.c:
echo "int main() { return 0; }" > all.c

%.c:
touch $@

clean:
rm -f *.c *.o all

Static Pattern Rules and Filter

此外,我們可以使用filter函式來過濾掉不需要的檔案,後面會再講到函式這裡只是先展示如何跟函式搭配使用,在下面的範例我們使用了 .raw 和 .result 這兩個擴展名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c

all: $(obj_files)
# Note: PHONY is important here. Without it, implicit rules will try to build the executable "all", since the prereqs are ".o" files.
.PHONY: all

# Ex 1: .o files depend on .c files. Though we don't actually make the .o file.
$(filter %.o,$(obj_files)): %.o: %.c
@echo "target: $@ prereq: $<"

# Ex 2: .result files depend on .raw files. Though we don't actually make the .result file.
$(filter %.result,$(obj_files)): %.result: %.raw
@echo "target: $@ prereq: $<"

%.c %.raw:
@echo "touch $@ prereq: $<"
touch $@

clean:
rm -f $(src_files)

執行的結果如下,首先執行第一條規則all: $(obj_files)產生第一個target foo.result,並由$(filter %.result,$(obj_files)): %.result: %.raw產生foo.resultfoo.result的prerequisitfoo.raw%.c %.raw:產生。
可以看到,我們把foo.result bar.o lose.o的規則都合併成一個規則$(filter %.result,$(obj_files)): %.result: %.raw。首先foo.result符合%.result,且它的所有prerequisite都符合%.raw,因此會執行%.result: %.raw的規則。

1
2
3
4
5
6
touch foo.raw
target: foo.result prereq: foo.raw
touch bar.c
target: bar.o prereq: bar.c
touch lose.c
target: lose.o prereq: lose.c

Pattern Rules

我們可以將Pattern Rules視為兩種用法。

  • 自定義的implicit rules

    1
    2
    3
    # 自訂一個 pattern rule 將每一個.c檔編譯成.o檔
    %.o : %.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
  • 簡化版的static pattern rules

    1
    2
    3
    4
    # 定義一個沒有 prerequisites 的 pattern rule
    # 他將會在需要的時候產出一個空白的.c檔
    %.c:
    touch $@

    在這裡%代表任意非空白的字串。

Double-Colon Rules

Double-Colon Rules 很少被用到,他允許對同一個target定義多個規則,且這些規則可以是不同的commands。如以下範例

1
2
3
4
5
6
7
all: blah

blah::
@echo "hello"

blah::
@echo "hello again"

他的輸出是

1
2
hello
hello again

Commands and execution

指令回顯/禁用

在預設情況下,make會回顯每一個command,如果你不想要回顯,可以在command前面加上@,如下

1
2
3
all: 
@echo "This make line will not be printed"
echo "But this will"

命令執行

每一行命令都會在一個新的shell中執行,因此如果你想要在同一個shell中執行,可以使用分號;來連接命令,如下

1
2
3
4
5
6
7
8
9
10
11
all: 
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd`

# This cd command affects the next because they are on the same line
cd ..;echo `pwd`

# Same as above
cd ..; \
echo `pwd`

預設shell

預設情況下,make會使用/bin/sh來執行命令,如果你想要使用其他的shell,可以使用.SHELL來指定,如下

1
2
3
4
SHELL=/bin/bash

cool:
echo "Hello from bash"

$$符號

在Makefile中,$$代表一個$符號,如此一來,我們就可以在Makefile中使用bash或是sh的shell variable。在下面這個例子中特別注一一下 Makefile variables 和 Shell variables

1
2
3
4
5
6
7
make_var = I am a make variable
all:
# Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
sh_var='I am a shell variable'; echo $$sh_var

# Same as running "echo I am a make variable" in the shell
echo $(make_var)

-k-i-進行錯誤處理

執行make的時候使用-k參數可以讓make繼續執行,即使其中一個target失敗了。執行make的時候使用-i參數可以讓make忽略所有的錯誤。

在command前面加上-可以讓make忽略該command的錯誤,如下

1
2
3
4
one:
# This error will be printed but ignored, and make will continue to run
-false
touch one

打斷或是結束make

ctrl + c可以打斷或是結束make,他將會刪掉剛生成的target

遞迴使用 make

為了遞迴調用 Makefile,使用特殊的 $(MAKE) 代替 make,因為它將為您傳遞 make 標誌,並且不會受到這些標誌的影響。

當使用 $(MAKE) 來遞迴調用 Makefile 時,它將傳遞先前用於調用 make 的所有標誌和選項,以及在 Makefile 中定義的任何其他變量。這有助於確保在整個項目中使用相同的編譯選項和變量。同時,$(MAKE) 不會受到當前 make 的影響,這可以避免不必要的錯誤和不一致性。

1
2
3
4
5
6
7
8
new_contents = "hello:\n\ttouch inside_file"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)

clean:
rm -rf subdir

Export, environments, and recursive make

當make執行的時候,他會先把所有環境變數轉換成make的變數,例如下面範例假如我們先在shell設定環境變數shell_env_var

  1. 設定環境並且執行make
    1
    export shell_env_var='I am an environment variable'; make
  2. 執行下面的makefile
    1
    2
    3
    4
    5
    6
    all:
    # Print out the Shell variable
    echo $$shell_env_var

    # Print out the Make variable
    echo $(shell_env_var)
  • make的export指令可以把make的變數直接轉換成環境變數
    1
    2
    3
    4
    5
    shell_env_var=Shell env var, created inside of Make
    export shell_env_var
    all:
    echo $(shell_env_var)
    echo $$shell_env_var

如此一來當我們在make command呼叫make的時候,就可以利用export指令將變數傳遞給子make程式。下面範例中cooly變數會被傳遞到子資料夾內所執行的make的makefile裡面。這裡可以注意到,cooly變數在all target之前被定義,但是他還是可以被all target使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new_contents = "hello:\n\techo \$$(cooly)"

all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)

# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly

clean:
rm -rf subdir
  • .EXPORT_ALL_VARIABLES
    .EXPORT_ALL_VARIABLES可以直接把所有的make變數都轉換成環境變數
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    .EXPORT_ALL_VARIABLES:
    new_contents = "hello:\n\techo \$$(cooly)"

    cooly = "The subdirectory can see me!"
    # This would nullify the line above: unexport cooly

    all:
    mkdir -p subdir
    printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
    @echo "---MAKEFILE CONTENTS---"
    @cd subdir && cat makefile
    @echo "---END MAKEFILE CONTENTS---"
    cd subdir && $(MAKE)

    clean:
    rm -rf subdir

make的命令列選項

這裡有make的命令列選項,可以注意--dry-run, --touch, --old-file這幾個。
另外make可以一次接受多個target,例如make clean run test就會先執行clean,接著run和test。

變數part2

兩種賦值方法

  • 延遲賦值(lazy evaluation)=:

    1
    2
    3
    4
    5
    6
    7
    VAR = foo
    VAR2 = $(VAR)
    VAR = bar

    all:
    # 在此處 VAR2 的值將是 "bar",因為VAR2直到被真正使用展開
    echo $(VAR2)

    ?=這個符號可以為沒被設定過的店數設定值

    1
    2
    3
    4
    5
    6
    7
    one = hello
    one ?= will not be set #one被設定過了,所以沒作用
    two ?= will be set #two還沒被設定做,將設定值

    all:
    echo $(one)
    echo $(two)

    輸出如下

    1
    2
    3
    4
    echo hello
    hello
    echo will be set
    will be set
  • 立即賦值(immediate assignment):=:

    1
    2
    3
    4
    5
    6
    7
    VAR := foo
    VAR2 := $(VAR)
    VAR := bar

    all:
    # 在此處 VAR2 的值將是 "foo",因為VAR2在:=的時候就展開了
    echo $(VAR2)

    因此:=可以append variable,如果是=就會齣戲無窮迴圈錯誤

    1
    2
    3
    4
    5
    6
    7
    one = hello
    # 這段程式可以運行
    # one gets defined as a simply expanded variable (:=) and thus can handle appending
    one := ${one} there

    all:
    echo $(one)

    下面這段程式會出現無窮迴圈錯誤。

    1
    2
    3
    4
    5
    6
    one = hello
    # 注意這裡用的是 = ,會造成無窮迴圈錯誤
    one = ${one} there

    all:
    echo $(one)

空白

一行字起頭的空白會被make忽略掉,但是尾巴空白的不會,要在起頭加空白可以用$(nullstring),更精確地說其實未定義的變數都是empty string

1
2
3
4
5
6
7
8
9
with_spaces =   hello   # with_spaces has many spaces after "hello"
after = $(with_spaces)there

start_space = $(nullstring) hello

all:
echo "$(after)"
echo "$(start_space)"
echo $(nowhere) #這也會輸出empty string

append

+=可以用來append variable

1
2
3
4
5
foo := start
foo += more

all:
echo $(foo)

覆寫make命列列參數

override可以用來覆寫make命令列參數,例如下面這個例子,分別用make option_one=himake option_two=hi去執行,可以發現只有option_one會被覆寫

1
2
3
4
5
6
7
# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_two = not_override
all:
echo $(option_one)
echo $(option_two)

針對target設定變數

變數可以只設定給指定的target,例如下面例子,one只定義給all target

1
2
3
4
5
6
7
all: one = cool

all:
echo one is defined: $(one)

other:
echo one is nothing: $(one)

Pattern-specific variables

變數也可以指定義給特定的target patterns,例如下面例子,只有符合%.cpattern的target會被定義one

1
2
3
4
5
6
7
%.c: one = cool

blah.c:
echo one is defined: $(one)

other:
echo one is nothing: $(one)

Makefile判斷式

if/else

1
2
3
4
5
6
7
8
foo = ok

all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif

檢查變數是否為空

1
2
3
4
5
6
7
8
9
10
nullstring =
foo = $(nullstring) # end of line; there is a space here

all:
ifeq ($(strip $(foo)),)
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
echo "nullstring doesn't even have spaces"
endif

檢查變數是否被定義

1
2
3
4
5
6
7
8
9
10
bar =
foo = $(bar)

all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "but bar is not"
endif

$(MAKEFLAGS)

下面範例展示如何使用 findstringMAKEFLAGS 測試 make flag。分別用make指令和make -i指令執行下面makefile

1
2
3
4
5
all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
echo "i was passed to MAKEFLAGS"
endif

Functions

First Functions

函式主要用來處理文字。呼叫函式的方法有$(fn, arguments)${fn, arguments},而make也內建許多函式。例如subst替換掉文字。

1
2
3
bar := ${subst not, totally, "I am not superman"}
all:
@echo $(bar)

而如果你相替換掉空白或是逗號,可以利用變數。

1
2
3
4
5
6
7
8
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))

all:
@echo $(bar)

特別注意到不要在逗號和下一個參數之間留空白,因為它會被視為文字。

1
2
3
4
5
6
7
8
9
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))

all:
# Output is ", a , b , c". Notice the spaces introduced
@echo $(bar)

字串替換

函式$(patsubst pattern,replacement,text)的功能如下。

1
2
3
4
5
6
7
8
9
10
11
foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)

all:
echo $(one)
echo $(two)
echo $(three)

The foreach function

foreach函式的用法為$(foreach var,list,text),foreach會把以空白間區隔文字的list一個一個賦值給var,而text會累加前面的結果,範例如下

1
2
3
4
5
6
7
foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)

all:
# Output is "who! are! you!"
@echo $(bar)

if function

用法如下

1
2
3
4
5
6
7
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)

all:
@echo $(foo)
@echo $(bar)

The call function

make可以用call來呼叫自定義函式

1
2
3
4
5
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)

all:
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
@echo $(call sweet_new_fn, go, tigers)

The shell function

make也可以呼叫shell函式,但是會把輸出的換行符號改成空白鍵

其他功能

Include Makefiles

使用Include可以讓makefile裡面呼叫其他makefile

vpath 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vpath %.h ../headers ../other-directory

# Note: vpath allows blah.h to be found even though blah.h is never in the current directory
some_binary: ../headers blah.h
touch some_binary

../headers:
mkdir ../headers

# We call the target blah.h instead of ../headers/blah.h, because that's the prereq that some_binary is looking for
# Typically, blah.h would already exist and you wouldn't need this.
blah.h:
touch ../headers/blah.h

clean:
rm -rf ../headers
rm -f some_binary

換行

指令太長可以利用\換行

1
2
3
some_file: 
echo This line is too long, so \
it is broken up into multiple lines

.phony

在目標中添加”.PHONY”將防止Make將虛擬目標與文件名混淆。在這個例子中,如果創建了名為”clean”的文件,”make clean”仍然會運行。從技術上講,我應該在每個帶有”all”或”clean”的例子中都使用它,但為了保持例子的清晰,我沒有這樣做。此外,”phony”目標通常具有很少用作文件名的名稱,在實踐中許多人都會跳過這一步。

1
2
3
4
5
6
7
8
some_file:
touch some_file
touch clean

.PHONY: clean
clean:
rm -f some_file
rm -f clean

.delete_on_error

如果命令返回非零的退出狀態,make工具將停止運行規則(並將向前傳播到前置要求)。
DELETE_ON_ERROR將在規則以這種方式失敗時刪除該規則的目標。這將對所有目標發生,不僅僅是像PHONY這樣的目標。儘管由於歷史原因,make工具沒有使用這個選項,但始終使用它是一個好主意。

1
2
3
4
5
6
7
8
9
10
.DELETE_ON_ERROR:
all: one two

one:
touch one
false

two:
touch two
false