遗传算法与深度学习实战(27)——进化卷积神经网络

遗传算法与深度学习实战(27)——进化卷积神经网络

    • 0. 前言
    • 1. 自定义交叉算子
    • 2. 自定义突变操作符
    • 3. 进化卷积神经网络
    • 小结
    • 系列链接

0. 前言

DEAP toolbox 中提供的标准遗传操作符对于自定义的网络架构基因序列来说是不够的。这是因为任何标准的交叉算子都可能破坏卷积神经网络 (Convolutional Neural Network, CNN) 的基因序列的格式。相反,为了构建进化卷积神经网络,我们需要为交叉和突变都构建自定义遗传算子。在本节中,我们首先介绍如何自定义交叉和突变算子,然后基于自定义遗传算子,构建进化卷积神经网络 (Evolutionary Convolutional Neural Network, EvoCNN)。

1. 自定义交叉算子

下图展示了如何将自定义交叉算子应用于父代双亲,该操作通过将两个父本中的各种层集合提取到不同列表中——一个用于卷积,一个用于池化等等。从每个列表中,随机选择一对层进行基因序列之间的交换,生成的基因序列为后代基因。

交叉算子

这只是执行交叉操作的一种方式,我们也可以考虑使用其他方式,重要的是保证在交叉操作后基因序列保持正确的格式。

(1) 首先,导入所需库、超参数、数据集以及辅助函数:

import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import numpy as np
import math
import time
import random

import matplotlib.pyplot as plt
from livelossplot import PlotLossesKeras

dataset = datasets.fashion_mnist
(x_train, y_train), (x_test, y_test) = dataset.load_data()

# normalize and reshape data
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype("float32") / 255.0
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1).astype("float32") / 255.0

x_train = x_train[:1000]
y_train= y_train[:1000]
x_test = x_test[:100]
y_test= y_test[:100]

class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
               'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']

def plot_data(num_images, images, labels):
    grid = math.ceil(math.sqrt(num_images))
    plt.figure(figsize=(grid*2,grid*2))
    for i in range(num_images):
        plt.subplot(grid,grid,i+1)
        plt.xticks([])
        plt.yticks([])
        plt.grid(False)     
        plt.imshow(images[i].reshape(28,28))
        plt.xlabel(class_names[labels[i]])      
    plt.show()

plot_data(25, x_train, y_train)

max_layers = 5
max_neurons = 128
min_neurons = 16
max_kernel = 5
min_kernel = 2
max_pool = 3
min_pool = 2

CONV_LAYER = -1
CONV_LAYER_LEN = 4
POOLING_LAYER = -2
POOLING_LAYER_LEN = 3
BN_LAYER = -3
BN_LAYER_LEN = 1
DENSE_LAYER = -4
DENSE_LAYER_LEN = 2

def generate_neurons():
    return random.randint(min_neurons, max_neurons)

def generate_kernel():
    part = []
    part.append(random.randint(min_kernel, max_kernel))
    part.append(random.randint(min_kernel, max_kernel))
    return part

def generate_bn_layer():
    part = [BN_LAYER] 
    return part

def generate_pooling_layer():
    part = [POOLING_LAYER] 
    part.append(random.randint(min_pool, max_pool))
    part.append(random.randint(min_pool, max_pool))
    return part

def generate_dense_layer():
    part = [DENSE_LAYER] 
    part.append(generate_neurons())  
    return part

def generate_conv_layer():
    part = [CONV_LAYER] 
    part.append(generate_neurons())
    part.extend(generate_kernel())
    return part

def create_offspring():
    ind = []
    for i in range(max_layers):
        if random.uniform(0,1)<.5:
            #add convolution layer
            ind.extend(generate_conv_layer())
            if random.uniform(0,1)<.5:
                #add batchnormalization
                ind.extend(generate_bn_layer())
            if random.uniform(0,1)<.5:
                #add max pooling layer
                ind.extend(generate_pooling_layer())
    ind.extend(generate_dense_layer())
    return ind
        
individual = create_offspring()
print(individual)

(2) 定义函数 build_model() 用于根据基因创建神经网络模型:

