Tag

2018年1月25日 星期四

使用 git submodule 管理 project 所需的其他模組

故事是這樣子的,最近在寫一些分析資料的程式,用的是 python 跟 numpy。
一開始改寫的時候,發現一開始 load 資料的地方,Python 實在太慢了,載入 20000 筆資料耗時超過 150 秒,後來決定用 C++ 改寫載入數據的部分,同時利用別人寫好的 cnpy 這個 project,寫出 numpy 檔案作分析,結果載入速度竟然一口氣降到 4.5s,加速 15 dB,太可怕了(yay。
為了要使用 cnpy 這個 project,順手研究了一下要如何給使用 git submodule,這篇文章就做個介紹:

基本上submodule 利用的場合,就如我上面說,我的 project 要用到其他 project 的程式碼,我希望讓我的使用者能直接拿到其他 project 的程式碼,這樣他們不用自己再去載,麻煩之外可能還會載到錯誤的版本。
另一方面,我們又不想把對方的程式全部加到我的 repository 裡面,這樣會帶來不好的後果,上游的程式碼修改,要自己手動更新,沒辦法用 git 的方式更新,增加錯誤的機會。
git 針對這個使用情景的解決方式就是 submodule:

遇到 submodule 的時候通常有兩種狀況,第一種比較常見的,是載了一個別人的project,發現裡面有用到 submodule,例如知名的補齊工具 YouCompleteMe ,裡面針對各種語言的剖析工具都是 submodule,這些專案載下來的時候, submodule 裡面都還是空的,要先用下列指令把 submodule 載下來:
git submodule init
git submodule update
或者可以用一行指令解決:
git submodule update --init --recursive
--recursive 會在 submodule 裡面還有 submodule 的時候,一口氣都設定好。
又或者可以在 clone 專案的時候就指定要一齊複製 submodule(不過通常在 clone project 的時候還不知道裡面有 submodule,所以…通常不會這樣下):
git clone --recurse-submodules url

第二種狀況如我上面所述,我們自己新增一個 submodule,我要做的就是新增 cnpy 為我的 submodule:
git submodule add git@github.com:rogersce/cnpy.git cnpy
後面的 cnpy 是指定 submodule 要放在哪個資料夾裡面,通常名稱都跟 project 本來的名稱相同,才不會搞混;這時候記得會開始把這個 project 下載下來,接著檢視 status 的話會看到下面的內容:
new file: .gitmodules
new file: cnpy
.gitmodules 檔案裡面記錄了 submodule 的名字,現在的路徑以及遠端 url,這是可以用 add 及 commit 將這個 submodule 保存下來。

在一個有 submodule 的專案裡面工作,會和一般的工作內容稍有不同,當進到 submodule 內部的時候;submodule 從外面來看只是一個參照,如果真的進到這個資料夾,用起來就會像另外一個 git repository 一樣,一樣會有 master 等等 branch,可以用 remote 拉別人的修改下來,或者自己送 commit 出去。
比較讓人疑惑的通常是在外面的 project,當內部的內容有修改的時候,外面會出現一些讓人很疑惑的訊息,例如當我們對 cnpy 這個 project 新增一個 commit,從外面會看到這樣的訊息:
git status
modified: cnpy (new commits)
這則訊息的意思是,cnpy 這個 submodule 有了修改,修改的內容是新增了 commits;可以把git submodule 想成一個快照,現在 submodule 的狀態已經脫離這個快照,從 git diff 就會看出差別,最下面是 commit 的修改訊息:
git diff
diff --git a/cnpy b/cnpy
index f19917f..8f997be 160000
--- a/cnpy
+++ b/cnpy
@@ -1 +1 @@
-Subproject commit f19917f6c442885dcf171de485ba8b17bd178da6
+Subproject commit 8f997be1f87279f09054acbdb896162b1e9d3963

這時對這個 submodule 做 add, commit,就會更新這個 submodule 的快照值;另外如果我們想要 submodule 維持在之前的快照上,用 git submodule update ,git 即會將 submodule 簽回到當初記錄的版本:
git submodule update
Submodule path 'cnpy': checked out 'f19917f6c442885dcf171de485ba8b17bd178da6'

不過 update 之後會有一些不好的效果,因為這時 submodule 被強制簽出 f19917 這個 commit ,裡面就出現了一些沒有 commit 的修改,在這裡有內容未被 commit,所以它會顯示:
git status modified: cnpy (untracked content)
從外面會看到 submodule 有修改,但要消掉這個 untracked content 的訊息,就要進到 submodule 資料夾裡面,用 checkout 或 clean 的方式,讓 submodule 的狀態回到 clean 才行。
但同時也要注意的,這時候 submodule 就處在 detach HEAD 的狀態(在上面的例子,submodule 落後 master 一個 commit ),這時進到 submodule 做些 commit,這些 commit 並沒有 branch 參照,下一次再下 submodule update 的時候,所做的修改就會消失,如果有要修改的話,建議要先在 submodule 裡面生成一個 branch 來保留修改。

另外一個比較需要注意的,大概就是在移動 submodule 的參照的時候,儘量可以用 git mv 的方式來移動,用 os 本身的 mv 似乎比較容易出問題。

我想 submodule 我們就講到這裡,下面這篇官方的參考資料:
https://git-scm.com/book/en/v2/Git-Tools-Submodules
裡面還有很多 git submodule 神奇的用法,例如從外面用 git submodule 指令一口氣更新所有 submodule 的狀態到最新,把所有 submodule 現下的狀態推送到遠端,等等。
但我個人認為 submodule 相對來說是比較偏門的指令,自己也是用了這麼久,最近才第一次用到 submodule,大家還是用到再來查文件比較實在;另外話又說回來, git submodule 能管理的相關 project 數量也是有個限度,數量多到一定程度,submodule 也會顯得捉襟見肘,因而 google android 才會另外推 repo 這樣大量 git repository 的管理工具吧。

2017年12月31日 星期日

Git 教學影片系列

故事是這樣子的,自己大概三四年前開始使用git,一路用到現在,對 git 相關的功能算是相當熟悉,有時也會負責教其他人使用 git,自己的 blog 上其實也留了不少 git 相關的文章:
https://yodalee.blogspot.tw/search/label/git
大約兩個禮拜前突發奇想,反正都要教,乾脆就錄個影片,以後只要貼影片給別人看,不只教認識的,還能教虛擬世界中「沉默的多數」(誤),想著想著稍微規劃了一下,好像還真的有點模樣,於是就開始了我變身網(ㄈㄟˊ)紅(ㄓㄞˊ)的第一步。

最後確定的版本有以下幾集的影片,一部是相關的功能,順便還會介紹一些社群習慣或是我自己的習慣,講得滿雜的,也有些出錯的地方:

Ep. 1 安裝與設定
Ep. 2 Add, commit:50/72 rule, gitignore, commit hash, DAG
Ep. 3 如何指定一個 commit:hash, HEAD, ^ ~, reference, show, log, diff, diff --staged
Ep. 4 patch add and amend
Ep. 5 branch, checkout, merge:解衝突
Ep. 6 Rebase:rebase -i,解衝突
Ep. 7 遠端開發,使用 github:用 gitgraph.js 的開發經驗,來說明如何使用 github
Ep. 8 stash
Ep. 9 format-patch, apply, am, cherry-pick 各種搬移工作的方法
Ep. 10 bisect, blame
Ep. End ending:講一下一些沒提的東西

自己做起來就發現,想當 youtuber 要費的功夫真的超級大,除了初期收集資料跟準備材料,投影片跟 demo 用的 project,還要確認要講的內容沒有錯誤;真正錄的時候可能還要多錄幾遍,確定哪裡說不好要改進,如果不小心說錯了,要重頭再錄一次,事後還要上傳 Youtube 修改影片資訊等等。
像後來,就發現我在安裝的那章其實有個錯誤,Windows系統不能只安裝 Tortoise git,還要安裝 git 才行…不過暫時還沒想到怎麼去修正它(yay;如果像那些網紅一樣還要加後製,那成本真的超級大,我猜不組個小團隊其實是很難撐起來的

總之這些是最後的成品,收到一個 youtube 播放清單中:
https://www.youtube.com/playlist?list=PLlyOkSAh6TwcvJQ1UtvkSwhZWCaM_S07d
或者下面是嵌入式的影片:

自己回想起來其實花了非常多時間在錄這些教學影片,還去買了新的麥克風,錄製跟剪輯影片分別使用 obs 跟 ffmpeg 剪輯指令,因為加特效太麻煩,所以就沒加特效 =w=,希望這些影片能對大家有所幫助。
雖然本人比較喜歡低調路線,不過想想,既然都花了這麼多時間,這些影片要是沒有人看就太可惜了,因此來學一下農場的做法,希望大家覺得影片有你幫助的話,就幫小弟分享一下,無論是這篇文章或是上面 youtube 播放清單的連結都可以。

ps. 也能透過blog 旁邊的連結,用 Paypal 給點賞,鼓勵一下小弟,不過這不強求啦,畢竟 Paypal 手續費抽滿貴的。

2017年12月5日 星期二

不當行為

本書的全名是:<不當行為:行為經濟學之父教你更聰明的思考、理財、看世界>
本書的內容,是在說明作者從他研究所的研究開始,從開始單純收集資料,其後開始設計實驗發表研究,挑戰主流經濟學的觀點,最後創立行為經濟學這一個經濟學的分枝的整體回顧。

所謂的不當行為,其實是從當時主流的經濟學標準模型來看,標準模型將人類行為理想化之後,藉由簡化後的<人類>來對經濟做出預測,並在數十年間越發展越強勢,變成所有經濟學的現象都要依靠標準模型化的<人類>來解釋,任何偏離標準模型的研究,或者宣稱人類不符合標準模型的行為,在當時都會被視為異端邪說。
作者一路以來,就是不斷從人類的行為中尋找與標準模型不同的行為(也就是所謂的<不當行為>),試圖去推翻標準模型。

這裡可以舉一些很簡單的例子,例如標準模型可能假設,無論任何情境下,人對金錢是一視同仁的。
但有研究指出,我們對待同樣的 100 元,獲得 100 元跟損失 100元的快樂跟難過卻不是等價的,人們對損失會比獲得敏感,傾向規避損失。
又或者去便利超商買東西,如果店員告訴你,現在買的50元的商品在一個街口之外有5折的活動,你可能會為了這25元走一個街口;但如果你今天是買一台10000元的電視,一個街口外的賣場有 9950 的商品,你卻不會為了省這50元跑過去。對同樣的 50 元,我們在不同的情境下會有不同的價值,有時會想省有時不會--又是一個違反標準模型假設的行為。

當然如果只是把行為列出來是不會有什麼效果,畢竟人類如此複雜,標準模型也會推出它們對此類行為的解釋。為了推翻標準模型的假設,作者必須設計實驗,把實驗變數壓到最低,證實很難找到其他理由,讓標準模型能夠合理解釋這樣的行為,從而慢慢推翻標準模型中<人類的行為是完全理性、完全數字>的假設,書中對這些實驗的設計都有粗略的介紹。

要澄清的是,其實標準模型並沒有不好,人類行為跟思考太過複雜,沒經過適當的簡化,如果一開始就栽進複雜的世界,從一開始就會卡住完全做不出成果。
如果要我比喻,用我熟悉的電路來說,半導體的行為十分複雜,如果我們一開始就用完全真實的電晶體去做設計,光是最簡單的電路都做不出來,所以研究上會弄出截止區、飽和區的平方模型,幫助我們在大方向上可以近似電晶體的行為,才能有效的去設計複雜的電路。
但當模型被用過頭,假設所有電晶體都是理想的元件,許多真實電晶體的行為反而被忽略,而在設計上無法精準預測電路行為,這時就要打破舊有模型的框架,去創造新的模型才能更好的預測電路。
把平方模型/電晶體/電路代換成標準模型/人的行為/經濟活動,差不多就是這本書在談的事情。

在書中最後幾章,也有舉些例子說明,如何把行為經濟學的研究成果放在政府的政策下, 配合上面所說的,人類對損失比獲得更為敏感,讓我想到最近高雄市為了因應空汙推動大眾運輸,推出冬季搭乘大眾運輸免費的政策,這是一種獎賞或是<蘿蔔>的策略;如果要推大眾運輸,還有一種方式就是提高私人運具的成本,例如加強違停的拖吊,提高路邊停車的收費,這則是懲罰或是<棒子>的策略。
對照上面的人的行為,會知道用<棒子>政策可能會比<蘿蔔>有效很多,要把同樣人次趕向大眾運輸,騎機車被開單20元,可能在要大眾運輸上補貼40 元才有同樣效果,當然也是因為這樣,<棒子>的政策通常比較被大家討厭,造成政府常常在補貼政策上花大錢,成效卻不是很好,也許這是政策制定者值得參考人類行為研究的部分了。

若要說這本書有什麼缺點,我覺得這本書有點草率,全書的翻譯不是很好,漏字錯字有點多(我至少看到三個,沒看到應該會更多),注譯選擇在每章節結尾而不是出現的當下,書末也沒有附翻譯表跟專有名詞索引,讓人看得有點痛苦,原作有可能比譯作更完善一些。
我覺得以行為經驗學來說,這本書不算深入,但要說是導讀,說得有不夠全面能夠真正了解行為經濟學,如果沒有要完全看懂背後的經濟學原理,當小故事書看過去還算可以接受。整理來說不推,相關的議題,市面上應該會有更好的書才是。

最後是書名,其實後面那個「行為經濟學之父教你更聰明的思考、理財、看世界」根本是亂加的,真正的書名就是「不當行為:行為經濟學的形成史」而已,至於幹嘛加上思考、理財,我猜純粹為了吸引眼球吧,實際內容一點和理財一點關係也沒有。

2017年10月7日 星期六

論共享

其實這篇文章7月底就寫好了,只是一直都沒有寫完整貼出來,現在共享經濟已經慢慢進到衰退期,新聞也沒這麼多了,貼出來單純就是留個記錄XD

隨 obike 進駐台灣,讓台灣自 Uber, Airbnb 之後,再一次體會一個共享的商業模式,撇開 obike 違停造成的爭議不談,有一派的說法是,共享其實毫無新奇之處,飯店就是共享豪宅,圖書館是共享冷氣…等等。

我認為共享其實是個新概念,而不如上述所說,只是一個很潮的新名詞。

在過去其實是沒有共享,而是<公用>和<租用>的綜合,提供方是政府公家單位,亦或法人企業,就如上例中的圖書館、飯店,要服務大量使用者,清潔和維護都需要成本,因此會出現一個單一節點的單位為管理者。
與過去的公用及租用不同,現今的<共享>是運用科技,將管理的成本降到極低,同時允許一般民眾都能成為服務提供者,Uber, Airbnb 屬於此類,Uber 直接結合 GPS、刷卡付費、手機App 等科技工具,跳過管理計程車的規則,讓每個人都能成為計程車司機;Airbnb 則是允許任何有閒餘房子的人,上網登記提供給任何有短期需要的背包客。

兩者的重點都在於,透過網路大範圍的招募供應方和租用方,這和過去的公共提供,有單一節點的供應方截然不同。

就我這個定義來看,共享單車是否可稱為共享其實是存疑的,畢竟任何共享單車仍然有對應的管理單位,而非共享每個人擁有的腳踏車,我家裡有多的腳踏車,我並沒有辦法上網登錄,然後把它丟到路上讓大家使用,使用現行的腳踏車,一樣要向公司繳交押金、使用費用(對…其實就租金),這跟 Uber, Airbnb 所謂的共享,仍有一段差距。

目前共享在各地引起的爭議有兩種,第一如 Uber,在於直接挑戰各地政府對於出租車的規定;第二如共享單車,在於企業出租腳踏車大量佔有公眾資源(ex,違停佔用機車格、人行道),造成社會整體效能下降,引來政府管制。
共享單車的爭議其實不算太複雜,本來基於社會最大福祉,維護公有資源的使用量,政府就會對市場上的交易有所管制(我知道我寫最大福祉會有點爭議…不過我們姑且相信是這樣,才能討論下去),例如路邊停車格的設計,就是限制路邊(公共空間)的使用,以價制量,並對不守規矩的人拖吊,才不會變成馬路上都停滿車子;另外像是客運路線,政府也會介入,管制單一路線由哪些業者經營,以免熱門路線被大客車佔滿,偏遠路線卻無業者經營。

資源愈是珍稀,管制的範圍就會愈廣,由政府單一節點介入管制的機會也愈高;Obike 的爭議,充其量只是它把原先公眾免費的資源,使用到盡乎枯竭,引來政府介入罷了。

當然,這裡面牽扯的,又有更大程度的問題,像是政府規範的界限,政府究竟要無所不包還是儘量退縮?以 obike 的案例來看,這條界限是浮動的,取決於資源是否已經稀有到,需要政府介入分配,以免造成社會整體利益減損。
裡面牽扯的,包括我一直很好奇的,如日本的大手私鐵(ex. 阪急電鐵),它們在城市內建設地下化車站的時候,究竟是如何進行公眾相關的工作,例如土地取得、出口用地取得、交通維持等,這類具有稀有性和排他性的公眾資源使用。
我不認為一般的私人公司有這樣的權利進行這樣等級的資源分配,但若是由私鐵請求政府協助進行,豈不是政府直接圖利廠商?在市區內鐵道這種高度珍稀資源,難不成竟然是私鐵先搶先贏,建成之後完全排除了其他私鐵競爭的能力?想起來多少覺得有點怪怪的。

總之,針對共享經濟,我認為這是科技對社會又一衝擊,每每都在挑戰社會的接受度和容忍度,以及政府畫定管制界限的位置,除了完全禁止和完全開放之外,我們會需要更多的民眾討論,動態的進行實驗,我想是比較有效的解決方式。

2017年8月25日 星期五

使用rust closure實作fizzbuzz

之前用Rust 重寫Understanding Computation 裡面的ruby code,目前從 github 上來看,我的 Rust code 應該是僅次於原作者的 code,完成度最高的一個版本。
從去年五月,把大部分的 code 完成以來,唯一一個沒寫的章節:chapter 6 的 fizzbuzz,最近終於實作出來了\weee/。

本來我是用了比較直接的方法,也就是把closure 用function 來實作,使用generic 的方式來處理參數,例如在數字的部分,我們就要接受一個函式跟一個參數,這個函式要吃一個參數然後吐一個參數……。
例如我那時候實作的正整數的部分:
fn ZERO<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { x }
fn ONE<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(x) }
fn TWO<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(p(x)) }
fn THREE<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(p(p(x))) }
fn FIVE<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(p(p(p(p(x))))) }

