第一章 NAS-Bench-101的使用
第二章NAS-Bench-201的使用
第三章NAS-Bench-oneshotone的使用
随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,但是随着NAS-Bench搜索空间的不断优化,以前使用众多GPU甚至TPU搜索想要的神经网络架构在个人PC上搜索想要的神经网络架构成为可能,同样推动自动机器学习的发展,本文将通过一些小案例展示NAS-Bench101,NAS-Bench201,NAS-Bench-oneshotone的基本使用方法。
提示:以下是本篇文章正文内容,下面案例可供参考
现代的深度神经网络有时会包含多种类型的层,而且这些层不止一个。Skip connections和子模块方法也被用来促进模型的收敛,它们对可能形成的模型体系结构的空间没有限制。目前大多数的深度神经网络结构都是根据人类经验建立起来的,这种方式需要一个漫长而繁琐的试错过程。NAS试图在不需要人工干预的情况下能够针对特定深度学习问题检测出有效架构。
一般来说,NAS可以分为三个维度——搜索空间、搜索策略和性能评估策略。
搜索空间决定了哪些神经结构要被评估。好的搜索空间可以降低寻找合适神经结构的复杂性。一般来说,搜索空间要有约束,并且还要具有灵活性。约束消除了非直观的神经结构,可以创建有限的空间进行搜索。搜索空间包含了能够由NAS方法生成的每一个架构设计(通常是无限多的)。搜索空间中可能涉及堆叠在一起的所有层配置集(图2a)或包含跳过连接的更复杂的体系结构(图2b)。为了减少搜索空间的维数,可以进行子模块的设计。随后子模块堆叠在一起就可以生成模型架构(图2c)。
搜索策略可以看作我们在搜索空间中寻找最优结构的方法,它可以识别出好的结构,对好的结构进行性能评估,这样可以避免测试不好的架构。搜索策略主要包括随机和网格搜索、基于梯度的策略、进化算法和强化学习策略,在接下来的例子中我们使用随机和基于进化算法的策略进行演示,其他的方法由于本人水平有限暂时还未实现。同时本文中搜索的主要是基于不同搜索空间或者说nas基准的结构是空间本身提供的结果,类似于查询表格的方式进行搜索,如需添加自己的数据请参考另一篇文章。
性能评估策略将提供一个数字,这个数字可以反映搜索空间中所有结构的效率。当一个参考数据集在预先定义的训练轮次数上先进行训练,再进行测试时,通常就可以得到模型结构的准确性。性能评估技术通常还会考虑一些因素,比如训练或推理的计算难度。在任何情况下,评估结构性能的计算成本都很高。通俗的讲就是将得到的模型进行从头训练得到一些评估模型优劣的指标,例如模型的loss,test_acc,vaild_acc等。
!!!注意!!!:在使用NAS-Bench-101需要在官方GitHub下载相应的训练好的数据集,t同时使用pip安装:
!curl -O https://storage.googleapis.com/nasbench/nasbench_full.tfrecord
!git clone https://github.com/google-research/nasbench
!pip install ./nasbench
代码如下(示例):
from nasbench import api
# 使用nasbench_full。完整数据集的Tfrecord(运行上面的下载命令)
nasbench = api.NASBench('nasbench_full.tfrecord')
# 加载好需要运行的数据集之后,还需要规定一些常量方便后续搜索
# 开始之前我们还需要导入需要的包
import copy
import numpy as np
import matplotlib.pyplot as plt
import random
import torch
from tqdm import tqdm, trange
import pandas as pd
from nasbench.api import NASBench
# 规定一些常用的常量
API = NASBench('nasbench_full.tfrecord')
# 有用的常量,一些搜索的关键的几个结构
INPUT = 'input'
OUTPUT = 'output'
CONV3X3 = 'conv3x3-bn-relu'
CONV1X1 = 'conv1x1-bn-relu'
MAXPOOL3X3 = 'maxpool3x3'
# 限制边和顶点数
NUM_VERTICES = 7
MAX_EDGES = 9
# 计算上三角矩阵
EDGE_SPOTS = NUM_VERTICES * (NUM_VERTICES - 1) / 2
# 输入/输出顶点是固定的
OP_SPOTS = NUM_VERTICES - 2
# 允许的结构列表
ALLOWED_OPS = [CONV3X3, CONV1X1, MAXPOOL3X3]
# 二元邻接矩阵
ALLOWED_EDGES = [0, 1]
这样我们就初始化好了我们的搜索空间并将数据加载进我们的内存方便查询,同时规定了搜索call的基础结构。
代码如下(示例):
设置一些初始功能函数方便下一步操作
def random_spec():
"""返回随机的有效范式,也就是我们的随机产生的space"""
while True:
matrix = np.random.choice(ALLOWED_EDGES, size=(NUM_VERTICES, NUM_VERTICES))
matrix = np.triu(matrix, 1)
ops = np.random.choice(ALLOWED_OPS, size=(NUM_VERTICES)).tolist()
ops[0] = INPUT
ops[-1] = OUTPUT
spec = api.ModelSpec(matrix=matrix, ops=ops) # 将随机产生的op_names和邻接矩阵加载到空间中
# 验证空间的有效性,主要对op_names和matrix对应关系的验证
if nasbench.is_valid(spec):
return spec
def mutate_spec(old_spec, mutation_rate=1.0):
"""从old_spec计算一个有效的变异规范,这里为了方便下面在做进化算法时对种群的变异操作"""
while True:
new_matrix = copy.deepcopy(old_spec.original_matrix)
new_ops = copy.deepcopy(old_spec.original_ops)
# V边反转(变异的操作)
edge_mutation_prob = mutation_rate / NUM_VERTICES
for src in range(0, NUM_VERTICES - 1):
for dst in range(src + 1, NUM_VERTICES):
if random.random() < edge_mutation_prob:
new_matrix[src, dst] = 1 - new_matrix[src, dst]
# 对ops_name重采样
op_mutation_prob = mutation_rate / OP_SPOTS
for ind in range(1, NUM_VERTICES - 1):
if random.random() < op_mutation_prob:
available = [o for o in nasbench.config['available_ops'] if o != new_ops[ind]]
new_ops[ind] = random.choice(available)
new_spec = api.ModelSpec(new_matrix, new_ops)
if nasbench.is_valid(new_spec):
return new_spec
def random_combination(iterable, sample_size):
"""从itertools.组合中随机选择(iterable, r)"""
pool = tuple(iterable)
n = len(pool)
indices = sorted(random.sample(range(n), sample_size))
return tuple(pool[i] for i in indices)
制定我们随机搜索策略:
def run_random_search(max_time_budget=5e6):
"""在固定的时间预算下进行一次随机搜索。"""
nasbench.reset_budget_counters()
# 规定一下列表来存储搜索返回的数据,并记录搜索轨迹
times, best_valids, best_tests = [0.0], [0.0], [0.0]
while True:
spec = random_spec()
data = nasbench.query(spec)
if data['validation_accuracy'] > best_valids[-1]:
best_valids.append(data['validation_accuracy'])
best_tests.append(data['test_accuracy'])
else:
best_valids.append(best_valids[-1])
best_tests.append(best_tests[-1])
time_spent, _ = nasbench.get_budget_counters()
times.append(time_spent)
if time_spent > max_time_budget:
# 超出时间预算时,打破循环
break
return times, best_valids, best_tests
规定我们的进化算法策略:
def run_evolution_search(max_time_budget=5e6,
population_size=50,
tournament_size=10,
mutation_rate=1.0):
"""在固定的时间预算下运行一次正规化的演进的滚动。"""
nasbench.reset_budget_counters()
times, best_valids, best_tests = [0.0], [0.0], [0.0]
population = [] # 存储(validation, spec)元组
# 对于第一个种群大小的个体,在种群中随机播种
# 生成细胞
for _ in range(population_size):
spec = random_spec()
data = nasbench.query(spec)
time_spent, _ = nasbench.get_budget_counters()
times.append(time_spent)
population.append((data['validation_accuracy'], spec))
if data['validation_accuracy'] > best_valids[-1]:
best_valids.append(data['validation_accuracy'])
best_tests.append(data['test_accuracy'])
else:
best_valids.append(best_valids[-1])
best_tests.append(best_tests[-1])
if time_spent > max_time_budget:
break
# 种群播种后,继续进化种群。
sample = population
while True:
sample = random_combination(population, tournament_size)
best_spec = sorted(sample, key=lambda i:i[0])[-1][1]
new_spec = mutate_spec(best_spec, mutation_rate)
data = nasbench.query(new_spec)
time_spent, _ = nasbench.get_budget_counters()
times.append(time_spent)
# 在正规化进化中,我们杀死种群中最老的个体。
population.append((data['validation_accuracy'], new_spec))
population.pop(0)
if data['validation_accuracy'] > best_valids[-1]:
best_valids.append(data['validation_accuracy'])
best_tests.append(data['test_accuracy'])
else:
best_valids.append(best_valids[-1])
best_tests.append(best_tests[-1])
if time_spent > max_time_budget:
break
return times, best_valids, best_tests
代码如下(示例):
# 每种算法运行10轮
# 列表只是用于存储结果
random_data = []
evolution_data = []
for repeat in range(10):
print('Running repeat %d' % (repeat + 1))
times, best_valid, best_test = run_evolution_search()
evolution_data.append((times, best_valid, best_test))
times, best_valid, best_test = run_random_search()
random_data.append((times, best_valid, best_test))
# 这里我们得到最终模型结果后,使用模型随时间的变化acc变化作为评估策略
def plot_data(data, color, label, gran=10000, max_budget=5000000):
"""计算固定时间步长的平均值和IQR"""
xs = range(0, max_budget+1, gran)
mean = [0.0]
per25 = [0.0]
per75 = [0.0]
repeats = len(data)
pointers = [1 for _ in range(repeats)]
cur = gran
while cur < max_budget+1:
all_vals = []
for repeat in range(repeats):
while (pointers[repeat] < len(data[repeat][0]) and
data[repeat][0][pointers[repeat]] < cur):
pointers[repeat] += 1
prev_time = data[repeat][0][pointers[repeat]-1]
prev_test = data[repeat][2][pointers[repeat]-1]
next_time = data[repeat][0][pointers[repeat]]
next_test = data[repeat][2][pointers[repeat]]
assert prev_time < cur and next_time >= cur
# 线性插值测试之间的两个周围的点
cur_val = ((cur - prev_time) / (next_time - prev_time)) * (next_test - prev_test) + prev_test
all_vals.append(cur_val)
all_vals = sorted(all_vals)
mean.append(sum(all_vals) / float(len(all_vals)))
per25.append(all_vals[int(0.25 * repeats)])
per75.append(all_vals[int(0.75 * repeats)])
cur += gran
plt.plot(xs, mean, color=color, label=label, linewidth=2)
plt.fill_between(xs, per25, per75, alpha=0.1, linewidth=0, facecolor=color)
plot_data(random_data, 'red', 'random')
plot_data(evolution_data, 'blue', 'evolution')
plt.legend(loc='lower right')
plt.ylim(0.92, 0.95)
plt.xlabel('total training time spent (seconds)')
plt.ylabel('accuracy')
plt.grid()
这里绘制的acc范围规定在92%到95%之间
NAS-Bench-101: Towards Reproducible Neural Architecture Search