DeepStream動態增減串流

本篇文章參考:
https://developer.nvidia.com/blog/managing-video-streams-in-runtime-with-the-deepstream-sdk/
https://github.com/NVIDIA-AI-IOT/deepstream_reference_apps/tree/master/runtime_source_add_delete

Glib定時執行函式

為了到動態增減串流,必須在main thread之外有一個thread定期的察看目前串流的表。Glib提供了g_timeout_add_seconds這個函式讓我們可以定期呼叫函式。g_timeout_add_seconds可以讓我們設定每間隔多少時間呼叫一次函數。

1
guint g_timeout_add_seconds (guint interval, GSourceFunc function, gpointer data)

g_timeout_add_seconds有三個參數分別是
Interval: 每間隔多少秒呼叫函數一次
function: 要被呼叫的函式
data: 要傳送給函式的參數

在我們動態增減的範例中,我們可以寫一個watchDog函式來讀去資料庫目前有無需要新增或刪減串流。

Linux 核心設計實作摘要

C語言

指標理解

C Traps and Pitfalls 的 “Understanding Declarations”小節提到如何解讀指標。每一個C的變數宣告可以拆成兩個部分。

  1. 型態
  2. 一串將會回傳這個型態的表達式

float f, g;代表f, g將會回傳float
float ff();代表ff()將會回傳float,因此ff是一個function並且會回傳float
float *pf;代表*pf江會回傳float,因此pf是一個pointer
float *g(), (*h)();首先()的優先度大於*因此*g() 可以改寫成*(g()),由此可知g是function並且會回傳一個pointer to a float的指標。而(*h)()代表h是一個pointer to a function指標,而且這個function會回傳float。


知道如何宣告變數後就可以寫出,就可以知道如何寫出這個類型強制轉型的寫法,只需要把變數名稱和分號移除,最後再加上括號
float *g();在這裡的g是一個回傳pointer的function,這個pointer指向float。而g強制轉型的寫法即為(float *())

Byte和Bit

1 Byte = 8 Bits

指標運算符號

  1. Address-of operator
    &稱為 Address-of operator

  2. Dereference operator
    *稱為 Dereference operator
    將pointer所指向的值給另一個變數(This is called a “load” operation.)

    1
    int bar = *foo_ptr;

    將值儲存到pointer所指的位置(This is called a “store” operation.)

    1
    *foo_ptr = 42; Sets foo to 42

->操作符

定義一個struct foo和一個foo的指針foo_ptr

1
2
3
4
5
6
struct foo {
size_t size;
char name[64];
int answer_to_ultimate_question;
unsigned shoe_size;
};

如果要查看foo_ptr所指向的內容,可以用

1
(*foo_ptr).size = new_size;

或者是用->操作符

1
foo_ptr->size = new_size;

陣列Array

宣告陣列

1
int array[] = { 45, 67, 89 };

在C語言,你宣告了一個陣列array之後,當你使用的時候,array這個變數其實是一個指向這個陣列第一個元素的指針,我們把這個行為稱為decaying,因為陣列被decays成指針了。不過他還是和針的指針有一點不同,其中就是如果用sizeof(array)來看陣列的話,回傳的將會是陣列的總長度(在這個範例就是(sizeof(int) = 4) × 3 = 12)而不是單一個指針的長度。
下面這三種狀況對陣列來說都是一樣的

1
array == &array == &array[0]

他們分別代表“array”, “pointer to array”, 和 “pointer to the first element of array”,但在C這三個東西是一樣的

陣列++

對於一般變數來說variable += 1代表對變數+1,但是對於指標來說代表對目前指標的位置加上資料型態的大小。以我們上一個例子來說我們的陣列儲存的是int,而array被decays成pointer了,所以array + 1就是加上sizeof(int),等同於我們把指針移動到下一個元素。

Indexing

首先看一下以下例子

1
2
3
int array[] = { 45, 67, 89 };
int *array_ptr = &array[1];
printf("%i\n", array_ptr[1]);

這段程式宣告的一個三個元素的陣列array,還有一個int指針array_ptr,可以用下面這張圖可以看到array[1]array_ptr[0]指向同一個記憶體。
image info{: w=”700” h=”200” }
我們可以看到其實[]是指針的操作符,array[1]等同於*(array + 1)

參考:
Everything you need to know about pointers in C
https://boredzo.org/pointers/

GDB除錯Python C extension

測試範例

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
#include <Python.h>

static PyObject *method_myadd(PyObject *self, PyObject *args){
int x, y, z = -1;

/* Parse arguments */
if(!PyArg_ParseTuple(args, "ii", &x, &y)){
return NULL;
}

/* The actual bit of code I need */
z = x + y;

return PyLong_FromLong(z);
}

static PyMethodDef myaddMethods[] = {
{"myadd", method_myadd, METH_VARARGS, "Python interface for myadd C library function"},
{NULL, NULL, 0, NULL}
};

static struct PyModuleDef myaddmodule = {
PyModuleDef_HEAD_INIT,
"myadd",
"Python interface for the myadd C library function",
-1,
myaddMethods
};

PyMODINIT_FUNC PyInit_myadd(void) {
return PyModule_Create(&myaddmodule);
}

{: file=’myadd.cpp’}

1
2
3
4
5
6
7
import myadd

print("going to ADD SOME NUMBERS")

x = myadd.myadd(5,6)

print(x)

{: file=’myscript.py’}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from distutils.core import setup, Extension

def main():
setup(name="myadd",
version="1.0.0",
description="Python interface for the myadd C library function",
author="Nadiah",
author_email="nadiah@nadiah.org",
ext_modules=[Extension("myadd", ["myadd.cpp"])],
)


if __name__ == "__main__":
main()

{: file=’setup.py’}

安裝GCC

1
sudo apt-get install build-essential

安裝python標頭檔

1
apt-get install python3-dev

安裝venv

最好要使用venv來開發c extension,因為如果沒有使用venv,python3 setup.py install編譯好的套件

1
apt install python3-venv

編譯範例程式

1
python3 setup.py install

安裝Python debug symbol

下面指令python的版本可以改成你自己的python版本

1
apt-get install python3.10-dbg

以GDB執行Python

1
gdb python

注意!!必須先正確安裝Python的debug symbol再執行這一步,完成後你應該要可以看到成功載入Python debug symbol,gdb的顯示類似如下

1
2
3
4
5
6
7
8
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
.....
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from python...
Reading symbols from /usr/lib/debug/.build-id/75/c83caa11a9418b8e5ae8feb0bb8f2e5d00c47b.debug...

如果你看到(No debugging symbols found in python)表示GDB找不到Python debug symbols。

GDB下中斷點

這一步會預先建立一個中斷點,引為此時我們的extension還沒被Python載入。再這個範例我們把中斷點下在myadd.cpp第12行z = x + y的位置

