引言:当进化论遇见深度学习——自动化的黎明

在深度学习的蛮荒时代,我们是“手工匠人”。我们依靠直觉、前辈的经验(ResNet 为什么是152层而不是153层?)、大量的试错以及那么一点点玄学,在架构的黑暗森林中摸索前行。设计一个高性能的神经网络(Neural Network, NN),如同在中世纪手工锻造一把名剑,极度依赖铁匠(研究者/工程师)的个人技艺与运气。这个过程被戏称为“梯度下降炼丹术”,调的是超参数,炼的是时间和GPU资源。

但随着问题日益复杂,数据维度不断攀升,神经网络架构搜索(Neural Architecture Search, NAS) 应运而生,它预示着深度学习“工业革命”的到来。NAS的核心思想是:让算法自动地发现最优的网络架构,将人类从繁重重复的试错中解放出来。

在众多NAS方法中,基于进化计算(Evolutionary Computation),特别是遗传算法(Genetic Algorithm, GA) 的方法,因其强大的全局搜索能力、对问题域假设少以及天然的可并行性,占据了重要一席。它模拟了达尔文的自然选择理论:物竞天择,适者生存。让网络架构在代码的世界里“交配”、“变异”、“竞争”,一步步进化出最适应特定任务的“强者”。

本篇,我们将深入这场自动模型设计的进化之旅。我们将使用强大的进化算法库DEAP(Distributed Evolutionary Algorithms in Python),从零开始,构建一个属于我们自己的NAS系统,让遗传算法为我们自动设计卷积神经网络(CNN),并在经典数据集上验证其威力。


第一章:遗传算法(GA)—— 进化思想的数字涅槃

理论:生命游戏的代码镜像

遗传算法并非精确的数学求解器,而是一种受生物进化启发的元启发式(Meta-heuristic)优化算法。它的美妙之处在于,你不需要知道“最优解”长什么样,只需要定义一个方法来判断一个解(个体)的好坏(适应度),剩下的,交给进化。

其核心流程是一个精妙的循环,如下图所示,它完美复刻了自然选择的过程:

关键概念剖析:

  • 个体(Individual)与种群(Population):单个解称为一个个体(如:一个特定的网络架构配置)。所有个体的集合称为种群。在NAS中,一个个体就是一套完整的架构描述。

  • 基因(Gene)与染色体(Chromosome):描述个体特征的编码串称为染色体,其中的每一个单元称为基因。在NAS中,这可以是层类型、滤波器数量、连接方式等。

  • 适应度(Fitness):衡量个体优劣的数值。这是驱动整个进化过程的“上帝之手”。在NAS中,它通常是模型在验证集上的准确率(或准确率的函数)。

  • 选择(Selection):根据适应度概率性地选择优秀的个体作为父代,以产生后代。适应度越高,被选中的概率越大。常用策略有锦标赛选择(Tournament Selection)轮盘赌选择(Roulette Wheel Selection)

  • 交叉(Crossover):模仿有性繁殖的基因重组。随机选择两个父代个体,交换它们的一部分染色体,生成新的子代个体。这是探索解空间新区域的关键操作。

  • 变异(Mutation):以较低的概率随机改变个体中某个基因的值。它为种群注入新的多样性,避免算法过早陷入局部最优解。

实战:用DEAP实现一个简单的遗传算法

让我们先抛开神经网络,用DEAP解决一个经典问题:最大化一元函数 f(x) = x * sin(10π * x) + 1.0,其中 x 在区间 [-1, 2]。我们的目标是找到使得 f(x) 最大的 x

步骤1:安装DEAP

pip install deap

步骤2:搭建进化流程

