寫給 Python 開發者的 10 條機器學習建議!

寫給 Python 開發者的 10 條機器學習建議!

有時候,作為一個數據科學家,我們常常忘記了初心。我們首先是一個開發者,然後才是研究人員,最後才可能是數學家。我們的首要職責是快速找到無 bug 的解決方案。

我們能做模型並不意味著我們就是神。這並不是編寫垃圾代碼的理由。

自從我開始學習機器學習以來,我犯了很多錯誤。因此我想把我認 機器學習工程中最常用的技能分享出來。在我看來,這也是目前這個行業最缺乏的技能。

我稱他們為不懂軟件的數據科學家,因為他們中很大一部分人都沒有系統地學習過計算機科學課程。而我自己也是如此。

如果要選擇僱傭一個偉大的數據科學家和一個偉大的機器學習工程師,我會選擇僱傭後者。

下面開始我的分享。

學習編寫抽象類

一旦開始編寫抽象類,你就能體會到它給帶來的好處。抽象類強制子類使用相同的方法和方法名稱。許多人在同一個項目上工作, 如果每個人去定義不同的方法,這樣做沒有必要也很容易造成混亂。

<code> 1import os
2from abc import ABCMeta, abstractmethod
3
4
5class DataProcessor(metaclass=ABCMeta):
6    """Base processor to be used for all preparation."""
7    def __init__(self, input_directory, output_directory):
8        self.input_directory = input_directory
9        self.output_directory = output_directory
10
11    @abstractmethod
12    def read(self):
13        """Read raw data."""
14
15    @abstractmethod
16    def process(self):
17        """Processes raw data. This step should create the raw dataframe with all the required features. Shouldn't implement statistical or text cleaning."""
18
19    @abstractmethod
20    def save(self):
21        """Saves processed data."""
22
23
24class Trainer(metaclass=ABCMeta):
25    """Base trainer to be used for all models."""
26
27    def __init__(self, directory):
28        self.directory = directory

29        self.model_directory = os.path.join(directory, 'models')
30
31    @abstractmethod
32    def preprocess(self):
33        """This takes the preprocessed data and returns clean data. This is more about statistical or text cleaning."""
34
35    @abstractmethod
36    def set_model(self):
37        """Define model here."""
38
39    @abstractmethod
40    def fit_model(self):
41        """This takes the vectorised data and returns a trained model."""
42
43    @abstractmethod
44    def generate_metrics(self):
45        """Generates metric with trained model and test data."""
46
47    @abstractmethod
48    def save_model(self, model_name):
49        """This method saves the model in our required format."""
50
51
52class Predict(metaclass=ABCMeta):
53    """Base predictor to be used for all models."""
54
55    def __init__(self, directory):
56        self.directory = directory
57        self.model_directory = os.path.join(directory, 'models')
58
59    @abstractmethod
60    def load_model(self):
61        """Load model here."""
62
63    @abstractmethod
64    def preprocess(self):
65        """This takes the raw data and returns clean data for prediction."""
66
67    @abstractmethod
68    def predict(self):
69        """This is used for prediction."""
70
71
72class BaseDB(metaclass=ABCMeta):
73    """ Base database class to be used for all DB connectors."""
74    @abstractmethod
75    def get_connection(self):
76        """This creates a new DB connection."""
77    @abstractmethod
78    def close_connection(self):

79        """This closes the DB connection."""/<code>


寫給 Python 開發者的 10 條機器學習建議!

固定隨機數種子

實驗的可重複性是非常重要的,隨機數種子是我們的敵人。要特別注重隨機數種子的設置,否則會導致不同的訓練 / 測試數據的分裂和神經網絡中不同權重的初始化。這些最終會導致結果的不一致。

<code>
<code>1def set_seed(args):
2    random.seed(args.seed)
3    np.random.seed(args.seed)
4    torch.manual_seed(args.seed)
5    if args.n_gpu > 0:
6        torch.cuda.manual_seed_all(args.seed)/<code>

先加載少量數據

如果你的數據量太大,並且你正在處理比如清理數據或建模等後續編碼時,請使用 `nrows `來避免每次都加載大量數據。當你只想測試代碼而不是想實際運行整個程序時,可以使用此方法。

非常適合在你本地電腦配置不足以處理那麼大的數據量, 但你喜歡用 Jupyter/VS code/Atom 開發的場景。

<code>1f_train = pd.read_csv(‘train.csv’, nrows=1000) /<code>


寫給 Python 開發者的 10 條機器學習建議!

預測失敗 (成熟開發人員的標誌)

總是檢查數據中的 NA(缺失值),因為這些數據可能會造成一些問題。即使你當前的數據沒有,並不意味著它不會在未來的訓練循環中出現。所以無論如何都要留意這個問題。

<code>1print(len(df))
2df.isna().sum()
3df.dropna()
4print(len(df))/<code>

寫給 Python 開發者的 10 條機器學習建議!

顯示處理進度

在處理大數據時,如果能知道還需要多少時間可以處理完,能夠了解當前的進度非常重要。

方案1:tqdm

<code> 1from tqdm import tqdm
2import time
3
4tqdm.pandas()
5
6df['col'] = df['col'].progress_apply(lambda x: x**2)
7
8text = ""
9for char in tqdm(["a", "b", "c", "d"]):
10    time.sleep(0.25)
11    text = text + char/<code>

方案2:fastprogress

<code>1from fastprogress.fastprogress import master_bar, progress_bar
2from time import sleep
3mb = master_bar(range(10))
4for i in mb:
5    for j in progress_bar(range(100), parent=mb):
6        sleep(0.01)
7        mb.child.comment = f'second bar stat'
8    mb.first_bar.comment = f'first bar stat'
9    mb.write(f'Finished loop {i}.')/<code>


解決 Pandas 慢的問題

如果你用過 pandas,你就會知道有時候它的速度有多慢ーー尤其在團隊合作時。與其絞盡腦汁去尋找加速解決方案,不如通過改變一行代碼來使用 modin。

<code>1import modin.pandas as pd 記錄函數的執行時間/<code>

並不是所有的函數都生來平等。

即使全部代碼都運行正常,也並不能意味著你寫出了一手好代碼。一些軟錯誤實際上會使你的代碼變慢,因此有必要找到它們。使用此裝飾器記錄函數的時間。

<code> 1import time
2
3def timing(f):
4    """Decorator for timing functions
5    Usage:
6    @timing
7    def function(a):
8        pass
9    """
10
11
12    @wraps(f)
13    def wrapper(*args, **kwargs):
14        start = time.time()
15        result = f(*args, **kwargs)
16        end = time.time()
17        print('function:%r took: %2.2f sec' % (f.__name__,  end - start))
18        return result
19    return wrapp/<code>


<code>不要在雲上燒錢/<code>

沒有人喜歡浪費雲資源的工程師。

我們的一些實驗可能會持續數小時。跟蹤它並在完成後關閉雲實例是很困難的。我自己也犯過錯誤,也看到過有些人會有連續幾天不關機的情況。

這種情況經常會發生在我們週五上班,留下一些東西運行,直到週一回來才意識到。

只要在執行結束時調用這個函數,你的屁股就再也不會著火了!

使用 `try` 和 `except` 來包裹 main 函數,一旦發生異常,服務器就不會再運行。我就處理過類似的案例

讓我們多一點責任感,低碳環保從我做起。

<code>
<code> 1import os
2
3def run_command(cmd):

4    return os.system(cmd)
5
6def shutdown(seconds=0, os='linux'):
7    """Shutdown system after seconds given. Useful for shutting EC2 to save costs."""
8    if os == 'linux':
9        run_command('sudo shutdown -h -t sec %s' % seconds)
10    elif os == 'windows':
11        run_command('shutdown -s -t %s' % seconds)/<code>
<code>
寫給 Python 開發者的 10 條機器學習建議!

創建和保存報告

在建模的某個特定點之後,所有的深刻見解都來自於對誤差和度量的分析。確保為自己和上司創建並保存格式正確的報告。

不管怎樣,管理層都喜歡報告,不是嗎?

<code>
<code> 1import json
2import os
3
4from sklearn.metrics import (accuracy_score, classification_report,
5                             confusion_matrix, f1_score, fbeta_score)
6
7def get_metrics(y, y_pred, beta=2, average_method='macro', y_encoder=None):
8    if y_encoder:
9        y = y_encoder.inverse_transform(y)
10        y_pred = y_encoder.inverse_transform(y_pred)
11    return {
12        'accuracy': round(accuracy_score(y, y_pred), 4),
13        'f1_score_macro': round(f1_score(y, y_pred, average=average_method), 4),
14        'fbeta_score_macro': round(fbeta_score(y, y_pred, beta, average=average_method), 4),
15        'report': classification_report(y, y_pred, output_dict=True),
16        'report_csv': classification_report(y, y_pred, output_dict=False).replace('\\n','\\r\\n')
17    }
18
19
20def save_metrics(metrics: dict, model_directory, file_name):
21    path = os.path.join(model_directory, file_name + '_report.txt')
22    classification_report_to_csv(metrics['report_csv'], path)
23    metrics.pop('report_csv')
24    path = os.path.join(model_directory, file_name + '_metrics.json')
25    json.dump(metrics, open(path, 'w'), indent=4)/<code>

寫出一手好 API

結果不好,一切都不好。

你可以做很好的數據清理和建模,但是你仍然可以在最後製造巨大的混亂。通過我與人打交道的經驗告訴我,許多人不清楚如何編寫好的 api、文檔和服務器設置。我將很快寫另一篇關於這方面的文章,但是先讓我簡要分享一部分。


需要資料的可以私信小編髮發關鍵詞“python” 就可以領取了!


分享到:


相關文章: