- arxiv論文(有附錄,但是字小)
Probabilistic Matrix Factorization for Automated Machine Learning - NIPS2018論文(字大但是沒有附錄)
Probabilistic Matrix Factorization for Automated Machine Learning - 代碼
https://github.com/rsheth80/pmf-automl
文章目錄
初窺項目文件
用jupyter lab打開all_normalized_accuracy_with_pipelineID.csv
all_normalized_accuracy_with_pipelineID.zip contains the performance observations from running 42K pipelines on 553 OpenML datasets. The task was classification and the performance metric was balanced accuracy. Unzip prior to running code.
行表示pipeline id,列表示dataset id,元素表示balanced accuracy
。
簡單查閱了一下pipelines.json
,基本只有pca
和polynomial
兩種preprocessor。
PMF模型訓練
數據切分
Ytrain, Ytest, Ftrain, Ftest = get_data()
>>> Ytrain.shape
Out[2]: (42000, 464)
>>> Ytest.shape
Out[3]: (42000, 89)
>>> Ftrain.shape
Out[4]: (464, 46)
>>> Ftest.shape
Out[5]: (89, 46)
訓練測試集切分,89個數據集作爲測試集,464個訓練集
初始隱變量
imp = sklearn.impute.SimpleImputer(missing_values=np.nan, strategy='mean')
X = sklearn.decomposition.PCA(Q).fit_transform(
imp.fit(Ytrain).transform(Ytrain))
>>> X.shape
Out[7]: (42000, 20)
根據目前的理解,整個訓練過程就是根據GP來訓練X的隱變量。這個隱變量是用PCA初始化的。
處理訓練集的缺失值,並降維爲20維(42K個pipelines,數據集從553降爲20個隱變量)
論文:the elements of are given by as nonlinear function of the latent variables, , where is independent Gaussian noise.
這裏的指的是整個矩陣,那麼就是pipeline空間的隱變量,這裏隱變量維度, 的shape爲
模型的定義與訓練
模型的頂層定義:
kernel = kernels.Add(kernels.RBF(Q, lengthscale=None), kernels.White(Q))
m = gplvm.GPLVM(Q, X, Ytrain, kernel, N_max=N_max, D_max=batch_size)
optimizer = torch.optim.SGD(m.parameters(), lr=lr)
m = train(m, optimizer, f_callback=f_callback, f_stop=f_stop)
f_callback
和f_stop
都是兩個local函數
def f_callback(m, v, it, t):
varn_list.append(transform_forward(m.variance).item())
logpr_list.append(m().item()/m.D)
if it == 1:
t_list.append(t)
else:
t_list.append(t_list[-1] + t)
if save_checkpoint and not (it % checkpoint_period):
torch.save(m.state_dict(), fn_checkpoint + '_it%d.pt' % it)
print('it=%d, f=%g, varn=%g, t: %g'
% (it, logpr_list[-1], transform_forward(m.variance), t_list[-1]))
def f_stop(m, v, it, t):
if it >= maxiter-1:
print('maxiter (%d) reached' % maxiter)
return True
return False
看到訓練函數train
def train(m, optimizer, f_callback=None, f_stop=None):
it = 0
while True:
try:
t = time.time()
optimizer.zero_grad()
nll = m()
nll.backward()
optimizer.step()
it += 1
t = time.time() - t
if f_callback is not None:
f_callback(m, nll, it, t)
# f_stop should not be a substantial portion of total iteration time
if f_stop is not None and f_stop(m, nll, it, t):
break
except KeyboardInterrupt:
break
return m
論文公式(5):
看到GPLVM的前向函數gplvm.GPLVM#forward
,ix = self.get_batch()
的作用是在數據集這個維度上取了50的batch。
lp = torch.tensor([0.])
for j in ix:
lp += self.GPs[j]()
return lp*self.D/self.D_max
,表示數據集方向上的batch數
>>> type(self.GPs)
Out[11]: torch.nn.modules.container.ModuleList
>>> type(self.GPs[0])
Out[12]: gplvm.GP
D個高斯過程的定義
GPs的定義:
self.D = Y.shape[1]
for d in range(self.D):
ix = np.where(np.invert(np.isnan(Y[:,d])))[0]
y = Sparse1DTensor(Y[ix,d], torch.tensor(ix))
self.GPs.append(GP(dim, self.X, y, kernel, **kwargs))
表示數據集的數目,這裏爲。np.invert
表示對bool變量取反,ix
最終表示的是非nan值的index。
utils.Sparse1DTensor
是作者自定義的一個稀疏一維張量。
後驗分佈協方差矩陣的求解
transform_forward與transform_backward函數
transform_forward = lambda x: (1+x.exp()).log()
transform_backward = lambda x: (x.exp()-1).log()
transform_forward:
transform_backward:
get_cov函數的頂層設計
沿數據集方向,每個 ,即所有pipeline在該數據集上的表現向量,都會被處理爲Sparse1DTensor
。都會被傳入中。看到gplvm.GP
:
self.variance = torch.nn.Parameter(
transform_backward(torch.tensor([variance])))
variance
會在gplvm.GP#get_cov
中使用
看到GP的forward,mn = torch.zeros(ix.numel())
表示均值,cov = self.get_cov(ix=ix)
表示協方差矩陣,看到get_cov
:
return torch.potrf(self.kernel(self.X[ix])
+ torch.eye(ix.numel())
* transform_forward(self.variance),
upper=False)
該版本PyTorch不存在potrf
函數,我用cholesky
代替了他,並去掉了upper關鍵字。
>>> self.X[ix].shape
Out[22]: torch.Size([1000, 20])
>>> ix.shape
Out[23]: torch.Size([1000])
對pipeline的隱變量空間取1000的batch
>>> kernel
Out[5]:
Add(
(k1): RBF()
(k2): White()
)
kernel的RBF
看到kernels.RBF
def sqdist(X, Y):
assert X.size()[1] == Y.size()[1], 'dims do not match'
return ((X.reshape(X.size()[0], 1, X.size()[1])
- Y.reshape(1, Y.size()[0], Y.size()[1]))**2).sum(2)
>>> X.shape
Out[11]: torch.Size([1000, 20])
>>> X.reshape(X.size()[0], 1, X.size()[1]).shape
Out[13]: torch.Size([1000, 1, 20])
>>> Y.reshape(1, Y.size()[0], Y.size()[1]).shape
Out[15]: torch.Size([1, 1000, 20])
>>> (X.reshape(X.size()[0], 1, X.size()[1])
- Y.reshape(1, Y.size()[0], Y.size()[1])).shape
Out[14]: torch.Size([1000, 1000, 20])
>>> ((X.reshape(X.size()[0], 1, X.size()[1])
- Y.reshape(1, Y.size()[0], Y.size()[1]))**2).sum(2).shape
Out[16]: torch.Size([1000, 1000])
可以理解爲1000個隱變量,每個隱變量的長度爲20維(X,Y的隱變量長度shape1要一致)。1000個隱變量和自己(也可以是Y)逐個求舉例,形成一個的距離矩陣,這就是
調試打印變量發現,則:
記錄一些迷惑行爲
>>> transform_forward(self.variance)
Out[18]: tensor([1.], grad_fn=<LogBackward>)
>>> l
Out[19]:
tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1.]], grad_fn=<LogBackward>)
>>> self.lengthscale
Out[20]:
Parameter containing:
tensor([[0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413,
0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413, 0.5413,
0.5413, 0.5413]], requires_grad=True)
kernel的White
看到kernels.White
return torch.eye(X.size()[0])*transform_forward(self.variance)
>>> transform_forward(self.variance)
Out[21]: tensor([1.], grad_fn=<LogBackward>)
>>> torch.eye(X.size()[0])*transform_forward(self.variance)
Out[22]:
tensor([[1., 0., 0., ..., 0., 0., 0.],
[0., 1., 0., ..., 0., 0., 0.],
[0., 0., 1., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 1., 0., 0.],
[0., 0., 0., ..., 0., 1., 0.],
[0., 0., 0., ..., 0., 0., 1.]], grad_fn=<MulBackward0>)
求了半天是個 ?
求協方差矩陣覆盤
最後回到gplvm.GP#get_cov
中:
return torch.cholesky(self.kernel(self.X[ix])
+ torch.eye(ix.numel())
* transform_forward(self.variance),
upper=False)
>>> self.kernel(self.X[ix]).shape
Out[24]: torch.Size([1000, 1000])
>>> transform_forward(self.variance)
Out[25]: tensor([1.], grad_fn=<LogBackward>)
Cholesky 分解是把一個對稱正定的矩陣表示成一個下三角矩陣L和其轉置的乘積的分解
相當於:
這樣看清楚多了。具體細節還需要在論文中求證一下。
大概瞭解了協方差矩陣是怎麼求的。就是對於的pipeline隱變量,按照上面式子的方法利用RBF核函數求解。
GP前向函數的返回值的含義
回到GP的前向函數gplvm.GP#forward
def forward(self, ix=None):
if ix is None:
ix = self.get_batch()
mn = torch.zeros(ix.numel())
cov = self.get_cov(ix=ix)
pdf = dist.multivariate_normal.MultivariateNormal(mn, scale_tril=cov)
return -pdf.log_prob(self.y[ix])
>>> dist.__package__
Out[31]: 'torch.distributions'
>>> self.y[ix].shape
Out[35]: torch.Size([1000])
分析self.y[ix]
,看到utils.Sparse1DTensor#__getitem__
def __getitem__(self, k):
if not len(k.size()):
return self.v[self.ix[k.item()]]
else: # 命中的條件分支
return torch.tensor([self.v[self.ix[kk]] for kk in k.tolist()])
>>> self.v.shape
Out[40]: torch.Size([37224])
對於每個數據集,訓練一個,表示個pipelines在上的表現。對於個pipelines,根據0均值和核函數求一個概率密度函數。
然後將,也就是個pipelines在上的表現帶入這個分佈,直接求出在這個概率密度函數上的概率密度。
如果如果表現爲0,結果爲
最後結果是在多元概率密度函數上的概率密度,越大說明隱變量越合理。
>>> pdf.log_prob(self.y[ix]).item()
Out[44]: -1400.3924560546875