1
(gdb)b myadd.cpp:12

這時候GDB會問你是不是要建立一個未來使用的中斷點,回答是就可以

1
2
3
(gdb) b myadd.cpp:12
No source file named myadd.cpp.
Make breakpoint pending on future shared library load? (y or [n])

Python呼叫extension

最後在GDB裡面執行myscript.py程式,就可以看到程式停在我們的中斷點

1
(gdb) run myscript.py

debug Python 好用的指令

參考:

編譯教學
https://uwpce-pythoncert.github.io/Py300/notes/BuildingExtensions.html

安裝debug symbol
https://wiki.python.org/moin/DebuggingWithGdb

gdb除錯參考:
https://scipy-lectures.org/advanced/debugging/index.html#debugging-segmentation-faults-using-gdb

加入gdb路徑
https://devguide.python.org/advanced-tools/gdb/index.html#gdb-7-and-later

範例程式
https://nadiah.org/2020/03/01/example-debug-mixed-python-c-in-visual-studio-code/

除錯
https://developers.redhat.com/articles/2021/09/08/debugging-python-c-extensions-gdb#python_commands_in_gdb

統計學課本重點摘要

CH1統計學基本元素

Population, Sample, Experimental unit

statistics element

Variable

Variable

Measurement

Measurement

Inferenital Statistical Problem最大的重點,計算reliability

Inferenital Statistical Problem

Quantitative data和Qualitative data

Quantitative data: 可以測量的資料,例如高度、溫度
Qualitative data:無法測量的資料,例如滿意度、車子種類,為了方便計算可以給予數值,但數值本身沒有任何意義,只是一個代號

Representative sample

representative sample的特遮會和母體的特徵一樣。

CH2 描述一組資料

資料集

data set: $$x_1, x_2, x_3, \cdots , x_n$$,每個元素都是一個量測結果。
例如我們量測5個商品的長度,並且記錄結果為$$x_1=5, x_2=3, x_3=8, x_4=5, x_5=4$$

加總符號

如果要表達所有元素的加總,我們可以寫成$$x_1 + x_2 + x_3 + \cdots + x_n$$,或是我們可以用符號
$$\sum$$代表

$$x_1 + x_2 + x_3 + \cdots + x_n = \sum_{i=1}^nx_i$$

如果我們要計算每個元素的平方和,可以表達成下面方式

$$x_1^2 + x_2^2 + x_3^2 + \cdots + x_n^2 = \sum_{i=1}^nx_i^2$$

描述資料的方式

通常描述資料會有兩中方式

  1. 集中趨勢(central tendency)
  2. 變異性(variability)
    (補集中趨勢和變異性的圖)

集中趨勢(Central Tendency)

  1. 我們最常用的Central Tendency計算方式就是平均值,我們用$$\bar{x}$$(音:x bar)代表”樣本平均數”,他的計算方式如下

$$\bar{x} = \frac{\sum_{i=1}^nx_i}{n}$$

而我們用$$\mu$$代表母體平均數。通常我們用英文字母代表樣本,希臘字母代表母體。

  1. 中位數,比起平均數更能夠對付極端值。

變異性(variability)

  • deviation: 每一個資料點和平均的”距離”和”方向”,注意deviation是有正負號的,所以直接相加平均正負會相消。
  • sample variance(變異數): 為了解決正負相消的問題,我們可以將每一個deviation平方後相加再除以元數個數-1。

$$s^2 = \frac{\sum_{i=1}^n(x_i - \bar{x})^2}{n-1}$$

  • sample standard devitaion: 為了得到有意義的變異測量值,將sample variance開根號後就可以得到sample standard devitaion

$$s = \sqrt{s^2}$$

$$s^2 = $$ sample variance(樣本變異數)
$$s = $$ sample standard devitaion(樣本標準差)
$$\sigma^2 =$$ population variance(母體變異數)
$$\sigma =$$ population standard devitaion(母體標準差)

用標準差來描述單一樣本(單一資料集)

前面我們已經知道如果比較兩個樣本,standard devitaion越大表示變異性越大,也就是我們知道用standard devitaion來比較兩個樣本的相對變異程度。這節我們要用standard devitaion來描述單一個樣本。
如果對於frequency distributionj為對稱鐘形,根據經驗法則,

  1. 通常68%的Experimental unit會落在平均的正負一個標準差之內,也就是對於樣本來說$$(\bar{x} - s, \bar{x} + s)$$,對於母體來說$$(\mu - \sigma, \mu + \sigma)$$
  2. 通常95%的Experimental unit會落在平均的正負兩個標準差之內,也就是對於樣本來說$$(\bar{x} - 2s, \bar{x} + 2s)$$,對於母體來說$$(\mu - 2\sigma, \mu + 2\sigma)$$
  3. 通常99.7%的Experimental unit會落在平均的正負三個標準差之內,也就是對於樣本來說$$(\bar{x} - 3s, \bar{x} + 3s)$$,對於母體來說$$(\mu - 3\sigma, \mu + 3\sigma)$$

描述單一個測量值在全部測量值的位置

如果要描述一個測量值在所有測量值的位置,例如個人所得在所有勞工所得的位置。我們可以用z-score來描述。z-score利用平均和標準差來描述一個側量值所在的位置。其公式如下。
對於樣本:

$$z = \frac{x - \bar{x}}{s}$$

對於母體:

$$z = \frac{x - \mu}{\sigma}$$

z-score是有正負號的,正值越大代表這個測量值大於平均值越多,反之越少。
(補上p79的圖)

CH3機率

事件、樣本空間、和機率

以丟硬幣為例

  • observation, measurement:紀錄丟硬幣出現的結果
  • experiment: 完成投擲多次硬幣並且記錄丟硬幣的結果的過程。
  • sample point: 整個experiment最基礎的出現結果,以硬幣來說,正面反面各是一個sample point,以骰子來說1,2,3,4,5,6都分別是一個sample point。
    已丟兩個硬幣為例,總共有4個sample point,分別為正正、正反、反正、反反
  • sample space:包含所有sample point的集合
    丟一個硬幣的sample space為 S:{正, 反}
    丟兩個硬幣的sample space為 S:{正正、正反、反正、反反}
    丟骰子的sample space為 S:{1,2,3,4,5,6}

sample point的機率規定

  1. 每一個sample point的機率一定要介於0和1之間。
  2. sample space裡面所有sample point的機率加總必須為1

event事件

event是一組sample point,他可以只包含一個sample point,也可以包含多個sample point

event的機率

就是event內所有的sample point的總和

聯集Unions和交集Intersections

聯集(or)
(P131的圖)
交集(and)
(P131的圖)

Complementary Event補集合

event A的補集合的事件就是由所有不包含A事件sample point所構成。$$A$$的補集合記為$$A^s$$