# 导入必要的库
import random  # 用于生成随机数
import numpy as np  # 用于数学计算
from deap import base, creator, tools, algorithms  # DEAP框架的主要模块# 定义问题类型:创建一个适应度类FitnessMax,用于最大化问题
# weights=(1.0,)表示单目标最大化问题,如果是多目标可以设置多个权重
creator.create("FitnessMax", base.Fitness, weights=(1.0,))# 创建个体类Individual,继承自list类型,并添加fitness属性
# 这样每个个体都是一个列表,同时具有适应度属性
creator.create("Individual", list, fitness=creator.FitnessMax)# 初始化工具箱,用于注册各种遗传算法操作
toolbox = base.Toolbox()# 注册基因生成器:定义一个名为attr_float的函数
# 该函数使用random.uniform生成[-1, 2)范围内的随机浮点数
toolbox.register("attr_float", random.uniform, -1, 2)# 注册个体生成器:定义一个名为individual的函数
# 使用initRepeat方法重复调用attr_float函数n次来创建个体
# 这里n=1表示每个个体只有一个基因(即x值)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=1)# 注册种群生成器:定义一个名为population的函数
# 使用initRepeat方法重复调用individual函数来创建包含多个个体的种群
toolbox.register("population", tools.initRepeat, list, toolbox.individual)# 定义适应度函数(目标函数),用于评估个体的适应度
def eval_function(individual):# 从个体中提取x值(个体只有一个基因)x = individual[0]# 计算目标函数值:f(x) = x * sin(10π * x) + 1.0# 使用np.sin计算正弦值,10*np.pi*x是正弦函数的参数# 返回一个元组(因为DEAP要求适应度值以元组形式返回)return (x * np.sin(10 * np.pi * x) + 1.0),# 在工具箱中注册适应度评估函数
toolbox.register("evaluate", eval_function)# 注册交叉操作:使用混合交叉(模拟二进制交叉)
# alpha参数控制交叉的程度,0.5表示等比例混合
toolbox.register("mate", tools.cxBlend, alpha=0.5)# 注册变异操作:使用高斯变异
# mu=0表示变异的均值为0,sigma=0.2表示标准差为0.2
# indpb=0.2表示每个基因有20%的概率发生变异
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=0.2, indpb=0.2)# 注册选择操作:使用锦标赛选择
# tournsize=3表示每次锦标赛选择中随机选择3个个体进行比较
toolbox.register("select", tools.selTournament, tournsize=3)# 创建初始种群:生成包含50个个体的种群
pop = toolbox.population(n=50)# 定义统计指标,用于记录进化过程
# 使用Statistics对象收集进化过程中的统计信息
stats = tools.Statistics(lambda ind: ind.fitness.values)  # 提取个体的适应度值# 注册统计函数:计算适应度的平均值
stats.register("avg", np.mean)# 注册统计函数:计算适应度的标准差
stats.register("std", np.std)# 注册统计函数:找到适应度的最小值
stats.register("min", np.min)# 注册统计函数:找到适应度的最大值
stats.register("max", np.max)# 运行进化算法:使用eaSimple算法执行进化过程
# pop: 初始种群
# toolbox: 包含所有注册操作的工具箱
# cxpb=0.5: 交叉概率为50%
# mutpb=0.2: 变异概率为20%
# ngen=40: 进化40代
# stats: 统计对象,用于记录进化过程
# halloffame=None: 不保留名人堂(最优个体历史记录)
# verbose=True: 打印进化过程信息
pop, logbook = algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40,stats=stats, halloffame=None, verbose=True)# 从最终种群中找到最佳个体:使用selBest函数选择适应度最高的个体
# k=1表示只选择最好的一个个体,[0]获取这个个体
best_individual = tools.selBest(pop, k=1)[0]# 获取最佳个体的适应度值
best_fitness = best_individual.fitness.values[0]# 打印最优解:格式化输出x值和对应的函数值
# :.6f表示保留6位小数
print(f"\n最优解: x = {best_individual[0]:.6f}, f(x) = {best_fitness:.6f}")

示例输出与解析:

gen     nevals  avg     std     min     max    
0       50      1.20518 0.511133 0.130204 1.85027
1       31      1.40997 0.305915 0.545786 1.85027
...
40      36      2.84977 0.012657 2.81321 2.85027最优解: x = 1.850547, f(x) = 2.850270

这个简单的例子展示了GA的核心框架。eaSimple 函数实现了完整的进化循环。我们可以看到,经过40代进化,种群的平均适应度和最大适应度都在稳步提升,最终找到了一个非常接近全局最优解(理论最大值在x≈1.85处)的结果。


第二章:神经网络架构的编码艺术——基因如何描述网络?

理论:从架构到染色体的映射

要让遗传算法为我们设计网络,首要任务是将抽象的神经网络架构(表现型, Phenotype)编码成一串数字(基因型, Genotype)。这是NAS中最关键也最具挑战性的一步。

常见的编码方式有:

  1. 直接编码(Direct Encoding):显式地定义每一层的类型和参数。

    • 示例基因[('conv', 32, 3, 1), ('pool', 'max', 2), ('conv', 64, 3, 1), ...]

    • 优点:直观,易于解码和理解。

    • 缺点:染色体长度固定,限制了搜索空间的灵活性。

  2. 基于细胞的编码(Cell-based Encoding):目前主流方法。先搜索一个或几种最优的细胞(Cell) 结构,然后通过堆叠重复的细胞来构建最终网络(如ResNet中的残差块,NASNet中的Normal Cell/Reduction Cell)。

    • 优点:大大缩小了搜索空间,泛化能力强,易于迁移到不同规模的数据集。

    • 缺点:编码和解码过程更复杂。

本节策略:为了清晰演示,我们采用一种简化的直接编码。我们固定网络的层数,但允许每一层的类型(卷积/全连接) 和超参数(滤波器数量/神经元数量) 变化。

