2021年6月13日 星期日

[筆記][C/C++]Class之間使用Callback Function

前言

最近工作需要想辦法優化架構,因此設計了兩個類別各自負責自己工作,但有些資訊是B類別動作後,A類別需要進行更新。

最直覺想到的寫法,是讓B物件認識A物件,但是這樣A物件就跟B物件產生了關聯性(耦合)。

第二種想到寫法,是A物件需要這個資訊的時候,檢查資訊有沒有更新。但這樣A物件需要多些判斷,且每個需要資訊的地方都需要加這個判斷式,後續維護不容易。

有沒有什麼辦法是,B不需要認識A,A也不需要一直檢查呢?

最後想到的解法是:使用Callback Function!!!!!

什麼是Callback Function?

一般Function是主動執行,比如說前言中提到最直覺的寫法,B物件呼叫A的方法,主動執行A更新的動作(或是可以想成是動態執行);至於Callback Function,則是B先記得A的更新Method的位址(指標),要動作時再呼叫這個函式指標對應到的Function(靜態執行)。

簡單的說,一般Call Function是直接呼叫Function,而Callback function則是記得function的位址,需要時再執行該Pointer指向的函式。

常見的Callback Function

Callback Function其實滿常見的,以下列出幾個可以看到Callback Function的API
  1. std::sort
  2. openCV MouseCallback
  3. gstreamer AppSink

Callback Function怎麼用?

Function Pointer

首先,先聊一下函式指標怎麼寫呢?

C++函式指標寫法如下:

void (*func )(int, int)

這一段翻譯成人類的語言就是:func是一個函式指標,這個指標指向的函式引數為兩個int,回傳值則為void。

不過為了使用上的便利性,通常會用typedef的方式給他名字,例如:

typedef void (*FUNC )(int, int)

後續就能宣告FUNC並給予別名。

宣告及使用Callback Function

先來一段範例程式碼

#include <iostream>
using namespace std;

typedef void (*CALLBACK)( int, int, void* );

class A{
public:
    A( void );
    // constructor

    static void UpdateCallback( int, int, void* );
    // callback function pointer pointed to

    void PrintMember( void ) const;
    // show the member
private:
    int a;
    int b;
};

class B{
public:
    B( void );
    // constructor

    void RegisterCallback( CALLBACK, void* );
    // register callback and the callee

    void CallFunction( void );
    // call by main

private:
    CALLBACK pFunc;

    void *m_pCallee;
};

A::A( void )
// constructor
{
    a = 0;
    b = 0;
}

void A::UpdateCallback( int inputA, int inputB, void *Self )
// callback function pointer pointed to
{
    // cast user data to A
    A* pA = (A*)Self;

    // update with input value
    pA->a = inputA;
    pA->b = inputB;
}

void A::PrintMember( void ) const
// show the member
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

B::B( void )
// constructor
{
    // do nothing
}

void B::RegisterCallback( CALLBACK FuncCB, void* pCallee )
// register callback and the callee
{
    pFunc = FuncCB;
    m_pCallee = pCallee;
}

void B::CallFunction( void )
// call by main
{
    cout << "execute function pointer" << endl;
    pFunc( 2, 3, m_pCallee ); 
}


int main( void )
{
    A instanceA;
    B instanceB;
    // register callback to B
    instanceB.RegisterCallback( instanceA.UpdateCallback, &instanceA );

    cout << "Before run B::CallFunction: " << endl;
    instanceA.PrintMember();

    instanceB.CallFunction();

    cout << "After run B::CallFunction: " << endl;
    instanceA.PrintMember();

    return 0;
}

從程式碼中可以看到,一開始要將A類別的UpdateCallback註冊到B類別,也就是告訴B類別Callback Function的位址以及該Callback function的使用者(Callee,傳進一個void pointer即可)。
要傳進物件A的pointer的原因是因為Callback Function中需要修改到A,如果沒有傳進A物件本身,那將無法直接修改到A物件中的值。(可能有其他更好的寫法,還要再研究)

另外要注意實作功能的Callback Function必須是static。
(註:C++11以後用function跟bind好像就可以不用宣告為static)

註冊Callback Function後,B物件執行CallFunction的時候,就會執行Callback指向的函式,也就是A的UpdateCallback,並將Callee傳入(在這個情境下是instanceA的pointer),在UpdateCallback中,會將void pointer轉型成class A的pointer,並將值填入A的pointer中。

可以發現class B並不需要知道class A(只需要傳入一個void pointer),也就是說透過Callback Function的機制,就達成B類別不需要認識A類別,就可以叫A做事情的目的!

另外以程式碼擴展性來說,Callback Function增加了更多彈性~如果我後續修改需求,有一個新的類別C,也是要進行類似的動作(比如說使用B傳入兩個int進行相加,再存進member),那我只要在C類別中實作Callback Function,然後註冊進B就可以了,完全不需要修改B。

結論

Callback Function的實用性很高,對於程式碼擴展性有很大的幫助。希望整理完後,之後可以用得更爐火純青~
另外也要找時間去熟悉C#的delegate,因為好像是類似的概念!

參考資料:

https://dangerlover9403.pixnet.net/blog/post/83880061-%5B%E6%95%99%E5%AD%B8%5Dc-c++-callback-function-%E7%94%A8%E6%B3%95-%E7%AF%84%E4%BE%8B-(%E5%85%A7%E5%90%ABfunctio

https://openhome.cc/Gossip/CppGossip/FunctionPointer.html

2019年7月24日 星期三

[筆記][macOS] Anaconda 安裝 Intel RealSense Python package on Mac

之前介紹過如何在windows上的anaconda安裝realsense的python套件
https://jennyli6079633.blogspot.com/2019/01/anaconda-intel-realsense-python-package.html
不過其實macOS才是最需要自己編譯library的系統啊...(Linux應該也是)
因為直接pip是沒辦法安裝的
官方是說還沒開發出適用Unix平台的pypi安裝法

在這邊介紹一下如何在mac上用cmake編譯pyrealsense並使用:
1. 下載cmake
google一下應該很多資料
連結是我參考的文章
https://blog.csdn.net/baimafujinji/article/details/78588488
照著做直到terminal輸入cmake是可以執行的就行了


2. 下載source code
直接從github clone下來最快
$git clone https://github.com/IntelRealSense/librealsense.git

3. 編譯pyrealsense2
在terminal中進入剛剛clone下來的realsense資料夾
輸入下面的指令
$mkdir build
$cd build
$cmake ../ -DBUILD_PYTHON_BINDINGS=TRUE
$make -j4
*如果environment的python版本不一樣
   在cmake指令後面再加上這個argument就可以了
   -DPYTHON_EXECUTABLE=[full path to the exact python executable]

接著讓電腦跑(伴隨mac的悲鳴??)
都沒有error就OK了

其實跟windows系統的大同小異
不過不用像windows的cmake需要那麼多設定

4. 移動.so並建立symbolic link
跑完之後在librealsens/build/wrappers/python 資料夾中會有這些檔案


我們要建立pyrealsense2與pybackend2移到python的lib並建立的sybolic link
系統才找得到

將pybackend2.2.24.0.cpython-37m-darwin.so
與pyrealsense2.2.24.0.cpython-37m-darwin.so
複製到anaconda3⁩/⁨envs⁩/⁨(Env name)/⁨lib⁩/python3.7/site-packages
並在這個資料夾
然後使用下面的command
$ln -s pyrealsense2.2.24.0.cpython-37m-darwin.so pyrealsense2.so
$ln -s pybackend2.2.24.0.cpython-37m-darwin.so pybackend2.so

目前測試在anaconda的virtual env可行
如果沒有裝虛擬環境
在python的site-package加入也可以

另外還有一個方式是用make install
但是我不想直接裝在我的系統上所以我沒有使用這方法

5. 測試(可import但無法測試是否真的能跑)
進入你的虛擬環境
然後import pyrealsense2
沒有跳出找不到的錯誤就可以了


可惜我的mac沒有usb 3.0可以插所以不能測能不能真的跑起來
看有沒有人可以幫忙測的XDD

歡迎留言討論

Reference:




2019年5月21日 星期二

[語言]一個月新制多益935分!!!我如何準備

為了求職更順利
而且高三時考的多益證照(825分)其實也已經過期了
所以三月的時候決定去考新制多益

考慮到口試完之後會有另一波求職
因此我報考2019/04/28的場次

5/20懷著忐忑的心查成績
結果竟然出乎我預料


(人生的高峰所以請讓我炫耀一下><)

太開心的PO文之後滿多人跑來問我做了什麼準備
所以整理一下我讀了那些書用了什麼資源
讓想準備的人可以參考

1. 刷題
練習題目是最重要的一環
原因是要練習控制時間以及專注度
雖然練習時的時間掌控狀況跟實際考試上一定有落差(比如說練習時都可以剩10分鐘結果實戰時最後十分鐘還剩兩個題組)
不過如果你練習時都寫不完
就要嘗試找出是哪一個部分是弱項最拖時間

我使用的題本是這一本:

New TOEIC多益新制黃金團隊5回全真試題+詳解(附2MP3+防水書套)


我個人覺得他的單字難度與題目方向跟真的多益考試差不多
詳解本有附帶題目也是很棒的設計
PS: 阿滴有推薦這一本的樣子

因為覺得自己閱讀比較弱(考出來之後的確是閱讀比較弱)
我有另外刷閱讀的題目:


但是這一本的題目與單字就有點偏難
直接寫可能會造成自信心低落
時間不夠所以我也只有寫+檢討三回


2. 閱讀的準備
閱讀的部分我覺得我的弱項是一開始的文法與單字部分
所以我用這一本書補強文法:

不過我覺得這一本其實比較適合有多一點時間準備的人
因為他內容真的超多(而且超級重)
基本上我只有翻完他的文法部分而已

3. 聽力的準備(其實沒什麼準備)
聽力的部分我個人認為是無法速成
所以我也沒有特別練習
頂多有空檔的時候看TED(而且還選不是商業的影片哈哈)
考前一天突然想起成大有Eazy Test這個資源
所以就用它的多益訓練課程來練聽力
課程內容提供了滿紮實的考試技巧訓練
(如果我早一點拿出來用或許閱讀會更高分)

4. 單字
單字我也是有空就背
前半個月我用百詞斬(中國大陸的APP)
後半個月我有空就會翻

他的編排方式是單元式
每一單元約40個單字
單元後面會有分級的單字表
可以視自己的程度去選擇要背什麼單字

檢討的時候我也會把不會單字抄下來
好處是很多常出卻不會的單字會因此印象更深刻

5. 檢討
檢討也是很重要的一環
我會在檢討的時候把不會的單字片語或常用句子整理起來
容易搞錯或不會的文法也會再抄寫一遍或自己想例句


以上是我一個月準備新制多益的方法與資源
當然其實我覺得能考成這樣有一部分也是運氣不錯
畢竟我很幸運地坐在撥放器前面
聽力聽得超級清楚

還在徬徨如何準備的朋友可以參考看看
祝福大家都拿到金色證書

2019年5月17日 星期五

[筆記][tensorflow]Batch Normalization Layer用法(Key Variable not found in checkpoint)

最近在改寫3D Faster RCNN時遇到了一些使用上的問題 ,
因此稍微筆記一下。

首先介紹一下Batch Normalization~
Batch Normalization 最早是在2015年被提出,
主要是解決network在訓練過程中遇到的Covariate shift問題
透過正規化minibatch,加速網絡訓練及穩定性。

最常用來解釋Batch nomalization對於增加訓練網絡的穩定性的方式為下圖:


圖片中我們可以看到,
加入Batch normalization的每一層網絡輸出的分布比較常態,
沒有發生有效值被shift到兩個極端,
這對於使用tanh或sigmoid兩種activation function的網絡來說有很大的幫助,
因為這兩個函數最敏感(變化最大)的區域有一定的範圍,
輸入的分佈超過範圍就會有飽和的問題。


因此如果加入Batch normalization,
會讓訓練避免陷入飽和的狀態。

而ReLU雖然不會遇到飽和的問題,
但是使用Batch Normalization還是可以避免梯度爆炸與加速訓練。

目前我謹了解其物理意義,
對於Batch Normalization的數學模型與詳細細節有興趣的朋友也可以去找他的原始paper或是其他資源來看。
或是之後我有空看完也會來努力補上。

接下來是Batch Normalization的tensorflow用法,
我使用的tensorflow版本是1.12。

要將Batch Normalization layer加在convolution layer的activation function之前,
這邊我用的是tf.layers.batch_normalization,
beta值、gamma值、mean與variance等超參數都使用預設值,
這些值可以自己調整,
(另外如果用的是tf.nn.batch_normalization,
就要自己先定義存超參數的tensor。)

要正確的訓練記得將training這個argument設為true。

接下來就是我遇到問題的地方。
因為batch_normalization的參數不屬於trainable variables,
若沒有設定tf.train.Saver儲存global_variable,
儲存的model將不包含訓練好的batch_normalization layer,
inference的時候會出現找不到的錯誤:Key Variable not found in checkpoint
因此在宣告saver時要加入save global variable的設定 var_list=tf.global_variables()



就可以解決了~~


本篇主要是筆記一下
(1) batch normalization的物理意義
(2) tensorflow的寫法
(3) save batch normalization model時的注意事項

參考資料:





https://www.tensorflow.org/api_docs/python/tf/layers/batch_normalization

https://www.tensorflow.org/api_docs/python/tf/nn/batch_normalization

2019年3月18日 星期一

[筆記][Git/Github][存參] 檔案過大 error: File ******** is 527.84 MB; this exceeds GitHub's file size limit of 100.00 MB

最近開始把一些時作放到github上
今天遇到了一個問題
就是我的pretrainied model太大了沒辦法push
但是刪掉了之後
error message還是一直出現

後來搜尋了解決辦法
這裡筆記下來
應該會記的比較清楚
(雖然之後會注意檔案大小不要再亂上傳一通)

Error message:
$ git push origin master
Enumerating objects: 20, done.
Counting objects: 100% (20/20), done.
Delta compression using up to 8 threads
Compressing objects: 100% (14/14), done.
Writing objects: 100% (17/17), 489.89 MiB | 14.18 MiB/s, done.
Total 17 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
remote: error: Trace: 4d46485696166b616f2204cef94eef80
remote: error: See http://git.io/iEPt8g for more information.
remote: error: File xxxxx is 527.84 MB; this exceeds GitHub's file size limit of 100.00 MB
To https://github.com/j6079633/CrossModalDistillation_Keras.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://github.com/j6079633/CrossModalDistillation_Keras.git'

Solution:
$ git filter-branch --tree-filter 'rm -rf path/to/your file' HEAD

這一個指令是過濾過去提交紀錄中超出限制大小的檔案移除。

結束後再下一個push的指令即可

但是因為前面整個流程我亂試一通變得有點亂
所以我有先下一個pull rebase的指令

$ git pull --rebase

如果是只有自己的project
很清楚明白有哪些地方被修改的話
可以使用force pull

$ git push -f

最後進行push就可以了

git 是專案管理滿重要的工具
CP值滿高的技能值
雖然大學時有稍微學一下但後來不常用就忘了
不過現在撿回來磨一下應該還來得及><

參考資料:

https://andy6804tw.github.io/2018/12/09/git-exceeds-size/

https://gitbook.tw/chapters/github/fail-to-push.html

https://blog.yorkxin.org/2011/07/29/git-rebase.html