GoogleNet閱讀筆記
Going Deeper with Convolutions
摘要
我們在ImageNet大規模視覺識別挑戰賽2014(ILSVRC14)上提出了一種代號為Inception的深度卷積神經網路結構,並在分類和檢測上取得了新的最好結果。這個架構的主要特點是提高了網路內部計算資源的利用率。通過精心的手工設計,我們在增加了網路深度和廣度的同時保持了計算預算不變。為了優化質量,架構的設計以赫布理論和多尺度處理直覺為基礎。我們在ILSVRC14提交中應用的一個特例被稱為GoogLeNet,一個22層的深度網路,其質量在分類和檢測的背景下進行了評估。
1. 引言
過去三年中,由於深度學習和卷積網路的發展[10],我們的目標分類和檢測能力得到了顯著提高。一個令人鼓舞的訊息是,大部分的進步不僅僅是更強大硬體、更大資料集、更大模型的結果,而主要是新的想法、演算法和網路結構改進的結果。例如,ILSVRC 2014競賽中最靠前的輸入除了用於檢測目的的分類資料集之外,沒有使用新的資料資源。我們在ILSVRC 2014中的GoogLeNet提交實際使用的引數只有兩年前Krizhevsky等人[9]獲勝結構引數的1/12,而結果明顯更準確。在目標檢測前沿,最大的收穫不是來自於越來越大的深度網路的簡單應用,而是來自於深度架構和經典計算機視覺的協同,像Girshick等人[6]的R-CNN演算法那樣。
另一個顯著因素是隨著移動和嵌入式裝置的推動,我們的演算法的效率很重要——尤其是它們的電力和記憶體使用。值得注意的是,正是包含了這個因素的考慮才得出了本文中呈現的深度架構設計,而不是單純的為了提高準確率。對於大多數實驗來說,模型被設計為在一次推斷中保持15億乘加的計算預算,所以最終它們不是單純的學術好奇心,而是能在現實世界中應用,甚至是以合理的代價在大型資料集上使用。
在本文中,我們將關注一個高效的計算機視覺深度神經網路架構,代號為Inception,它的名字來自於Lin等人[12]網路論文中的Network與著名的“we need to go deeper”網路迷因[1]的結合。在我們的案例中,單詞“deep”用在兩個不同的含義中:首先,在某種意義上,我們以“Inception module”的形式引入了一種新層次的組織方式,在更直接的意義上增加了網路的深度。一般來說,可以把Inception模型看作論文[12]的邏輯頂點同時從Arora等人[2]的理論工作中受到了鼓舞和引導。這種架構的好處在ILSVRC 2014分類和檢測挑戰賽中通過實驗得到了驗證,它明顯優於目前的最好水平。
2. 近期工作
從LeNet-5 [10]開始,卷積神經網路(CNN)通常有一個標準結構——堆疊的卷積層(後面可以選擇有對比歸一化和最大池化)後面是一個或更多的全連線層。這個基本設計的變種在影像分類著作流行,並且目前為止在MNIST,CIFAR和更著名的ImageNet分類挑戰賽中[9, 21]的已經取得了最佳結果。對於更大的資料集例如ImageNet來說,最近的趨勢是增加層的數目[12]和層的大小[21, 14],同時使用丟棄[7]來解決過擬合問題。
儘管擔心最大池化層會引起準確空間資訊的損失,但與[9]相同的卷積網路結構也已經成功的應用於定位[9, 14],目標檢測[6, 14, 18, 5]和行人姿態估計[19]。
受靈長類視覺皮層神經科學模型的啟發,Serre等人[15]使用了一系列固定的不同大小的Gabor濾波器來處理多尺度。我們使用一個了類似的策略。然而,與[15]的固定的2層深度模型相反,Inception結構中所有的濾波器是學習到的。此外,Inception層重複了很多次,在GoogleNet模型中得到了一個22層的深度模型。
Network-in-Network是Lin等人[12]為了增加神經網路表現能力而提出的一種方法。在他們的模型中,網路中新增了額外的1 × 1卷積層,增加了網路的深度。我們的架構中大量的使用了這個方法。但是,在我們的設定中,1 × 1卷積有兩個目的:最關鍵的是,它們主要是用來作為降維模組來移除卷積瓶頸,否則將會限制我們網路的大小。這不僅允許了深度的增加,而且允許我們網路的寬度增加但沒有明顯的效能損失。
最後,目前最好的目標檢測是Girshick等人[6]的基於區域的卷積神經網路(R-CNN)方法。R-CNN將整個檢測問題分解為兩個子問題:利用低層次的訊號例如顏色,紋理以跨類別的方式來產生目標位置候選區域,然後用CNN分類器來識別那些位置上的物件類別。這樣一種兩個階段的方法利用了低層特徵分割邊界框的準確性,也利用了目前的CNN非常強大的分類能力。我們在我們的檢測提交中採用了類似的方式,但探索增強這兩個階段,例如對於更高的目標邊界框召回使用多盒[5]預測,並融合了更好的邊界框候選區域分類方法。
3. 動機和高層思考
提高深度神經網路效能最直接的方式是增加它們的尺寸。這不僅包括增加深度——網路層次的數目——也包括它的寬度:每一層的單元數目。這是一種訓練更高質量模型容易且安全的方法,尤其是在可獲得大量標註的訓練資料的情況下。但是這個簡單方案有兩個主要的缺點。更大的尺寸通常意味著更多的引數,這會使增大的網路更容易過擬合,尤其是在訓練集的標註樣本有限的情況下。這是一個主要的瓶頸,因為要獲得強標註資料集費時費力且代價昂貴,經常需要專家評委在各種細粒度的視覺類別進行區分,例如圖1中顯示的ImageNet中的類別(甚至是1000類ILSVRC的子集)。
圖1: ILSVRC 2014分類挑戰賽的1000類中兩個不同的類別。區分這些類別需要領域知識。
統一增加網路尺寸的另一個缺點是計算資源使用的顯著增加。例如,在一個深度視覺網路中,如果兩個卷積層相連,它們的濾波器數目的任何統一增加都會引起計算量平方式的增加。如果增加的能力使用時效率低下(例如,如果大多數權重結束時接近於0),那麼會浪費大量的計算能力。由於計算預算總是有限的,計算資源的有效分佈更偏向於尺寸無差別的增加,即使主要目標是增加效能的質量。
解決這兩個問題的一個基本的方式就是引入稀疏性並將全連線層替換為稀疏的全連線層,甚至是卷積層。除了模仿生物系統之外,由於Arora等人[2]的開創性工作,這也具有更堅固的理論基礎優勢。他們的主要成果說明如果資料集的概率分佈可以通過一個大型稀疏的深度神經網路表示,則最優的網路拓撲結構可以通過分析前一層啟用的相關性統計和聚類高度相關的神經元來一層層的構建。雖然嚴格的數學證明需要在很強的條件下,但事實上這個宣告與著名的赫布理論產生共鳴——神經元一起激發,一起連線——實踐表明,基礎概念甚至適用於不嚴格的條件下。
遺憾的是,當碰到在非均勻的稀疏資料結構上進行數值計算時,現在的計算架構效率非常低下。即使演算法運算的數量減少100倍,查詢和快取丟失上的開銷仍占主導地位:切換到稀疏矩陣可能是不可行的。隨著穩定提升和高度調整的數值庫的應用,差距仍在進一步擴大,數值庫要求極度快速密集的矩陣乘法,利用底層的CPU或GPU硬體[16, 9]的微小細節。非均勻的稀疏模型也要求更多的複雜工程和計算基礎結構。目前大多數面向視覺的機器學習系統通過採用卷積的優點來利用空域的稀疏性。然而,卷積被實現為對上一層塊的密集連線的集合。為了打破對稱性,提高學習水平,從論文[11]開始,ConvNets習慣上在特徵維度使用隨機的稀疏連線表,然而為了進一步優化平行計算,論文[9]中趨向於變回全連線。目前最新的計算機視覺架構有統一的結構。更多的濾波器和更大的批大小要求密集計算的有效使用。
這提出了下一個中間步驟是否有希望的問題:一個架構能利用濾波器水平的稀疏性,正如理論所建議的那樣,但能通過利用密集矩陣計算來利用我們目前的硬體。稀疏矩陣乘法的大量文獻(例如[3])認為對於稀疏矩陣乘法,將稀疏矩陣聚類為相對密集的子矩陣會有更佳的效能。在不久的將來會利用類似的方法來進行非均勻深度學習架構的自動構建,這樣的想法似乎並不牽強。
Inception架構開始是作為案例研究,用於評估一個複雜網路拓撲構建演算法的假設輸出,該演算法試圖近似[2]中所示的視覺網路的稀疏結構,並通過密集的、容易獲得的元件來覆蓋假設結果。儘管是一個非常投機的事情,但與基於[12]的參考網路相比,早期可以觀測到適度的收益。隨著一點點調整加寬差距,作為[6]和[5]的基礎網路,Inception被證明在定位上下文和目標檢測中尤其有用。有趣的是,雖然大多數最初的架構選擇已被質疑並分離開進行全面測試,但結果證明它們是區域性最優的。然而必須謹慎:儘管Inception架構在計算機上領域取得成功,但這是否可以歸因於構建其架構的指導原則仍是有疑問的。確保這一點將需要更徹底的分析和驗證。
4. 架構細節
Inception架構的主要想法是考慮怎樣近似卷積視覺網路的最優稀疏結構並用容易獲得的密集元件進行覆蓋。注意假設轉換不變性,這意味著我們的網路將以卷積構建塊為基礎。我們所需要做的是找到最優的區域性構造並在空間上重複它。Arora等人[2]提出了一個層次結構,其中應該分析最後一層的相關統計並將它們聚整合具有高相關性的單元組。這些聚類形成了下一層的單元並與前一層的單元連線。我們假設較早層的每個單元都對應輸入層的某些區域,並且這些單元被分成濾波器組。在較低的層(接近輸入的層)相關單元集中在區域性區域。因此,如[12]所示,我們最終會有許多聚類集中在單個區域,它們可以通過下一層的1×1卷積層覆蓋。然而也可以預期,將存在更小數目的在更大空間上擴充套件的聚類,其可以被更大塊上的卷積覆蓋,在越來越大的區域上塊的數量將會下降。為了避免塊校正的問題,目前Inception架構形式的濾波器的尺寸僅限於1×1、3×3、5×5,這個決定更多的是基於便易性而不是必要性。這也意味著提出的架構是所有這些層的組合,其輸出濾波器組連線成單個輸出向量形成了下一階段的輸入。另外,由於池化操作對於目前卷積網路的成功至關重要,因此建議在每個這樣的階段新增一個替代的並行池化路徑應該也應該具有額外的有益效果(看圖2(a))。
由於這些“Inception模組”在彼此的頂部堆疊,其輸出相關統計必然有變化:由於較高層會捕獲較高的抽象特徵,其空間集中度預計會減少。這表明隨著轉移到更高層,3×3和5×5卷積的比例應該會增加。
上述模組的一個大問題是在具有大量濾波器的卷積層之上,即使適量的5×5卷積也可能是非常昂貴的,至少在這種樸素形式中有這個問題。一旦池化單元新增到混合中,這個問題甚至會變得更明顯:輸出濾波器的數量等於前一階段濾波器的數量。池化層輸出和卷積層輸出的合併會導致這一階段到下一階段輸出數量不可避免的增加。雖然這種架構可能會覆蓋最優稀疏結構,但它會非常低效,導致在幾個階段內計算量爆炸。
這導致了Inception架構的第二個想法:在計算要求會增加太多的地方,明智地減少維度。這是基於嵌入的成功:甚至低維嵌入可能包含大量關於較大影像塊的資訊。然而嵌入以密集、壓縮形式表示資訊並且壓縮資訊更難處理。這種表示應該在大多數地方保持稀疏(根據[2]中條件的要求】)並且僅在它們必須彙總時才壓縮訊號。也就是說,在昂貴的3×3和5×5卷積之前,1×1卷積用來計算降維。除了用來降維之外,它們也包括使用線性修正單元使其兩用。最終的結果如圖2(b)所示。
通常,Inception網路是一個由上述型別的模組互相堆疊組成的網路,偶爾會有步長為2的最大池化層將網路解析度減半。出於技術原因(訓練過程中記憶體效率),只在更高層開始使用Inception模組而在更低層仍保持傳統的卷積形式似乎是有益的。這不是絕對必要的,只是反映了我們目前實現中的一些基礎結構效率低下。
該架構的一個有用的方面是它允許顯著增加每個階段的單元數量,而不會在後面的階段出現計算複雜度不受控制的爆炸。這是在尺寸較大的塊進行昂貴的卷積之前通過普遍使用降維實現的。此外,設計遵循了實踐直覺,即視覺資訊應該在不同的尺度上處理然後聚合,為的是下一階段可以從不同尺度同時抽象特徵。
計算資源的改善使用允許增加每個階段的寬度和階段的數量,而不會陷入計算困境。可以利用Inception架構建立略差一些但計算成本更低的版本。我們發現所有可用的控制允許計算資源的受控平衡,導致網路比沒有Inception結構的類似執行網路快3—10倍,但是在這一點上需要仔細的手動設計。
5. GoogLeNet
通過“GoogLeNet”這個名字,我們提到了在ILSVRC 2014競賽的提交中使用的Inception架構的特例。我們也使用了一個稍微優質的更深更寬的Inception網路,但將其加入到組合中似乎只稍微提高了結果。我們忽略了該網路的細節,因為經驗證據表明確切架構的引數影響相對較小。表1說明了競賽中使用的最常見的Inception例項。這個網路(用不同的影像塊取樣方法訓練的)使用了我們組合中7個模型中的6個。
所有的卷積都使用了修正線性啟用,包括Inception模組內部的卷積。在我們的網路中感受野是在均值為0的RGB顏色空間中,大小是224×224。“#3×3 reduce”和“#5×5 reduce”表示在3×3和5×5卷積之前,降維層使用的1×1濾波器的數量。在pool proj列可以看到內建的最大池化之後,投影層中1×1濾波器的數量。所有的這些降維/投影層也都使用了線性修正啟用。
網路的設計考慮了計算效率和實用性,因此推斷可以單獨的裝置上執行,甚至包括那些計算資源有限的裝置,尤其是低記憶體佔用的裝置。當只計算有引數的層時,網路有22層(如果我們也計算池化層是27層)。構建網路的全部層(獨立構建塊)的數目大約是100。確切的數量取決於機器學習基礎設施對層的計算方式。分類器之前的平均池化是基於[12]的,儘管我們的實現有一個額外的線性層。線性層使我們的網路能很容易地適應其它的標籤集,但它主要是為了方便使用,我們不期望它有重大的影響。我們發現從全連線層變為平均池化,提高了大約top-1 %0.6
的準確率,然而即使在移除了全連線層之後,丟失的使用還是必不可少的。
給定深度相對較大的網路,有效傳播梯度反向通過所有層的能力是一個問題。在這個任務上,更淺網路的強大效能表明網路中部層產生的特徵應該是非常有識別力的。通過將輔助分類器新增到這些中間層,可以期望較低階段分類器的判別力。這被認為是在提供正則化的同時克服梯度消失問題。這些分類器採用較小卷積網路的形式,放置在Inception (4a)和Inception (4b)模組的輸出之上。在訓練期間,它們的損失以折扣權重(輔助分類器損失的權重是0.3)加到網路的整個損失上。在推斷時,這些輔助網路被丟棄。後面的控制實驗表明輔助網路的影響相對較小(約0.5),只需要其中一個就能取得同樣的效果。
包括輔助分類器在內的附加網路的具體結構如下:
- 一個濾波器大小5×5,步長為3的平均池化層,導致(4a)階段的輸出為4×4×512,(4d)的輸出為4×4×528。
- 具有128個濾波器的1×1卷積,用於降維和修正線性啟用。
- 一個全連線層,具有1024個單元和修正線性啟用。
- 丟棄70%輸出的丟棄層。
- 使用帶有softmax損失的線性層作為分類器(作為主分類器預測同樣的1000類,但在推斷時移除)。
最終的網路模型圖如圖3所示。
圖3:含有的所有結構的GoogLeNet網路。
6. 訓練方法
GoogLeNet網路使用DistBelief[4]分散式機器學習系統進行訓練,該系統使用適量的模型和資料並行。儘管我們僅使用一個基於CPU的實現,但粗略的估計表明GoogLeNet網路可以用更少的高階GPU在一週之內訓練到收斂,主要的限制是記憶體使用。我們的訓練使用非同步隨機梯度下降,動量引數為0.9[17],固定的學習率計劃(每8次遍歷下降學習率4%)。Polyak平均[13]在推斷時用來建立最終的模型。
影像取樣方法在過去幾個月的競賽中發生了重大變化,並且已收斂的模型在其他選項上進行了訓練,有時還結合著超引數的改變,例如丟棄和學習率。因此,很難對訓練這些網路的最有效的單一方式給出明確指導。讓事情更復雜的是,受[8]的啟發,一些模型主要是在相對較小的裁剪影像進行訓練,其它模型主要是在相對較大的裁剪影像上進行訓練。然而,一個經過驗證的方案在競賽後工作地很好,包括各種尺寸的影像塊的取樣,它的尺寸均勻分佈在影像區域的8%——100%之間,方向角限制為[34,43][34,43]之間。另外,我們發現Andrew Howard[8]的光度扭曲對於克服訓練資料成像條件的過擬合是有用的。
7. ILSVRC 2014分類挑戰賽設定和結果
ILSVRC 2014分類挑戰賽包括將影像分類到ImageNet層級中1000個葉子結點類別的任務。訓練影像大約有120萬張,驗證影像有5萬張,測試影像有10萬張。每一張影像與一個實際類別相關聯,效能度量基於分類器預測的最高分。通常報告兩個數字:top-1準確率,比較實際類別和第一個預測類別,top-5錯誤率,比較實際類別與前5個預測類別:如果影像實際類別在top-5中,則認為影像分類正確,不管它在top-5中的排名。挑戰賽使用top-5錯誤率來進行排名。
我們參加競賽時沒有使用外部資料來訓練。除了本文中前面提到的訓練技術之外,我們在獲得更高效能的測試中採用了一系列技巧,描述如下。
- 我們獨立訓練了7個版本的相同的GoogLeNet模型(包括一個更廣泛的版本),並用它們進行了整體預測。這些模型的訓練具有相同的初始化(甚至具有相同的初始權重,由於監督)和學習率策略。它們僅在取樣方法和隨機輸入影像順序方面不同。
- 在測試中,我們採用比Krizhevsky等人[9]更積極的裁剪方法。具體來說,我們將影像歸一化為四個尺度,其中較短維度(高度或寬度)分別為256,288,320和352,取這些歸一化的影像的左,中,右方塊(在肖像圖片中,我們採用頂部,中心和底部方塊)。對於每個方塊,我們將採用4個角以及中心224×224裁剪影像以及方塊尺寸歸一化為224×224,以及它們的映象版本。這導致每張影像會得到4×3×6×2 = 144的裁剪影像。前一年的輸入中,Andrew Howard[8]採用了類似的方法,經過我們實證驗證,其方法略差於我們提出的方案。我們注意到,在實際應用中,這種積極裁剪可能是不必要的,因為存在合理數量的裁剪影像後,更多裁剪影像的好處會變得很微小(正如我們後面展示的那樣)。
- softmax概率在多個裁剪影像上和所有單個分類器上進行平均,然後獲得最終預測。在我們的實驗中,我們分析了驗證資料的替代方法,例如裁剪影像上的最大池化和分類器的平均,但是它們比簡單平均的效能略遜。
在本文的其餘部分,我們分析了有助於最終提交整體效能的多個因素。
競賽中我們的最終提交在驗證集和測試集上得到了top-5 6.67%
的錯誤率,在其它的參與者中排名第一。與2012年的SuperVision方法相比相對減少了56.5%,與前一年的最佳方法(Clarifai)相比相對減少了約40%,這兩種方法都使用了外部資料訓練分類器。表2顯示了過去三年中一些表現最好的方法的統計。
我們也分析報告了多種測試選擇的效能,當預測影像時通過改變表3中使用的模型數目和裁剪影像數目。
8. ILSVRC 2014檢測挑戰賽設定和結果
ILSVRC檢測任務是為了在200個可能的類別中生成影像中目標的邊界框。如果檢測到的物件匹配的它們實際類別並且它們的邊界框重疊至少50%(使用Jaccard索引),則將檢測到的物件記為正確。無關的檢測記為假陽性且被懲罰。與分類任務相反,每張影像可能包含多個物件或沒有物件,並且它們的尺度可能是變化的。報告的結果使用平均精度均值(mAP)。GoogLeNet檢測採用的方法類似於R-CNN[6],但用Inception模組作為區域分類器進行了增強。此外,為了更高的目標邊界框召回率,通過選擇搜尋[20]方法和多箱[5]預測相結合改進了區域生成步驟。為了減少假陽性的數量,超解析度的尺寸增加了2倍。這將選擇搜尋演算法的區域生成減少了一半。我們總共補充了200個來自多盒結果的區域生成,大約60%的區域生成用於[6],同時將覆蓋率從92%提高到93%。減少區域生成的數量,增加覆蓋率的整體影響是對於單個模型的情況平均精度均值增加了1%。最後,等分類單個區域時,我們使用了6個GoogLeNets的組合。這導致準確率從40%提高到43.9%。注意,與R-CNN相反,由於缺少時間我們沒有使用邊界框迴歸。
我們首先報告了最好檢測結果,並顯示了從第一版檢測任務以來的進展。與2013年的結果相比,準確率幾乎翻了一倍。所有表現最好的團隊都使用了卷積網路。我們在表4中報告了官方的分數和每個隊伍的常見策略:使用外部資料、整合模型或上下文模型。外部資料通常是ILSVRC12的分類資料,用來預訓練模型,後面在檢測資料集上進行改善。一些團隊也提到使用定位資料。由於定位任務的邊界框很大一部分不在檢測資料集中,所以可以用該資料預訓練一般的邊界框迴歸器,這與分類預訓練的方式相同。GoogLeNet輸入沒有使用定位資料進行預訓練。
在表5中,我們僅比較了單個模型的結果。最好效能模型是Deep Insight的,令人驚訝的是3個模型的集合僅提高了0.3個點,而GoogLeNet在模型整合時明顯獲得了更好的結果。
9. 總結
我們的結果取得了堅實的證據,即通過易獲得的密集構造塊來近似期望的最優稀疏結果是改善計算機視覺神經網路的一種可行方法。相比於較淺且較窄的架構,這個方法的主要優勢是在計算需求適度增加的情況下有顯著的質量收益。
我們的目標檢測工作雖然沒有利用上下文,也沒有執行邊界框迴歸,但仍然具有競爭力,這進一步顯示了Inception架構優勢的證據。
對於分類和檢測,預期通過更昂貴的類似深度和寬度的非Inception型別網路可以實現類似質量的結果。然而,我們的方法取得了可靠的證據,即轉向更稀疏的結構一般來說是可行有用的想法。這表明未來的工作將在[2]的基礎上以自動化方式建立更稀疏更精細的結構,以及將Inception架構的思考應用到其他領域。
相關文章
- 閱讀筆記筆記
- 閱讀筆記4筆記
- 閱讀筆記3筆記
- 閱讀筆記5筆記
- 【閱讀筆記:字典】筆記
- 閱讀筆記2筆記
- 閱讀筆記1筆記
- 閱讀筆記8筆記
- 閱讀筆記03筆記
- 閱讀筆記02筆記
- 閱讀筆記7筆記
- gdbOF閱讀筆記筆記
- JDK原始碼閱讀:String類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- SiamRPN++閱讀筆記筆記
- Flownet 2.0 閱讀筆記筆記
- 《Clean Code》閱讀筆記筆記
- 《潮騷》閱讀筆記Ⅱ筆記
- 閱讀影片方法筆記筆記
- Dependencies for Graphs 閱讀筆記筆記
- Keys for graphs閱讀筆記筆記
- JDK原始碼閱讀(7):ConcurrentHashMap類閱讀筆記JDK原始碼HashMap筆記
- JDK原始碼閱讀(5):HashTable類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀(4):HashMap類閱讀筆記JDK原始碼HashMap筆記
- 《Effective DevOps》閱讀筆記 82dev筆記
- Koa 原始碼閱讀筆記原始碼筆記
- 《Effective DevOps》閱讀筆記 59dev筆記
- 《Effective DevOps》閱讀筆記 19dev筆記
- MapReduce 論文閱讀筆記筆記
- The Data Warehouse Toolkit 閱讀筆記筆記
- CopyOnWriteArrayList原始碼閱讀筆記原始碼筆記
- ArrayList原始碼閱讀筆記原始碼筆記
- LinkedList原始碼閱讀筆記原始碼筆記
- 《影響力》閱讀筆記筆記
- 《人月神話》閱讀筆記筆記
- python原始碼閱讀筆記Python原始碼筆記
- 閱讀筆記(2024年春)筆記
- 《大圖景》閱讀筆記筆記