def build_model(individual):
    model = models.Sequential()
    il = len(individual)
    i = 0
    while i < il:
        if individual[i] == CONV_LAYER: 
            n = individual[i+1]
            k = (individual[i+2], individual[i+3])
            i += CONV_LAYER_LEN
            if i == 0: #first layer, add input shape      
                model.add(layers.Conv2D(n, k, activation='relu', padding="same", input_shape=(28, 28, 1)))      
            else:
                model.add(layers.Conv2D(n, k, activation='relu', padding="same"))    
        elif individual[i] == POOLING_LAYER: #add pooling layer
            k = k = (individual[i+1], individual[i+2])
            i += POOLING_LAYER_LEN
            model.add(layers.MaxPooling2D(k, padding="same"))      
        elif individual[i] == BN_LAYER: #add batchnormal layer
            model.add(layers.BatchNormalization())
            i += 1      
        elif individual[i] == DENSE_LAYER: #add dense layer
            model.add(layers.Flatten())      
            model.add(layers.Dense(individual[i+1], activation='relu'))
            i += 2
    model.add(layers.Dense(10))
    return model

model = build_model(individual) 

(3) get_layers() 函数用于从每个基因序列中提取网络层索引,可以使用一个列表推导式来完成此任务,通过检查序列中的每个值并提取列表中的匹配位置:

def get_layers(ind, layer_type):
    return [a for a in range(len(ind)) if ind[a] == layer_type]

(4) swap() 它负责交换每个个体的网络层块。swap() 函数通过从给定索引处提取序列中的每个网络层块进行交换。由于网络层类型的长度始终相同,因此可以简单的使用索引替换。需要注意的是,如果网络层块长度可变,就需要使用其它复杂的解决方案:

def swap(ind1, iv1, ind2, iv2, ll):
    ch1 = ind1[iv1:iv1+ll]
    ch2 = ind2[iv2:iv2+ll]
    print(ll, iv1, ch1, iv2, ch2)
    ind1[iv1:iv1+ll] = ch2
    ind2[iv2:iv2+ll] = ch1
    return ind1, ind2

(5) swap_layers() 函数是从序列中提取每种网络层类型并进行随机交换的地方,首先根据每个序列获取网络层的类型列表,c1c2 都是索引列表,通过循环确定交换点。从这些列表中,我们随机选择一个值来交换每个序列,并使用 swap() 函数执行交换:

def swap_layers(ind1, ind2, layer_type, layer_len):
    c1, c2 = get_layers(ind1, layer_type), get_layers(ind2, layer_type) 
    min_c = min(len(c1), len(c2))
    for i in range(min_c):
        if random.random() < 1:
            i1 = random.randint(0, len(c1)-1)
            i2 = random.randint(0, len(c2)-1)      
            iv1 = c1.pop(i1)
            iv2 = c2.pop(i2)    
            ind1, ind2 = swap(ind1, iv1, ind2, iv2, layer_len) 
    return ind1, ind2 

(6) 交叉函数 crossover() 为每组网络层调用 swap_layers() 函数:

def crossover(ind1, ind2):
    ind1, ind2 = swap_layers(ind1, ind2, CONV_LAYER, CONV_LAYER_LEN)
    ind1, ind2 = swap_layers(ind1, ind2, POOLING_LAYER, POOLING_LAYER_LEN)
    ind1, ind2 = swap_layers(ind1, ind2, BN_LAYER, BN_LAYER_LEN)
    ind1, ind2 = swap_layers(ind1, ind2, DENSE_LAYER, DENSE_LAYER_LEN)
    return ind1, ind2 

ind1 = create_offspring()
ind2 = create_offspring()
print(ind1)
print(ind2)

ind1, ind2 = crossover(ind1, ind2)
print(ind1)
print(ind2)

下图展示了在两个父代上执行 crossover() 函数后的结果,从中可以看出,交换了三个卷积层、一个池化层、一个批归一化层和一个全连接层组。

交叉结果

(7) 构建、编译、训练生成的个体,并输出结果。观察输出结果,确保交叉操作不会破坏基因序列的格式。现在,我们已经有了用于交叉和产生后代的交叉算子,接下来,将继续研究突变算子:

model = build_model(ind2) 

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

history = model.fit(x_train, y_train, epochs=3, 
                    validation_data=(x_test, y_test),
                    callbacks=[PlotLossesKeras()],
                    verbose=0)

model.summary()
model.evaluate(x_test, y_test)

2. 自定义突变操作符

DEAP 中提供的标准变异操作符对于我们的自定义基因序列是无效的。因此,我们需要自定义变异操作符来模拟对基因序列应用的突变操作。在本节中,我们保持突变相对简单,只改变当前的网络层块。在更高级的应用中,突变可以删除或添加新的网络层块。

(1) 突变函数 mutation() 首先检查个体是否为空,如果不为空,对每个网络层组执行突变。最后,按照 DEAP 约定,以元组形式返回结果:

def mutate(part, layer_type):
    if layer_type == CONV_LAYER and len(part)==CONV_LAYER_LEN:
        part[1] = int(part[1] * random.uniform(.9, 1.1))
        part[2] = random.randint(min_kernel, max_kernel)
        part[3] = random.randint(min_kernel, max_kernel)
    elif layer_type == POOLING_LAYER and len(part)==POOLING_LAYER_LEN:
        part[1] = random.randint(min_kernel, max_kernel)
        part[2] = random.randint(min_kernel, max_kernel)
    elif layer_type == DENSE_LAYER and len(part)==DENSE_LAYER_LEN:
        part[1] = int(part[1] * random.uniform(.9, 1.1)) 
    else:
        error = f"mutate ERROR {part}"    
        raise Exception(error) 
    return part

(2) mutate_layers() 函数循环遍历特定类型的网络层组,并仅相应的超参数发生突变。首先使用 get_layers() 函数提取给定类型的网络层组索引。然后,在 try/except 块中,通过调用 mutate() 函数替换给定索引的网络层块应用突变:

def mutate_layers(ind, layer_type, layer_len):
    layers = get_layers(ind1, layer_type)
    for layer in layers:
        if random.random() < 1:
            try:
                ind[layer:layer+layer_len] = mutate(
                    ind[layer:layer+layer_len], layer_type) 
            except:
                print(layers)
    return ind      

(3) mutate() 函数首先检查提取的部分是否具有正确的长度,这是为了防止个体发生潜在的格式损坏问题。接下来,根据网络层类型,可以改变滤波器的数量和卷积核大小。需要注意的是,我们将卷积核大小限制在给定最小/最大范围内的值,但允许滤波器的数量增加或减少。此时,还检查个体基因序列是否有任何损坏的块,即不匹配所需长度的块。如果在突变过程中发现基因序列损坏,则会抛出异常,异常会在 mutation() 函数中捕获到:

def mutation(ind):  
    if len(ind) > CONV_LAYER_LEN: #only mutate conv individuals
        ind = mutate_layers(ind, CONV_LAYER, CONV_LAYER_LEN)
        ind = mutate_layers(ind, DENSE_LAYER, DENSE_LAYER_LEN)
        ind = mutate_layers(ind, POOLING_LAYER, POOLING_LAYER_LEN)
    return ind,

print(ind1)
ind1 = mutation(ind1)[0]
print(ind1)

下图显示了在个体基因序列上执行突变函数的结果。可以看到,只有定义网络层组的滤波器数量或卷积核大小的超参数被修改。

突变算子

构建、编译和训练突变的基因序列,以确认我们仍然可以生成一个有效的 Keras 模型。多次执行突变操作,以确认输出的基因序列是有效的。我们已经学习了构建用于处理交叉和突变操作的自定义运算符,接下来,我们继续应用进化算法。
Keras 的模型编译具有健壮性和宽容性,这在我们随机构建的一些模型可能存在问题并且无法产生良好结果时非常有用。相比之下,像 PyTorch 这样的框架宽容性较差,并且可能会对构建问题提产生阻塞错误。使用 Keras,我们可以进行最小的错误处理,因为大多数模型都可以运行;然而,它们可能运行效果不佳。如果我们在 PyTorch 上应用相同的进化算法,可能会遇到更多的构建问题,因为它对一些较小的问题也非常敏感,导致更少的后代幸存下来。相反,Keras 将产生更多可行的后代,可能发展成为更合适的解决方案。

3. 进化卷积神经网络

我们已经了解了自定义运算符的工作原理,在本节中,我们将其扩展为执行进化架构搜索,实现进化卷积神经网络 (Evolutionary Convolutional Neural Network, EvoCNN)。

(1) 设置 DEAP toolbox,重用 create_offspring() 函数,并在 toolbox 注册为 network,用于创建新的后代。然后,使用列表来保存个体基因序列,使用列表能够创建一组基因长度不同的个体:

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)

toolbox = base.Toolbox()
toolbox.register("network", create_offspring)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.network)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

toolbox.register("select", tools.selTournament, tournsize=5)

(2) 注册自定义的交叉和突变函数:

toolbox.register("mate", crossover)
toolbox.register("mutate", mutation)

(3) 训练网络,在 compile_train() 函数中,将训练固定为 3epochs

def clamp(num, min_value, max_value):
    return max(min(num, max_value), min_value)

def compile_train(model):
    model.compile(optimizer='adam',
                loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                metrics=['accuracy'])
    model.fit(x_train, y_train, epochs=3,                     
                        verbose=0)
    return model

(4) 构建、编译、训练并评估模型。evaluate() 函数首先使用 build_model() 函数构建模型,然后使用 compile_train() 函数编译和训练模型,之后,返回值 1/accuracy (accuracy 范围在 01 之间),这样做是因为我们希望通过 1/accuracy 来最小化适应度。需要注意的是,使用 try/except 语句将代码包装起来,以确保在任何失败情况下都能优雅地恢复。由于代码仍然可能构建出毫无意义的模型,使用 try/except 语句可以防止失败。如果模型构建失败,返回 1/.5。通过这种方式,能够将这些失败个体保留在种群中,并在之后突变为更好的解决方案:

def evaluate(individual):  
    try:
        model = build_model(individual)
        model = compile_train(model)
        print('.', end='')    
        return 1/clamp(model.evaluate(x_test, y_test, verbose=0)[1], .00001, 1),
    except:
        return 1/.5,  

toolbox.register("evaluate", evaluate)   

适者生存,通过给失败的个体一定的基础适应度,使这些基因序列有机会留在种群池中。

(5) 设置进化过程,并可视化进化输出,由于基因序列相对较小,通常可以快速收敛,准确率大约为 81%。可以尝试增加种群的规模或代数的数量,查看其对结果的影响:

MU = 10 #@param {type:"slider", min:5, max:1000, step:5}
NGEN = 5 #@param {type:"slider", min:5, max:100, step:1}
RGEN = 1 #@param {type:"slider", min:1, max:100, step:1}
CXPB = .6
MUTPB = .3

pop = toolbox.population(n=MU)
hof = tools.HallOfFame(1)
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)

best = None
groups = { "fitness" : {"min", "max"}}
plotlosses = PlotLosses(groups=groups)

for g in range(NGEN):
    pop, logbook = algorithms.eaSimple(pop, toolbox, 
                cxpb=CXPB, mutpb=MUTPB, ngen=RGEN, stats=stats, halloffame=hof, verbose=False)
    best = hof[0] 
    
    print(f"Gen ({(g+1)*RGEN})")      
    for l in logbook:
        plotlosses.update({'min': 1/l["max"], 'max': 1/l["min"]})
    plotlosses.send()  # draw, update logs, etc

运行结果

(6) 进化完成后,构建、编译和训练最佳个体,查看结果,可以看到在 3epochs 后模型仍存在过拟合情况,这表明如果我们想要一个泛化能力更高的模型,可能需要增加训练 epochs,但会极大的增加演化时间:

build_compile_train(best, epochs=5)

运行结果

我们可以根据需要修改代码,并添加优化超参数:

  • 数据集大小:在本节中,我们大幅减小了原始数据集的大小以减少运行时间。如果增加数据集的大小,相应的运行时间也会增加
  • 训练 epochs:在本节中,我们将训练限制为 3epochs。根据数据集规模,可能需要增加或减少 epochs
  • 层类型:在本节中,我们只使用了标准层类型,如卷积、池化、批归一化和全连接层。我们也可以添加不同的层类型,如 dropout,或增加全连接层的数量等
  • 交叉/突变:在本节中,我们实现了自定义交叉和突变运算符。除了这种方式外,我们仍有进一步定制化的空间,例如在突变过程中添加或删除网络层块
  • 适应度/评估函数:本节中,个体的适应度基于准确率得分。如果我们想要最小化可训练参数或网络层数,也可以将其作为逻辑加入到 evaluate() 函数中

小结

卷积神经网络 (Convolutional Neural Network, CNN) 的设置和定义对于各种图像识别任务来说较为复杂的,通常得到最佳 CNN 超参数需要花费大量时间分析和调整。使用遗传算法进化一组个体,能够优化特定数据集上的 CNN 模型体系结构。本节中,介绍了自定义交叉和突变算子的构建方式,并使用自定义遗传算子实现进化卷积神经网络 (Evolutionary Convolutional Neural Network, EvoCNN)。

系列链接

遗传算法与深度学习实战(1)——进化深度学习
遗传算法与深度学习实战(2)——生命模拟及其应用
遗传算法与深度学习实战(3)——生命模拟与进化论
遗传算法与深度学习实战(4)——遗传算法(Genetic Algorithm)详解与实现
遗传算法与深度学习实战(5)——遗传算法中常用遗传算子
遗传算法与深度学习实战(6)——遗传算法框架DEAP
遗传算法与深度学习实战(7)——DEAP框架初体验
遗传算法与深度学习实战(8)——使用遗传算法解决N皇后问题
遗传算法与深度学习实战(9)——使用遗传算法解决旅行商问题
遗传算法与深度学习实战(10)——使用遗传算法重建图像
遗传算法与深度学习实战(11)——遗传编程详解与实现
遗传算法与深度学习实战(12)——粒子群优化详解与实现
遗传算法与深度学习实战(13)——协同进化详解与实现
遗传算法与深度学习实战(14)——进化策略详解与实现
遗传算法与深度学习实战(15)——差分进化详解与实现
遗传算法与深度学习实战(16)——神经网络超参数优化
遗传算法与深度学习实战(17)——使用随机搜索自动超参数优化
遗传算法与深度学习实战(18)——使用网格搜索自动超参数优化
遗传算法与深度学习实战(19)——使用粒子群优化自动超参数优化
遗传算法与深度学习实战(20)——使用进化策略自动超参数优化
遗传算法与深度学习实战(21)——使用差分搜索自动超参数优化
遗传算法与深度学习实战(22)——使用Numpy构建神经网络
遗传算法与深度学习实战(23)——利用遗传算法优化深度学习模型
遗传算法与深度学习实战(24)——在Keras中应用神经进化优化
遗传算法与深度学习实战(25)——使用Keras构建卷积神经网络
遗传算法与深度学习实战(26)——编码卷积神经网络架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/935377.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年第十五届蓝桥杯青少组C++国赛—割点

割点 题目描述 一张棋盘由n行 m 列的网格矩阵组成&#xff0c;每个网格中最多放一颗棋子。当前棋盘上已有若干棋子。所有水平方向或竖直方向上相邻的棋子属于同一连通块。 现给定棋盘上所有棋子的位置&#xff0c;如果要使棋盘上出现两个及以上的棋子连通块&#xff0c;请问…

嵌入式硬件-- 元器件焊接

1.锡膏的使用 锡膏要保存在冰箱里。 焊接排线端子&#xff1b;138度的低温锡&#xff08;锡膏&#xff09;&#xff0c; 第一次使用&#xff0c;直接拿东西挑一点涂在引脚上&#xff0c;不知道多少合适&#xff0c;加热台加热到260左右&#xff0c;放在上面观察锡融化&#…

Linux 简单命令总结

1. 简单命令 1.1. ls 列出该目录下的所有子目录与文件&#xff0c;后面还可以跟上一些选项 常用选项&#xff1a; ・-a 列出目录下的所有文件&#xff0c;包括以。开头的隐含文件。 ・-d 将目录象文件一样显示&#xff0c;而不是显示其下的文件。如&#xff1a;ls -d 指定目…

深入探讨可调电位器:原理、应用及编程实现

在电子电路和嵌入式系统中&#xff0c;可调电位器是一种常见且实用的元件&#xff0c;用于调节电压或电阻。无论是硬件设计还是控制系统开发&#xff0c;可调电位器都扮演着重要角色。本文将从可调电位器的工作原理、常见应用及其在C编程中的实际使用&#xff0c;帮助读者深入了…

MVC配置文件及位置

配置文件位置 默认位置 WEB-INF目录下&#xff0c;文件名&#xff1a;<servlet-name>-servlet.xml <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.…

26. Three.js案例-自定义多面体

26. Three.js案例-自定义多面体 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染场景的主要类。它支持 WebGL 渲染&#xff0c;并提供了多种配置选项。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选参数对象&…

Java_实例变量和局部变量及this关键字详解

最近得看看Java,想学一学Flink实时的东西了&#xff0c;当然Scala语法也有这样的规定&#xff0c;简单看一下这两个吧&#xff0c;都比较容易忽视 实例变量和局部变量 实例变量和局部变量是常见的两种变量类型&#xff0c;区别 作用域&#xff1a; 实例变量&#xff1a;实例变…

C++50道经典面试题

文章结尾有最新热度的文章,感兴趣的可以去看看。 本文是经过严格查阅相关权威文献和资料,形成的专业的可靠的内容。全文数据都有据可依,可回溯。特别申明:数据和资料已获得授权。本文内容,不涉及任何偏颇观点,用中立态度客观事实描述事情本身 导读 作为一种通用且面向对…

ansible自动化运维(三)jinja2模板roles角色管理

相关文章ansible自动化运维&#xff08;一&#xff09;简介及清单,模块-CSDN博客ansible自动化运维&#xff08;二&#xff09;playbook模式详解-CSDN博客ansible自动化运维&#xff08;四&#xff09;运维实战-CSDN博客 三.Ansible jinja2模板 Jinja2是Python的全功能模板引…

池化在深度学习中增强特征的作用

目录 ​编辑 引言 池化的基本作用与特征降维 池化的定义与目的 池化操作的实现 提取关键特征与计算效率的提升 池化对特征提取的影响 平均池化的应用 提高特征鲁棒性与过拟合的防止 池化对模型鲁棒性的贡献 池化防止过拟合的原理 增强多级特征与特征表达能力的提升…

【OJ题解】面试题三步问题

个人主页: 起名字真南的CSDN博客 个人专栏: 【数据结构初阶】 &#x1f4d8; 基础数据结构【C语言】 &#x1f4bb; C语言编程技巧【C】 &#x1f680; 进阶C【OJ题解】 &#x1f4dd; 题解精讲 目录 **题目链接****解题思路****1. 问题分析****2. 递归思路****3. 优化方案&a…

a few paper talked about software building process, so I learned

see also https://martinfowler.com/bliki/BlueGreenDeployment.html https://martinfowler.com/books/continuousDelivery.html https://martinfowler.com/articles/continuousIntegration.html https://swizec.com/blog/why-software-only-moves-forward/

Jenkins参数化构建详解(This project is parameterized)

本文详细介绍了Jenkins中不同类型的参数化构建方法&#xff0c;包括字符串、选项、多行文本、布尔值和git分支参数的配置&#xff0c;以及如何使用ActiveChoiceParameter实现动态获取参数选项。通过示例展示了传统方法和声明式pipeline的语法 文章目录 1. Jenkins的参数化构建1…

Tablesaw封装Plot.ly实现数据可视化

上文介绍tablesaw的数据处理功能&#xff0c;本文向你展示其数据可视化功能&#xff0c;并通过几个常用图表示例进行说明。 Plot.ly包装 可视化是数据分析的重要组成部分&#xff0c;无论你只是“查看”新数据集还是验证机器学习算法的结果。Tablesaw是一个开源、高性能的Java…

物流行业新突破:数字孪生的核心作用解析

在现代物流行业&#xff0c;效率和精准度是企业竞争的关键。随着数字化技术的发展&#xff0c;数字孪生作为一种新兴技术&#xff0c;正在智慧物流领域崭露头角。通过构建真实物流系统的虚拟映射&#xff0c;数字孪生帮助企业实现全方位的管理优化&#xff0c;为智慧物流带来了…

手机租赁系统开发全流程解析与实用指南

内容概要 在如今快速发展的科技时代&#xff0c;手机租赁系统已经成为一种新兴的商业模式&#xff0c;非常符合当下市场需求。那么&#xff0c;在开发这样一个系统的时候&#xff0c;首先要从需求分析和市场调研开始。在这一阶段&#xff0c;你需要了解用户需要什么&#xff0…

ViewModel

ViewMode是MVVM架构模式中VM层对应的类&#xff0c;它的作用是存储界面数据&#xff0c;并和界面发生数据交互。ViewModel能感知生命周期&#xff0c;并且在界面由于配置问题发生重建时候&#xff0c;可以保持当前的数据不变。生命周期如下&#xff1a; ViewMode由ViewModePr…

Android -- [SelfView] 自定义弹窗式颜色选择器

Android – [SelfView] 自定义弹窗式颜色选择器 PS: 1. 弹框式显示&#xff1b; 2. 支持透明度设置&#xff1b; 3. 支持拖动控件选择颜色&#xff1b; 4. 支持 ARGB | HEX 数值填写预览颜色并返回&#xff1b; 5. 输出支持Hex 和 Int 两种格式&#xff1b;效果 使用方法&…

open cv学习之图片矫正

一&#xff0c;实验原理 图像矫正的原理是透视变换 图像畸变主要有两类&#xff1a;径向畸变和切向畸变。径向畸变通常会导致图像的四个角向外或向内弯曲&#xff1b;切向畸变则是由于相机与图像平面不完全平行引起的。而OpenCV 提供了一个相机标定的工具&#xff0c;能够自动…

微信开发工具卡优化

微信开发者工具优化 设置-通用设置-不勾选 使用GPU加速模式 设置-通用设置-内存限制 1024调整为2048 详情-本地设置-不勾选 启用多核心编译 详情-本地设置-勾选 自动压缩脚本和样式 app.json “lazyCodeLoading”: “requiredComponents”