Pytorch 如何 優化/調整 模型參數

Pytorch 如何自動優化/調整 模型超參

背景

對於優化模型性能表現而言,主要可歸納爲兩種方式:

  • 採用NAS的方式搜索,獲得某個任務上最優的模型結構以及模型參數設置
  • 優化模型參數

誠然,對於第一種方式來說,NAS對算力的消耗是巨大的,因而需要具備巨量計算資源才能夠進行,因此具有較高的門檻;而第二種方式來說,消耗的資源要小很多,因而對於本錢小的用戶來說,採用第二種是比較合理的方式;尤其是在諸如kaggle等比賽上,很多團隊並不具備類似於google那樣的算力,因而採用第二種方法提高模型表現是最重要的手段。

優化模型參數

首先需要搞清楚,這裏所指的優化模型參數是指在深度學習時代優化模型的超參。什麼是超參了?超參是指,必須由人工設定的模型參數,比如學習率,比如mini-batch 的batch size,比如衰減率等等。參數優化特指的是這一類超參。


通常而言,在傳統機器學習時代,針對一些結構較爲簡單的模型,比如隨機數森林;其超參數量有限,並且超參的取值範圍一般來說比較小,且並非連續數字;比如隨機森林的depth參數只能是取整數;因而在這種情況下主要採用的是 網格搜索的方法即grid search 的方法進行合適超參的選取,即超參的優化。
很明顯的是,採用grid search的方法不但浪費大量計算資源,並且效率無法保障。經過前人的驗證,在傳統機器學習時代形成了三種主要的調參方法:

  • grid search
  • random search(隨機調參)
  • bayesian optimize (貝葉斯調參/貝葉斯優化)

其中普遍來說,後兩者的效果要更好;尤其是bayesian optimzie 性能更好,更加優異;在當時廣泛用在諸如kaggle等比賽上(我曾經見過有kaggle的比賽團隊使用貝葉斯調參將模型accuracy從76% 調到95%)。

貝葉斯優化

貝葉斯優化的主體思想是基於貝葉斯原理,即根據現有發生的事情(一組超參下模型表現情況)來推斷如果採用另一組超參模型會是什麼樣的表現;簡而言之就是基於少量的現有超參組合來估計整個超參組合空間,從中挑選出最爲合適的超參組合;這個過程常常採用基於高斯迴歸來做估計;具體的原理可以參考[1]鏈接。如果需要嘗試下貝葉斯優化的效果,在使用傳統算法時可以參考鏈接,這裏有使用案例,可以配合sklearn 進行測試。我這裏提供一段代碼以供測試

# coding:utf-8

from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
from bayes_opt import BayesianOptimization

x,y = make_classification(n_samples=1000,n_features=10,n_classes=2)
rf = RandomForestClassifier()
#rf.fit(x,y)
print(np.mean(cross_val_score(rf,x,y,cv=20,scoring='roc_auc')))

def rf_cv(n_estimators,min_samples_split,max_features,max_depth):
    '''這裏面輸入的參量全是超參'''
    val = cross_val_score(
        RandomForestClassifier(n_estimators=int(n_estimators),
                               min_samples_split=int(min_samples_split),
                               max_features=min(max_features,0.999),
                               max_depth=int(max_depth),
                               random_state=2),
        x,
        y,
        scoring='roc_auc',cv=20
    ).mean()
    return val

rf_bo = BayesianOptimization(rf_cv,
                             {'n_estimators': (10, 250),
                              'min_samples_split': (2, 25),
                              'max_features': (0.1, 0.999),
                              'max_depth': (5, 15)},
                             )

rf_bo.maximize()

深度學習框架下的參數優化

那麼現在問題來了,在深度學習框架下,一個模型往往比之前複雜的多,且計算資源消耗得多;即便是使用貝葉斯方法,理論上需要運行的次數更少,但是也難以承擔這樣的開銷。
針對這樣的問題,普遍的做法是針對特定的框架開發專門的平臺來進行優化。

目前主流的優化方式有兩種:1. google vizier 2.pytorch ax and Botorch [4]
其中前者是原先google內部進行調參所開發的工具,並於2017年發表相應論文[2]鏈接; 目前該方法僅僅公開了調用接口,優化過程需要在google的雲平臺上進行;當然google vizier實際上是一款強大的auto ML的框架,具有很多優良特性,但是針對普通開發者並不友好。
後者是facebook 推出的基於pytorch的貝葉斯調參工具以及適應性試驗平臺,適用性較爲優良。一般來說針對普通需求的團隊或者組織,出於快速開發的需要,還是建議使用pytorch ax botorch 爲好。

平臺安裝

這裏的平臺安裝部署非常簡單,保持了pytorch 一貫的易於維護風格,直接使用pip 安裝就可以了;安裝過程可以參考
ax
botorch
不過需要注意的是,上面兩個平臺需要基於torch 1.5 版本

使用參考

使用AX 和 Botorch 優化我們自定義的模型是第一步,這裏給出一段代碼以供參考

class VAE(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 400)
        self.fc21 = nn.Linear(400, 20)
        self.fc22 = nn.Linear(400, 20)
        self.fc3 = nn.Linear(20, 400)
        self.fc4 = nn.Linear(400, 784)

    def encode(self, x):
        h1 = F.relu(self.fc1(x))
        return self.fc21(h1), self.fc22(h1)

    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5*logvar)
        eps = torch.randn_like(std)
        return mu + eps*std

    def decode(self, z):
        h3 = F.relu(self.fc3(z))
        return torch.sigmoid(self.fc4(h3))

    def forward(self, x):
        mu, logvar = self.encode(x.view(-1, 784))
        z = self.reparameterize(mu, logvar)
        return self.decode(z), mu, logvar

