遷移學習,即Transfer learning 可以簡單定義為:將在源域S的知識遷移到目標域T任務,提高在目標域T的任務下模型預測的性能。

顧名思義就是遷移其他領域的數據經驗,用于當前的任務,以解決當前領域任務的數據積累不充分的問題。而通常地,其他領域的數據和當前目標域任務的數據分布是有差異的。

在NLP、CV任務中很天然地適合做遷移學習,各種的CV、NLP預訓練模型層出不窮(如Bert、GPT、MAE)。一個例子如文本分類任務,我們可以借用在其他文本數據已經訓練好的預訓練模型(word2vec、BERT、GPT等等),用于當前的新聞分類任務里面。這樣可以大大減少當前任務需要的數據,而且預訓練模型是基于大樣本學習的經驗,做下微調,應用效果也杠杠的。

二、風控的遷移學習

回到金融風控任務,需要寄望于遷移學習的場景還是挺多的。很經常的,業(yè)務有擴展,引入了新的一個經營客群,而新的客群樣本量剛開始肯定是很少的,這時就很需要借助下舊客群的數據。

而難點在于,風控領域很難像NLP領域那樣的文字表示直接遷移,NLP中一個任務的文本表示可能就很適用另一文本任務。

風控面對的主要是結構化數據(Tabular Data),一個任務的數據組成、特征含義就很復雜多樣了,盡管可以抽取出同一組特征表示,數據分布可能也是天差地別。這種情況下怎么做遷移學習呢?

引用下遷移學習經典論文《A survey on transfer learning》,遷移學習可以分為四種基本的方法:

下面結合風控的信用評分卡的任務,具體介紹遷移學習方法及項目代碼實踐。

2.1 基于樣本的遷移

基于樣本的遷移,是通過遷移源域的某些樣本或設定樣本權重到目標域學習。

基于樣本應該是結構化數據任務應用較為廣泛的遷移方法,但這類方法通常只在領域間分布差異較小時有效。最簡單直接的,可以把原領域的樣本直接加入目標領域一起學習訓練,但由于數據分布差異的問題,這樣可能效果反而更差。

# 代碼來源:公眾號-算法進階
# 完整代碼:github.com/aialgorithm/Blog 請見文章對應項目代碼

train_xa = pd.concat([train_x,train_source[filter_feas]]) # 直接把全量原領域數據加到目標領域訓練
train_ya = pd.concat([train_y,train_source['isDefault']])
lgb_source=lightgbm.LGBMClassifier(n_estimators=100, num_leaves=4,class_weight= 'balanced',metric = 'AUC',lambda_l1=1,lambda_l2=1)

lgb_source.fit(train_xa, train_ya)
print(train_ya.value_counts())
# 以目標領域的測試集判斷效果
print('train ',model_metrics(lgb_source,train_x, train_y))
print('test ',model_metrics(lgb_source,test_x,test_y))

對于本項目直接拼湊兩份領域數據用于訓練,效果提升并不大。相比暴力拼湊,還有有兩種做法可以減少分布差異。

2.1.1 樣本選擇法

只從原領域遷移與目標域分布一致(相似)的那部分樣本。

我們可以通過像one class-SVM 、孤立森林等異常檢測方法,以目標域樣本為訓練單分類SVM,選取預測原領域的樣本中與目標域相似概率(閾值)較高的樣本。

from sklearn.svm import OneClassSVM
from sklearn.ensemble import IsolationForest
ir = IsolationForest()#OneClassir()
ir.fit(train_x.fillna(0))
# 預測
train_source['isnormal'] = ir.predict(train_source[filter_feas].fillna(0)) #-1為分布異常的樣本
train_source['normal_score'] = ir.score_samples(train_source[filter_feas].fillna(0))+1 #正常程度數值
train_source['normal_score'].hist(bins=10)

#篩選出分布正常的樣本,拼接訓練集

train_xa = pd.concat([train_x,train_source.loc[train_source['isnormal']==1,filter_feas]])
train_ya = pd.concat([train_y,train_source.loc[train_source['isnormal']==1,'isDefault']])

lgb_source.fit(train_xa, train_ya)
print(train_ya.value_counts())
# 以目標領域的測試集判斷效果
print('train ',model_metrics(lgb_source,train_x, train_y))
print('test ',model_metrics(lgb_source,test_x,test_y))

其中預測概率的閾值是個經驗值,可以通過搜索不同閾值下實際效果確定。另外,可以結合實際效果,進行多次迭代選擇。

2.1.2 樣本權重法

對與目標域分布一致性較高的樣本,就給以較高的樣本學習權重。反之亦然。通過樣本權重控制對原領域樣本的有效利用。

import numpy as np

from sklearn.svm import OneClassSVM
from sklearn.ensemble import IsolationForest
ir = IsolationForest()#OneClassir()
ir.fit(train_x.fillna(0))
# 預測
train_source['isnormal'] = ir.predict(train_source[filter_feas].fillna(0)) #-1為與目標域分布異常的樣本
train_source['normal_score'] = ir.score_samples(train_source[filter_feas].fillna(0))**2 #正常程度數值
train_source['normal_score'].hist(bins=10)

#拼接訓練集

train_xa = pd.concat([train_x,train_source.loc[:,filter_feas]])
train_ya = pd.concat([train_y,train_source.loc[:,'isDefault']])
weights = np.append(np.array([1]*train_x.shape[0]) ,train_source['normal_score']) #目標域權重固定為1,原領域按照正常程度加權

lgb_source.fit(train_xa, train_ya,sample_weight=weights)
print(train_ya.value_counts())
# 以目標領域的測試集判斷效果
print('train ',model_metrics(lgb_source,train_x, train_y))
print('test ',model_metrics(lgb_source,test_x,test_y))
#拼接訓練集

train_xa = pd.concat([train_x,train_source.loc[:,filter_feas]])
train_ya = pd.concat([train_y,train_source.loc[:,'isDefault']])

train_source['proba'] = lgb_tar.predict_proba(train_source[filter_feas])[:,1]
train_source['samplew'] = 1-abs(train_source['proba']-train_source['isDefault'])
weights = np.append(np.array([1]*train_x.shape[0]) ,train_source['samplew']) #目標域權重固定為1,原領域按照分類準確程度加權

lgb_source.fit(train_xa, train_ya,sample_weight=weights)
print(train_ya.value_counts())
# 以目標領域的測試集判斷效果
print('train ',model_metrics(lgb_source,train_x, train_y))
print('test ',model_metrics(lgb_source,test_x,test_y))

以上幾種方法都可以在原領域樣本引入一個先驗的樣本權重,加入到模型訓練的sample_weights。其中,目標領域的樣本就沒必要加樣本權重或加入個較大的權重。當然,權重方法的好壞最終還是要結合實際效果。

不同于上面直接給一個固定的樣本權重,我們還可以在訓練學習中動態(tài)地調整好權重,這里有個現(xiàn)成的遷移學習boosting框架TrAdaboost 《Boosting for Transfer Learning 》

TrAdaboost可以通過動態(tài)調整樣本權重,在目標領域的任務上利用好原領域的數據。權重的調整的大概思路:以目標域樣本評估訓練損失,對于造成較大誤差的原領域的那部分樣本,則認為其和目標領域數據差異較大,不斷降低它的權重。而對于目標領域分類錯誤的樣本則提升樣本權重(這個和Adaboost難樣本一脈相承)。核心代碼如下

"""" 附錄相關資料
論文地址:cse.hkust.edu.hk/~qyang/Docs/2007/tradaboost.pdf
代碼來源:github.com/loyalzc/transfer_learning/blob/master/TrAdaboost
論文解讀:by馬東什么zhuanlan.zhihu.com/p/109540481
""""
class TrAdaboost:
def __init__(self, base_classifier=DecisionTreeClassifier(), N=10):
self.base_classifier = base_classifier
self.N = N
self.beta_all = np.zeros([1, self.N])
self.classifiers = []

def fit(self, x_source, x_target, y_source, y_target):
x_train = np.concatenate((x_source, x_target), axis=0)
y_train = np.concatenate((y_source, y_target), axis=0)
x_train = np.asarray(x_train, order='C')
y_train = np.asarray(y_train, order='C')
y_source = np.asarray(y_source, order='C')
y_target = np.asarray(y_target, order='C')

row_source = x_source.shape[0]
row_target = x_target.shape[0]

# 初始化權重
weight_source = np.ones([row_source, 1]) / row_source
weight_target = np.ones([row_target, 1]) / row_target
weights = np.concatenate((weight_source, weight_target), axis=0)

beta = 1 / (1 + np.sqrt(2 * np.log(row_source / self.N)))

result = np.ones([row_source + row_target, self.N])
for i in range(self.N):
weights = self._calculate_weight(weights)
self.base_classifier.fit(x_train, y_train, sample_weight=weights[:, 0])
self.classifiers.append(self.base_classifier)

result[:, i] = self.base_classifier.predict(x_train)
error_rate = self._calculate_error_rate(y_target,
result[row_source:, i],
weights[row_source:, :])

print("Error Rate in target data: ", error_rate, 'round:', i, 'all_round:', self.N)

if error_rate > 0.5:
error_rate = 0.5
if error_rate == 0:
self.N = i
print("Early stopping...")
break
self.beta_all[0, i] = error_rate / (1 - error_rate)

# 調整 target 樣本權重 錯誤樣本權重變大
for t in range(row_target):
weights[row_source + t] = weights[row_source + t] * np.power(self.beta_all[0, i], -np.abs(result[row_source + t, i] - y_target[t]))
# 調整 source 樣本 錯分樣本變小
for s in range(row_source):
weights[s] = weights[s] * np.power(beta, np.abs(result[s, i] - y_source[s]))

def predict(self, x_test):
result = np.ones([x_test.shape[0], self.N + 1])
predict = []

i = 0
for classifier in self.classifiers:
y_pred = classifier.predict(x_test)
result[:, i] = y_pred
i += 1

for i in range(x_test.shape[0]):
left = np.sum(result[i, int(np.ceil(self.N / 2)): self.N] *
np.log(1 / self.beta_all[0, int(np.ceil(self.N / 2)):self.N]))

right = 0.5 * np.sum(np.log(1 / self.beta_all[0, int(np.ceil(self.N / 2)): self.N]))

if left >= right:
predict.append(1)
else:
predict.append(0)
return predict

def predict_prob(self, x_test):
result = np.ones([x_test.shape[0], self.N + 1])
predict = []

i = 0
for classifier in self.classifiers:
y_pred = classifier.predict(x_test)
result[:, i] = y_pred
i += 1

for i in range(x_test.shape[0]):
left = np.sum(result[i, int(np.ceil(self.N / 2)): self.N] *
np.log(1 / self.beta_all[0, int(np.ceil(self.N / 2)):self.N]))

right = 0.5 * np.sum(np.log(1 / self.beta_all[0, int(np.ceil(self.N / 2)): self.N]))
predict.append([left, right])
return predict

def _calculate_weight(self, weights):
sum_weight = np.sum(weights)
return np.asarray(weights / sum_weight, order='C')

def _calculate_error_rate(self, y_target, y_predict, weight_target):
sum_weight = np.sum(weight_target)
return np.sum(weight_target[:, 0] / sum_weight * np.abs(y_target - y_predict))

2.2 基于特征的遷移

基于特征的遷移方法是指將源域和目標域的數據特征變換到統(tǒng)一特征空間中,來減少源域和目標域之間的差距。它基于這樣的假設:“為了有效的遷移,良好的表征應該是對主要學習任務的區(qū)別性,以及對源域和目標域的不加區(qū)分。”

基于特征的遷移學習方法也是一大熱門,大多是與神經網絡的表示學習進行結合,應用于文本、圖像數據等任務。

對于結構化數據,個人理解基于特征的遷移通常是要結合樣本、模型層面的遷移。對應著,可以通過特征加工轉換到同一分布減少些差異(如歸一化什么的),以及可以做下特征選擇,尋找既適用于源域又適用于目標域的可遷移特征。比如可以通過特征重要性及穩(wěn)定性PSI進行篩選,在原領域 與目標領域的分布比較一致的特征。PSI表示的是實際分布與預期兩個分布間的差異,PSI=SUM( (實際占比 – 預期占比)* ln(實際占比 / 預期占比) )。特征選擇方法可以參考這篇《特征選擇》

特征處理完,再進一步選擇合適的樣本或模型遷移方法。

2.3 基于模型的遷移

基于模型的方法是NLP、CV領域較為普遍的思路,通過大數據訓一個預訓練模型,然后下游任務只需要微調下就可以用了。

而對于結構化數據任務,這里我們可以采用熟悉的模型融合方式,簡單高效地利用好原領域的這部分數據信息,基于模型的方法很大程度也包含了特征層面的加工及遷移。

一個融合的思路是,我們可以先以原領域的數據訓練模型A,將模型A的預測概率作為特征加入 以目標領域訓練的模型。這個思路還是和預訓練微調很像的。

# 先從原領域學習一個模型
lgb_source=lightgbm.LGBMClassifier(n_estimators=100, num_leaves=6,class_weight= 'balanced',metric = 'AUC',lambda_l1=1,lambda_l2=1)

lgb_source.fit(train_source.loc[:,filter_feas], train_source.loc[:,'isDefault'])

# 原領域模型評分作為特征輸入目標領域模型
train_bank['source_score'] = lgb_source.predict_proba(train_bank.loc[:,filter_feas])[:,1]
train_x, test_x, train_y, test_y = train_test_split(train_bank[filter_feas+['source_score']], train_bank.isDefault,test_size=0.3, random_state=0)

lgb_tar=lightgbm.LGBMClassifier(n_estimators=100, num_leaves=6,class_weight= 'balanced',metric = 'AUC',lambda_l1=1,lambda_l2=1)
lgb_tar.fit(train_x, train_y)
print(train_y.value_counts())
print('train ',model_metrics(lgb_tar,train_x, train_y))
print('test ',model_metrics(lgb_tar,test_x,test_y))
## 輸出特征重要性
from lightgbm import plot_importance

plot_importance(lgb_tar)

文末小結,本文介紹了在金融風控的結構化數據的場景中,基于樣本、特征、模型的遷移學習的方法,以充分利用不同領域的樣本提高模型效果。方法及代碼較為簡化,重在結合實際場景的驗證及優(yōu)化。

文章轉自微信公眾號@算法進階

上一篇:

機器學習回歸模型的最全總結!

下一篇:

機器學習統(tǒng)計概率分布全面總結(Python)
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費