文章

[Side Project] 互動式教學神經網路反向傳播 Interactive Computational Graph

圖片
成品 互動式網頁連結:  Interactive Computational Graph 微積分課經典的範例 df/dx sin(x) / x: 2 個 inputs, 1 個 output, 沒有 hidden layer 的 fully connected neural network: 為何要做這套工具 以前在學 machine learning 的時候覺得最難理解的部分就是 backpropagation,也就是神經網路到底是怎麼用數學來做訓練的。 我們知道神經網路就是長這樣子: Source: Wikipedia 但實際上每個 neuron (圓圈圈) 裡面有許多基本的數學運算,像是 sum 還有 activation function: Source:  Neural Networks and Machine Learning 當我們把所有東西綜合在一起,腦袋就會打結,到底 chain rule 是怎麼在這麼複雜的神經網路之間套用的? 就算了解基礎也很難相信實際上到底怎麼運作的。我又沒有要真的使用的話,網路上的教學就覺得看看就好 Source:  A Step by Step Backpropagation Example 但決心要了解 backpropagation 細節之後,不少教學都提到了 computational graph,從基礎的數學運算當作 nodes 來去理解似乎是比較簡單的方式。 後來找到了這篇文章  Calculus on Computational Graphs: Backpropagation  把 chain rule 講解的簡單至極,於是就在想,為何我不做一個網頁,讓大家可以自由地組合想要的 graph,然後看一下 chain rule 是怎麼流通的。 失敗作經驗 在做這次 side project 前,其實早在一年前我就開始作另一個 side project 是想展現神經網路的訓練過程。名稱叫做 explain-derivatives,是被 TensorFlow 的一個網頁 A Neural Network Playground 啟發的。 但由於一開始欠缺規劃,想到什麼就加什麼功能進去,造成架構一直修改,最後由於程式碼架構太過複雜就放棄了。 這個 project 是用 Cytoscape.js 為底,加上 TypeScr

[工作經驗] Rust tokio 不想 await task 但卻想提早知道有沒有 error

圖片
假設我們想要平行地跑兩個 tasks A 和 B,我們用 async task A 來跑主要的程式,用 task B 來監控正在跑的程式。Task A 跑完了我們就不管 task B 可以直接結束程式了,所以 task B 我們並不想用 await 去等待他。 這時候就有一個小陷阱正在等著我們:如果 task B 提早發生錯誤,我們可能就會忘記去處理他。 壞程式 最近就不小心寫出像下面這樣的 code: use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { let task_a_handle = tokio::spawn(async move { tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; if true { anyhow::bail!("Something went wrong in task A"); } else { Ok(()) } }); tokio::task::spawn_blocking(move || { std::thread::sleep(std::time::Duration::from_secs(1)); if true { anyhow::bail!("Something went wrong in task B"); } else { Ok(()) } }); match task_a_handle.await? { Ok(_) => println!("Finished"), Err(e) => panic!("Something wrong: {e}"), } Ok(()) } 我們可以看

[工作經驗] 用 ChatGPT 加速軟體開發流程

圖片
ChatGPT 問世已經一段時間了,但我一直沒試著將它融入平常的軟體開發,因為之前問的問題覺得好像還是無法取代 Google 或是 StackOverflow。直到同事說他用 ChatGPT 幫他寫平常不熟悉的程式語言,我才覺得好像應該來試用看看? 試用了一個月之後,我發現 ChatGPT 還真的對軟體開發加速了不少,以下是我平常愛用的一些方法。 結合多個簡單的答案: 1+1>2 我知道怎麼用 Python 讀取檔案嗎? 簡單,用 with open 我知道怎麼用 Python 搜尋資料夾底下所有的檔案嗎? 大概 Google 一下就知道 但我知道要如何計算資料夾底下、任何有包含某個字串的檔案數量嗎? 大概要花我 5 分鐘 Google 好幾篇文章然後自己想辦法用一些 for 迴圈組合起來。 但如果我把這樣的問題丟給 ChatGPT: How do you find the number of files that contain a certain substring in all the files under a directory recursively with python 3? ChatGPT 就能在 10 秒內給出答案: import os import fnmatch def count_files_with_substring(directory, substring): count = 0 for root, dirs, files in os.walk(directory): for file in files: file_path = os.path.join(root, file) if fnmatch.fnmatch(file, '*.*'): # filter by file extension if needed with open(file_path, 'r') as f: content = f.read() if substring in content:

[技巧] 用 VSCode 把 OneNote 快速轉換到 Markdown + 包含程式碼

圖片
工作上的筆記總是寫在 OneNote,但如果想要貼部分資訊到 GitHub/GitLab 中,單純用複製貼上整個格式就會跑掉,還要手動一個一個調整滿麻煩的。有沒有什麼快速的方法來把 OneNote 轉換到 Marrkdown 格式呢? 範例 OneNote 文件 由於 OneNote 中我常常用 tables 來把 code 片段包起來,所以很常筆記會變成下面這樣: Pandoc 工具 網路上常見的解法背後其實常常用了 Pandoc 這個工具,如果我們按照這個 教學方法 把上面的 OneNote 文件轉換成 Word 再轉成 Markdown 會變成這樣: 我們會發現 tables 與程式碼的 indents 整個都跑掉了,就不是我預期的。 而且還要先匯出成 Word,再開 PowerShell 輸入指令,如果頻繁的做這件事就會覺得很累,尤其是還在修改部分細節的時候。 VSCode + Replace Rules 插件 如果我們直接複製 OneNote 的文字,在 VSCode 貼上,會發現他會貼上這樣的格式: 除了文字前面的小圖示,我們會發現他的 indents 終於有保留下來。所以我一開始都是手動用 Ctrl+D 或 Ctrl+Shift+L 把 tabs + 小圖示選起來再改成 Markdown 的格式。Tables 比較麻煩要手動在前後加上 ``` 這個動作做久了之後我就想到,這個動作其實和 find regex & replace 在做的事其實差不多,為何我不用 regex 來自動做到呢? 如果我們去裝 Replace Rules 這個插件,並且在 settings.json 中加上一些設定 (在文章的最後)。 之後,先在最下方空兩行 (為了讓 table bottom regex 可以正常運作),我們就可以在 VSCode 中按 Ctrl+Shift+P 然後輸入 Run Ruleset -> Ruleset: OneNote plaintext to markdown,剛剛貼過來的 OneNote 文件就會變成下方這樣: 我們再手動把 Title/Subtitle 加上 # 與 ##,把結果貼到 GitHub 的 editor 上,預覽就會正確地把 code block 顯示出來: 再也不用手動慢慢轉換啦。 筆記格式的限制 這個方法看

[工作經驗] 為何同事(自己)寫的程式碼總是很醜?

圖片
Code 可讀性低又到處重複的話,經常導致 bugs 不斷發生。一旦我們用 git blame 發現是同事幾年前寫的 code,就會想說為什麼這麼醜? 但如果是自己寫的,就...沒辦法啦,以前不知道在想什麼亂寫的。真的可以這樣想嗎? 沒辦法寫出 Good Code 的阻力 要怎麼寫出好的 code? 有些原則我們都可能聽過:  KISS , DRY , YAGNI , SOLID , 甚至是 CRISP ,一定還有一大堆我沒列出來的原則,這些都是不錯的 building box,但是老是有一些現實因素干擾著我們: 時間緊迫 隔天就要 release 的程式,user 今天臨時才發現一個很嚴重的 bug 要修。但仔細一看其實是簡單的小錯誤,但又發現要改的 code 剛好可以 refactor 達到 DRY,我們會怎麼做? 當然要 DRY,於是改了大概一百行程式碼 還是改發生錯誤的那一行就好,避免 refactor 又製造不可預期的 bug 但其實這並不是二分法,大多時候我們會評估時間還有程式碼的複雜度。有可能折衷一下: refactor 20 行信心高的 code,發個新 issue 等到下次 release 再去 refactor 剩下的 80 行 code。 不熟悉 寫程式的怎麼會不熟悉? 但一個大型 project 通常都是分工的,我們如果剛好是新進員工,或是剛踏進同事負責的領域,周圍一定圍繞著我們不熟悉的程式碼,這時我們也不敢大刀闊斧地更改同事已經寫好的程式碼。 有時候更慘,寫這段 code 的人已經離開公司,或剛好休長假,但目前要做的 issue 就剛好需要你改同事的程式碼,這時候我們可能只會追求 correctness,把其他 principles 全部拋於腦後,寫出醜到爆的程式碼,但至少功能正確。 Legacy code 也常常是我們都不熟悉的範圍,甚至是當初寫這段 code 的同事也早就忘記他當初為何這樣寫了。 太複雜 當 issue 非常複雜,牽扯到一些困難的 algorithm,但已經有明訂的 design spec 的時候,實作時才發覺是不是另外一種寫法比較好,但如果發生問題我們就很難清楚地知道到底是 design spec 有問題,還是因為我們自作聰明亂改? 與其給自己找麻煩,還不如就乖乖照著 spec 走。 當可以寫 Good Code 的時

[工作效率][Linux] 指令完成寄信通知我,讓我先去看劇?

圖片
有些再簡單不過的指令像是 mv, cp, rm 遇到檔案一多的時候有時候可以跑好幾分鐘,甚至一小時以上。我也不曉得他什麼時候會跑完,30 秒? 10 分鐘? 2小時? 都有可能。 手邊也暫時沒其他工作可以平行進行的話,也只能放著讓他跑。 但時不時就會好奇他跑完了沒,一直來回切換視窗,或是去忙別的事卻一直跑回房間看螢幕。這時候我們就會很想要讓他跑完時通知我們。 Sequential Commands 最簡單的方法就是與其執行一個指令,不如執行 multiple commands,讓前面指令跑完就跑通知用的指令,我們這邊以 sleep 當作一個跑很久的指令: sleep 10; notify_me 要注意的是這裡最好用 ; 把指令串起來,最好不要用 &&,因為 && 一定要前面的指令成功 (return zero exit code),後面的指令才會執行,我們最好別太把握前面的指令跑到一半一定會不會發生錯誤。 通知的媒介 至於通知的指令要用哪種方式,我就有試過以下的方式: 播放聲音通知 在 VNC 視窗傳送通知到 VNC 桌面 (e.g., notify-send) 用第三方工具傳送通知 (e.g., Slack, Teams) 寄送 email 通知 但實際用過之後才發現一些缺點。聲音很容易因為人不在房間就沒聽到;VNC 視窗傳送就限制你只能在 VNC 視窗跑指令,而且人沒盯著螢幕也很容易錯過;第三方工具看起來最棒,但有時因為第三方工具或網路不穩就是會沒送到通知。 寄信+手機 app 還是最可靠 最可靠的還是透過簡單的指令像是 mail, sendmail, mutt 等工具來寄信。不過 IT 要先設定好信箱,但公司通常都會有。 於是我們就可以在 .bashrc 寫一個 function,例如我們可以使用 mutt 來當作寄信的工具: # Send an email with only the subject # # Examples: # mail2me # mail2me "Command finished" function mail2me() { local -r subject="$1" mutt -s "${subject}" &quo

[工作經驗] 為了一個句點 debug 了三小時

圖片
有沒有什麼 bug 讓你 debug 了超級久,結果卻發現只是一兩個字的差異? 我們應該都有類似的經驗,可能是打錯字,或是複製貼上卻忘了改一個東西。這次我遇到的 bug 只是一個句點之差,就一個句點! 這次的問題是由 Python script 去執行 LSF job,做的事非常簡單,就是執行一個 "test.sh" 而已。但在 LSF 卻會遇到錯誤,看了 log 卻久久不能明白為什麼: bash: test.sh: command not found 但這個檔案 test.sh 如果我直接在 terminal 執行根本沒有問題,權限也有設定可以 chmod +x 可以直接執行。 更讓人覺得詭異的是,如果我直接把一樣的指令從 terminal 執行 bsub 出去,就不會有問題,但從 Python script 執行就有問題。 Debug 過程 因為這個檔案是動態建立出來再執行的,一開始以為是不是建立的時間太晚,那我們把動態建立關掉,讓這個檔案從頭到尾都存在,發現還是一樣的問題。 那既然檔案都在,是不是 LSF 那邊硬碟之類的有問題? 或是 current working directory 沒有設定好? 在跑 test.sh 之前先用 ls 看一下目前資料夾的檔案,結果 test.sh 有顯示,而且模式也有 +x 可以執行。 感覺越來越奇妙了,檔案明明就在,你為什麼就不給我執行? 通常在不同 shell 環境下,我們也只能懷疑是不是 environment variables 造成什麼影響,所以我就用了一個 "env | sort" 指令觀察一下 Python 執行的 bsub 與自己手動跑 bsub 的 LSF 環境下到底有什麼差異。 沒想到還真的找到了不一樣的地方。我發現手動執行 bsub 的環境的 $PATH 變數多了一個 "." 但 Python script 執行的卻沒有,這時才突然恍然大悟,原來錯誤訊息抱怨的是 "command not found" 並不是抱怨檔案找不到。當我們用 "test.sh" 去執行,並不是用 "./test.sh" 執行的話,Linux 會找所有的 $PATH 來找可以執行的 binary,因為 &qu

此網誌的熱門文章

[試算表] 追蹤台股 Google Spreadsheet (未實現損益/已實現損益)

[Side Project] 互動式教學神經網路反向傳播 Interactive Computational Graph

[插件] 在 Chrome 網頁做區分大小寫的搜尋