vae_model = VAE().to(device)
vae_state_dict = torch.load(os.path.join(PRETRAINED_LOCATION, "mnist_vae.pt"), map_location=device)
vae_model.load_state_dict(vae_state_dict);

從上面可以看得出,這就是一個普通的pytorch 模型結構定義的方式,接下來就是構建貝葉斯優化要優化的目標函數,這當然是一個黑盒過程。這個函數其實也就是用於描述模型表現的一個函數

def score(y):
    """Returns a 'score' for each digit from 0 to 9. It is modeled as a squared exponential
    centered at the digit '3'.
    """
    return torch.exp(-2 * (y - 3)**2)


# Given the scoring function, we can now write our overall objective, which as discussed above, starts with an image and outputs a score. Let's say the objective computes the expected score given the probabilities from the classifier.

# In[6]:


def score_image_recognition(x):
    """The input x is an image and an expected score based on the CNN classifier and
    the scoring function is returned.
    """
    with torch.no_grad():
        probs = torch.exp(cnn_model(x))  # b x 10
        scores = score(torch.arange(10, device=device, dtype=dtype)).expand(probs.shape)
    return (probs * scores).sum(dim=1)


# Finally, we define a helper function `decode` that takes as input the parameters `mu` and `logvar` of the variational distribution and performs reparameterization and the decoding. We use batched Bayesian optimization to search over the parameters `mu` and `logvar`

# In[7]:


def decode(train_x):
    with torch.no_grad():
        decoded = vae_model.decode(train_x)
    return decoded.view(train_x.shape[0], 1, 28, 28)

然後就是使用Botorch 和Ax進行模型貝葉斯優化的過程,這個過程分爲4個主體步驟,下面代碼中一一介紹

# 1.  定義每一次的模型初始化
from botorch.models import SingleTaskGP
from gpytorch.mlls.exact_marginal_log_likelihood import ExactMarginalLogLikelihood


bounds = torch.tensor([[-6.0] * 20, [6.0] * 20], device=device, dtype=dtype)


def initialize_model(n=5):
    # generate training data  
    train_x = (bounds[1] - bounds[0]) * torch.rand(n, 20, device=device, dtype=dtype) + bounds[0]
    train_obj = score_image_recognition(decode(train_x))
    best_observed_value = train_obj.max().item()
    
    # define models for objective and constraint
    model = SingleTaskGP(train_X=train_x, train_Y=train_obj)
    model = model.to(train_x)
    
    mll = ExactMarginalLogLikelihood(model.likelihood, model)
    mll = mll.to(train_x)
    
    return train_x, train_obj, mll, model, best_observed_value

from botorch.optim import joint_optimize


BATCH_SIZE = 3

# 2. 定義要優化的參量
def optimize_acqf_and_get_observation(acq_func):
    """Optimizes the acquisition function, and returns a new candidate and a noisy observation"""
    
    # optimize 這裏定義的調的超參是batch size
    candidates = joint_optimize(
        acq_function=acq_func,
        bounds=bounds,
        q=BATCH_SIZE,
        num_restarts=10,
        raw_samples=200,
    )

    # observe new values 
    new_x = candidates.detach()
    new_obj = score_image_recognition(decode(new_x))
    return new_x, new_obj
 
# 3. 定義優化過程
from botorch import fit_gpytorch_model
from botorch.acquisition.monte_carlo import qExpectedImprovement
from botorch.sampling.samplers import SobolQMCNormalSampler

seed=1
torch.manual_seed(seed)

N_BATCH = 50
MC_SAMPLES = 2000
best_observed = []

# call helper function to initialize model
train_x, train_obj, mll, model, best_value = initialize_model(n=5)
best_observed.append(best_value)

# 4. 開始進行 優化
import warnings
warnings.filterwarnings("ignore")

print(f"\nRunning BO ", end='')
from matplotlib import pyplot as plt

# run N_BATCH rounds of BayesOpt after the initial random batch
for iteration in range(N_BATCH):    

    # fit the model
    fit_gpytorch_model(mll)

    # define the qNEI acquisition module using a QMC sampler
    qmc_sampler = SobolQMCNormalSampler(num_samples=MC_SAMPLES, seed=seed)
    qEI = qExpectedImprovement(model=model, sampler=qmc_sampler, best_f=best_value)

    # optimize and get new observation
    new_x, new_obj = optimize_acqf_and_get_observation(qEI)

    # update training points
    train_x = torch.cat((train_x, new_x))
    train_obj = torch.cat((train_obj, new_obj))

    # update progress
    best_value = score_image_recognition(decode(train_x)).max().item()
    best_observed.append(best_value)

    # reinitialize the model so it is ready for fitting on next iteration
    model.set_train_data(train_x, train_obj, strict=False)
    
    print(".", end='')

基本來說,採用Botorch Ax進行優化 就是上面的4個步驟,主要是要把優化的參數在candidates裏面列好。
上述的步驟是針對一般性的優化函數,比距高斯迴歸優化函數的步驟,Botorch 還支持自定義的RBF優化函數;關於這一塊的內容可以具體參考Botorch的相關文檔。目前有一些研究結果[3]表明一些特定的RBF函數有較好的優化效果。

參考

  1. https://www.cnblogs.com/marsggbo/p/9866764.html
  2. Google Vizier: A Service for Black-Box Optimization
  3. Efficient Hyperparameter Optimization of Deep Learning Algorithms Using Deterministic RBF Surrogates
  4. botorch:programmable bayesian opt in pytorch
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章