我们的染色体将是一个列表,假设我们限定网络最多5层,每层有两种可能:

  • 如果是卷积层(C),基因表示为 ('C', filters)

  • 如果是全连接层(F),基因表示为 ('F', units)

例如,一个染色体 [('C', 64), ('C', 128), ('F', 256)] 代表一个3层网络:64滤波器的卷积层 -> 128滤波器的卷积层 -> 256个神经元的全连接层。

实战:定义架构的基因型与表现型转换

python

# 导入必要的库
import tensorflow as tf  # 导入TensorFlow深度学习框架
from tensorflow.keras.models import Sequential  # 导入Sequential模型类,用于按顺序堆叠层
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout  # 导入各种神经网络层
from tensorflow.keras.optimizers import Adam  # 导入Adam优化器# 定义函数:将基因型(个体)解码为Keras模型(表现型)
# individual: 遗传算法中的个体,表示网络架构的基因编码
# input_shape: 输入数据的形状,例如(28, 28, 1)表示MNIST图像的尺寸
def create_network(individual, input_shape):"""将基因型(个体)解码为Keras模型(表现型)个体格式: [('C', 32), ('C', 64), ('F', 128), ...]其中'C'表示卷积层,后面的数字表示滤波器数量'F'表示全连接层,后面的数字表示神经元数量"""# 创建一个Sequential模型,这是一种按顺序堆叠层的线性模型model = Sequential()# 添加第一个卷积层:使用个体中的第一个基因来定义# individual[0][1]获取第一个元组的第二个元素,即滤波器数量# (3, 3)表示卷积核大小# activation='relu'使用ReLU激活函数# padding='same'表示使用相同填充,保持空间维度不变# input_shape指定输入数据的形状model.add(Conv2D(individual[0][1], (3, 3), activation='relu', padding='same', input_shape=input_shape))# 添加最大池化层,池化窗口大小为(2, 2),用于降低特征图的空间维度model.add(MaxPooling2D((2, 2)))# 遍历个体中剩余的基因(从第二个开始)for layer_type, param in individual[1:]:# 如果基因类型是'C'(卷积层)if layer_type == 'C':# 添加卷积层,使用指定数量的滤波器和3x3卷积核model.add(Conv2D(param, (3, 3), activation='relu', padding='same'))# 添加最大池化层,进一步降低特征图维度model.add(MaxPooling2D((2, 2)))# 如果基因类型是'F'(全连接层)elif layer_type == 'F':# 在添加第一个全连接层之前,检查是否已经存在展平层# 使用any和isinstance检查模型中是否有Flatten层if not any(isinstance(layer, Flatten) for layer in model.layers):# 如果没有展平层,则添加一个展平层,将多维特征图转换为一维向量model.add(Flatten())# 添加全连接层,使用指定数量的神经元和ReLU激活函数model.add(Dense(param, activation='relu'))# 添加Dropout层,丢弃率为0.5,用于防止过拟合# Dropout在训练过程中随机丢弃一部分神经元,增强模型泛化能力model.add(Dropout(0.5))# 确保在输出层之前有展平层# 再次检查模型中是否有展平层,防止遗漏if not any(isinstance(layer, Flatten) for layer in model.layers):# 如果没有展平层,则添加一个model.add(Flatten())# 添加输出层:使用10个神经元,对应10个类别(假设是十分类任务)# 使用softmax激活函数,输出每个类别的概率分布model.add(Dense(10, activation='softmax'))# 返回构建好的模型return model# 示例:创建一个个体并解码为模型
# 定义一个示例个体,表示网络架构的基因编码
# [('C', 32)表示一个具有32个滤波器的卷积层
# ('C', 64)表示一个具有64个滤波器的卷积层
# ('F', 100)表示一个具有100个神经元的全连接层
example_individual = [('C', 32), ('C', 64), ('F', 100)]# 定义输入数据的形状,这里使用MNIST数据集的图像尺寸
# 28x28像素,1个颜色通道(灰度图像)
input_shape = (28, 28, 1)# 调用create_network函数,将基因型转换为表现型(Keras模型)
example_model = create_network(example_individual, input_shape)# 编译模型:配置模型的学习过程
# 使用Adam优化器,学习率设为0.001
# 使用稀疏分类交叉熵作为损失函数,适用于整数标签的分类问题
# 设置评估指标为准确率
example_model.compile(optimizer=Adam(learning_rate=0.001),loss='sparse_categorical_crossentropy',metrics=['accuracy'])# 打印模型的结构信息,包括每层的类型、输出形状和参数数量
example_model.summary()# 注意:在实际使用中,还需要准备训练数据和标签
# 例如:使用MNIST数据集
# (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 然后对数据进行预处理(归一化、重塑形状等)
# 最后使用model.fit()方法训练模型

