iT邦幫忙

2

別讓你的AI專案變成一團亂!打造乾淨、模組化、可擴展的 AI python 專案

  • 分享至 

  • xImage
  •  

一、為什麼要有乾淨的專案架構?

隨著 AI 的快速發展,越來越多人開始寫 AI 程式專案。然而,在學校或教科書上,許多 AI 專案或作業往往能夠用一個 .ipynb.py 檔案就解決,這使得許多 AI 開發者習慣將所有流程塞進單一檔案中,導致程式碼難以維護、擴充困難,甚至連除錯(debug)都變得極為痛苦。

通常一個完整的機器學習專案通常包含 資料讀取、前處理、模型訓練、最佳化、評估及部署 等環節。如果沒有良好的架構規劃習慣,隨著專案變大,程式碼會變得混亂不堪,影響可讀性與可維護性,甚至降低開發效率。

乾淨的專案架構能帶來以下優勢:
1.提升可讀性:開發者可以輕鬆理解每個檔案和模組的功能,不需要花大量時間尋找程式碼邏輯。
2.提高維護性:當專案需要更新功能或套件時,可以精確修改相應的模組,而不會影響整體架構。
3.方便團隊協作:多人開發時,各自負責不同模組,減少合併衝突,加速開發流程。
4.更容易除錯:模組化的程式碼讓問題範圍更明確,有助於快速定位錯誤,提升除錯效率。

無論是個人專案還是公司專案,良好的專案架構都是確保專案成功的關鍵。也因此接下來,我將透過一個簡單的示範,建立 乾淨且可擴展 的機器學習專案架構,並且未來可以依不同需求舉一反三!

/images/emoticon/emoticon33.gif核心精神: 不同的工作,就要模組化,分開放!


二、專案結構範例

以下是一個簡化的專案架構示例,讓你在初始階段就能清楚規劃。通常建議在同一層級放置 requirements.txt,並有一份 README.md 作為使用說明。

my_ml_project/
├── data_processor.py    # 主要用於資料讀取、前處理、切分、特徵縮放、抽樣等
├── trainers.py          # 主要放置模型訓練、超參數調整、評估等相關程式
├── main.py              # 專案進入點;整合前處理、模型訓練與評估的流程
├── requirements.txt     # 所需套件及其版本
├── README.md            # 專案簡介、安裝及執行方式說明
└── ...                  # 其他可能需要的資料夾 (e.g. configs, docs, tests ...等)

1.data_processor.py:與資料處理相關的邏輯都封裝在此,例如分割訓練/測試集、資料前處理、特徵縮放、資料不平衡處理(如 SMOTE)等。
2.trainers.py:與模型相關邏輯都封裝在這裡,例如不同的模型 Trainer 類別、超參數搜尋與調整、訓練與評估函式等等。
3.main.py:作為整個程式的進入點,負責串接所有流程,從讀取與前處理資料開始,到最後的模型訓練與評估。

因此,通常只需要執行 main.py 就能跑完整個專案,並且引用 dataprocessor.py、trainers.py 等相關模組。如果需要擴充新功能,只需新增一個新的 .py 檔案,或者在現有的 .py 檔案中擴展功能,而不會讓主程式變得雜亂。這樣的架構不僅能保持程式碼乾淨、有條理,也讓除錯變得更加直覺,開發者能快速定位並修正問題,提高維護性與可讀性。

4.requirements.txt:使用 pip freeze > requirements.txt 可以將專案中使用的套件版本鎖定,利於他人建立相同環境。
5.README.md:說明專案架構、使用方式、環境需求等。
6.其他....


三、範例程式

  1. data_processor.py
    此檔案用於封裝所有資料前處理與切分的邏輯。範例中使用 StandardScaler 進行特徵縮放,並在訓練集上應用不平衡資料處理(如 SMOTE)來平衡類別數量。
    (碎碎念: 雖然有些人不在乎,但我建議先進行縮放調整後,再進行不平衡處理,原因是先用不平衡括處理並擴增或減少樣本後,此時的平均、標準差就不會是原始資料的統計量,這時候進行標準化會失真! 但若是minmax,則因為不太會改變,我認為先後就沒差。但上述兩種情境,都會因為執行先後而有差異,我將在另一篇文章示範)
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from imblearn.over_sampling import SMOTE

class DataProcessor:
    def __init__(self, test_size=0.2, random_state=42, resampler=None, sampling_strategy='auto'):
        self.test_size = test_size
        self.random_state = random_state
        self.scaler = StandardScaler()
        # 若未指定 resampler,預設使用 SMOTE
        self.resampler = resampler if resampler else SMOTE(
            sampling_strategy=sampling_strategy,
            random_state=random_state
        )
        
    def process(self, X, y):
        # 切分資料
        X_train, X_test, y_train, y_test = train_test_split(
            X, y,
            test_size=self.test_size,
            stratify=y,
            random_state=self.random_state
        )
        # 特徵縮放
        X_train_scaled = self.scaler.fit_transform(X_train)
        X_test_scaled = self.scaler.transform(X_test)
        
        # 不平衡抽樣
        if self.resampler:
            X_train_scaled, y_train = self.resampler.fit_resample(X_train_scaled, y_train)
            
        return X_train_scaled, X_test_scaled, y_train, y_test

  1. trainers.py
    此檔案主要存放模型訓練與評估邏輯。以下範例展示了如何建立抽象基底類別 BaseTrainer,並在其上擴充兩種不同的模型訓練器(AdaBoostTrainer 與 RandomForestTrainer)。此外,也示範了如何進行超參數搜尋 (GridSearchCV) 和評估 (accuracy_score, classification_report, confusion_matrix)。
from abc import ABC, abstractmethod
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier

class BaseTrainer(ABC):
    def __init__(self):
        self.model = None
        
    @abstractmethod
    def train(self, X_train, y_train):
        pass

    def tune_parameters(self, X_train, y_train, param_grid):
        grid_search = GridSearchCV(self.model, param_grid, cv=5, scoring='f1', n_jobs=-1)
        grid_search.fit(X_train, y_train)
        self.model = grid_search.best_estimator_
        return grid_search.best_params_

    def evaluate(self, X_test, y_test):
        y_pred = self.model.predict(X_test)
        accuracy = accuracy_score(y_test, y_pred)
        conf_matrix = confusion_matrix(y_test, y_pred)
        print("Accuracy:", accuracy)
        print("Classification Report:\n", classification_report(y_test, y_pred))
        print("Confusion Matrix:\n", conf_matrix)
        return accuracy, conf_matrix

class AdaBoostTrainer(BaseTrainer):
    def __init__(self):
        super().__init__()
        self.model = AdaBoostClassifier(
            estimator=DecisionTreeClassifier(max_depth=1),
            random_state=42
        )

    def train(self, X_train, y_train):
        self.model.fit(X_train, y_train)

class RandomForestTrainer(BaseTrainer):
    def __init__(self):
        super().__init__()
        self.model = RandomForestClassifier(random_state=42)

    def train(self, X_train, y_train):
        self.model.fit(X_train, y_train)

  1. main.py
    此檔案為整個專案的「進入點」。讀取資料集後,會透過 DataProcessor 完成前處理,再透過 AdaBoostTrainer 或 RandomForestTrainer 進行模型訓練、超參數調整與評估。此檔案主要用於串接整個專案流程。
from sklearn.datasets import load_breast_cancer
from imblearn.over_sampling import SMOTE 
from data_processor import DataProcessor # 引用我們建立的 DataProcessor.py
from trainers import AdaBoostTrainer, RandomForestTrainer # 引用我們建立的 trainers.py

# 載入資料 (以乳腺癌資料集為例)
data = load_breast_cancer()
X, y = data.data, data.target

# 建立 DataProcessor,並設定 SMOTE 參數
processor = DataProcessor(
    test_size=0.2,
    resampler=SMOTE(sampling_strategy=0.8, random_state=42)
)
X_train, X_test, y_train, y_test = processor.process(X, y)

print("\n==== AdaBoost ====")
ada_trainer = AdaBoostTrainer()
ada_trainer.train(X_train, y_train)
# 超參數搜尋
param_grid = {'n_estimators': [50, 100], 'learning_rate': [0.1, 1.0]}
best_params = ada_trainer.tune_parameters(X_train, y_train, param_grid)
print("Best Parameters:", best_params)
ada_trainer.evaluate(X_test, y_test)

print("\n==== RandomForest ====")
rf_trainer = RandomForestTrainer()
rf_trainer.train(X_train, y_train)
param_grid = {'n_estimators': [50, 100]}
best_params = rf_trainer.tune_parameters(X_train, y_train, param_grid)
print("Best Parameters:", best_params)
rf_trainer.evaluate(X_test, y_test)

4、如何進行後續擴充與維護
(1) 新增模型:若要新增其他模型(如 XGBoost、LightGBM、SVM 等),可以在 trainers.py 內新增一個類似 XGBoostTrainer 的類別,並繼承 BaseTrainer。如此可保持整體結構一致。
(2) 撰寫測試:可以在專案根目錄下新增 tests/,利用框架如 pytest 或 unittest,撰寫單元測試及整合測試,確保前處理與模型訓練流程正確。
(3) 管理設定檔:若你的專案需要更複雜的設定,可考慮在 config/ 內放置 YAML 或 JSON 等格式的設定檔,集中管理超參數或路徑資訊。
(4) 對外文件與說明:README.md 中除了介紹專案背景外,也可以描述執行步驟 (如:pip install -r requirements.txt、python main.py),或者若專案複雜程度較高,建議另開 docs/ 目錄,並有更詳細的技術文件或範例使用說明。

這些步驟有助於讓機器學習專案更容易擴充、新增功能、維護,以及讓其他人更快上手與理解。


四、結語

透過上述的架構與程式碼範例,可以打造一個乾淨且清晰的機器學習專案。這樣的設計能夠讓開發者或團隊在實作各種功能時更具彈性,並能簡單擴充至更多模型、不同的前處理方法、或進階的參數搜尋策略。

在實務應用中,每個專案的需求皆不相同,但只要依循「讓資料處理、模型訓練、流程整合分開」的思路,就能有效降低維護成本、提高可讀性。希望這份範例能對大家的專案開發帶來幫助,並讓你在打造機器學習系統的過程中更得心應手。
(下次也就不要隨便交出一個.jpynb 或.py給你的主管或同事了XD)


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言