編程進階之路:用簡單的面向對象編程提升深度學習原型
選自Towardsdatascience
作者:Tirthajyoti Sarkar
機器之心編譯
參與:胡曦月、Geek AI
不會寫一手漂亮代碼的數據科學家不是好工程師。將面向對象編程中那些簡單的概念(如函數化和類繼承),應用到深度學習原型代碼中,可以獲得巨大的收益。
簡介
本文的目標讀者是像我這樣沒有軟體工程師背景的數據科學家和機器學習(ML)從業者,而非經驗豐富的軟體工程師。
由於 Python 語言對 ML 和數據科學社區來說實在是太棒了,我們通常在工作中使用 Python。它在現代的數據驅動分析和人工智慧(AI)應用領域中一路高歌猛進,成為增長最快的主要語言。
然而,Python 同樣被用於簡單的腳本開發,比如辦公自動化、假設檢驗、創建用於進行頭腦風暴的互動式圖表,控制實驗儀器等等。
但事實是,用 Python 開發軟體和用 Python 寫腳本並非完全相同——至少在數據科學領域中是如此。
腳本(主要)是寫給自己用的代碼,而軟體則是和隊友們一起寫給別人用的代碼集合。
不得不承認的是,大多數沒有軟體工程師背景的數據科學家在編寫 Python 程序實現 AI/ML 模型或者做統計分析時,往往是寫代碼給自己用。他們只想快速地直達隱藏在數據里的模式,而沒有深入考慮普通用戶的需求。
他們寫代碼來繪製出信息豐富的、精美的圖表,但卻不會專門創建一個相關的函數,便於以後復用。
他們會導入很多標準庫中的方法和類,但是卻不會通過繼承和添加新的方法來創建自己的子類,以此來擴展類的功能。
函數、繼承、方法、類——這些都是魯棒的面向對象編程(OOP)的核心思想,但是如果你只是想用 Jupyter notebook 來做數據分析和繪圖,那麼這些概念也不是非用不可。
你可以避免使用 OOP 的那些法則帶來的最初的痛苦,然而你總會付出代價的,那就是代碼無法復用也無法擴展。
簡而言之,你的代碼除了你自己以外誰都用不了,到最後你自己也會忘了當初寫這段代碼的邏輯。但可讀性(和由此帶來的可重用性)至關重要,這是對你所產出代碼的真實考驗,不是針對自己,而是面向他人。
但最重要的是,為了降低那些年輕而充滿幹勁的學習者的負擔,網路上數百門有關數據科學和 AI/ML 的在線課程或慕課(MOOC)也都沒有強調這方面的編碼問題。他們是來學習炫酷的演算法和神經網路優化的,而非 Python 中的 OOP。因此,編碼方面的問題仍然被忽視。
那麼,你能為此做些什麼?
簡單運用 OOP 的原理就可以大幅改善你的深度學習(DL)代碼
我有生以來從未做過軟體工程師,因此,當我開始探索 ML 和數據科學時,我草草地寫了一大堆的不可重用的代碼。
但我逐漸開始嘗試優化代碼,通過簡單地增強代碼風格來使代碼對於其他人更加有用。
而且,我還發現在有關數據科學的代碼中開始應用 OOP 原則並不難。
只要你站在別人的立場上去思考他人會怎樣建設性地接受並採用你的代碼,即使你從未上過軟體工程課程,有些想法也會自然而然地出現在你的腦海中。
當你在做數據分析時,如果某一個代碼塊(完全相同或者略有不同地)出現了不止一次,你能否為其創建一個函數進行封裝?
當你創建了這個函數,應該向其傳遞哪些參數?有哪些參數可以是可選參數?參數的默認值應該是多少?
如果在當前情況下無法確定需要傳遞多少參數,你使用 Python 中提供的 *args 和 *kwargs 了嗎?
你有沒有為這個函數寫一個「docstring」注釋,來說明函數實現的功能、需要的參數以及使用示例等信息?
當你已經寫了大量此類實用函數後,你是繼續在同一個 notebook 上工作,還是新開一個 notebook,然後通過調用「from my_utility_script import func1, func2, func3」導入函數?(前提是你已經根據之前 Jupyter notebook 的代碼創建了一個簡單的 Python 腳本文件「my_utility_script」。)
你有沒有把「my_utility_script」腳本放進一個文件夾,然後在該文件夾下創建一個「__init__.py」文件(哪怕是空文件),以此來創建一個像 NumPy 或者 Pandas 一樣的可導入的 Python 模塊呢?
你有沒有想過在使用像 NumPy 或 TensorFlow 那樣功能強大的包時,不僅僅是從中導入類和方法,你還可以向其中加入自己的方法來擴展它們的功能?
以上這些到底意味著什麼呢?接下來我們通過一個簡單的例子來加以說明——基於「fashion MNIST」數據集來實現一個 DL 圖像分類問題。
DL 分類任務案例說明
方法
詳細代碼見我的 Github 代碼倉庫。歡迎讀者將其克隆(fork)到自己的代碼倉庫中進行使用和擴展。
代碼地址:https://github.com/tirthajyoti/Computer_vision/blob/master/Notebooks/OOP_principle_deep_learning.ipynb
代碼對於構建優秀軟體至關重要,但卻並不適合寫文章分析。你可以閱讀下面的代碼來獲得啟發,而非實際調試或者重構練習。
因此,我只選取一部分代碼片段,以此說明我如何編碼實現前文中詳細介紹的那些原則。
核心 ML 任務和更高階的業務問題
核心的 ML 任務很簡單——為 fashion MNIST 數據集構建一個深度學習分類器,該數據集是對於傳統的著名的 MNIST 手寫數字數據集的有趣變體。Fashion MNIST 數據集包含 60,000 張像素大小為 28 x 28 的訓練圖像,圖像內容為與時尚相關的物品,比如帽子、鞋子、褲子、T 恤、裙子等等。該數據集還包含 10,000 張測試圖像用於驗證和測試。
Fashion MNIST 數據集
但是,如果圍繞此核心 ML 任務存在更高階的優化或可視化分析問題,那麼模型架構的複雜度會如何影響達到目標準確率所需的最小迭代次數(epoch)呢?
讀者應該清楚我們為什麼要為這個問題煩惱,因為這與整體業務優化有關。訓練神經網路不是一個簡單的計算問題。因此,研究達到目標性能指標必須進行的最少的訓練工作,以及架構選擇對該性能指標的影響,是很有必要的。
在本例中,我們甚至不採用卷積網路,因為一個簡單的密集連接的神經網路就可以達到相當高的準確率,並且事實上我們也需要一些次優的性能來說明前文提到的高階優化問題的要點。
解決方案
那麼,我們需要解決兩個問題——
如何確定達到目標準確率所需最小的 epoch 數量?
特定模型架構如何影響最小的 epoch 數或者訓練行為?
為了實現這兩個目標,我們將使用以下兩個簡單的 OOP 原則:
從基類對象創建出一個繼承的類;
創建實用函數,然後在代碼塊中調用它們,該代碼塊可以給外部用戶進行更高階的優化和分析。
良好實踐的代碼片段示例
我們將通過展示下面的一些代碼片段,來說明如何簡單使用 OOP 原則來實現我們的解決方案。為了便於理解,代碼中添加了相關的注釋。
首先,我們繼承一個 Keras 類從而創建了我們的子類,在子類中添加了一個查看訓練準確率並根據該值作出反應的方法。
這個簡單的回調函數可以動態控制 epoch——當準確率達到指定閾值後訓練自動停止。
我們將 Keras 的模型構造代碼封裝在一個實用函數中,從而使得任意層數架構的模型(只要它們是密集連接的)都可以通過簡單的用戶介面傳遞函數參數來生成。
我們甚至可以將編譯和訓練代碼封裝在一個實用函數中,從而在更高階的優化循環中方便地使用超參數。
接下來,我們將編寫可視化代碼,同樣地,我們通過函數化實現該功能。通用繪圖函數將原始數據作為輸入。然而,如果我們有這樣一個特殊目的——繪製出訓練集上準確率的演化情況並且顯示出其與目標準確率的對比,那麼我們的繪圖函數只需要將深度學習模型作為輸入,然後繪製目標圖形。
典型的結果如下所示,
最終緊湊簡單的分析代碼
現在我們可以充分利用之前定義的所有函數和類,將其組合來實現更高階的任務。
因此,最終的代碼將十分緊湊,但它將生成同樣有趣的、各種準確率閾值和神經網路架構的損失和準確率隨著 epoch 增多而變化的示意圖,如前文所示。
這將使得用戶可以使用最少的代碼來完成性能指標(本例中是準確率)與神經網路架構的選擇的可視化分析。這是構建一個優化的機器學習系統的第一步。
我們生成了一些分析案例,
最終的分析/優化代碼簡潔易懂,適用於高級用戶,他們不需要了解 Keras 模型構建或回調類的複雜性。
這是 OOP 背後蘊含的核心原則——為完成深度學習任務所做的複雜層次的抽象。
請注意我們將「print_msg = False」傳遞給類實例的方法。儘管我們在初始檢查/調試時確實需要列印出基本的狀態,但卻需要對優化任務靜默地進行分析。如果我們在定義類時未設置該參數,後面就難以停止列印調試信息了。
我們展示了一些通過執行上述代碼自動生成的具有代表性的結果。可以清楚看到,如何通過最少的高階代碼來生成可視化分析,從而判斷通過各級性能指標衡量的各種神經架構的相對性能。這使得用戶可以根據其性能需求,在不調整較低級別功能的情況下輕鬆地選擇模型。
另外,請注意每個圖表的自定義標題。這些標題清楚地闡明了目標性能和神經網路的複雜度,從而使分析變得容易。
它是繪圖實用函數的一個小細節,但這表明在創建這樣的函數時需要仔細設計。如果我們沒有為函數設置這樣的參數,就不可能為每個圖生成自定義標題。這種應用程序介面(API)的精心設計是良好 OOP 的重要組成部分。
最後,將腳本變成簡單的 Python 模塊
到目前為止,你可能一直在用 Jupyter notebook 工作,但要想在未來任何時候導入這些功能,就需要將其轉換成清爽的 Python 模塊。
如同「from matplotlib import pyplot」一樣,你可以在任何地方導入這些實用函數(Keras 模型的構建、訓練和繪圖)。
總結和結論
本文展示了一些從 OOP 借鑒而來的簡單的良好實踐,將其應用於 DL 分析任務。這些內容對於經驗豐富的軟體開發者來說似乎微不足道,但本文的目標讀者是那些可能沒有這種背景,但又應該理解在機器學習工作流程中灌輸這些良好實踐的重要性的數據科學家新人。
冒著重複自己太多次的風險,讓我在這裡再次總結一下,
只要有機會,就為重複的代碼塊生成函數。
一定要仔細設計 API 和函數(比如,所需要的最小參數集是怎樣的?它們是如何為高級編程任務服務的?)
不要忘了為函數寫注釋,哪怕只簡單寫一行說明也行。
如果你為同一對象積累了許多實用函數,那麼就該考慮為其定義一個類,並且將這些實用函數作為該類的方法。
只要有機會使用繼承完成複雜分析,就可以擴展類的函數。
不要僅僅停留在使用 Jupyter notebooks。請將代碼轉換成腳本文件,並將它們封裝在小模塊中。養成模塊化工作的習慣,這樣任何人都可以輕鬆地復用和擴展它。
說不定當你攢了足夠多的實用的類和子模塊時,你就可以在 Python 包管理倉庫(PyPi 伺服器)上發布實用程序包,然後,你就可以大肆吹噓自己發布過原始開源軟體包了。:-)
本文為機器之心編譯,轉載請聯繫本公眾號獲得授權。
------------------------------------------------
※p 值是什麼?數據科學家用最簡單的方式告訴你
※2019世界人工智慧大會·黑客馬拉松賽題徵集中,共推AI高質量發展
TAG:機器之心 |