这个 create_network 函数就是我们连接遗传算法世界和深度学习世界的桥梁。它将一个编码好的染色体,实例化成了一个可以编译、训练、评估的Keras模型。


第三章:定义进化之力——适应度、交叉与变异

理论:NAS中的进化算子设计

在NAS的语境下,我们需要重新定义GA中的三个核心算子:

  1. 适应度函数(Fitness Function)

    • 任务:量化一个神经网络架构的好坏。

    • 实现:通常是在一个验证集(Validation Set) 上计算准确率、F1分数等指标。为了节省计算资源,有时会使用低保真度评估(Low-Fidelity Evaluation),如在子数据集上训练、训练更少的轮数(Epoch)、使用权重共享等。

    • 挑战:计算成本极高!评估一个个体可能需要几分钟到几小时。

  2. 交叉(Crossover)

    • 任务:交换两个父代架构(个体)的“优秀部件”以产生子代。

    • 实现:对于直接编码,可以随机选择切点进行交换。例如,父代A [A1, A2, A3, A4] 和父代B [B1, B2, B3, B4] 在位置2交叉,产生子代 [A1, A2, B3, B4] 和 [B1, B2, A3, A4]

  3. 变异(Mutation)

    • 任务:随机微调架构,引入多样性。

    • 实现:以一定概率随机改变基因。例如:

      • 改变一层滤波器的数量(如从64变为128)。

      • 改变层的类型(如将卷积层变为全连接层,需注意维度兼容)。

      • 添加或删除一层(如果编码支持可变长度)。

实战:实现NAS的进化算子
# 导入必要的库
import random  # 用于生成随机数和随机选择
from deap import tools  # 导入DEAP框架的工具模块
import tensorflow as tf  # 导入TensorFlow深度学习框架
from tensorflow.keras.optimizers import Adam  # 导入Adam优化器
import gc  # 导入垃圾回收模块,用于释放内存# 假设已经定义了create_network函数和加载了MNIST数据
# 这里我们假设x_train, y_train, x_val, y_val已经定义# 1. 注册个体和种群创建方法(基于我们的编码)
# 定义网络层数,固定搜索5层网络
NUM_LAYERS = 5# 定义函数:随机生成一层网络结构
def random_layer():"""随机生成一层网络结构"""# 随机选择层类型:卷积层('C')或全连接层('F')layer_type = random.choice(['C', 'F'])# 根据层类型选择不同的参数范围if layer_type == 'C':# 卷积层的滤波器数量在[16, 32, 64, 128]中选择param = random.choice([16, 32, 64, 128])else:# 全连接层的神经元数量在[64, 128, 256, 512]中选择param = random.choice([64, 128, 256, 512])# 返回层类型和参数的元组return (layer_type, param)# 在工具箱中注册层生成器
# attr_layer函数使用random_layer生成随机层
toolbox.register("attr_layer", random_layer)# 注册个体生成器:使用initRepeat方法重复调用attr_layer函数NUM_LAYERS次
# 创建一个包含NUM_LAYERS个层的个体
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_layer, n=NUM_LAYERS)# 注册种群生成器:使用initRepeat方法重复调用individual函数创建多个个体
toolbox.register("population", tools.initRepeat, list, toolbox.individual)# 2. 定义适应度评估函数(这是最耗时的部分)
def evaluate_individual(individual):"""评估一个个体(网络架构)的适应度返回: (准确率, )"""# 设置训练参数以减少计算时间EPOCHS = 5  # 训练轮数,减少以加速搜索BATCH_SIZE = 256  # 批处理大小# 解码个体为Keras模型# 使用之前定义的create_network函数将基因型转换为表现型model = create_network(individual, (28, 28, 1))# 编译模型:配置优化器、损失函数和评估指标model.compile(optimizer=Adam(learning_rate=0.001),loss='sparse_categorical_crossentropy',metrics=['accuracy'])# 训练模型# 使用训练数据进行训练,验证集用于计算适应度# verbose=0表示不输出训练过程信息,减少输出干扰history = model.fit(x_train, y_train,batch_size=BATCH_SIZE,epochs=EPOCHS,validation_data=(x_val, y_val),verbose=0  # 不输出训练过程)# 取最后一个epoch的验证准确率作为适应度值# 使用验证集准确率而不是训练集准确率,以避免过拟合accuracy = history.history['val_accuracy'][-1]# 非常重要!清理模型和释放GPU内存# 删除模型引用del model# 清除Keras会话,释放GPU内存tf.keras.backend.clear_session()# 调用垃圾回收,确保内存被释放gc.collect()# 返回适应度值(准确率),注意返回元组格式return (accuracy, )# 在工具箱中注册评估函数
toolbox.register("evaluate", evaluate_individual)# 3. 定义交叉算子(单点交叉)
def crossover_individuals(ind1, ind2):"""对两个个体进行单点交叉"""# 确保个体长度相同,取较小长度size = min(len(ind1), len(ind2))# 随机选择交叉点(从1到size-1,确保不是端点)cxpoint = random.randint(1, size - 1)# 交换两个个体从交叉点到末尾的部分ind1[cxpoint:], ind2[cxpoint:] = ind2[cxpoint:], ind1[cxpoint:]# 返回交叉后的两个个体return ind1, ind2# 在工具箱中注册交叉操作
toolbox.register("mate", crossover_individuals)# 4. 定义变异算子
def mutate_individual(individual, indpb=0.2):"""以概率indpb变异个体的每一个基因"""# 遍历个体的每一个基因(层)for i in range(len(individual)):# 以indpb概率决定是否变异该基因if random.random() < indpb:# 如果决定变异,则用random_layer生成新的层替换原有层individual[i] = random_layer()# 返回变异后的个体(注意返回元组)return individual,# 在工具箱中注册变异操作,设置默认变异概率为0.2
toolbox.register("mutate", mutate_individual, indpb=0.2)# 5. 选择算子(沿用锦标赛选择)
# 注册选择操作:使用锦标赛选择, tournament大小为3
toolbox.register("select", tools.selTournament, tournsize=3)# 注意:在实际应用中,还需要定义进化算法的主循环
# 通常包括以下步骤:
# 1. 创建初始种群
# 2. 评估初始种群
# 3. 进行多代进化:
#     a. 选择父代
#     b. 应用交叉和变异产生子代
#     c. 评估子代
#     d. 选择下一代种群
# 4. 输出最优个体和其性能# 示例:创建和评估一个随机个体
if __name__ == "__main__":# 创建一个随机个体ind = toolbox.individual()print("随机个体:", ind)# 评估个体(需要确保MNIST数据已加载)# 注意:在实际运行前,需要先加载MNIST数据# (x_train, y_train), (x_val, y_val) = load_mnist_data()# fitness = evaluate_individual(ind)# print("个体适应度:", fitness)

