大家好!去年我發(fā)表了幾篇關(guān)于使用神經(jīng)網(wǎng)絡(luò)進(jìn)行財(cái)務(wù)預(yù)測(cè)的教程猴凹,我認(rèn)為其中一些結(jié)果至少很有意思呜笑,值得在真實(shí)交易應(yīng)用中應(yīng)用娶吞。如果您閱讀它們,您必須注意到牵舵,當(dāng)您嘗試將某些機(jī)器學(xué)習(xí)模型放在“隨機(jī)”數(shù)據(jù)上時(shí),希望找到隱藏的模式倦挂,您往往會(huì)過(guò)度適應(yīng)火車(chē)畸颅。我們使用不同的正則化技術(shù)和額外的數(shù)據(jù)來(lái)解決這個(gè)問(wèn)題,但它非常耗時(shí)并且提醒盲目搜索方援。
今天我想介紹一種略微不同的方法來(lái)擬合相同的算法没炒。用概率觀點(diǎn)對(duì)待它們使我們能夠從數(shù)據(jù)本身學(xué)習(xí)正則化,估計(jì)我們的預(yù)測(cè)中的確定性犯戏,使用更少的數(shù)據(jù)進(jìn)行訓(xùn)練并在我們的模型中注入額外的概率依賴性送火。我不會(huì)深入研究貝葉斯模型或變分推理的技術(shù)或數(shù)學(xué)細(xì)節(jié)拳话,我會(huì)給出一些概述,但也會(huì)更多地關(guān)注應(yīng)用种吸。與往常一樣弃衍,您可以在此處查看代碼。
I also recommend to check my previous tutorials on financial forecasting with neural nets:
- 簡(jiǎn)單的時(shí)間序列預(yù)測(cè)
- 糾正一維時(shí)間序列預(yù)測(cè)+回測(cè)
- 多變量時(shí)間序列預(yù)測(cè)
- 波動(dòng)率預(yù)測(cè)和定制損失
- 多任務(wù)和多模式學(xué)習(xí)
- 超參數(shù)優(yōu)化
- 用神經(jīng)網(wǎng)絡(luò)增強(qiáng)經(jīng)典策略
- 概率編程和Pyro預(yù)測(cè)
- pandas中進(jìn)行回測(cè)
如需要進(jìn)一步了解概率編程坚俗、貝葉斯模型及其應(yīng)用镜盯,推薦閱讀以下資源:
- Pattern recognition and machine learning
- Bayesian methods for hackers
- 可以看以下以下Python庫(kù)及相關(guān)文檔:
- PyMC3
- Edward
- Pyro
概率編程
這里的概率指什么,為什么稱之為編程? 首先我們回憶一下“正常的”神經(jīng)網(wǎng)絡(luò)及其輸出猖败。神經(jīng)網(wǎng)絡(luò)帶有參數(shù)(權(quán)重)速缆,這些是以矩陣形式表示的,神經(jīng)網(wǎng)絡(luò)的輸出通常是一些標(biāo)量值或向量(例如做分類(lèi)時(shí))辙浑。當(dāng)模型訓(xùn)練完成后激涤,比如說(shuō)使用SGD進(jìn)行訓(xùn)練,得到一些固定的權(quán)重矩陣判呕,網(wǎng)絡(luò)對(duì)于相同的樣本會(huì)輸出相同的結(jié)果倦踢。沒(méi)錯(cuò)!那么如果把參數(shù)和輸出看做相互依賴的分布會(huì)怎么樣呢侠草?神經(jīng)網(wǎng)絡(luò)里每個(gè)權(quán)重可以看做某個(gè)分布的樣本辱挥,同樣輸出可以看做全網(wǎng)絡(luò)作為分布的一個(gè)樣本,這個(gè)分布依賴于網(wǎng)絡(luò)中所有參數(shù)边涕。這告訴我們什么?
我們從最基本的開(kāi)始說(shuō)晤碘。如果把網(wǎng)絡(luò)看做一組相互依賴的分布,它首先是一個(gè)聯(lián)合分布 p(y, z|x), 這里y是輸出功蜓、z是模型“內(nèi)部的”潛變量园爷,依賴于輸入 x (常規(guī)神經(jīng)網(wǎng)絡(luò)都可以這樣看)。有趣的是這樣的神經(jīng)網(wǎng)絡(luò)分布式撼,可以看做從 y ~ p(y|x) 采樣童社,然后把輸出作為其分布(其中輸出通常是該分布的樣本期望,其標(biāo)準(zhǔn)差?—?—作為不確定性的估計(jì)?—?—分布的尾部越大——輸出的置信度越低)著隆。
有了這樣的設(shè)定就后面理解略微清晰了點(diǎn)扰楼,我們只需要記得,從現(xiàn)在開(kāi)始模型中所有的參數(shù)美浦、輸入和輸出都是分布弦赖。當(dāng)我們訓(xùn)練模型時(shí),需要擬合這些分布的參數(shù)浦辨,在實(shí)際任務(wù)中獲得更高的精度蹬竖。這里我們還需要知道,參數(shù)分布的形態(tài)是由我們來(lái)設(shè)定的 (開(kāi)始時(shí)所有權(quán)重都初始化為 w ~ Normal(0, 1)分布,此后通過(guò)訓(xùn)練獲得正確的均值和方差)案腺。最初的分布是先驗(yàn)分布庆冕,經(jīng)過(guò)訓(xùn)練以后的分布是后驗(yàn)分布。我們用后者去采樣獲得輸出劈榨。
模型擬合是怎么做的? 通用框架叫做變分推斷访递。如果不看細(xì)節(jié)我們可以假定,需要找到一個(gè)模型最大化對(duì)數(shù)似然 p_w(z|x), 這里w是模型參數(shù) (分布的參數(shù)), z 是潛變量 (隱神經(jīng)元的輸出, 采樣自參數(shù)w的分布) 同辣,x是輸入樣本數(shù)據(jù)拷姿。我們的模型就是這樣的。在Pyro可引入這樣一個(gè)實(shí)體作為該模型的guide旱函,其中包含所有潛變量的分布q_ф(z), 此處 ф 稱為變分參數(shù)响巢。這個(gè)分布必須近似“實(shí)際”模型參數(shù)的分布,也就是最好的擬合輸入數(shù)據(jù)棒妨。
訓(xùn)練目標(biāo)是最小化[log(p_w(z|x))?—?log(q_ф(z))] 關(guān)于輸入數(shù)據(jù)和guide樣本的期望踪古。這里不會(huì)過(guò)多介紹訓(xùn)練的細(xì)節(jié),因?yàn)榭赡苌婕昂脦组T(mén)大學(xué)課程券腔,現(xiàn)在把這個(gè)看做黑盒優(yōu)化就好伏穆。
好的 那為什么稱為編程? 通常這樣的統(tǒng)計(jì)模型(神經(jīng)網(wǎng)絡(luò))被描述為從一個(gè)變量到另一個(gè)變量的有向圖, 這樣直接顯示變量的依賴:
最初概率編程語(yǔ)言被用于定義諸如這樣的模型并以此進(jìn)行推斷。
使用概率編程的原因
從數(shù)據(jù)中學(xué)習(xí)它作為額外的潛變量纷纫,而不是傳統(tǒng)的在模型中使用dropouts或L1正則化枕扫。考慮到所有權(quán)重都是分布辱魁,可以從那里采樣N次并得到輸出的分布烟瞧,這里可以看一下標(biāo)準(zhǔn)差估計(jì)一下模型輸出結(jié)果的置信度。這種方法的好處是染簇,我們只需要較少的訓(xùn)練數(shù)據(jù)并可靈活的在變量間增加依賴参滴。
不使用概率編程的原因
我在貝葉斯模型使用尚沒(méi)有積累大量的經(jīng)驗(yàn),不過(guò)在使用Pyro和PyMC3的過(guò)程中我發(fā)現(xiàn)锻弓,訓(xùn)練過(guò)程很長(zhǎng)且難以確定先驗(yàn)概率砾赔。另外處理生產(chǎn)環(huán)境的樣本分布可能導(dǎo)致誤解和模棱兩可的情況。
數(shù)據(jù)準(zhǔn)備
我從網(wǎng)上獲取每日以太幣的牌價(jià)等數(shù)據(jù)弥咪,其中包括OHLCV (開(kāi)盤(pán)过蹂、最高十绑、最低聚至、收盤(pán)、成交量) 本橙,另外還獲取了每天涉及以太幣的推特?cái)?shù)量扳躬。這里選取7天價(jià)格、成交量和推特?cái)?shù)的換算為變動(dòng)%,預(yù)測(cè)下一個(gè)交易日的變動(dòng)贷币。
價(jià)格 推特?cái)?shù)和成交量變動(dòng)
上圖為采樣的數(shù)據(jù)?—藍(lán)色表示價(jià)格變動(dòng), 黃色表示推特?cái)?shù)變動(dòng)击胜,綠色是成交量變動(dòng)。這些變量之間存在一些正相關(guān)(大概0.1–0.2之間), 所以我們可利用數(shù)據(jù)中的模式來(lái)訓(xùn)練我們的模型役纹。
貝葉斯線性回歸
首先我想看一下簡(jiǎn)單線性回歸效果如何(直接從Pyro教程復(fù)制結(jié)果). 下面定義PyTorch模型 (官方教程里有各個(gè)詳細(xì)的說(shuō)明):
class RegressionModel(nn.Module):
def __init__(self, p):
super(RegressionModel, self).__init__()
self.linear = nn.Linear(p, 1)
def forward(self, x):
# x * w + b
return self.linear(x)</pre>
這是一個(gè)簡(jiǎn)單的確定性模型和之前一樣, 不過(guò)這就是在Pyro中定義概率模型的方式:
def model(data):
# Create unit normal priors over the parameters
mu = Variable(torch.zeros(1, p)).type_as(data)
sigma = Variable(torch.ones(1, p)).type_as(data)
bias_mu = Variable(torch.zeros(1)).type_as(data)
bias_sigma = Variable(torch.ones(1)).type_as(data)
w_prior, b_prior = Normal(mu, sigma), Normal(bias_mu, bias_sigma)
priors = {'linear.weight': w_prior, 'linear.bias': b_prior}
lifted_module = pyro.random_module("module", regression_model, priors)
lifted_reg_model = lifted_module()
with pyro.iarange("map", N, subsample=data):
x_data = data[:, :-1]
y_data = data[:, -1]
# run the regressor forward conditioned on inputs
prediction_mean = lifted_reg_model(x_data).squeeze()
pyro.sample("obs",
Normal(prediction_mean, Variable(torch.ones(data.size(0))).type_as(data)),
obs=y_data.squeeze())</pre>
在上面的代碼中我們看到, 用W和b作為廣義線性回歸模型分布的參數(shù)偶摔,它們服從~Normal(0, 1)分布,這里命名為prior促脉。構(gòu)造Pyro隨機(jī)函數(shù)(PyTorch里用RegressionModel), 賦值prior ({‘linear.weight’: w_prior, ‘linear.bias’: b_prior}) 基于輸入數(shù)據(jù)x從 *p(y|x) *采樣辰斋。
模型的guide函數(shù)定義如下:
def guide(data):
w_mu = Variable(torch.randn(1, p).type_as(data.data), requires_grad=True)
w_log_sig = Variable(0.1 * torch.ones(1, p).type_as(data.data), requires_grad=True)
b_mu = Variable(torch.randn(1).type_as(data.data), requires_grad=True)
b_log_sig = Variable(0.1 * torch.ones(1).type_as(data.data), requires_grad=True)
mw_param = pyro.param("guide_mean_weight", w_mu)
sw_param = softplus(pyro.param("guide_log_sigma_weight", w_log_sig))
mb_param = pyro.param("guide_mean_bias", b_mu)
sb_param = softplus(pyro.param("guide_log_sigma_bias", b_log_sig))
w_dist = Normal(mw_param, sw_param)
b_dist = Normal(mb_param, sb_param)
dists = {'linear.weight': w_dist, 'linear.bias': b_dist}
lifted_module = pyro.random_module("module", regression_model, dists)
return lifted_module()</pre>
接下來(lái)為需要訓(xùn)練的分布定義變分分布∪澄叮可以看到宫仗,定義的W和b分布的形狀是一致的。為了更符合現(xiàn)實(shí) (根據(jù)我們的假定)旁仿,在本例中將分布收窄一些 (~Normal(0, 0.1))藕夫。
接下來(lái)訓(xùn)練模型:
for j in range(3000):
epoch_loss = 0.0
perm = torch.randperm(N)
# shuffle data
data = data[perm]
# get indices of each batch
all_batches = get_batch_indices(N, 64)
for ix, batch_start in enumerate(all_batches[:-1]):
batch_end = all_batches[ix + 1]
batch_data = data[batch_start: batch_end]
epoch_loss += svi.step(batch_data)</pre>
擬合后從模型中采樣y,重復(fù)100次枯冈,檢查預(yù)測(cè)的均值和標(biāo)準(zhǔn)差 (標(biāo)準(zhǔn)差越高, 本次預(yù)測(cè)的置信度越低).
preds = []
for i in range(100):
sampled_reg_model = guide(X_test)
pred = sampled_reg_model(X_test).data.numpy().flatten()
preds.append(pred)</pre>
在金融場(chǎng)景中預(yù)測(cè)中經(jīng)典的指標(biāo)是MSE, MAE 或 MAPE毅贮,這里麻煩?—?—?相對(duì)誤差率較小并不等于模型效果就好,還需要檢查那些樣本以外的數(shù)據(jù)在模型中的效果并繪制成圖:
貝葉斯模型30天預(yù)測(cè)
看起來(lái)不太理想霜幼,不過(guò)最后那跳的形狀還不錯(cuò)嫩码,我們繼續(xù)!
普通神經(jīng)網(wǎng)絡(luò)
這里嘗試用以下這個(gè)簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)得到一些有趣的特性,首先我們構(gòu)造一個(gè)MLP罪既,隱層含有25神經(jīng)元后面跟著線性激活函數(shù):
def get_model(input_size):
main_input = Input(shape=(input_size, ), name='main_input')
x = Dense(25, activation='linear')(main_input)
output = Dense(1, activation = "linear", name = "out")(x)
final_model = Model(inputs=[main_input], outputs=[output])
final_model.compile(optimizer='adam', loss='mse')
return final_model</pre>
訓(xùn)練100個(gè)epoch:
model = get_model(len(X_train[0]))
history = model.fit(X_train, Y_train,
epochs = 100,
batch_size = 64,
verbose=1,
validation_data=(X_test, Y_test),
callbacks=[reduce_lr, checkpointer],
shuffle=True)</pre>
以下是訓(xùn)練結(jié)果:
Keras 神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)30天預(yù)測(cè)
結(jié)果不如簡(jiǎn)單貝葉斯回歸铸题,此外模型給不出確定性估計(jì),更重要的是模型也不是正則化的琢感。
貝葉斯神經(jīng)網(wǎng)絡(luò)
現(xiàn)在把剛才用Keras定義的神經(jīng)網(wǎng)絡(luò)用PyTorch框架改寫(xiě)一下:
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer
self.predict = torch.nn.Linear(n_hidden, 1) # output layer
def forward(self, x):
x = self.hidden(x)
x = self.predict(x)
return x</pre>
對(duì)比貝葉斯回歸模型丢间,現(xiàn)在有2組參數(shù) (輸入到隱層以及隱層到輸出),這里我們稍微修改一下模型先驗(yàn):
priors = {'hidden.weight': w_prior,
'hidden.bias': b_prior,
'predict.weight': w_prior2,
'predict.bias': b_prior2}</pre>
以及分布:
dists = {'hidden.weight': w_dist,
'hidden.bias': b_dist,
'predict.weight': w_dist2,
'predict.bias': b_dist2}</pre>
需要給模型中所有分布設(shè)定不同的名字驹针,因?yàn)榇颂幉荒苡心@鈨煽苫蛑貜?fù)! 代碼中可以看到更多細(xì)節(jié)烘挫。我們看一下模型擬合后采樣的最終結(jié)果:
Pyro神經(jīng)網(wǎng)絡(luò)30天預(yù)測(cè)
看起來(lái)比之前所有的結(jié)果都要好一些!
關(guān)于正則化或者說(shuō)貝葉斯模型得到的權(quán)重比之普通模型,要看一下權(quán)重的統(tǒng)計(jì)值柬甥∫可以這樣檢查Pyro模型的參數(shù):
for name in pyro.get_param_store().get_all_param_names():
print name, pyro.param(name).data.numpy()</pre>
在Keras 模型中是這么查看的:
import tensorflow as tf
sess = tf.Sessiom()
with sess.sa_default():
tf.global_variables_initializer().run()
dense_weights, out_weights = None, None
with sess.as_default():
for layer in model.layers:
if len(layer.weights) > 0:
weights = layer.get_weights()
if 'dense' in layer.name:
dense_weights = layer.weights[0].eval()
if 'out' in layer.name:
out_weights = layer.weights[0].eval()</pre>
比如Keras模型最后一層權(quán)重的均值和方差分別為 -0.0025901748, 0.30395043,Pyro模型的均值和方差分別為0.0005974418, 0.0005974418苛蒲。小了很多卤橄,這挺好! 這是很多正則化手段例如L2或Dropout處理的,將參數(shù)逼近至0臂外,可以用變分推斷實(shí)現(xiàn)! 隱層的情景就更有趣了窟扑。我們看一下權(quán)重向量圖, 藍(lán)色表示Keras權(quán)重, 橙色表示Pyro權(quán)重:
輸入和隱層之間部分權(quán)重
事實(shí)上有趣的是喇颁,均值方差變小了,權(quán)重也變得稀疏嚎货。令人驚奇的是橘霎,最終學(xué)習(xí)得到一個(gè)稀疏表示第一組類(lèi)似L1正則化,第二組類(lèi)似L2正則化殖属〗闳可以跑一下代碼 !
結(jié)論
我們使用新的方法訓(xùn)練神經(jīng)網(wǎng)絡(luò),通過(guò)更新權(quán)重的分布(而不是依次更新靜態(tài)權(quán)重)洗显,得到了有趣的并有前景的結(jié)果七蜘。我想強(qiáng)調(diào)貝葉斯方法可以幫助我們?cè)诓皇止ぬ砑诱齽t化器的情況下正則化神經(jīng)網(wǎng)絡(luò),幫助理解模型的不確定性墙懂,并通過(guò)較少數(shù)據(jù)得到較好的訓(xùn)練結(jié)果橡卤。敬請(qǐng)關(guān)注! :)
P.S.
請(qǐng)關(guān)注我Facebook 賬號(hào),上面會(huì)寫(xiě)一些 AI 短文, 還有我個(gè)人Instagram 和 Linkedin號(hào)!