1.超参数搜索
为什么要超参数搜索???
超参数:在神经网络训练过程中不变的参数
- 网络结构参数:层数,每层宽度,每层激活函数等
- 训练参数:batch_size,学习率,学习率衰减算法等
超参数搜索可以缓解手工设置超参数的时间耗费
搜索策略
- 网格搜索
- 定义n维网格
- 每个网格对应一组超参数
- 一组一组尝试参数
- 随机搜索
- 参数的生成方式为随机
- 可探索的空间更大
- 遗传算法
- 对自然界的模拟
- A.初始化候选参数集合->训练->得到模型指标作为生存概率
- B.选择->交叉->变异->产生下一代集合
- C.重新到A
- 启发式搜索
- 研究热点-AutoML
- 使用循环神经网络来生成参数
- 使用强化学习来进行反馈,使用模型来训练生成参数
2.用于超参数随机化搜索的几个分布
机器学习中超参数搜索的常用方法为 Grid Search,然而如果参数一多则容易碰到维数诅咒的问题,即参数之间的组合呈指数增长。如果有 mm 个参数,每个有 nn 个取值,则时间复杂度为 Θ(nm)Θ(nm)。 Bengio 等人在 《Random Search for Hyper-Parameter Optimization》 中提出了随机化搜索的方法。他们指出大部分参数空间存在 “低有效维度 (low effective dimensionality)” 的特点,即有些参数对目标函数影响较大,另一些则几乎没有影响。而且在不同的数据集中通常有效参数也不一样。 在这种情况下 Random Search 通常效果较好,下图是一个例子,其中只有两个参数,绿色的参数影响较大,而黄色的参数则影响很小:
Grid Search 会评估每个可能的参数组合,所以对于影响较大的绿色参数,Grid Search 只探索了3个值,同时浪费了很多计算在影响小的黄色参数上; 相比之下 Random Search 则探索了9个不同的绿色参数值,因而效率更高,在相同的时间范围内 Random Search 通常能找到更好的超参数 (当然这并不绝对)。 另外,Random Search 可以在连续的空间搜索,而 Grid Search 则只能在离散空间搜索,而对于像神经网络中的 learning rate,SVM 中的 gamma 这样的连续型参数宜使用连续分布。
在实际的应用中,Grid Search 只需为每个参数事先指定一个参数列表就可以了,而 Random Search 则通常需要为每个参数制定一个概率分布,进而从这些分布中进行抽样。然而对什么样的参数应该选择什么样的分布?这就大有讲究了,如果选的分布不恰当可能就永远找不到合适的参数值了,本文主要介绍一些超参数搜索的常用分布以及它们的特点和使用范围。这些分布都出自 scipy.stats
模块,共同特点是提供了 rvs
方法用于独立随机抽样。
Randint 分布
Randint 分布的概率质量函数 (PMF) 为:
其中 x=low,...,high−1,下面画出随机抽样10000次后各个取值的分布图:
np.random.seed(42)
randint = sp.stats.randint(low=-10, high=11)
randint_distribution = randint.rvs(size=10000, random_state=42)
start = randint.ppf(0.01)
end = randint.ppf(0.99)
x = np.arange(start, end+1)
randint_dict = dict(zip(*np.unique(randint_distribution, return_counts=True))) # 计算各个数的频次
randint_count = list(map(lambda x: x[1], sorted(list(randint_dict.items()), key=lambda x: x[0])))
plt.figure(figsize=(8,5))
plt.bar(x, randint_count, color='b', alpha=0.5, edgecolor='k', label="random_samples")
plt.axhline(y=450, xmin=0.01, xmax=0.99, color='#FF00FF', linestyle="--")
plt.legend(frameon=False, fontsize=10)
plt.title("randint distribution", fontsize=17)
plt.show()
从上图可以看出 Randint 分布为离散型均匀分布,适用于必须为整数的参数 (比如神经网络的层数,决策树的深度)。
Uniform 分布
Uniform 是 Randint 分布的连续版本,概率密度函数为:
其中 x∈[low,high]
np.random.seed(42)
uniform = sp.stats.uniform(loc=10, scale=10)
uniform_distribution = uniform.rvs(size=10000, random_state=42)
start = uniform.ppf(0.01)
end = uniform.ppf(0.99)
x = np.linspace(start, end, num=10000)
plt.figure(figsize=(8,5))
plt.plot(x, uniform.pdf(x), 'r--', lw=2, label="uniform distribution PDF")
plt.hist(uniform_distribution, bins=30, color='b', alpha=0.5, edgecolor = 'k', normed=True, label="random samples")
plt.legend(frameon=False, fontsize=10)
plt.title("uniform distribution", fontsize=17)
plt.show()
Geometric 分布
Geometric 分布的概率质量函数为 :
其中 x⩾1
np.random.seed(42)
plt.figure(figsize=(8,5))
geom_distribution = sp.stats.geom.rvs(0.5, size=10000, random_state=42)
plt.hist(geom_distribution, bins=30, color='b', alpha=0.5, edgecolor = 'k', label="random samples")
plt.legend(frameon=False, fontsize=10)
plt.title("Geometric Distribution, p = 0.5", fontsize=17)
plt.show()
Geometric 分布为离散型分布,表示得到一次成功所需要的试验次数,如果参数集中于少数几个值且可能性呈离散型单调递减,则适用此分布。
Geometric 分布概率质量函数中的 pp 指定了一次试验成功的概率。如果改变此值则会增大或缩小采样范围。
np.random.seed(42)
plt.figure(figsize=(15,5))
plt.subplot(121)
geom_distribution = sp.stats.geom.rvs(0.8, size=10000, random_state=42)
plt.hist(geom_distribution, bins=30, color='b', alpha=0.5, edgecolor = 'k', label="random samples")
plt.legend(frameon=False, fontsize=10)
plt.title("Geometric Distribution, p = 0.8", fontsize=17)
plt.subplot(122)
geom_distribution = sp.stats.geom.rvs(0.01, size=10000, random_state=42)
plt.hist(geom_distribution, bins=30, color='b', alpha=0.5, edgecolor = 'k', label="random samples")
plt.legend(frameon=False, fontsize=10)
plt.title("Geometric Distribution, p = 0.01", fontsize=17)
plt.show()
Exponential 分布
Exponential 分布是 Geometric 分布的连续版本,其概率密度函数为 :
可以看到上图中当Geometric 分布中的 pp 非常小时,就会变得非常接近 exponential 分布。
plt.figure(figsize=(16,4))
expon_distribution = sp.stats.expon.rvs(loc=0, scale=1, size=10000, random_state=42)
plt.subplot(121)
start = sp.stats.expon.ppf(0.001)
end = sp.stats.expon.ppf(0.999)
x = np.linspace(start, end, num=10000)
plt.plot(x, sp.stats.expon.pdf(x), 'r--', lw=2, label=" exponential \ndistribution PDF")
plt.hist(expon_distribution, bins=30, color='b', alpha=0.5, edgecolor = 'k', normed=True, label="random samples")
plt.legend(frameon=False, fontsize=13)
plt.title("exponential distribution", fontsize=17)
plt.subplot(122)
plt.hist(np.log(expon_distribution), bins=30, edgecolor = 'k', color='b', alpha=0.5)
plt.title("log of exponential distribution", fontsize=17)
plt.axvline(x=-5, color='#FF00FF', linestyle="--")
plt.axvline(x=2, color='#FF00FF', linestyle="--")
plt.show()
从右边的 log 分布图来看,大部分值集中于 e−5e−5 到 e2e2 之间,即 0.0067∼7.3890.0067∼7.389 。如果有一些先验知识,知道参数在0附近,且值越大可能性越小 (如svm中的gamma),则适用此分布。当然也可以调整位置 (loc) 和 比例 (scale) 参数来改变搜索范围。此时对应的概率密度函数为 (下面演示 loc=10,scale=10 的情况):
Reciprocal 分布
reciprocal 分布的概率密度函数为:
其中 a<x<b,b>a>0
np.random.seed(42)
plt.figure(figsize=(15,5))
reciprocal = sp.stats.reciprocal(a=0.1, b=100)
reciprocal_distribution = reciprocal.rvs(size=10000, random_state=42)
plt.subplot(121)
start = reciprocal.ppf(0.3)
end = reciprocal.ppf(0.99)
x = np.linspace(start, end, num=10000)
plt.plot(x, reciprocal.pdf(x), 'r--', lw=2, label=" reciprocal \ndistribution PDF")
plt.hist(reciprocal_distribution, bins=30, color='b', alpha=0.5, edgecolor = 'k', normed=True, label="random samples")
plt.legend(frameon=False, fontsize=13)
plt.title("reciprocal distribution", fontsize=17)
plt.subplot(122)
plt.hist(np.log10(reciprocal_distribution), bins=30, color='b', alpha=0.5, edgecolor = 'k')
plt.title("log of reciprocal distribution", fontsize=17)
plt.show()
上图中 reciprocal 分布的PDF和 exponential 分布比较相似,然而右边的 log 分布图却是比较平均的,可见 reciprocal 分布是一个典型的对数均匀分布,以10为底为例,线性空间中10倍的差距在对数空间中均为1,设x2,x1:
下面用 np.random.uniform 可以模拟类似的分布。
np.random.seed(42)
log_uniform = 10 ** np.random.uniform(-1, 2, size=10000)
plt.figure(figsize=(15,5))
plt.subplot(121)
plt.hist(log_uniform, bins=30, color='b', alpha=0.5, normed=True, edgecolor='k')
plt.title("$10^{(-1 \sim 2)}$ distribution", fontsize=17)
plt.subplot(122)
plt.hist(np.log10(log_uniform), bins=30, color='b', alpha=0.5, edgecolor='k')
plt.title("log of $10^{(-1 \sim 2)}$ distribution", fontsize=17)
plt.show()
这种分布的好处是在不同的取值范围内也能均匀地抽样。如上图中参数 xx 的取值范围是 即,如果是一般的均匀分布中抽样,10∼100这个范围被取样到的概率会远大于 1∼10 和 0.1∼1这两个范围,因为前者的距离更大,但在对数均匀分布中三者的范围却是一样的,都是10的倍数,这样被抽样到的概率也就类似。下面的代码显示一个例子:
a = 0
b = 0
c = 0
reciprocal_distribution = sp.stats.reciprocal.rvs(a=0.1, b=100, size=10000, random_state=42)
for val in reciprocal_distribution:
if val > 10 and val < 100:
a += 1
elif val > 1 and val < 10:
b += 1
elif val > 0.1 and val < 1:
c +=1
print("10 到 100 之间取样 {} 次".format(a)) # 10 到 100 之间取样 3233 次
print("1 到 10 之间取样 {} 次".format(b)) # 1 到 10 之间取样 3392 次
print("0.1 到 1 之间取样 {} 次".format(c)) # 0.1 到 1 之间取样 3375 次
对于像 learning rate 这样的参数,我们希望 0.01∼0.10.01∼0.1 和 0.1∼10.1∼1 范围之间的抽样概率是类似的。举例来说,0.11和0.1的学习率可能相差不大,但0.01和0.02的学习率结果更可能大不相同,虽然这两个范围的绝对差异均为0.01。因此在这样的参数中不同值之间的比率更适合作为超参数变化范围。 另外实际上我们可以做到 “完全” 的对数均匀分布,这要用到 numpy 中的 logspace。 然而使用 np.logspace
的缺点是只能生成一个间隔均匀的固定数组进行采样,从而丧失了一定的随机性。
logspace = np.logspace(-1, 2, base=10, num=10000)
plt.figure(figsize=(15,4))
plt.subplot(121)
plt.hist(logspace, bins=30, color='b', alpha=0.5, normed=True, edgecolor='k')
plt.title("logspace", fontsize=17)
plt.subplot(122)
plt.hist(np.log10(logspace), bins=30, color='b', alpha=0.5, edgecolor='k')
plt.title("log of logspace", fontsize=17)
plt.show()
3.房价预估超参数sklearn实战:
1.加载california_housing数据集
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()
print(housing.DESCR)
print(housing.data.shape)
print(housing.target.shape)
from sklearn.model_selection import train_test_split
x_train_all, x_test, y_train_all, y_test=train_test_split(housing.data,housing.target,random_state=7)
x_train, x_valid, y_train, y_valid = train_test_split(
x_train_all, y_train_all, random_state = 11)
print(x_train.shape, y_train.shape)
print(x_valid.shape, y_valid.shape)
print(x_test.shape, y_test.shape)
[output]:
(11610, 8) (11610,)
(3870, 8) (3870,)
(5160, 8) (5160,)
2.数据归一化
#x=(x-u)/std
from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
x_train_scaled=scaler.fit_transform(x_train)
x_valid_scaled=scaler.fit_transform(x_valid)
x_test_scaled=scaler.fit_transform(x_test)
3.构建模型
使用sklearn中的RandomizedSearchCV实现超参数搜索
- 1.使用keras.wrappers.scikit_learn.KerasRegressor(build_fn=None, **sk_params)将keras定义的model转化为sklearn的model
- 2.定义参数集合字典param_distribution
- 3.搜索参数RandomizedSearchCV
from scipy.stats import reciprocal
from sklearn.model_selection import RandomizedSearchCV
def build_model(hidden_layers=1,layer_size=30,learning_rate=3e-3):
model=keras.Sequential()
model.add(keras.layers.Dense(30,activation='relu',input_shape=x_train.shape[1:]))
for _ in range(hidden_layers-1):
model.add(keras.layers.Dense(layer_size,activation='relu'))
model.add(keras.layers.Dense(1))
optimizer=keras.optimizers.SGD(learning_rate)
model.compile(loss="mean_squared_error", optimizer=optimizer)
return model
sklearn_model=keras.wrappers.scikit_learn.KerasRegressor(build_fn=build_model)
callbacks = [keras.callbacks.EarlyStopping(patience=5, min_delta=1e-2)]
param_distribution={
"hidden_layers":[1,2,3,4],
"layer_size":np.arange(1,100),
"learning_rate":reciprocal(1e-4,1e-2),
}
random_search_CV=RandomizedSearchCV(sklearn_model,
param_distribution,
n_iter=10,
n_jobs=1)
#隐含使用cross_validation:训练集分成n分,n-1个训练,最后一份验证
random_search_CV.fit(x_train_scaled,y_train,epochs=100,validation_data=(x_valid_scaled,y_valid),
callbacks=callbacks)
4.查看最好的参数,分数,模型
print(random_search_CV.best_params_)
print(random_search_CV.best_score_)
print(random_search_CV.best_estimator_)
[output]:
{'hidden_layers': 3, 'layer_size': 99, 'learning_rate': 0.008059827662974122}
-0.34396713873520685
<tensorflow.python.keras.wrappers.scikit_learn.KerasRegressor object at 0x00000249321B7F60>
5.使用最好的model在测试集上评估
model=random_search_CV.best_estimator_.model
model.evaluate(x_test_scaled,y_test)
[output]:
5160/5160 [==============================] - 0s 14us/sample - loss: 0.3947
0.3947229888088019
参考: