農林漁牧網

您現在的位置是:首頁 > 林業

哪個更好:一個通用模型還是多個專用模型?

2023-02-02由 大資料文摘 發表于 林業

多元迴歸是什麼

哪個更好:一個通用模型還是多個專用模型?

大資料文摘授權轉載自資料派THU

作者:Samuele Mazzanti

翻譯:歐陽錦

校對:趙茹萱

我最近聽到一家公司宣稱:“我們在生產中有60個流失模型。”(注:流失模型是一種透過數學來建模流失對業務的影響。)我問他們為什麼這麼多。他們回答說,他們擁有 5 個品牌,在 12 個國家/地區運營,並且由於他們想為每個品牌和國家/地區的組合開發一種模型,因此共計 60 種模型。於是,我問他們:“你試過只用一種模型嗎?”

他們認為這沒有意義,因為他們的品牌彼此之間非常不同,他們經營的目標國家也是如此:“你不能訓練一個單一的模型並期望它對品牌 A 的美國客戶和對品牌 B 的德國客戶”。

由於在業內經常聽到這樣的說法,我很好奇這個論點是否反映在資料中,或者只是沒有事實支援的猜測。

這就是為什麼在本文中,我將系統地比較兩種方法:

將所有資料提供給一個模型,也就是一個通用模型(general model);

為每個細分市場構建一個模型(在前面的示例中,品牌和國家/地區的組合),也就是許多專業模型(specialized models)。

我將在流行的Python庫Pycaret提供的12個真實資料集上測試這兩種策略。

通用模型與專用模型

這兩種方法究竟是如何工作的?

假設我們有一個數據集。資料集由預測變數矩陣(稱為X)和目標變數(稱為y)組成。此外,X包含一個或多個可用於分割資料集的列(在前面的示例中,這些列是“品牌”和“國家/地區”)。

現在讓我們嘗試以圖形方式表示這些元素。我們可以使用X的其中一列來視覺化這些段:每種顏色(藍色、黃色和紅色)標識不同的段。我們還需要一個額外的向量來表示訓練集(綠色)和測試集(粉色)的劃分。

哪個更好:一個通用模型還是多個專用模型?

哪個更好:一個通用模型還是多個專用模型?

訓練集的行標記為綠色,而測試集的行標記為粉紅色。X 的彩色列是分段列:每種顏色標識不同的分段(例如,藍色是美國,黃色是英國,紅色是德國)。圖源作者。

鑑於這些要素,以下是這兩種方法的不同之處。

第一種策略:通用模型

在整個訓練集上擬合一個獨特的模型,然後在整個測試集上測量其效能:

哪個更好:一個通用模型還是多個專用模型?

通用模型。所有片段(藍色、黃色和紅色)都被饋送到同一個模型。圖源作者

第二個策略:專業模型

第二種策略涉及為每個段建立模型,這意味著重複訓練/測試過程k次(其中k是片段數,在本例中為 3)。

哪個更好:一個通用模型還是多個專用模型?

圖片

專用模型。每個段被饋送到不同的模型。[作者圖片]

請注意,在實際用例中,分段的數量可能是相關的,從幾十個到數百個不等。因此,與使用一個通用模型相比,使用專用模型存在幾個實際缺點,例如:

更高的維護工作量;

更高的系統複雜度;

更高的(累積的)培訓時間;

更高的計算成本:

更高的儲存成本。

那麼,為什麼會有人想要這樣做呢?

對通用模型的偏見

專用模型的支持者聲稱,獨特的通用模型在給定的細分市場(比如美國客戶)上可能不太精確,因為它還了解了不同細分市場(例如歐洲客戶)的特徵。

我認為這是因使用簡單模型(例如邏輯迴歸)而產生的錯誤認識。讓我用一個例子來解釋。

假設我們有一個汽車資料集,由三列組成:

汽車型別(經典或現代);

汽車時代;

車價。

我們想使用前兩個特徵來預測汽車價格。這些是資料點:

哪個更好:一個通用模型還是多個專用模型?

哪個更好:一個通用模型還是多個專用模型?

具有兩個部分(經典汽車和現代汽車)的資料集,顯示出與目標變數相關的非常不同的行為。圖源作者

如您所見,根據汽車型別,有兩種完全不同的行為:隨著時間的推移,現代汽車貶值,而老爺車價格上漲。

現在,如果我們在完整資料集上訓練線性迴歸:

linear_regression