$$P(A)+P(A^s)=1$$

互斥事件的加法法則(Mutually Exclusive Events)

加法法則 $$P(A \cup B) = P(A) + P(B) - P(A \cap B)$$
互斥事件代表兩個事件不會同時發生,例如丟硬幣正面和反面不會同時出現,因此$$P(A \cap B)=0$$。因此對於互斥事件,$$P(A \cup B) = P(A) + P(B)$$

條件機率

給定A事件發生的條件下,B事件發生的機率。例如丟一個骰子,在丟出來的數字小於3的條件下,出現偶數的機率。

$$P(A \mid B) = \frac{P(A \cap B)}{P(B)}$$

乘法定律與獨立事件(Independent Event)

從前面的條件機率經過移項後就可以得到

$$P(A \cap B) = P(B)P(A \mid B)$$

而如果A和B的機率不會互相影響,A B兩個事件就是獨立事件,也就是

$$P(A \mid B) = P(A)$$

$$P(B \mid A) = P(B)$$
獨立事件有三個重點

  1. 獨立事件沒辦法用作圖或是直覺判斷,必須透過計算來驗證他是獨立事件。
  2. 互斥事件(Mutually Exclusive Events)不是獨立事件(Independent Event),假設A,B為互斥事件,因為互斥事件的關係,如果B發生則$$P(A \mid B) = 0$$,而因此$$P(A) \neq P(A \mid B)$$,所以不可能滿足獨立事件的條件$$P(A \mid B) = P(A)$$。
  3. 計算獨立事件的交集十分簡單,$$P(A \cap B) = P(A)P(B \mid A)$$,因為獨立事件的關係$$P(B \mid A)=P(B)$$,所以獨立事件的交集事件為$$P(A \cap B) = P(A)P(B)$$

隨機抽樣

每一個樣本被抽中的機率都相同

貝氏定律

$$P(A \mid B) = \frac{P(A \cap B)}{P(B)}$$

CH4隨機變數random variable和機率分布

隨機變數的兩種類型

  • 隨機變數的定義
    隨機變數是把實驗會出現的所有結果用數字表達,每一個sample point都會有一個數字。例如以丟兩個銅板為例,我們可以計算出現人頭的數量,因此我們的random variable就會有0, 1, 2 三個。random在這裡代表的意義是這些數字在每次實驗中都是隨機出現的。

兩種類型的random variable

  • discrete random variable 例如丟兩個銅板出現頭的個數
  • continuous random variable 例如鑽油井挖到多深會挖到石油

probability distribution of diserete random variable

diserete random variable 的probability distribution可以是一個圖、表或是公式。他描述的是每一個random variable的發生的機率。
以丟兩個硬幣為例,我們以觀察到頭的個數作為random variable並計為x,x總共有三種可能,分別是頭出現0次,1次,2次(0, 1, 2)。
丟硬幣可能出現的sampling point共有HH, HT, TH, TT四種(H:頭, T:字)。
計算probability distribution如下

$$P(x=0)=P(TT) = \frac{1}{4}$$

$$P(x=1)=P(TH) + P(HT) = \frac{1}{2}$$

$$P(x=2)=P(HH) = \frac{1}{4}$$

probability distribution的必要條件

Discrete Random Variable x 的probability distribution必要條件

  1. $$p(x) \geq 0$$
  2. $$\sum{p(x)} = 1$$,其中 $$\sum{p(x)}$$ 是把所有可能的random variable x都算進去加總

期望值Expected Value

期望值其實就是population mean
以隨機變數Random Variable x為例
$$\mu = E(x) = \sum{xp(x)}$$
注意,期望值不一定會是Random Variable可能出現的值,以擲骰子為例,期望值不一定會是1~6其中一個整數

期望值的直觀解釋
https://www.probabilisticworld.com/intuitive-explanation-expected-value/

隨機變數的變異數(Variance)

$$\sigma^2 = E[(x - \mu)^2] = \sum{(x - \mu)^2p(x)}$$

隨機變數的標準差(Standard deviation)

$$\sigma = \sqrt{\sigma^2}$$

Sample Distributions

  • parameters: 用來描述母體probability distributions的數值,例如用來描述binomial distributaion的成功機率p, 或者是描述normal distribution的$$\mu$$平均和$$\sigma$$標準差都是parameters。
    因為是描述母體,所以parameters是固定的數值,但是通常也是未知的或是永遠無法確切知道的

  • sample statistic: 用來描述sample的數值,他是由sample的oberservation計算而來的。例如$$\bar{x}$$平均、$$s^2$$以及s標準差。
    藉由這些sample statistic內含的一些資訊,我們可以用來推斷母體的parameter。

sample statistic能夠直接拿來推論parameters嗎?

我們從由sample取得的一個oberservation可以算出sample statistic。每次的oberservation所計算的sample statistic也不完全相同。
舉例來說,我們想要推斷丟公平骰子出現點數的期望值$$\mu$$,而我們也已知$$\mu = 3.5$$(在現實情況下$$\mu$$幾乎都是未知的)。
假設每次oberservation都丟三次,第一次的觀察結果是2, 2, 6,$$\bar{x} = 3.33$$ 中位數m為3,我們可以看到$$\bar{x}$$比較接近母體的$$\mu$$。
第二次oberservation結果為3, 4, 6,$$\bar{x} = 4.33$$,m=4,這次反而是中位數比較接近母體的$$\mu$$。
由此可知我們沒辦法直接比較哪一個sample statistic比較適合拿來推論parameters,而其根本的原因是因為sample statistic本身也是random variable,因為不同的sample本身就會產生出不同的sample statistic。
也因為sample statistic是random variable的原因,要比較他們就必須用他們的probility distribution

sample statistic的sampling distribution

sample statistic的probility distribution稱為sampling distribution。舉例來說,假設一間工廠生產的鐵條長度$$\mu = 0.3$$標準差為0.005。假設一個實驗每次隨機抽出25根鐵條,並且量測每一根的長度後計算平均長度$$\bar{x}$$。假如這個實驗做很多次的話,每一次實驗的$$\bar{x}$$都會不太一樣,而這些大量實驗所產生的$$\bar{x}$$的分布圖就是$$\bar{x}$$的sampling distribution。
sampling distribution是經由重複操作”抽取n個measurements”的實驗所計算的sample statistic的分布

圖例:
https://onlinestatbook.com/stat_sim/sampling_dist/index.html

Sample Distribution形狀的特性

假如被抽樣的母體是常態分布,而且也只有母體是常態分布的情況下,則不管實驗抽樣的n的大小,他的Sample Distribution也一定是常態分佈

中央極限定理(Central Limit Theorem)

假設我們想要推論一個母體的平均數$$\bar{x}$$,於是我們進行抽樣並且每次實驗都抽取n個樣本來計算平均,這個實驗重複非常多次而得到Sample Distribution,我們可以觀察到$$\bar{x}$$的Sample Distribution的母體平均數$$\mu_\bar{x}$$、標準差$$\sigma_\bar{x}$$,以及被抽樣的母體的平均數$$\mu$$和標準差$$\sigma$$的關係為

  1. Sample Distribution的平均 = 母體的平均,$$\mu_\bar{x} = E(\bar{x}) = \mu$$
  2. $$Sample Distribution的標準差等於 = \frac{母體標準差}{\sqrt{n}}$$,也就是
    $$\sigma_\bar{x} = \frac{\sigma}{\sqrt{n}}$$
    不管母體的分布是什麼,當n月大的時候Sample Distribution就越接近常態分佈,而且並議會越小越,資料越集中。

CH5 Inference Based on a Single Sample

本章的重點在於如何運用一組資料(Single Sample)來進行預測

target parameter

對於母體我們有興趣但是未知的參數我們稱為target parameter例如母體平均數。

point estimator

利用樣本的一個數值來預測母體的target parameter稱為point estimator。例如我們利用樣本的平均$$\bar{x}$$來推估

母體平均的信賴區間 : Normal (z) Statistic

假設銀行要推估所有欠債兩個月以上的帳號平均欠債的金額,於是做了一次實驗抽出一百個欠債兩個月以上的帳號並計算樣本平均$$\bar{x}$$。接下來要計算樣本平均$$\bar{x}$$推估母體平均的準確度。
先回顧一下根據中央極限定理,樣本平均的Sample Distribution在每次抽樣的樣本數n夠大的時候會接近常態分佈。而interval estimator如下:
$$\bar{x} \pm 1.96\sigma_\bar{x}= \bar{x} \pm \frac{1.96\sigma}{\sqrt{n}}$$

從Sample Distribution得圖上來看,我們畫了一個上下邊界在Sample Distribution上,而邊界的中心是母體標準差。(根據中央極限定理,Sample Distribution的平均數會近似於母體平均數)。
回到我們計算欠債平均金額的實驗中,我們這次實驗取得的樣本會落在這上下邊界範圍內的機率是多少?因為如果我們取得的樣本可以落在這上下邊界之內,我們所算出來的interval estimator就會包含母體平均,超過邊界則interval estimator內不會包含母體平均。
從常態分佈下抽取一個樣本落在距離平均一個標準差內機率0.95。
可以參考下面網站

簡單來說,我們在這裡算出一個interval estimator,而真正的母體平均數會落在這個interval estimator內的機率是confidence coefficient。
{: .prompt-warning }

confidence level 和 confidence coefficient

confidence coefficient是我們隨機抽取的樣本所匯出的confidence interval包含母體平均的機率,而confidence level則是confidence coefficient以百分比的方式呈現。例如confidence coefficient為0.95,則confidence level為95%。

使用Normal (z) Statistic的條件

  1. 樣本數n要大於30,因為根據中央極限定理當n大於30時,Sample Distribution會接近常態分佈。
  2. 樣本必須是從母體中隨機抽取的

$$\alpha$$與confidence coefficient

利用樣本的一個數值來預測母體的target parameter稱為point estimator。例如我們利用樣本的平均$$\bar{x}$$來推估。
例如:
假設我們想要估計一個城市的平均收入,但我們無法調查每一個人。因此,我們可以從這個城市隨機抽取一些人(樣本),並計算他們的平均收入(樣本平均值)。然後,我們可以使用這個樣本平均值作為整個城市平均收入的點估計。

interval estimator

interval estimator是一個公式,它告訴我們如何使用樣本數據來計算一個區間,以估計目標參數。

Confidence Interval for a Population Mean: Normal (z) Statics

假設銀行想推估呆帳平均所欠下的金額,於是隨機抽樣100個呆帳帳戶出來計算出sample mean $$\bar{x}$$,並且想利用$$\bar{x}$$推估母體的平均$$\mu$$。在這裡$$\bar{x}$$就是$$\mu$$的point estimator。
接下來要計算interval estimator,而根據Central Limit Theorem可以知道如果每次抽樣的數量n夠大,則Sample Distribution接近為常態分布,而且Sample Distribution的平均值

如此一來我們可以改寫confidence interval的公式為:

$$\bar{x} \pm (z_{\frac{\alpha}{2}})\sigma_{\bar{x}}= \bar{x} \pm z_{\frac{\alpha}{2}}(\frac{\sigma}{\sqrt{n}})$$

其中$$z_{\frac{\alpha}{2}}$$為z值在頭部面積為$$\frac{\alpha}{2}$$的時候的值。
而$$\sigma_{\bar{x}}$$ 是sample statistic $$\bar{x}$$ 的Sample Distribution,計算方式是母體標準差除以樣本數的平方根。當樣本數夠大的時候(通常大於30),可以用單次抽樣的標準差sample statistic s代替母體標準差$\sigma$。
也就是說當樣本數大於30時,式子可以改寫成
$$\bar{x} \pm (z_{\frac{\alpha}{2}})\frac{s}{\sqrt{n}}$$

概念釐清

特別注意,這個章節我們的目標是只做一次experiment,進而推斷出母體平均數。所以我們Sample Distribution是未知的,因為Sample Distribution要做很多次實驗才能得到。
這也就是為什麼$$\sigma_{\bar{x}}$$是未知的,而且$$\sigma_{\bar{x}}$$所指的母體是Sample Distribution,跟我們要推估的母體不是同一個母體。
{: .prompt-warning }

5.3 Student’s t Statistic

有些狀況下我們可以抽取的樣本數很少,例如藥物的人體實驗,這是後使用z statistic就會變得不準確。這裡將介紹t Statistic來處理這個狀況。
當樣本數小於30的時候我們面臨兩個問題

  1. 樣本數小於30,不能使用中央極限定理,也因此不能直接假設Sample Distribution為常態分佈。
    • 解法:在前面我們可以發現到,如果母體為常態分佈,那即使樣本很少,Sample Distribution也會接近常態分佈。引此我們假設母體為常態分布。
  2. 母體標準差$\sigma$是未知的而且我們不能再用單次抽樣的標準差s來代替,因為樣本數太少了。所以z statistic的公式也不能使用,因為他需要$\sigma$良好的估計值。
    • 解法:我們定義t statistic來處理這個問題。t statistic的公式如下
      $$t=\frac{\bar{x}-\mu}{s/\sqrt{n}}$$

在這裡sample statistic s是單次抽樣的標準差,取代了母體標準差$\sigma$。
假如我們是從常態分佈的母體抽樣,那麼t statistic的分佈會接近常態分佈。而t statistic和z statistic的差別在於t statistic多了一個random quantities s,也因此他的變動會比z statistic大。

t的Sample Distribution中實際變異量取決於樣本大小n。我們通常將他表示為自由度(degrees of freedom)為n-1的t分佈。回顧一下(n-1)是計算$s^2$的分母,所以如果n越小那sample distribution的變異量就越大。

對於small-sample 的confidence interval有以下結論

  • 對於平均數$\mu$,small-sample的confidence interval如下,其中$t_{\frac{\alpha}{2}}$為(n-1)自由度

$\bar{x} \pm t_{\frac{\alpha}{2}}\frac{s}{\sqrt{n}}$

  • 計算small-sample的confidence interval有以下條件
    • 樣本是隨機從母體抽出
    • 母體的分布必須接近常態分布

5.3 Large-Sample Confidence Interval for a Population Proportion

以市場調查為例,一間公司想知道消費者會選擇自己的品牌或是其他品牌。注意在這裡選擇品牌是一個qualitative variable,所以我們要用proportion來描述,而且這個問題是一個二元問題,因此我們要計算的是binoimal experiment中的p,也就是成功比例。要估計p我們可以利用計算樣本的成功比$\hat p$

$$\hat p=\frac{x}{n}=\frac{消費者選擇這間公司的品牌的人數}{問卷總人數}$$

而為了要計算$\hat p$的可靠度,我們將$\hat p$視為平均數,也就是說選擇這間公司品牌的人p計為1,選擇其他品牌的人q計為0,全部相加後除與總抽樣人數n,如此一來就可以用前面計算平均數的方法來推估$\hat p$的可靠度。

$\hat p$ 的Sampling Distribution

  1. $\hat p$的Sampling Distribution的平均數為p
  2. $\hat p$的Sampling Distribution的標準差為$\sqrt{\frac{pq}{n}}$,也就是$\sigma_{\hat p} = \sqrt{\frac{pq}{n}}$
  3. 對於large samples,$\hat p$的Sampling Distribution接近常態分佈。large samples的定義為np>5且nq>5。

常用參考資料

https://cqeacademy.com/cqe-body-of-knowledge/quantitative-methods-tools/point-estimates-and-confidence-intervals/

BatchNormalization論文閱讀與實作

gdb教學一基本操作

本篇文章修改參考這篇文章製作範例和教學,首先我們先寫一個有bug的程式factorial.c

基本操作

編譯程式給GDB

如果要給GDB除錯,一定要加上-g選項

1
gcc -g <any other flags e.g. -Wall> -o <file> <file.c>

開啟GDB session

在終端機輸入gdb和執行檔名稱即可開啟gdb session

1
2
3
4
5
6
7
8
9
gdb <program_name>
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from factorial...
(gdb)

run指令

用run指令執行程式。

1
(gdb) run

如果有程式有接收參數,可以直接放在run後面

1
(gdb) run <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) run < <data>

start指令

用start指令執行程式,並且在main函式的第一行停下來。

1
(gdb) start

如果有程式有接收參數,可以直接放在run後面

1
(gdb) start <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) start < <data>

quit指令

離開GDB可以用quit指令

1
(gdb) quit

範例

以下面程式為範例

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
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

用以下指令編譯

1
gcc -Wall -g -o factorial factorial.c

接下來開啟GDB session

1
gdb factorial

在GDB session中執行程式,會發現程式輸出錯誤的值

1
(gdb) run

如果要結束GDB session指令可以用quit。

1
(gdb) quit

中斷點

break指令

如果要放置中斷點在特定檔案的某一行,可以用break <filename>:<line number>指令

1
(gdb) break <filename>:<line number>

你也可以直接指定要放在哪一個function(gdb) break <filename>:<function>

1
(gdb) break <filename>:<function>

列出全部中斷點

要列出全部的中斷點可以用以下指令

1
(gdb) info break

delete指令

如果要刪除中斷點可以先用info break所有中斷點的號碼再刪除

1
(gdb) delete <breakpoint number>

範例

接著前面的例子,我們懷疑程式的第15行有錯誤,因此在GDB session中把中斷點放在第15行並且執行程式

1
2
(gdb) break 15
(gdb) run

我們應該會看到類似以下的輸出,你可以看到GDB輸出我們的第15行程式,代表現在程式停在第15行。
注意!!這時候程式並還沒有執行第15行。GDB告訴我們的是他下一行將要執行的程式。

1
2
3
4
5
6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
The factorial of 5 is 120.

Breakpoint 1, main () at factorial.c:15
15 f = factorial(n);

查看資料

print指令

印出變數

1
(gdb) print <variable_name>

info locals指令

印出當前所有的local variable,除了輸入函式的參數以外。

1
(gdb) info locals

info args指令

印出輸入給函式的參數

1
(gdb) info args

範例

接續前面的範例,們想查看程式運作到中斷點時變數的值。首先查看f和n的值

1
2
3
4
(gdb) print f
$1 = 120
(gdb) print n
$2 = 17

我們也可以用info locals來查看所有區域變數。

1
2
3
(gdb) info locals
n = 17
f = 120

查看程式運作

當程式在中斷點停下後,利用step, next and continue可以控制程式運作以便我們逐行觀察程式運作的情形。

step指令

執行下一行程式,如果下一行程式是呼叫function,gdb就會進到function裡面。

1
(gdb) step

next指令

執行下一行程式,與step不同的是,如果下一行是function,則將function執行完而不進入function。

1
(gdb) next

continue指令

執行程式直到碰到下一個中斷點

1
(gdb) continue

where指令

印出function call stack

1
(gdb) where

list指令

印出目前所在的行數以及前後各兩行。

1
(gdb) list

範例

接續前面的範例。我們想要查看我們的factorial函式如何運作的。因此用step指令進入factorial函式。

1
2
3
(gdb) step
factorial (n=17) at factorial.c:24
24 int f = 1;

接下來我們想一步一步看看factorial函式如和運作的

1
2
3
4
5
6
7
8
(gdb) next
25 int i = 1;
(gdb) next
26 while (i <= n) {
(gdb) next
27 f = f * i;
(gdb) next
28 i++;

除了不斷輸入重複的指令,你也可以直接按Enter,GDB會重複你上一個指令。
接下來我們預期執行到這裡,if應該要等於1

1
2
3
4
(gdb) print i
$2 = 1
(gdb) print f
$3 = 1

如果我們想查看目前在程式哪一行,可以用where指令來印出call stack

1
2
3
(gdb) where
#0 factorial (n=17) at factorial.c:28
#1 0x0000555555555196 in main () at factorial.c:15

如果要印出目前行數前後的程式可以用list

1
2
3
4
5
6
7
8
(gdb) list
23 int factorial(int n) {
24 int f = 1;
25 int i = 1;
26 while (i <= n) {
27 f = f * i;
28 i++;
29 }

我們也可以用continue指令和中斷點來加速除錯,首先先下一個中斷點在第28行。

1
2
(gdb) break 28
Breakpoint 2 at 0x5555555551e1: file factorial.c, line 28.

接下來用continue指令直接跳到這個中斷點

1
2
3
4
5
(gdb) continue
Continuing.

Breakpoint 2, factorial (n=17) at factorial.c:28
28 i++;

然後依次印出所有區域變數

1
info locals

我們不斷重複這個動作,可以發現前面都還運作正常,直到i=13時,答案開始出出錯了,如果繼續執行會發現答案越來越小,甚至變成負的。這個錯誤原因是int這個資料型態無法儲存這麼大的值,我們必須使用更大的資料型態才能儲存。

Call Stack

call Stack是由stack frames所組成。stack frames是用來儲存呼叫函式的時候函式的區域變數。如果函式內又呼叫另一個函式,新的一個stack frames會被放當前函式的stack frames的上面。當一個函式完成後,就會移除一個stack frames。

where指令

印出call stack並且包含檔名和行數。

up指令

往上移動stack一層frame

1
2
(gdb) up
(gdb) up <n_frames>

down指令

往下移動stack一層frame

1
2
(gdb) down
(gdb) down <n_frames>

frame指令

移動到指定的frame

1
(gdb) frame <frame_number>

範例

如果錯誤出現在函式庫的程式碼,這時候用call stack來debug就會很有用,我們可以利用call stack來尋找我們的程式在什麼地方出錯。用下面範例corrupted_linked_list.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
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
struct node *list1 = create_list(7);
print_list(list1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list){
struct node *curr = list;

while (curr != NULL) {
printf("%d->", curr->data);

curr == curr->next;
}
printf("X\n");
}

{:file=’corrupted_linked_list.c’}

首先我們編譯並且執行程式,我們會發現程式進入無窮迴圈,於是我們強制程式停下來。

1
2
3
4
$ gcc -g -o corrupted_linked_list corrupted_linked_list.c
./corrupted_linked_list
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->
**ctrl + c**

我們預期程式的輸出應該像下面這樣

1
2
./corrupted_linked_list
0->1->2->3->4->5->6->X

為了要了解程式到底錯在哪裡,我們在GDB session裡面執行程式。並且中斷程式

1
2
3
4
5
6
7
$ gdb corrupted_linked_list
(gdb) run
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0
**ctrl + c**
Program received signal SIGINT, Interrupt.
0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.

中斷後我們可以用where指令看一下目前所在的位置,輸出會類似如下

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) where
#0 0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1 0x00007fffff0a8bff in _IO_new_file_write (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=0x6020f0, n=512) at fileops.c:1263
#2 0x00007fffff0aa409 in new_do_write (to_do=512,
data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., fp=0x7fffff3f5620 <_IO_2_1_stdout_>) at fileops.c:518
#3 _IO_new_do_write (fp=0x7fffff3f5620 <_IO_2_1_stdout_>,
data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., to_do=512) at fileops.c:494
#4 0x00007fffff0a947d in _IO_new_file_xsputn (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=<optimised out>, n=2) at fileops.c:1331
#5 0x00007fffff07d92d in _IO_vfprintf_internal (s=0x7fffff3f5620 <_IO_2_1_stdout_>, format=<optimised out>, ap=ap@entry=0x7ffffffedf08) at vfprintf.c:1663
#6 0x00007fffff085899 in __printf (format=<optimised out>) at printf.c:33
#7 0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
#8 0x0000000000400628 in main () at corrupted_linked_list.c:17

可以看到程式被中斷在標準函式庫的程式,不過我們想看一看輸入函式庫的參數是什麼。因此我們可以用up指令從frame 0 移動到 frame 1。或者我們直接用frame指令移動到我們要的地方

1
2
3
(gdb) frame 7
#7 0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
50 printf("%d->", curr->data);

首先我們先看一下區域變數

1
2
(gdb) info locals
curr = 0x602010

可以用ptype指令查看curr的型態,可以發現他是一個node struct的指針。

1
2
3
4
5
(gdb) ptype curr
type = struct node {
int data;
struct node *next;
} *

我們dereference查看一下內容

1
2
(gdb) print *curr
$1 = {data = 0, next = 0x602030}

也可以查看其他的內容

1
2
3
4
5
(gdb) print *(curr->next)
$2 = {data = 1, next = 0x602050}
(gdb) print *(curr->next->next)
$3 = {data = 2, next = 0x602070}
(gdb)

Core Dumps

程式當機當下程式的狀態對於除錯十分有幫助,我們可以利用core dump檔來記錄這些狀態。對於一些不定時發生的錯誤,這些除錯資訊就十分珍貴了。

Core Dumps設定

首先查看Core Dumps記錄功能有沒有被開啟,如果回傳0代表沒有打開Core Dumps記錄功能

1
ulimit -c

用以下指令設定打開Core Dumps記錄功能

1
ulimit -c unlimited

通常Core Dumps檔產生的位置會記路在/proc/sys/kernel/core_pattern這個設定,用以下指令查看Core Dumps檔的位置。

產生Core Dump

當Core Dumps紀錄功能打開後,如果程式遇到Segmentation fault的錯誤,就會產生Core Dump檔。

用GDB查看Core Dump檔

要查看Core Dump檔可以用以下指令。注意當我們利用Core Dump檔來除錯的時候,程式實際上並沒有在運作,所以step, nextcontinue 這些指令這時候是沒有功能的。

1
gdb <binary-file> <core-dump-file>

範例

利用下面範例broken_linked_list.c將說明如何使用Core Dump除錯。

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
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list, int length);

int main(void){
int length1 = 7;
struct node *list1 = create_list(length1);
print_list(list1, length1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list, int length){
struct node *curr = list;
int i = 0;
while (i <= length) {
printf("%d->", curr->data);
curr = curr->next;
i++;
}
printf("X\n");
}

{: file=’broken_linked_list.c’}
編譯並且執行程式,可以看到程式出現Segmentation fault (core dumped)錯誤

1
2
3
$ gcc -g -o broken_linked_list broken_linked_list.c
$ ./broken_linked_list
Segmentation fault (core dumped)

接下來讀取Core Dump檔

1
gdb broken_linked_list core

GDB將會顯示程式出錯的位置

1
2
3
4
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055be9593e283 in print_list (list=0x55be96c20260, length=7)
at broken_linked_list.c:51
51 printf("%d->", curr->data);

從這裡可以知道我們在第51行正在嘗試存取一個非法的記憶體位置。因此我們可以推測可能是curr的位址不正確或者是data是不可以讀取的位址。於是先嘗試印出curr的值

1
2
(gdb) print curr
$1 = (struct node *) 0x0

可以看到curr是一個NULL址針。接下來我們在印出當前狀況的區域變數

1
2
3
(gdb) info locals
curr = 0x0
i = 7

可以看到i是7,也就是現在是第8次執行for迴圈,但是我們的Linking list只有7個節點,而且依照我們的建立Linking list的方式,第8個節點會是NULL址針,所以程式會出錯。
我們在檢查一下Linking list本身是否有問題。

1
2
(gdb) print *list
$2 = {data = 0, next = 0x55be96c20280}

可以看到Linking list的位址沒問題,因此可以更加確定問題就是迴圈多執行了一次。我們可以用list指令看一下程式現在所在的位置。

1
2
3
4
5
6
7
8
9
10
11
(gdb) list
46
47 void print_list(struct node *list, int length){
48 struct node *curr = list;
49 int i = 0;
50 while (i <= length) {
51 printf("%d->", curr->data);
52 curr = curr->next;
53 i++;
54 }
55 printf("X\n");

GDB Init File

如果單純使用GDB的指令,有些變數就會變得難以查看,例如如果想要查看linked list的所有成員就會變得很麻煩。而GDB提供讓使用者自定義指令讓我們可以容易議處想要的結果。

GDB User Initialization File

GEB啟動時會載入User Initialization File所記錄的指令,你可以建立一個新的User Initialization File,他的檔名是.gdbinit,放置的位置是home directory

1
~/.gdbinit

建立之後在檔案內加入下面指令,如此一來就可以在每一個專案下各自建立專屬的initialization file .gdbinit

GDB

專案自己的initialization file位在專案的跟目錄下,使用者可以自訂義指令或是GDB啟動的行為。

1
~/<file_path>/.gdbinit

基礎用法

例如你的專案想要每次使用GDB的時候都會放一個breakpoint在某一個function,你就可以在.gdbinit寫入下面這行。

1
break <function_name>

GDB腳本語法

定義命令

你可以用以下命令定義一個自訂義命令

1
2
3
define <command>
<code>
end

增加命令說明

你可以用以下命令為自訂義命令增加註解

1
2
3
document <command>
<information about the command>
end

如果要查看命令說明可以用以下命令

1
2
(gdb) help <command>
<information about the command>

命令參數

如果需要傳入參數給自訂義命令可以用以下方式

1
(gdb) <command> <arg0> <arg1> <arg2> ...

如果要在指令內使用參數可以用以下方式

1
2
3
4
5
$argc
$arg0
$arg1
$arg2
...

方便的內建變數

可以用以下方式定義

1
$<variable_name>

設定變數

用以下指令設定變數

1
set $<variable_name> = <value_or_expression>

if 聲明

用以下方式可以做if聲明

1
2
3
4
5
if <condition>
<code>
else
<code>
end

while loop

1
2
3
while <condition>
<code>
end

Printing

GDB的print語法跟c十分相似

1
printf "<format string>", <arg0>, <arg1>, ...

使用自訂義命令

使用者自訂義命令的用法跟內建指令的用法相同。

1
(gdb) <command> <arg0> <arg1> <arg2> ...

範例:除錯Linked lists

下面將以除錯Linked list作為範例linked_list.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
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
int data;
struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
struct node *list1 = create_list(7);
print_list(list1);

return 0;
}

struct node *create_node(int data){
struct node *new = malloc(sizeof(struct node));
assert(new != NULL);
new->data = data;
new->next = NULL;
return new;
}

struct node *create_list(int length) {

struct node *head = NULL;
if (length > 0) {
head = create_node(0);
int i = 1;
struct node *curr = head;
while (i < length) {
curr->next = create_node(i);
curr = curr->next;
i++;
}
}
return head;
}

void print_list(struct node *list){
struct node *curr = list;

while (curr != NULL) {
printf("%d->", curr->data);
curr = curr->next;
}
printf("X\n");
}

{:file=’linked_list.c’}

首先先把下面這行加入~/.gdbinit

1
set auto-load safe-path /

接下來撰寫自訂義命令,在專案根目錄也建立一個.gdbinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
define p_generic_list
set var $n = $arg0
while $n
print *($n)
set var $n = $n->next
end
end

document p_generic_list
p_generic_list LIST_HEAD_POINTER
Print all the fields of the nodes in the linked list pointed to by LIST_HEAD_POINTER. Assumes there is a next field in the struct.
end



define indentby
printf "\n"
set $i_$arg0 = $arg0
while $i_$arg0 > 10
set $i_$arg0 = $i_$arg0 - 1
printf "%c", ' '
end
end

{:file=’.gdbinit’}

在命令裡面,我們建立一個變數來儲存第一個參數($arg0),也就是linked list的指針

1
set var $n = $arg0

接下來印出 linked list的內容。

1
print *($n)

接下來把把linked list的指針指向下一個元素

1
set var $n = $n->next

運行結果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ gcc -g -o linked_list linked_list.c
$ gdb -q ./linked_list
Reading symbols from ./linked_list...
(gdb) br 18
Breakpoint 1 at 0x11c3: file linked_list.c, line 18.
(gdb) r
Starting program: /home/steven/tmp/gcc_practice/linked_list
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at linked_list.c:18
18 print_list(list1);
(gdb) p_generic_list list1
$1 = {data = 0, next = 0x5555555592c0}
$2 = {data = 1, next = 0x5555555592e0}
$3 = {data = 2, next = 0x555555559300}
$4 = {data = 3, next = 0x555555559320}
$5 = {data = 4, next = 0x555555559340}
$6 = {data = 5, next = 0x555555559360}
$7 = {data = 6, next = 0x0}
(gdb)

watch and display

watch可以監看一個變數,每當這個變數數值改變的時候就暫停程式。display 則是每當程式停止的時候顯示變數。

watch

使用以下指令設定想要監看的變數

1
(gdb) watch <variable_name>

查看Watchpoints

查看Watchpoints的方式跟查看breakpoints的方式一樣

1
(gdb) info breakpoints

移除Watchpoints

使用以下指令移除Watchpoints

1
(gdb) disable <watchpoint_number>

display

使用以下指令設定display

1
(gdb) display expression

查看所有display

查看所有display

1
(gdb) info display

移除display

用以下指令移除display

1
(gdb) delete display <display_number>

範例

以下將以計算階乘的程式factorial.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
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

接著編譯程式並且用gdb讀取

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

首先先設定中斷點,可以看到n=5的時候晟是正常運作,讓嘗試繼續執行讓n=17

1
2
3
4
5
6
7
8
9
10
11
(gdb) br factorial
Breakpoint 1 at 0x11a5: file factorial.c, line 24.
(gdb) r
Starting program: ~/factorial
Breakpoint 1, factorial (n=5) at factorial.c:24
24 int f = 1;
(gdb) c
Continuing.
The factorial of 5 is 120.
Breakpoint 1, factorial (n=17) at factorial.c:24
24 int f = 1;

接下來設定watch和display,我們希望i初始化之後再設定watch和display,

1
2
3
4
(gdb) n
25 int i = 1;
(gdb) n
26 while (i <= n) {

然後設定watch和display

1
2
3
4
(gdb) watch f
Hardware watchpoint 2: f
(gdb) display i
1: i = 1

然後我們就可以觀察程式計算

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 1
New value = 2
factorial (n=17) at factorial.c:28
28 i++;
1: i = 2
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 2
New value = 6
factorial (n=17) at factorial.c:28
28 i++;
1: i = 3
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 6
New value = 24
factorial (n=17) at factorial.c:28
28 i++;
1: i = 4
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 24
New value = 120
factorial (n=17) at factorial.c:28
28 i++;
1: i = 5
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 120
New value = 720
factorial (n=17) at factorial.c:28
28 i++;
1: i = 6
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 720
New value = 5040
factorial (n=17) at factorial.c:28
28 i++;
1: i = 7
(gdb) c

Continuing.

Hardware watchpoint 2: f

Old value = 5040
New value = 40320
factorial (n=17) at factorial.c:28
28 i++;
1: i = 8
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 40320
New value = 362880
factorial (n=17) at factorial.c:28
28 i++;
1: i = 9
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 362880
New value = 3628800
factorial (n=17) at factorial.c:28
28 i++;
1: i = 10
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 3628800
New value = 39916800
factorial (n=17) at factorial.c:28
28 i++;
1: i = 11
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 39916800
New value = 479001600
factorial (n=17) at factorial.c:28
28 i++;
1: i = 12
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 479001600
New value = 1932053504
factorial (n=17) at factorial.c:28
28 i++;
1: i = 13

我們可以觀察到當n=13的時候程式就開始出錯。

條件式Breakpoints

利用conditional breakpoints可以讓程式達到特定條件的時候才停下來。

設定條件式Breakpoints的方法

  1. 建立一個中斷點
    如同前面所教的方法先建立一個中斷點。

    1
    2
    (gdb) break <file_name> : <line_number>
    (gdb) break <function_name>
  2. 查看所有中斷點
    下面指令可以查看目前已經設定過的中斷點。

    1
    (gdb) info breakpoints
  3. 設定條件
    首先我們必須要知道中斷點的編號,並且用以下指令設定條件

    1
    (gdb) condition <breakpoint_number> condition
  4. 移除中斷點的停止條件
    如果想要移除中斷點的停止條件,可以用以下指令

    1
    (gdb) condition <breakpoint_number>

範例

下面將繼續沿用階層計算程式factorial.c來示範conditional breakpoints

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
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {

int n = 5;
int f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);
n = 17;
f = factorial(n);
printf("The factorial of %d is %d.\n", n, f);

return 0;

}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
int f = 1;
int i = 1;
while (i <= n) {
f = f * i;
i++;
}
return f;
}

{:file=’factorial.c’}

編譯程式並啟動GDB

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

設定conditional breakpoints

我們已經知道程式在i <= 5 之前都正常運作,所以我們不必在確認i <= 5 之前的輸出結果。因此我們設定的條件是 i > 5。

1
2
3
4
5
$ gdb factorial
Reading symbols from factorial...done.
(gdb) br 28
Breakpoint 1 at 0x11bf: file factorial.c, line 28.
(gdb) condition 1 i > 5

開始除錯

接下來就可以執行程式並且觀察不同i之下的輸出變化

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
(gdb) r
Starting program: ~/factorial
The factorial of 5 is 120.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 720
i = 6
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 5040
i = 7
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 40320
i = 8
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 362880
i = 9
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 3628800
i = 10
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 39916800
i = 11
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 479001600
i = 12
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28 i++;
(gdb) info locals
f = 1932053504
i = 13
(gdb)

我們可以發現i=13之後數值就開始出現異常了。

除錯shared library

gcc印出所使用到的shared library

1
gcc -Wl,-t your_program.c -o your_program > ld_output.txt

gdb查看被載入的shared library

1
info share

每當有新的shared library被載入的時候就暫停

set stop-on-solib-events 1

https://jasonblog.github.io/note/gdb/li_yong_gdb_jin_xing_shared_library_de_chu_cuo.html

參考:
https://www.cse.unsw.edu.au/~learn/debugging/modules/gdb_basic_use/

實作yolov2(YOLO9000)

yolov2 詳細說明

https://senliuy.gitbook.io/advanced-deep-learning/chapter1/yolo9000-better-faster-stronger

Darknet-19與YOLOv2

目前Github上的YOLOv2已經被修改過,和論文上的描述並不一樣,必須參考最原始的cfg,而且必須要是voc的版本。
https://github.com/pjreddie/darknet/blob/c6afc7ff1499fbbe64069e1843d7929bd7ae2eaa/cfg/yolo_voc.cfg
{: .prompt-tip }

論文的第三章Faster提到Darknet-19是一個classification model,他是YOLOv2的基礎。
在Training for detection提到YOLOv2是刪掉Darknet-19的最後一個conv layers(以及他後面的其他layer),並且用3個3 x 3 x 1024 conv layer 最後搭配 1 x 1 x 類別數量的conv layer。

We modify this network for detection by removing the last convolutional layer and instead adding on three 3 × 3 convolutional layers with 1024 filters each followed by a final 1 × 1 convolutional layer with the number of outputs we need for detection.

Darknet源碼註解

region layer的biases就是anchors的值

實作Conv2dBatchLeaky

Conv

activation為linear時沒有做任何事 https://github.com/AlexeyAB/darknet/blob/0faed3e60e52f742bbef43b83f6be51dd30f373e/src/gemm.c#L2337

BatchNormal

問題:
在pytorch有momentum,在darknet有沒有?

Leaky

region layer

Pytorch實作

下面以圖片作為輸入來舉例

  • CONV2D: 輸入的tensor每一個維度所對應的是$$(N,C_{in},H,W)$$
    • $$N$$: 照片張數,一次一張照片為1
    • $$C_{in}$$:照片channel,彩色照片為3
    • $$H$$: 照片高度
    • $$W$$: 照片寬度

https://kikaben.com/yolo-v2-yolo9000/

參考:
yolov2架構講解(架構圖1x1 conv的地方有錯)
https://kikaben.com/yolo-v2-yolo9000/

完整yolov2實作(高參考價值)
https://github.com/Tencent/ObjectDetection-OneStageDet
https://zhuanlan.zhihu.com/p/45039781

http://yuenshome.space/timeline/2018-11/yolov2-region-source-code/
https://github.com/tztztztztz/yolov2.pytorch
https://github.com/gwinndr/YOLOv4-Pytorch/tree/master/model/layers

https://github.com/ruiminshen/yolo2-pytorch/blob/master/model/yolo2.py

韓文Darknet介紹
https://jjeamin.github.io/darknet_book/part3_src/batchnorm_layer.html