問題:如何做到對(duì)目標(biāo)值的區(qū)間范圍的預(yù)測
使用神經(jīng)網(wǎng)絡(luò)做回歸任務(wù),我們使用MSE、MAE作為損失函數(shù),最終得到的輸出y通常會(huì)被近似為y的期望值,例如有兩個(gè)樣本:(x=1, y=3)和(x=1, y=2),那只用這兩個(gè)樣本訓(xùn)練模型,預(yù)測x=1時(shí)y的值就是2.5。
但有些情況下目標(biāo)值y的空間可能會(huì)比較大,只預(yù)測一個(gè)期望值并不能幫助我們做進(jìn)一步的決策。我們想知道x=1時(shí),y的值最小會(huì)是多少,最大會(huì)是多少,使用MSE、MAE這些損失函數(shù)來構(gòu)建預(yù)測輸出區(qū)間模型時(shí)候,往往需要對(duì)樣本進(jìn)行非常復(fù)雜的處理才能達(dá)到目的,而且因?yàn)閿?shù)據(jù)的預(yù)處理需要加入很強(qiáng)的先驗(yàn)信息,建模效果肯定會(huì)打折扣,再一個(gè)如果數(shù)據(jù)規(guī)模比較大,那將會(huì)在數(shù)據(jù)預(yù)處理上浪費(fèi)大量的時(shí)間。
這里介紹一個(gè)特殊的損失函數(shù)——分位數(shù)損失,利用分位數(shù)損失我們不需要對(duì)數(shù)據(jù)進(jìn)行任何先驗(yàn)的處理,就可以輕松做到預(yù)測輸出y的某一分位數(shù)水平值,例如5%分位數(shù)或95%分位數(shù),利用這個(gè)輸出很自然就完成預(yù)測輸出范圍的回歸模型。
分位數(shù)損失函數(shù)的表達(dá)式如下圖:

其中γ是損失函數(shù)的參數(shù),從實(shí)際意義上可以理解為是我們需要的分位數(shù),這個(gè)損失函數(shù)從結(jié)構(gòu)上看,就是以一定的概率γ懲罰預(yù)測值大于實(shí)際值,同時(shí)鼓勵(lì)預(yù)測值小于實(shí)際值,這樣的效果就是學(xué)得了目標(biāo)y的γ分位數(shù)期望值。對(duì)于分位數(shù)損失函數(shù)的具體應(yīng)用可以參考下邊的例子。
我們這里以波士頓房價(jià)數(shù)據(jù)集為例理解一下分位數(shù)損失函數(shù)的效果。
首先加載數(shù)據(jù)并進(jìn)行分割,另外為了可視化方便,我們將x_test進(jìn)行PCA降維并排序:
boston_data = load_boston()
x = boston_data['data']
y = boston_data['target']
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=77)
# PCA對(duì)數(shù)據(jù)進(jìn)行降維處理,方便可視化
pca = PCA(n_components=1)
x_test_after_pca = pca.fit_transform(x_test, y_test)
# 對(duì)數(shù)據(jù)進(jìn)行排序
x_sort_id = np.argsort(x_test, axis=None)
x_test = x_test[x_sort_id]
y_test = y_test[x_sort_id]
x_for_visual = x_test_after_pca[x_sort_id]
第二步就是損失函數(shù)的定義,在keras中沒有分位數(shù)函數(shù)的定義,可以根據(jù)公式來進(jìn)行定義:
def quantile_loss(y_pred, y_true, r=0.5):
greater_mask = K.cast((y_true <= y_pred), 'float32')
smaller_mask = K.cast((y_true > y_pred), 'float32')
return K.sum((r-1)*K.abs(smaller_mask*(y_true-y_pred)), -1)+K.sum(r*K.abs(greater_mask*(y_true-y_pred)), -1)
還有一種更簡潔的定義方式可以參考這里:
def tilted_loss(q, y_true, y_pred):
e = (y_true-y_pred)
return K.mean(K.maximum(q*e, (q-1)*e), axis=-1)
構(gòu)建一個(gè)最簡單的模型:
def gen_model(ipt_dim):
l1 = Input(shape=(ipt_dim,))
l2 = Dense(10, activation='relu', kernel_initializer=glorot_normal(), bias_initializer=zeros())(l1)
l3 = Dense(5, activation='relu', kernel_initializer=glorot_normal(), bias_initializer=zeros())(l2)
l4 = Dense(1, activation='relu', kernel_initializer=glorot_normal(), bias_initializer=zeros())(l3)
m_model = Model(inputs=l1, outputs=l4)
return m_model
調(diào)用模型,并且對(duì)結(jié)果進(jìn)行可視化:
plt.figure()
plt.scatter(x_for_visual, y_test, label='actual')
q_list = [0.1, 0.5, 0.9]
for quantile in q_list:
model = gen_model(in_dims)
model.compile(loss=lambda y_t, y_p: tilted_loss(quantile, y_t, y_p), optimizer='adam')
model.fit(x_train, y_train, epochs=5, batch_size=8, verbose=1)
y_ = model.predict(x_test)
plt.plot(x_for_visual, y_, label=quantile)
plt.legend()
plt.show()
可視化的最終結(jié)果如下圖,我們可以看到學(xué)習(xí)到的三個(gè)檔位的分位數(shù)回歸模型。其中0.5的與普通MAE回歸結(jié)果是等價(jià)的,只不過0.5預(yù)測的是中位數(shù)而MAE預(yù)測的是期望值;0.1和0.9的兩條曲線可以作為預(yù)測結(jié)果的上下界,能夠包含其中80%的數(shù)據(jù)結(jié)果,如果我們追求更高的區(qū)間置信度,可以選擇更低的下界分位數(shù)和更高的上界分位數(shù)。

總結(jié)
- 在回歸任務(wù)中,我們可以輕松構(gòu)建能夠預(yù)測輸出值范圍的模型,并且不依賴對(duì)數(shù)據(jù)的先驗(yàn)處理,是一種非常高效的方法;
- 分位數(shù)損失函數(shù)的實(shí)現(xiàn),推薦使用文中的第二種方法,涉及的計(jì)算步驟更少,效率更高;
- 分位數(shù)損失函數(shù)與MAE損失類似,是一種線性的損失函數(shù),在loss在0附近的區(qū)間內(nèi)同樣存在導(dǎo)數(shù)不連續(xù)的問題,如果能有簡單的方法可以規(guī)避這個(gè)缺點(diǎn),則會(huì)更加好用。