天天看點

如何測試你的機器學習流水線?

談到資料産品,很多時候有一種誤解,認為這些産品無法通過自動化來進行測試。 盡管流水線的某些部分由于其實驗性和随機性而無法通過傳統的測試方法進行測試,但大部分流水線可以。 除此之外,更加不可預測的算法可以通過專門的驗證過程。

讓我們來看看傳統的測試方法,以及我們如何将這些方法應用到我們的資料/ ML 流水線中。

測試金字塔

标準簡化的測試金字塔如下所示:

如何測試你的機器學習流水線?

這個金字塔表示您将為應用程式編寫的測試類型。 我們從大量的單元測試開始,這些單元測試獨立于其他功能來測試單個功能。 然後我們編寫內建測試來檢查将我們隔離的元件組合在一起是否按預期工作。 最後,我們編寫 UI 或驗收測試,從使用者的角度檢查應用程式是否按預期工作。

在資料産品方面,金字塔并沒有太大的不同。 我們有或多或少相同的等級。

如何測試你的機器學習流水線?

注意:該産品仍将進行UI測試,但本文主要關注與資料流水線最相關的測試。

在一些科幻作家的幫助下,讓我們更仔細地看一下,在機器學習的背景下,每一項都意味着什麼。

單元測試

資料流水線中的大部分代碼都包含資料清理過程。 用于進行資料清洗的每個功能都有一個明确的目标。 例如,假設我們為輸出的模型選擇的特征之一是前一天和當天之間的值的變化。 我們的代碼可能看起來像這樣:

def add_difference(asimov_dataset):
    asimov_dataset['total_naughty_robots_previous_day'] =        
        asimov_dataset['total_naughty_robots'].shift(1)
 
    asimov_dataset['change_in_naughty_robots'] =    
        abs(asimov_dataset['total_naughty_robots_previous_day'] -
            asimov_dataset['total_naughty_robots'])
 
    return asimov_dataset[['total_naughty_robots', 'change_in_naughty_robots', 
        'robot_takeover_type']]      

在這裡,我們知道對于給定的輸入,我們期望得到一定的輸出,是以,我們可以使用以下代碼進行測試:

import pandas as pd
from pandas.testing import assert_frame_equal
import numpy as np
from unittest import TestCase
 
def test_change():
    asimov_dataset_input = pd.DataFrame({
        'total_naughty_robots': [1, 4, 5, 3],
        'robot_takeover_type': ['A', 'B', np.nan, 'A']
    })
 
    expected = pd.DataFrame({
        'total_naughty_robots': [1, 4, 5, 3],
        'change_in_naughty_robots': [np.nan, 3, 1, 2],
        'robot_takeover_type': ['A', 'B', np.nan, 'A']
    })
 
    result = add_difference(asimov_dataset_input)
 
    assert_frame_equal(expected, result)      

對于每個獨立的功能,您将編寫一個單元測試,以確定資料轉換過程的每個部分都對資料産生預期的影響。對于每個功能,您還應該考慮不同的場景(是否有 if 語句?那麼應該測試所有條件)。然後,這些将在每次送出時作為持續內建 (CI) 流水線的一部分運作。

除了檢查代碼是否符合預期之外,單元測試還可以幫助我們調試問題。通過添加一個重制新發現的錯誤的測試,我們可以確定在我們認為已經修複的情況下修複了該錯誤,并且我們可以確定該錯誤不會再次發生。

最後,這些測試不僅檢查代碼是否符合預期,還幫助我們記錄了我們在建立功能時的期望。

內建測試

這些測試旨在确定單獨開發的子產品組合在一起時是否按預期工作。就資料流水線而言,可以檢查如下事項:

  • 資料清理過程産生适合模型的資料集
  • 模型訓練可以處理提供給它的資料并輸出結果(確定将來可以重構代碼)

是以,如果我們采用上面的單元測試函數并添加以下兩個函數:

def remove_nan_size(asimov_dataset):
    return asimov_dataset.dropna(subset=['robot_takeover_type'])
 
def clean_data(asimov_dataset):
    asimov_dataset_with_difference = add_difference(asimov_dataset)
    asimov_dataset_without_na = remove_nan_size(asimov_dataset_with_difference)
 
    return asimov_dataset_without_na      

然後我們可以使用以下代碼來測試組合 ​

​clean_data​

​ 中的函數是否會産生預期的結果:

asimov_dataset_input = pd.DataFrame({
    'total_naughty_robots': [1, 4, 5, 3],
    'robot_takeover_type': ['A', 'B', np.nan, 'A']
})

expected = pd.DataFrame({
    'total_naughty_robots': [1, 4, 3],
    'change_in_naughty_robots': [np.nan, 3, 2],
    'robot_takeover_type': ['A', 'B', 'A']
}).reset_index(drop=True)

result = clean_data(asimov_dataset_input).reset_index(drop=True)

assert_frame_equal(expected, result)      

現在假設我們要做的下一件事是将上述資料輸入邏輯回歸模型。

from sklearn.linear_model import LogisticRegression
 
def get_reression_training_score(asimov_dataset, seed=9787):
    clean_set = clean_data(asimov_dataset).dropna()
 
    input_features = clean_set[['total_naughty_robots', 
        'change_in_naughty_robots']]
    labels = clean_set['robot_takeover_type']
 
    model = LogisticRegression(random_state=seed).fit(input_features, labels)
    return model.score(input_features, labels) * 100      

雖然我們不知道期望值,但我們可以確定我們始終得到相同的值。 測試這種內建對我們很有用,以確定:

  • 模型可以使用資料(每個輸入都存在一個标簽,資料類型由所選模型的類型決定,等等)
  • 我們能夠在未來重構我們的代碼,而不會破壞端到端的功能。

我們可以通過為随機生成器提供相同的種子來確定結果始終相同。 所有主要的庫都允許您設定種子(Tensorflow 有點特殊,因為它需要您通過 numpy 設定種子,是以請記住這一點)。 測試可能如下所示:

from numpy.testing import assert_equal
 
def test_regression_score():
    asimov_dataset_input = pd.DataFrame({
        'total_naughty_robots': [1, 4, 5, 3, 6, 5],
        'robot_takeover_type': ['A', 'B', np.nan, 'A', 'D', 'D']
    })
 
    result = get_reression_training_score(asimov_dataset_input, seed=1234)
    expected = 40.0
 
    assert_equal(result, 50.0)      

這類測試不會像單元測試那樣多,但它們仍然是 CI 流水線的一部分。 您将使用這些來檢查元件的端到端功能,是以,将測試更多主要場景。

機器學習驗證

現在我們已經測試了我們的代碼,我們還需要測試 ML 元件是否正在解決我們試圖解決的問題。當我們談論産品開發時,ML 模型的原始結果(無論基于統計方法多麼準确)幾乎從來都不是所需的最終輸出。這些結果通常在被使用者或其他應用程式使用之前與其他業務規則相結合。是以,我們需要驗證模型是否解決了使用者問題,而不僅僅是準确率/f1-score/其他統計量度是否足夠高。

這對我們有什麼幫助?

  • 它確定模型真正幫助産品解決手頭的問題
  • 例如,如果20%的準确率不正确導緻患者無法獲得所需的治療,那麼将蛇咬傷分類為緻命或非緻命的模型就不是一個好模型。
  • 它確定模型産生的價值在行業方面是有意義的
  • 例如,如果向使用者顯示的最終價格的值太低/太高,而在該行業/市場中沒有意義,那麼以 70% 的準确度預測價格變化的模型就不是一個好的模型。
  • 它提供了一層額外的決策文檔,幫助工程師在流程的後期加入團隊。
  • 它以通用語言提供産品的ML元件的可見性;讓客戶、産品經理和工程師以同樣的方式了解。

這種驗證應該定期運作(通過 CI 流水線或 cron 作業),其結果應該對組織可見。這確定了組織可以看到資料科學元件的進展,并確定及早發現由更改或陳舊資料引起的問題。

總結

ML 元件可以通過多種方式進行測試,為我們帶來以下優勢:

  • 産生一種資料驅動的方法,以確定代碼執行預期的操作
  • 確定我們可以在不破壞産品功能的情況下重構和清理代碼
  • 記錄功能、決策和以前的錯誤
  • 提供産品 ML 元件的進度和狀态的可見性

是以,不要害怕,如果你有編寫代碼的技能,你就有編寫測試的技能,并獲得上述所有優勢。

繼續閱讀