= LinearRegression()。fit(df[[

“car_type_classic”

“car_age”

]], df[

“car_price”

得到的係數是:

哪個更好:一個通用模型還是多個專用模型?

哪個更好:一個通用模型還是多個專用模型?

在資料集上訓練的線性迴歸係數。圖源作者

這意味著模型將始終為任何輸入預測相同的值12。

通常,如果資料集包含不同的行為(除非您進行額外的特徵工程),簡單模型將無法正常工作。因此,在這種情況下,人們可能會想訓練兩種專門的模型:一種用於經典汽車,一種用於現代汽車。

但是讓我們看看如果我們使用決策樹而不是線性迴歸會發生什麼。為了使比較公平,我們將生成一棵有3個分支的樹(即3個決策閾值),因為線性迴歸也有3個引數(3個係數)。

decision_tree

= DecisionTreeRegressor(max_depth=

2

)。fit(df[

[ “car_type_classic” , “car_age” ]]

, df

[ “car_price” ]

這是結果:

哪個更好:一個通用模型還是多個專用模型?

哪個更好:一個通用模型還是多個專用模型?

在玩具資料集上訓練的決策樹。[作者圖片]

這比我們用線性迴歸得到的結果要好得多!

關鍵是基於樹的模型(例如 XGBoost、LightGBM 或 Catboost)能夠處理不同的行為,因為它們天生就可以很好地處理特徵互動。

這就是為什麼在理論上沒有理由比一個通用模型更喜歡幾個專用模型的主要原因。但是,一如既往,我們並不滿足於理論解釋。我們還想確保這一猜想得到真實資料的支援。

實驗細節

在本段中,我們將看到測試哪種策略效果更好所需的 Python 程式碼。如果您對細節不感興趣,可以直接跳到下一段,我將在這裡討論結果。

我們的目標是定量比較兩種策略:

訓練一個通用模型;

訓練許多個專用模型。

比較它們的最明顯方法如下:

1。 獲取資料集;

2。 根據一列的值選擇資料集的一部分;

3。 將資料集拆分為訓練資料集和測試資料集;

4。 在整個訓練資料集上訓練通用模型;

5。 在屬於該段的訓練資料集部分上訓練專用模型;

6。 比較通用模型和專用模型在屬於該段的測試資料集部分上的效能。

圖形化:

圖片

哪個更好:一個通用模型還是多個專用模型?

X 中的彩色列是我們用來對資料集進行分層的列。[作者圖片]

這工作得很好,但是,由於我們不想被隨機性愚弄,我們將重複這個過程:

對於不同的資料集;

使用不同的列來分割資料集本身;

使用同一列的不同值來定義段。

換句話說,這就是我們要用虛擬碼做的:

for each dataset:

train general model on the training

set

for

each

column

of

the dataset:

for

each

value

of

the

column

train specialized

model

on

the portion

of

the training

set

for

which

column

=

value

compare

performance

of

general

model

vs。 specialized

model

實際上,我們需要對這個過程做一些微小的調整。

首先,我們說過我們正在使用資料集的列來分割資料集本身。這適用於分類列和具有很少值的離散數字列。對於剩餘的數字列,我們必須透過分箱(binning)使它們分類。

其次,我們不能簡單地使用所有的列。如果我們這樣做,我們將會懲罰專用模型。事實上,如果我們根據與目標變數無關的列選擇細分,就沒有理由相信專門的模型可以表現得更好。為避免這種情況,我們將只使用與目標變數有某種關係的列。

此外,出於類似的原因,我們不會使用所有細分列的值。我們將避免過於頻繁(超過50%)的值,因為期望在大多數資料集上訓練的模型與在完整資料集上訓練的模型具有不同的效能是沒有意義的。我們還將避免測試集中少於100個案例的值,因為結果肯定不會很重要。

鑑於此,這是我使用的完整程式碼:

for

dataset_name in tqdm(dataset_names):

# get data

X,

y, num_features, cat_features, n_classes = get_dataset(dataset_name)

# split index in training and test set, then train general model on the training set

ix_train,

ix_test = train_test_split(X。index, test_size=。25, stratify=y)

model_general

=

CatBoostClassifier()。fit(X=X。loc[ix_train,:], y=y。loc[ix_train], cat_features=cat_features, silent=True)

pred_general

=

pd。DataFrame(model_general。predict_proba(X。loc[ix_test, :]), index=ix_test, columns=model_general。classes_)

# create a dataframe where all the columns are categorical:

# numerical columns with more than 5 unique values are binnized

X_cat

=

X。copy()

X_cat。loc[

, num_features] = X_cat。loc[:, num_features]。fillna(X_cat。loc[:, num_features]。median())。apply(lambda col: col if col。nunique() <= 5 else binnize(col))

# get a list of columns that are not (statistically) independent

# from y according to chi 2 independence test

candidate_columns

=

get_dependent_columns(X_cat, y)

for

segmentation_column in candidate_columns:

# get a list of candidate values such that each candidate:

# - has at least 100 examples in the test set

# - is not more common than 50%

vc_test

=

X_cat。loc[ix_test, segmentation_column]。value_counts()

nu_train

=

y。loc[ix_train]。groupby(X_cat。loc[ix_train, segmentation_column])。nunique()

nu_test

=

y。loc[ix_test]。groupby(X_cat。loc[ix_test, segmentation_column])。nunique()

candidate_values

=

vc_test[(vc_test>=100) & (vc_test/len(ix_test)<。5) & (nu_train==n_classes) & (nu_test==n_classes)]。index。to_list()

for

value in candidate_values:

# split index in training and test set, then train specialized model

# on the portion of the training set that belongs to the segment

ix_value

=

X_cat。loc[X_cat。loc[:, segmentation_column] == value, segmentation_column]。index

ix_train_specialized

=

list(set(ix_value)。intersection(ix_train))

ix_test_specialized

=

list(set(ix_value)。intersection(ix_test))

model_specialized

=

CatBoostClassifier()。fit(X=X。loc[ix_train_specialized,:], y=y。loc[ix_train_specialized], cat_features=cat_features, silent=True)

pred_specialized

=

pd。DataFrame(model_specialized。predict_proba(X。loc[ix_test_specialized, :]), index=ix_test_specialized, columns=model_specialized。classes_)

# compute roc score of both the general model and the specialized model and save them

roc_auc_score_general

=

get_roc_auc_score(y。loc[ix_test_specialized], pred_general。loc[ix_test_specialized, :])

roc_auc_score_specialized

=

get_roc_auc_score(y。loc[ix_test_specialized], pred_specialized)

results

=

results。append(pd。Series(data=[dataset_name, segmentation_column, value, len(ix_test_specialized), y。loc[ix_test_specialized]。value_counts()。to_list(), roc_auc_score_general, roc_auc_score_specialized],index=results。columns),ignore_index=True

為了便於理解,我省略了一些實用函式的程式碼,get_dataset例如get_dependent_columns和get_roc_auc_score。但是,您可以在此GitHub儲存庫中找到完整程式碼。

結果

為了對通用模型與專用模型進行大規模比較,我使用了Pycaret(MIT許可下的 Python庫)中提供的12個真實世界資料集。

對於每個資料集,我發現列與目標變數顯示出一些顯著關係(獨立性卡方檢驗的p值<1%)。對於任何一列,我只保留不太罕見(它們必須在測試集中至少有100個案例)或過於頻繁(它們必須佔資料集的比例不超過50%)的值。這些值中的每一個都標識資料集的一個片段。

對於每個資料集,我在整個訓練資料集上訓練了一個通用模型(CatBoost,沒有引數調整)。然後,對於每個片段,我在屬於相應片段的訓練資料集部分上訓練了一個專門的模型(同樣是CatBoost,沒有引數調整)。最後,我比較了兩種方法在屬於該段的測試資料集部分上的效能(ROC曲線下的面積)。

讓我們看一下最終輸出:

哪個更好:一個通用模型還是多個專用模型?

哪個更好:一個通用模型還是多個專用模型?

12 個真實資料集的模擬結果。每行都是一個段,由資料集、列和值的組合標識。圖源作者。

原則上,要選出獲勝者,我們可以只看“roc_general”和“roc_specialized”之間的區別。然而,在某些情況下,這種差異可能是偶然的。因此,我也計算了差異何時具有統計顯著性(有關如何判斷兩個ROC分數之間的差異是否顯著的詳細資訊,請參閱本文)。

因此,我們可以在兩個維度上對601比較進行分類:通用模型是否優於專用模型以及這種差異是否顯著。這是結果:

哪個更好:一個通用模型還是多個專用模型?

哪個更好:一個通用模型還是多個專用模型?

601 比較的總結。“general > specialized”表示通用模型的ROC曲線下面積高於專用模型,“specialized > general”則相反。“顯著”/“不顯著”表明這種差異是否顯著。圖源作者。

很容易看出,通用模型在89%的時間(454+83/601)優於專用模型。但是,如果我們堅持重要的案例,一般模型在95%的時間(87箇中的83個)優於專用模型。

出於好奇,我們也將87個重要案例視覺化為一個圖表,x軸為專用模型的ROC分數,y軸為通用模型的ROC分數。

哪個更好:一個通用模型還是多個專用模型?

比較:專用模型的 ROC 與通用模型的 ROC。僅包括顯示出顯著差異的部分。圖源作者。

對角線上方的所有點都標識了通用模型比專用模型表現更好的情況。

但是,更好在哪裡?

我們可以計算兩個ROC分數之間的平均差。事實證明,在87個顯著案例中,通用模型的 ROC 平均比專用模型高2。4%,這是很多!

結論

在本文中,我們比較了兩種策略:使用在整個資料集上訓練的通用模型與使用專門針對資料集不同部分的許多模型。

我們已經看到,沒有令人信服的理由使用專用模型,因為強大的演算法(例如基於樹的模型)可以在本地處理不同的行為。此外,從維護工作、系統複雜性、訓練時間、計算成本和儲存成本的角度來看,使用專用模型涉及到幾個實際的複雜問題。

我們還在12個真實資料集上測試了這兩種策略,總共有601個可能的片段。在這個實驗中,通用模型在89%的時間裡優於專用模型。只看具有統計顯著性的案例,這個數字上升到95%,ROC得分平均提高2。4%。

您可以在這個GitHub儲存庫中找到本文使用的所有Python程式碼。

原文標題:

What Is Better: One General Model or Many Specialized Models?

原文連結:

https://towardsdatascience。com/what-is-better-one-general-model-or-many-specialized-models-9500d9f8751d