這個寫法的問題是啥?問題在於…我必須手動處理所有的型別,這在只有數字、布林的時候還容易處理,等到型別一複雜,這種函式宣告根本寫不出來,然後編譯器噴你一臉錯誤,最終完成的也只有number 跟 boolean,甚至連下一段的 is_zero 都實作不出來,程式碼我保留在 ch6-fizzbuzz 的分枝裡。

最近有天心血來潮,把我的 Rust code 實作成果貼到 rust forum:
https://users.rust-lang.org/t/computation-book-example-code-implemented-in-rust/12403
在討論串的下面有一位 jonh 回了我,他的辦法挺聰明的,實作的方式也比較符合這個 project 的要求,首先呢,我們不要處理這麼多型別的問題,把所有的型別都收到一個enum 之下:
pub enum Pol {
    C(Rc<Fn(Rp) -> Rp>),
    I(i32),
    B(bool),
}
pub type Rp = Rc<Pol>;
macro_rules! r {
    ($cl:expr) => {Rc::new(Pol::C(Rc::new($cl)))}
}

impl Pol {
    pub fn call(&self, x: Rp) -> Rp {
        match self {
            &Pol::C(ref c) => c(x),
            _ => panic!(),
        }
    }
}

型別 Rp 是用 rust 的 reference count pointer (Rc) 包裝這個 Pol 的型別,Pol::C 則是包裝一個 Rc 包裝的函式,該函式會吃一個Rp,吐一個Rp,等於是封裝了一個 lambda 函式。
另外我們利用自定義的 macro,讓產生這類封裝的 lambda 函式更容易,最後我們定義呼叫的 call 函式,它會把 Pol::C 裡的函式取出來,用 c 取用參數 x 執行。
這樣,就完成了函數的基本型態。
接著我們就能跟著這本書,一步步打造 fizzbuzz 的程式碼,例如上面提到的正整數的部分:
let zero  = r!(|_p| r!(|x| x));
let one   = r!(|p: Rp| r!(move |x| p.call(x)));
let two   = r!(|p: Rp| r!(move |x| p.call(p.call(x))));
let three = r!(|p: Rp| r!(move |x| p.call(p.call(p.call(x)))));
let five  = r!(|p: Rp| r!(move |x| p.call(p.call(p.call(p.call(p.call(x)))))));

這樣寫的問題是,我必須把所有的 closure 定義寫在 main 函式裡,因為 rust 不允許以 use 的方式,引入定義在別的檔案的 closure,以致最後 main.rs 高達 600 多行。
第二個問題是由於Rust 的所有權特性,在定義每個 closure 的時候,會需要不斷的 clone,例如 multiply 的 closure,需要用到 add 還有 zero,所以我們就要一路把 add 跟 zero clone 下去,寫到複雜一點的closure 例如 divide,需要使用 if, is_less_than, increment, subtract, zero,一個closure 的定義橫跨40 行,這寫法我覺得真的不行,不過一時之間真的找不到更好的寫法。
// multiply
// |m| { |n| { n(add(m))(zero) } }
let multiply = {
  let add = add.clone();
  let zero = zero.clone();
  r!(move |m: Rp| {
    let add = add.clone();
    let zero = zero.clone();
    r!(move |n: Rp| {
      n.call(add.call(m.clone())).call(zero.clone())
    })
  })
};
最後,我沒辦法把 Rp 這個函式印出來,像書裡面印出橫跨數頁,狀觀的lambda函式,這個問題也暫時無解。

最後的成果,完成的 fizzbuzz 所下所示:
let solution = {
  map.call(range.call(one.clone()).call(hundred.clone()))
   .call(r!(move |n:Rp| {
     _if.call(is_zero.call(module.call(n.clone()).call(fifteen.clone())))
        .call(fizzbuzz.clone())
        .call(
          _if.call(is_zero.call(module.call(n.clone()).call(three.clone())))
             .call(fizz.clone())
             .call(
               _if.call(is_zero.call(module.call(n.clone()).call(five.clone())))
                  .call(buzz.clone())
                  .call(to_digits.call(n.clone()))
             )
        )
   }))
};

執行起來慢的要死,fizzbuzz 1-100 費時 51s ,如果真的用 rust 寫,根本不用1 ms 好不好。
當然了,最終能用 rust 把這篇奇文給實作出來,還是覺得滿有趣的,中途也曾出現過,因為一個括號括錯地方,瞬間讓 multiply 變成 power 3*5 = 243,WTF!我至今還參不透,究竟為什麼括號括錯就會讓 multiply 瞬間升一級變 power OAO
我的程式碼都收到master branch 下面,可以參考 github連結,體會一下 functional programming 的奧妙之處XD
這篇文其實根本是「重新發明輪子的極致」,不止是演算法,我們要把整個整數系統、真偽值什麼的,都重新打造一遍,有一種我們先來種顆樹,長出來之後砍下來變木材,作成工具台之後開始打造輪子,感情我不是在寫 fizzbuzz,而是在玩 minecraft 呀(X。

Related Posts Plugin for WordPress, Blogger...