关键点说明

  • evaluate_individual 是性能瓶颈。这里我们只训练5个Epoch来粗略估计架构潜力,这是一种常见的加速策略。

  • 每次评估后清理内存至关重要,否则长时间运行后内存/显存会耗尽。

  • 交叉和变异操作要确保产生的个体是有效的(例如,全连接层之后可能不适合再接卷积层,我们的简单编码暂未处理此问题,更复杂的编码需要设计约束)。


第四章:运行进化NAS——释放算力,孕育架构

理论:并行进化与资源管理

现在,所有部件都已就位。但在按下“开始”按钮前,必须考虑两个现实问题:

  1. 并行计算(Parallelism):评估成百上千个个体是令人尴尬的并行(Embarrassingly Parallel) 任务,因为每个个体的评估是独立的。DEAP支持多种并行后端(如multiprocessing)。

  2. 早停(Early Stopping):如果某个架构在训练初期表现就极其糟糕,可以提前终止训练以节省资源。

实战:启动最终的架构搜索

我们将使用multiprocessing来并行评估个体,并运行完整进化流程。

# 导入必要的库
import multiprocessing  # 用于并行计算,加速适应度评估
import numpy as np  # 用于数学计算和统计
from deap import algorithms  # 导入DEAP的进化算法实现
import random  # 用于随机数生成
import tensorflow as tf  # 导入TensorFlow深度学习框架
from tensorflow.keras.optimizers import Adam  # 导入Adam优化器# 假设前面的代码已经定义了toolbox、create_network函数
# 以及加载了MNIST数据 (x_train, y_train, x_val, y_val, x_test, y_test)# 定义主函数,包含完整的神经架构搜索流程
def main():# 设置随机种子保证实验可重复性# 使用相同的种子可以复现实验结果random.seed(42)np.random.seed(42)# 设置TensorFlow的随机种子tf.random.set_seed(42)# 创建初始种群pop_size = 20  # 种群大小,即每代包含的个体数量pop = toolbox.population(n=pop_size)  # 使用工具箱创建初始种群# 设置并行评估,加速适应度计算# 使用多进程池,进程数为CPU核心数减1(保留一个核心给系统)pool = multiprocessing.Pool(processes=multiprocessing.cpu_count()-1)# 注册映射函数,使工具箱可以使用多进程并行评估个体toolbox.register("map", pool.map)# 定义统计和日志记录# 创建名人堂(HallOfFame),保存历代最好的3个个体hof = tools.HallOfFame(3)# 创建统计对象,用于记录进化过程中的统计信息stats = tools.Statistics(lambda ind: ind.fitness.values)  # 提取个体的适应度值# 注册统计函数:计算适应度的平均值stats.register("avg", np.mean)# 注册统计函数:计算适应度的标准差stats.register("std", np.std)# 注册统计函数:找到适应度的最小值stats.register("min", np.min)# 注册统计函数:找到适应度的最大值stats.register("max", np.max)# 运行进化算法 (代数=10)# 使用eaSimple算法执行进化过程# pop: 初始种群# toolbox: 包含所有注册操作的工具箱# cxpb=0.7: 交叉概率为70%# mutpb=0.3: 变异概率为30%# ngen=10: 进化10代# stats: 统计对象,用于记录进化过程# halloffame: 名人堂对象,保存历代最优个体# verbose=True: 打印进化过程信息pop, logbook = algorithms.eaSimple(pop, toolbox, cxpb=0.7, mutpb=0.3, ngen=10,stats=stats, halloffame=hof, verbose=True)# 关闭并行池,释放资源pool.close()  # 阻止任何更多任务被提交到池pool.join()   # 等待所有工作进程退出# 输出最终结果print("\n=== 进化结束 ===")print("历代最佳个体 (Hall of Fame):")# 遍历名人堂中的所有个体for i, ind in enumerate(hof):# 打印每个个体的排名、基因型和适应度(验证准确率)print(f"Rank {i+1}: {ind}, Fitness (Val Accuracy): {ind.fitness.values[0]:.4f}")# 找到绝对最好的个体(名人堂中的第一个个体)best_individual = hof[0]print(f"\n*** 找到的最佳架构: {best_individual} ***")print(f"*** 其验证准确率: {best_individual.fitness.values[0]:.4f} ***")# 完整训练这个最佳架构并评估在测试集上的性能print("\n--- 开始完整训练最佳架构 ---")# 将最佳个体解码为Keras模型best_model = create_network(best_individual, (28, 28, 1))# 编译模型best_model.compile(optimizer=Adam(learning_rate=0.001),loss='sparse_categorical_crossentropy',metrics=['accuracy'])# 使用更多轮次(50轮)完整训练模型# 使用训练数据和验证数据,verbose=1显示训练进度history = best_model.fit(x_train, y_train,batch_size=256,epochs=50,validation_data=(x_val, y_val),verbose=1)# 在测试集上评估模型性能test_loss, test_acc = best_model.evaluate(x_test, y_test, verbose=0)print(f"\n!!! 最佳架构在测试集上的最终性能: {test_acc:.4f} !!!")# 返回种群、日志和名人堂,便于后续分析return pop, logbook, hof# 主程序入口
if __name__ == "__main__":# 确保在Windows下使用multiprocessing的正确方式# 在Windows上,多进程需要这个保护语句multiprocessing.freeze_support()# 调用主函数main()# 注意:在实际运行前,需要确保以下内容已定义或加载:
# 1. 工具箱(toolbox)的注册,包括个体创建、评估、交叉、变异和选择操作
# 2. create_network函数,用于将基因型转换为神经网络模型
# 3. MNIST数据集已加载并预处理为(x_train, y_train), (x_val, y_val), (x_test, y_test)
# 4. 可能需要调整一些参数,如种群大小、进化代数、训练轮数等,以适应具体硬件和时间限制# 运行过程解析:
# 程序会输出每一代的统计信息,包括最大适应度、平均适应度等
# 随着代数增加,通常会看到适应度逐渐提升的趋势
# 名人堂(HallOfFame)会保存历史上最好的3个架构
# 最终,程序会选出冠军架构,并用更多的训练轮次重新训练它
# 最后在测试集上评估性能,这是衡量NAS成功与否的最终标准

运行过程解析
程序会输出每一代的统计信息。你会看到max(最佳适应度)和avg(平均适应度)随着代数增加而逐渐上升的趋势。HallOfFame对象会一直保存历史上最好的3个架构。最终,我们会选出冠军架构,并用更多的训练轮次(如50轮)重新训练它,并在从未见过的测试集上报告其最终性能。这才是衡量NAS成功与否的黄金标准。


第五章:超越与展望——NAS的前沿与挑战

我们的简单实验只是NAS世界的惊鸿一瞥。真正的工业级和研究级NAS要复杂得多:

  • 更高效的搜索策略

    • 权重共享(Weight Sharing):如ENAS。所有架构子模型在一个超网(Supernet)中共享权重,评估个体只需前向传播一次,极大加速适应度评估。

    • 基于性能预测器(Performance Predictor):训练一个回归模型,根据架构编码直接预测其性能,避免实际训练。

    • 多目标优化:不仅优化精度,还同时优化参数量、FLOPs、延迟等(weights=(-1.0, 0.5) 表示最小化参数量,最大化精度)。

  • 更复杂的搜索空间

    • 微分架构搜索(DARTS):将离散的架构选择松弛为连续可微的优化问题,可用梯度下降高效求解。

    • 层次化空间:同时搜索微观细胞结构和宏观网络骨架。

  • 挑战

    • 计算成本:尽管有各种加速技术,搜索一个好的架构仍然需要巨大的算力。

    • 可复现性:随机性的影响很大,两次搜索可能得到不同的结果。

    • 泛化能力:在一个数据集上搜到的最优架构,能否很好地迁移到其他数据集?

尽管挑战重重,自动化机器学习(AutoML)和NAS无疑是未来的重要方向,它们正在降低深度学习应用的门槛,并将专家的精力从繁琐的调参中解放出来,投入到更富创造性的工作中。


结论:从手工锻造到蒸汽时代

我们完成了一次奇妙的旅程:从遗传算法的基本概念出发,亲手将神经网络的架构编码成染色体,定义了进化所需的选择、交叉、变异算子,最终利用DEAP库和并行计算,成功地在架构的海洋中自动搜寻到了 promising 的设计。

这就像是从手工锻造冷兵器时代,迈入了利用蒸汽机(进化算法)进行机械化生产的工业时代萌芽。虽然我们的系统还很简单,但它清晰地展示了自动化设计的核心思想与巨大潜力。

现在,你拥有了这把钥匙。你可以尝试改进它:设计更灵活的编码方式、加入更复杂的进化算子、尝试不同的搜索空间、或者将其应用到你自己领域的实际问题中。进化的大门已经敞开,无限的架构可能正等待你的算法去发现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921824.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921824.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921824.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue框架技术详解——项目驱动概念理解【前端】【Vue】

Vue3框架 是前端渲染框架浏览器向服务器第一次发送请求&#xff0c;就会将所有页面的样式全部返回到浏览器vue中会将所有js文件最后打包成一个js文件&#xff0c;当前访问其中一个页面时&#xff0c;其他页面的样式也已经返回到浏览器中了&#xff0c;下次切换页面时&#xff…

HTML 网页静态托管 API 接口文档(可集成到智能体Agent)

HTML 网页静态托管 API 接口文档&#xff08;可集成到智能体Agent&#xff09; 接口概述 本接口用于将HTML代码转换为可访问的网页&#xff0c;支持通过API密钥进行身份验证。 API 密钥申请地址&#xff1a; https://www.cuobiezi.net/user/api_keys/apply API接口信息 接…

springboot vue sse消息推送,封装系统公共消息推送前后端方法

概述 1、封装springboot全局的消息推送接口&#xff1b; 注&#xff1a;1&#xff09;由于原生HTML5 EventSource 不支持添加header&#xff0c;所以要把连接创建接口加入身份验证白名单&#xff0c;并在接口内添加自己校验token2&#xff09;后台需定时心跳&#xff0c;保证链…

LeetCode 每日一题 2025/9/1-2025/9/7

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录9/1 1792. 最大平均通过率9/2 3025. 人员站位的方案数 I9/3 3027. 人员站位的方案数 II9/4 3516. 找到最近的人9/5 2749. 得到整数零需要执行的最少操作数9/6 3495. 使数组元…

小迪安全v2023学习笔记(八十讲)—— 中间件安全WPS分析WeblogicJenkinsJettyCVE

文章目录前记服务攻防——第八十天中间件安全&HW2023-WPS分析&Weblogic&Jetty&Jenkins&CVE应用WPS - HW2023-RCE&复现&上线CS介绍漏洞复现中间件 - Weblogic-CVE&反序列化&RCE介绍利用中间件 - Jenkins-CVE&RCE执行介绍漏洞复现CVE-20…

各webshell管理工具流量分析

哥斯拉哥斯拉是一个基于流量、HTTP全加密的webshell管理工具 特点 1.内置了3种Payload以及6种加密器&#xff0c;6种支持脚本后缀&#xff0c;20个内置插件 2.基于java&#xff0c;可以跨平台使用 3.可以自己生成webshell&#xff0c;根据管理来生成一些payload&#xff0c;然后…

pytest(1):fixture从入门到精通

pytest&#xff08;1&#xff09;&#xff1a;fixture从入门到精通前言1. Fixture 是什么&#xff1f;为什么我们需要它&#xff1f;2. 快速上手&#xff1a;第一个 Fixture 与基本用法3. 作用域 (Scope)&#xff1a;控制 Fixture 的生命周期4. 资源管理&#xff1a;Setup/Tear…

Java17 LTS 新特性用例

基于 Java 17 LTS 的 实用示例 以下是基于 Java 17 LTS 的 30 个实用示例,涵盖语言新特性、API 改进及常见场景。所有代码均兼容 Java 17 语法规范。 文本块(Text Blocks) String json = """{"name": "Java 17","type": &qu…

SpringBoot-Web开发-内容协商——多端内容适配内容协商原理HttpMessageConverter

其它篇章&#xff1a; 一&#xff1a;SpringBoot3-日志——日志原理&日志格式&日志级别&日志分组&文件输出&文件归档&滚动切割 二&#xff1a;SpringBoot3-Web开发-静态资源——WebMvcAutoConfiguration原理&资源映射&资源缓存&欢迎页&…

Spring MVC 类型转换与参数绑定:从架构到实战

在 Spring MVC 开发中&#xff0c;“前端请求数据” 与 “后端 Java 对象” 的格式差异是高频痛点 —— 比如前端传的String类型日期&#xff08;2025-09-08&#xff09;要转成后端的LocalDate&#xff0c;或者字符串male要转成GenderEnum.MALE枚举。Spring 并非通过零散工具解…

Spark提交任务的资源配置和优化

Spark 提交任务时主要可调的资源配置参数包括 Driver 资源&#xff08;内存、CPU&#xff09;、Executor 资源&#xff08;数量、内存、CPU&#xff09;以及 集群管理相关参数。配置和优化时一般结合集群硬件资源、数据规模、作业类型和作业复杂度&#xff08;SQL / 机器学习&a…

机器学习06——支持向量机(SVM核心思想与求解、核函数、软间隔与正则化、支持向量回归、核方法)

上一章&#xff1a;机器学习05——多分类学习与类别不平衡 下一章&#xff1a;机器学习07——贝叶斯分类器 机器学习实战项目&#xff1a;【从 0 到 1 落地】机器学习实操项目目录&#xff1a;覆盖入门到进阶&#xff0c;大学生就业 / 竞赛必备 文章目录一、间隔与支持向量&…

AI集群全链路监控:从GPU微架构指标到业务Metric关联

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;AI算力时代的监控挑战 随着深度学习模型规模的指…

K8s Ingress Annotations参数使用指南

Kubernetes Ingress Annotations 是与特定 Ingress 控制器&#xff08;如 Nginx、Traefik、HAProxy 等&#xff09;配合使用&#xff0c;用于扩展和定制 Ingress 资源行为的关键配置项。它们通常以键值对的形式添加在 Ingress 资源的 metadata部分。Ingress Annotations参数速查…

CodeBuddy Code深度实战:从零构建智能电商推荐系统的完整开发历程

项目背景与挑战作为一名有着多年全栈开发经验的技术人员&#xff0c;我最近接手了一个具有挑战性的项目&#xff1a;为某中型服装电商平台开发一套智能商品推荐系统。该系统需要在2个月内完成&#xff0c;包含以下核心功能&#xff1a;前端&#xff1a;React TypeScript构建的…

Day 19: 算法基础与面试理论精通 - 从思想理解到策略掌握的完整体系

Day 19: 算法基础与面试理论精通 - 从思想理解到策略掌握的完整体系 🎯 课程概述 核心目标:深度理解算法设计思想和核心原理,掌握面试高频算法概念,建立完整的算法知识体系 学习重点: ✅ 核心数据结构的本质理解和应用场景分析 ✅ 经典算法设计模式的思想精髓和解题策…

AI与AR融合:重塑石化与能源巡检的未来

在石化企业和新能源电站的巡检工作中&#xff0c;传统模式正被一场技术革命所颠覆。AI与AR&#xff08; www.teamhelper.cn &#xff09;的深度融合&#xff0c;不仅提升了巡检效率&#xff0c;更将巡检工作从被动响应转变为预测预防&#xff0c;开启了智能运维的新篇章。一、透…

滴滴二面(准备二)

手写防抖函数并清晰阐述其价值&#xff0c;确实是前端面试的常见考点。下面我将为你直接呈现防抖函数的代码&#xff0c;并重点结合滴滴的业务场景进行解释&#xff0c;帮助你向面试官展示思考深度。 这是防抖函数的一个基本实现&#xff0c;附带注释以便理解&#xff1a; func…

Kubernetes(四):Service

目录 一、定义Service 1.1 typeClusterIP 1.2 typeNodePort 1.3 typeLoadBalancer 1.4 typeExternalName 1.5 无标签选择器的Service 1.6 Headless Service 二、Kubernetes的服务发现 2.1 环境变量方式 2.2 DNS方式 Kubernetes 中 Service 是 将运行在一个或一组 Pod 上的应用…

在 Python 中实现观察者模式的具体步骤是什么?

在 Python 中实现观察者模式可以遵循以下具体步骤&#xff0c;这些步骤清晰地划分了角色和交互流程&#xff1a; 步骤 1&#xff1a;定义主题&#xff08;Subject&#xff09;基类 主题是被观察的对象&#xff0c;负责管理观察者和发送通知。需实现以下核心方法&#xff1a; 存…