生成ExecutionGraph

news2025/7/11 19:39:20

文章目录

  • step 1:构建ExecutionJobVertex节点
  • step 2:创建ExecutionEdge,按照拓扑模式进行连接
  • 总结

JobGraph由JobVertex(顶点)和IntermediateDataSet(中间结果数据集)组成,其中JobVertex表示对数据进行的转换操作,而IntermediateDataSet则是由JobVertex产生的中间结果,既是上游JobVertex的输出,又是下游JobVertex的输入。

ExecutionGraph是JobManager根据JobGraph生成的,可以将其理解为是JobGraph的“并行化”的产物。ExecutionGraph由ExecutionJobVertex(若干个ExecutionVertex子节点)和IntermediateResult(若干个IntermediateResultPartition)组成。

在这里插入图片描述

构建ExecutionGraph的本质就是将每个JobVertex节点都转换成ExecutionJobVertex节点,关键词:“拆分”并行度

/**
 * 初始化ExecutionGraph
 */
public static ExecutionGraph buildGraph(
    @Nullable ExecutionGraph prior,
    JobGraph jobGraph,
    Configuration jobManagerConfig,
    ScheduledExecutorService futureExecutor,
    Executor ioExecutor,
    SlotProvider slotProvider,
    ClassLoader classLoader,
    CheckpointRecoveryFactory recoveryFactory,
    Time rpcTimeout,
    RestartStrategy restartStrategy,
    MetricGroup metrics,
    BlobWriter blobWriter,
    Time allocationTimeout,
    Logger log,
    ShuffleMaster<?> shuffleMaster,
    JobMasterPartitionTracker partitionTracker,
    FailoverStrategy.Factory failoverStrategyFactory) throws JobExecutionException, JobException {

    // 省略部分代码...

    /**
	 * 核心:生成ExecutionJobVertex,并根据并行度来生成对应数量的ExecutionVertex
	 */
    executionGraph.attachJobGraph(sortedTopology);


    // 省略部分代码...
}

遍历每个JobVertex节点,每当处理一个JobVertex节点,就为其生成对应的ExecutionJobVertex节点,并进行“逻辑连接”

/**
 * 根据有序添加的List<JobVertex>集合,为每个JobVertex对应生成一个ExecutionJobVertex,并根据并行度来生成对应数量的ExecutionVertex
 */
public void attachJobGraph(List<JobVertex> topologiallySorted) throws JobException {

    // 省略部分代码...


    // 遍历有序添加的List<JobVertex>集合
    for (JobVertex jobVertex : topologiallySorted) {

        if (jobVertex.isInputVertex() && !jobVertex.isStoppable()) {
            this.isStoppable = false;
        }

        /**
		 * 核心:为每个JobVertex节点,对应创建一个ExecutionJobVertex节点
		 * (内部会创建对应数量的ExecutionVertex子节点和一定数量的IntermediateResultPartition)
		 */
        ExecutionJobVertex ejv = new ExecutionJobVertex(
            this,
            jobVertex,
            1,
            maxPriorAttemptsHistoryLength,
            rpcTimeout,
            globalModVersion,
            createTimestamp);

        /**
		 * (在Flink 1.13中 ExecutionEdge的概念被优化,将由ConsumedPartitionGroup和ConsumedVertexGroup来代替)
		 * 	核心:这里就是让“上游IntermediateResult”的每个IntermediateResultPartition,遵从拓扑模式去跟ExecutionEdge相连,
		 * 	所谓相连,就是以全局变量“持有”的方式,使双方产生联系
		 */
        ejv.connectToPredecessors(this.intermediateResults);

        // 省略部分代码...
    }

    // 省略部分代码...
}

step 1:构建ExecutionJobVertex节点

每当遍历到一个JobVertex节点,就会调用以下方法,创建对应(JobGraph中的中间结果数据集的)数量的IntermediateResult,创建一个ExecutionJobVertex节点及其所属的(JobVertex并行度数量的)ExecutionVertex子节点

/**
 * 遍历JobVertex节点时,构建ExecutionJobVertex节点(和JobVertex节点一一对应),同时创建ExecutionVertex子节点
 * 关键词:“拆”并行度
 */
public ExecutionJobVertex(
    ExecutionGraph graph,
    JobVertex jobVertex,
    int defaultParallelism,
    int maxPriorAttemptsHistoryLength,
    Time timeout,
    long initialGlobalModVersion,
    long createTimestamp) throws JobException {

    if (graph == null || jobVertex == null) {
        throw new NullPointerException();
    }

    this.graph = graph;
    this.jobVertex = jobVertex;

    // JobVertex内的并行度
    int vertexParallelism = jobVertex.getParallelism();
    // 对JobVertex的并行度进行安全判断
    int numTaskVertices = vertexParallelism > 0 ? vertexParallelism : defaultParallelism;

    // JobVertex的最大并行度
    final int configuredMaxParallelism = jobVertex.getMaxParallelism();

    this.maxParallelismConfigured = (VALUE_NOT_SET != configuredMaxParallelism);

    // 设置最大并行度
    setMaxParallelismInternal(maxParallelismConfigured ?
                              configuredMaxParallelism : KeyGroupRangeAssignment.computeDefaultMaxParallelism(numTaskVertices));

    // 如果JobVertex的并行度大于最大并行度,抛异常
    if (numTaskVertices > maxParallelism) {
        throw new JobException(
            String.format("Vertex %s's parallelism (%s) is higher than the max parallelism (%s). Please lower the parallelism or increase the max parallelism.",
                          jobVertex.getName(),
                          numTaskVertices,
                          maxParallelism));
    }

    // JobVertex最终确定下来的并行度
    this.parallelism = numTaskVertices;
    this.resourceProfile = ResourceProfile.fromResourceSpec(jobVertex.getMinResources(), MemorySize.ZERO);

    // 容纳当前ExecutionJobVertex节点内的ExecutionVertex的专用数组
    this.taskVertices = new ExecutionVertex[numTaskVertices];
    // 当前JobVertex节点内所有(经过链化形成的OperatorChain中的StreamOperator)的OperatorID
    this.operatorIDs = Collections.unmodifiableList(jobVertex.getOperatorIDs());
    this.userDefinedOperatorIds = Collections.unmodifiableList(jobVertex.getUserDefinedOperatorIDs());

    this.inputs = new ArrayList<>(jobVertex.getInputs().size());

    this.slotSharingGroup = jobVertex.getSlotSharingGroup();
    this.coLocationGroup = jobVertex.getCoLocationGroup();

    if (coLocationGroup != null && slotSharingGroup == null) {
        throw new JobException("Vertex uses a co-location constraint without using slot sharing");
    }

    // 根据JobVertex节点的中间结果数据集--IntermediateDateaSets(输出)数量,初始化IntermediateResult的空集合(指定大小 = 中间结果数据集--IntermediateDataSet的数量,下面会用IntermediateResult将其“填满”)
    // 这个JobVertex有几个IntermediateDataSet的数量(中间结果数据集),这个IntermediateResult集合的size就是几个
    // 比如:3个输出边(中间结果数据集),就有3个IntermediateResult(每个IntermediateResult都有N个IntermediateResultPartition)
    this.producedDataSets = new IntermediateResult[jobVertex.getNumberOfProducedIntermediateDataSets()];

    /**
	 * 为这个JobVertex节点的N个IntermediateDataSet(中间结果数据集),创建对应数量的IntermediateResult
	 */
    for (int i = 0; i < jobVertex.getProducedDataSets().size(); i++) {
        final IntermediateDataSet result = jobVertex.getProducedDataSets().get(i);

        // 创建IntermediateResult:将生成IntermediateResult,“填”到IntermediateResult数组的指定位置
        this.producedDataSets[i] = new IntermediateResult(
            result.getId(),
            this,
            // JobVertex节点的并行度
            numTaskVertices,
            result.getResultType());
    }

    /**
	 * 根据JobVertex节点的并行度,创建对应数量的ExecutionVertex(如果这个JobVertex的并行度=3,就创建3个ExecutionVertex)
	 */
    for (int i = 0; i < numTaskVertices; i++) {
        // 核心:创建ExecutionVertex
        ExecutionVertex vertex = new ExecutionVertex(
            this,
            i,
            producedDataSets,
            timeout,
            initialGlobalModVersion,
            createTimestamp,
            maxPriorAttemptsHistoryLength);

        // 将生成的ExecutionVertex,“填”到数组的指定index位置
        this.taskVertices[i] = vertex;
    }

    for (IntermediateResult ir : this.producedDataSets) {
        if (ir.getNumberOfAssignedPartitions() != parallelism) {
            throw new RuntimeException("The intermediate result's partitions were not correctly assigned.");
        }
    }

    try {
        @SuppressWarnings("unchecked")
        InputSplitSource<InputSplit> splitSource = (InputSplitSource<InputSplit>) jobVertex.getInputSplitSource();

        if (splitSource != null) {
            Thread currentThread = Thread.currentThread();
            ClassLoader oldContextClassLoader = currentThread.getContextClassLoader();
            currentThread.setContextClassLoader(graph.getUserClassLoader());
            try {
                // 根据JobVertex的并行度(即ExecutionVertex的数量),切分输入
                inputSplits = splitSource.createInputSplits(numTaskVertices);

                if (inputSplits != null) {
                    splitAssigner = splitSource.getInputSplitAssigner(inputSplits);
                }
            } finally {
                currentThread.setContextClassLoader(oldContextClassLoader);
            }
        }
        else {
            inputSplits = null;
        }
    }
    catch (Throwable t) {
        throw new JobException("Creating the input splits caused an error: " + t.getMessage(), t);
    }
}

首先根据当前JobVertex节点的中间结果数据集–IntermediateDateaSets的数量,确定出一个固定size的IntermediateResult数组。因为JobGraph中的IntermediateDateaSet是跟ExecutionGraph中的IntermediateResult一一对应的。

接着还是根据JobVertex节点的中间结果数据集–IntermediateDateaSets的数量,new出来对应数量的IntermediateResult,并将其保存到刚刚准备好的IntermediateResult数组中。

然后根据这个JobVertex节点的并行度,new出对应数量的ExecutionVertex子节点,并将其逐个“填充”到ExecutionVertex数组的对应位置上(new ExecutionJobVertex时会按照JobVertex的并行度,初始化出一个对应容量的ExecutionVertex数组)。

/**
 *  根据JobVertex节点的并行度,创建对应数量的ExecutionVertex:
 *  	1.每当创建一个ExecutionVertex,就会为所有的IntermediateResult创建一个IntermediateResultPartition
 *  	2.将需要部署的Task的相关信息包装成Execution,方便发送给TaskExecutor(TaskExecutor会基于它来启动Task)
 */
public ExecutionVertex(
    ExecutionJobVertex jobVertex,
    int subTaskIndex,
    IntermediateResult[] producedDataSets,
    Time timeout,
    long initialGlobalModVersion,
    long createTimestamp,
    int maxPriorExecutionHistoryLength) {

    this.jobVertex = jobVertex;
    this.subTaskIndex = subTaskIndex;
    this.executionVertexId = new ExecutionVertexID(jobVertex.getJobVertexId(), subTaskIndex);
    this.taskNameWithSubtask = String.format("%s (%d/%d)",
                                             jobVertex.getJobVertex().getName(), subTaskIndex + 1, jobVertex.getParallelism());

    // 根据JobVertex节点的并行度,创建有序集合--LinkedHashMap
    this.resultPartitions = new LinkedHashMap<>(producedDataSets.length, 1);

    // JobVertex有3个并行度,就会循环创建3次ExecutionVertex。由于ExecutionJobVertex中的ExecutionVertex子节点,
	// 和IntermediateResult中的IntermediateResultPartition是一一对应的,因此每当创建1个ExecutionVertex时,
	// 就会为每个IntermediateResult“安排”1个IntermediateResultPartition
    for (IntermediateResult result : producedDataSets) {
        // 根据IntermediateResult,创建IntermediateResultPartition(用来接收ExecutionVertex的输出)
        IntermediateResultPartition irp = new IntermediateResultPartition(result, this, subTaskIndex);
        result.setPartition(subTaskIndex, irp);

        // 将创建好的IntermediateResultPartition,保存到LinkedHashMap中
        resultPartitions.put(irp.getPartitionId(), irp);
    }

    // 根据JobVertex节点的所有JobEdge(输入边)的数量,准备一个固定大小的ExecutionEdge二元数组,记录的是每个“JobVertex输入”对应的ExecutionEdge的数量
    this.inputEdges = new ExecutionEdge[jobVertex.getJobVertex().getInputs().size()][];

    this.priorExecutions = new EvictingBoundedList<>(maxPriorExecutionHistoryLength);

    // JobMaster拿到对应TM节点的Slot资源后,会把部署Task所需的信息包装成Execution,
    // 后续会调用Execution#deploy()方法执行部署:调用RPC请求,将Execution内包装的信息发送给TaskExecutor。
    // TaskExecutor收到后,将其包装成Task对象,然后启动这个Task
    this.currentExecution = new Execution(
        getExecutionGraph().getFutureExecutor(),
        this,
        0,
        initialGlobalModVersion,
        createTimestamp,
        timeout);

    CoLocationGroup clg = jobVertex.getCoLocationGroup();
    if (clg != null) {
        this.locationConstraint = clg.getLocationConstraint(subTaskIndex);
    }
    else {
        this.locationConstraint = null;
    }

    getExecutionGraph().registerExecution(currentExecution);

    this.timeout = timeout;
    this.inputSplits = new ArrayList<>();
}

在为ExecutionJobVertex创建ExecutionVertex子节点过程中,由于ExecutionVertex和IntermediateResult是一一对应的,因此只要new一个ExecutionVertex子节点,就会为每个IntermediateResult都“准备”1个IntermediateResultPartition。

接着根据这个JobVertex节点的输入边(JobEdge)的数量,确定好ExecutionEdge数组的容量大小,后面会创建ExecutionEdge并保存其中。

step 2:创建ExecutionEdge,按照拓扑模式进行连接

/**
 * JobVertex经过“拆分”并行度后,会创建一个对应的ExecutionJobVertex,内含N个ExecutionVertex(个数等于并行度)。
 * 自然IntermediateResult也就有对应数量的IntermediateResultPartition,该方法就是让这个IntermediateResult的每个IntermediateResultPartition,
 * 通过指定的数据分发模式,去跟ExecutionEdge相连
 */
public void connectToPredecessors(Map<IntermediateDataSetID, IntermediateResult> intermediateDataSets) throws JobException {

    // 这个JobVertex节点的输入边的集合
    List<JobEdge> inputs = jobVertex.getInputs();

    if (LOG.isDebugEnabled()) {
        LOG.debug(String.format("Connecting ExecutionJobVertex %s (%s) to %d predecessors.", jobVertex.getID(), jobVertex.getName(), inputs.size()));
    }

    // 遍历这个JobVertex节点的所有输入边--JobEdge
    for (int num = 0; num < inputs.size(); num++) {
        // 拿到一个JobEdge
        JobEdge edge = inputs.get(num);

        if (LOG.isDebugEnabled()) {
            if (edge.getSource() == null) {
                LOG.debug(String.format("Connecting input %d of vertex %s (%s) to intermediate result referenced via ID %s.",
                                        num, jobVertex.getID(), jobVertex.getName(), edge.getSourceId()));
            } else {
                LOG.debug(String.format("Connecting input %d of vertex %s (%s) to intermediate result referenced via predecessor %s (%s).",
                                        num, jobVertex.getID(), jobVertex.getName(), edge.getSource().getProducer().getID(), edge.getSource().getProducer().getName()));
            }
        }

        // 这个JobEdge的“上线”--中间结果数据集,对应的中间结果IntermediateResult
        IntermediateResult ires = intermediateDataSets.get(edge.getSourceId());
        if (ires == null) {
            throw new JobException("Cannot connect this job graph to the previous graph. No previous intermediate result found for ID "
                                   + edge.getSourceId());
        }

        // 将IntermediateResult添加到List集合中
        this.inputs.add(ires);

        int consumerIndex = ires.registerConsumer();

        // 遍历这个JobVertex的并行度(创建ExecutionJobVertex时,会根据JobVertex并行度创建对应数量的ExecutionVertex)
        for (int i = 0; i < parallelism; i++) {
            // 从数组中取出这个ExecutionJobVertex内的ExecutionVertex
            ExecutionVertex ev = taskVertices[i];
            // 根据数据分发模式,让这个IntermediateResult的IntermediateResultPartition,去跟ExecutionEdge进行连接
            ev.connectSource(num, ires, edge, consumerIndex);
        }
    }
}

遍历这个JobVertex节点的所有输入边–JobEdge,每当拿到一个“上游JobEdge”,根据JobEdge拿到上游的IntermediateDataSet,根据IntermediateDataSet拿到对应的IntermediateResult。接着就要用这个“上游IntermediateResult”去创建ExecutionEdge了。

首先要拿着这个“上游IntermediateResult”,获取它所有的IntermediateResultPartition。然后根据拓扑模式,创建ExecutionEdge。

/**
 * 遍历JobVertex的每个并行度,确定需要创建的ExecutionEdge的个数
 */
public void connectSource(int inputNumber, IntermediateResult source, JobEdge edge, int consumerNumber) {

    // 这个JobEdge的数据分发模式
    final DistributionPattern pattern = edge.getDistributionPattern();
    // 获取当前ExecutionJobVertex节点的“上游IntermediateResult”的IntermediateResultPartition数组
    final IntermediateResultPartition[] sourcePartitions = source.getPartitions();

    // 容纳ExecutionEdge的数组
    ExecutionEdge[] edges;

    // 根据拓扑模式,创建ExecutionEdge
    switch (pattern) {
        case POINTWISE:
            // 1V1
            edges = connectPointwise(sourcePartitions, inputNumber);
            break;

        case ALL_TO_ALL:
            // 笛卡尔积:为每个IntermediateResult的每个IntermediateResultPartition,创建ExecutionEdge
            edges = connectAllToAll(sourcePartitions, inputNumber);
            break;

        default:
            throw new RuntimeException("Unrecognized distribution pattern.");

    }

    // 将创建好的ExecutionEdge,保存到二元数组中
    // 该数组在创建ExecutionVertex子节点时,就已经按照“JobVertex输入”准备好了
    inputEdges[inputNumber] = edges;

    for (ExecutionEdge ee : edges) {
        // Source就是IntermediateResultPartition,
        ee.getSource().addConsumer(ee, consumerNumber);
    }
}

以ALL_TO_ALL为例,

/**
 * 拓扑模式:ALL_TO_ALL
 */
private ExecutionEdge[] connectAllToAll(IntermediateResultPartition[] sourcePartitions, int inputNumber) {
    // 根据当前ExecutionJobVertex节点的“上游IntermediateResult”的IntermediateResultPartition数组的length,确定ExecutionEdge数组的长度
    ExecutionEdge[] edges = new ExecutionEdge[sourcePartitions.length];

    // 遍历“上游IntermediateResult”的每个IntermediateResultPartition,创建ExecutionEdge
    for (int i = 0; i < sourcePartitions.length; i++) {
        // 拿到“上游IntermediateResult”的所有IntermediateResultPartition
        IntermediateResultPartition irp = sourcePartitions[i];
        // 创建ExecutionEdge,并保存到ExecutionEdge[] 数组中
        edges[i] = new ExecutionEdge(irp, this, inputNumber);
    }

    return edges;
}

总结

假设当前处理的JobVertex节点是FlatMap,那么首先就会为其转换ExecutionJobVertex节点:

  • 1.准备固定size的IntermediateResult数组:由于FlatMap只有1个输出,因此IntermediateResult数组的size为1
  • 2.创建IntermediateResult:由于FlatMap只有1个输出,因此只会生成1个IntermediateResult,并保存到IntermediateResult数组中
  • 3.创建ExecutionVertex子节点:由于FlatMap的并行度为2,因此此时经过for循环new出来2个ExecutionVertex

在2次for循环为这个FlatMap创建2个ExecutionVertex子节点的过程中,会为当前ExecutionJobVertex节点的每个IntermediateResult,都new出1个IntermediateResultPartition。由于FlatMap只有1个IntermediateResult,因此2轮for循环分别为这个IntermediateResult各自new出来1个IntermediateResultPartition。
接着,准备容纳ExecutionEdge的数组:由于这个FlatMap只有1个输入边,因此这个ExecutionEdge数组的size=1。

至此,JobVertex节点向ExecutionJobVertex节点的转换就完成了!接下来就是参考当前JobVertex节点的上游输入边、拓扑模式,创建ExecutionEdge并确定连接!

由于FlatMap的输入边 only one,于是就通过这个仅有的JobEdge,拿到对应的IntermediateDataSet,并取出它对应的IntermediateResult,也就是Source的IntermediateResult,作为FlatMap的“上游IntermediateResult”。
构造ExecutionEdge:由于FlatMap的并行度为2,因此会构造ExecutionEdge的方法会循环2次。由于FlatMap的“上游IntermediateResult”的IntermediateResultPartition个数=1,因此2次循环中,每次会构造出1个size为1的ExecutionEdge数组,且会new出1个ExecutionEdge并保存到ExecutionEdge数组中。
所谓的连接就是建立联系,在new ExecutionEdge时会以全局变量的方式“持有”当前ExecutionEdge相连的“上游IntermediateResultPartition”和“下游ExecutionVertex子节点”。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习笔记-1.基本的数据操作

数据的基本操作1-tensor创建2-功能函数3-算术操作4-数据操作4_1. index_select4_2. masked_select4_3. torch.nonzero4_4. torch.gather4_5. view5-线性函数6-Tensor与Numpy相互转换6_1. broadcasting6_2. 内存机制6_3. 自动梯度求导在深度学习中&#xff0c;我们通常会频繁地对…

佩戴舒适真无线蓝牙耳机怎么选?久戴不痛的蓝牙耳机推荐

本身佩戴蓝牙耳机听音乐是一件舒心&#xff0c;非常享受的事情&#xff0c;但是&#xff0c;因为每个人的耳道都不一样&#xff0c;所以有很多人因为佩戴不舒适而选择放弃蓝牙耳机。今天&#xff0c;小编特意给大家搜集了4款公认佩戴最舒适的蓝牙耳机&#xff0c;让佩戴不再成为…

实验6 图像压缩

本次实验大部分素材来源于山大王成优老师的讲义以及冈萨雷斯&#xff08;MATLAB版&#xff09;&#xff0c;仅作个人学习笔记使用&#xff0c;禁止用作商业目的。 文章目录一、实验目的二、实验例题1. 二维离散余弦变换(Discrete Cosine Transform, DCT)的基图像2. JPEG 压缩2.…

扬帆优配|太猛了!最高暴拉170%,港股这一板块狂飙!“带货”起飞?

A股商场和港股商场上午均跌落&#xff0c;不过到上午收盘&#xff0c;A股商场上涨个股数量多于跌落个股。 港股方面&#xff0c;首要指数跌幅较大&#xff0c;但影视传媒股鹤立鸡群&#xff0c;团体大涨。其间&#xff0c;为内地观众所熟知的TVB母公司今天上午股价再度暴升&…

如何搞定MySQL锁(全局锁、表级锁、行级锁)?这篇文章告诉你答案!太TMD详细了!!!

概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&…

文件如何批量复制保存在多个文件夹中

在日常工作中经常需要整理文件&#xff0c;比如像文件或文件夹重命名或文件批量归类&#xff0c;文件批量复制到指定某个或多个文件来中保存备份起来。一般都家最常用方便是手动一个一个去重命名或复制到粘贴到某个文件夹中保存&#xff0c;有没有简单好用的办法呢&#xff0c;…

第九届省赛——8等腰三角形(找规律)

题目&#xff1a;本题目要求你在控制台输出一个由数字组成的等腰三角形。具体的步骤是&#xff1a;1. 先用1,2,3&#xff0c;...的自然数拼一个足够长的串2. 用这个串填充三角形的三条边。从上方顶点开始&#xff0c;逆时针填充。比如&#xff0c;当三角形高度是8时&#xff1a…

【卷积神经网络】激活函数 | Tanh / Sigmoid / ReLU / Leaky ReLU / ELU / SiLU / GeLU

文章目录一、Tanh二、Sigmoid三、ReLU四、Leaky ReLU五、ELU六、SiLU七、Mish本文主要介绍卷积神经网络中常用的激活函数及其各自的优缺点 最简单的激活函数被称为线性激活&#xff0c;其中没有应用任何转换。 一个仅由线性激活函数组成的网络很容易训练&#xff0c;但不能学习…

蓝桥杯C/C++b组第一题个人整理合集(5年真题+模拟题)

蓝桥杯C/Cb组填空第一题合集 前言 比赛标准的签到题&#xff0c;比赛时的第一题。不会考到什么算法&#xff0c;甚至都不需要你打代码。但有时候第一题都没做出来的确是非常挫灭信心 看了看历年题目。很多小陷阱也不少 今年的比赛也正好还有一个月&#xff0c;自己对填空题第…

XCPC第九站———背包问题!

1.01背包问题 我们首先定义一个二维数组f&#xff0c;其中f[i][j]表示在前i个物品中取且总体积不超过j的取法中的最大价值。那么我们如何得到f[i][j]呢&#xff1f;我们运用递推的思想。由于第i个物品只有选和不选两种情况&#xff0c;当不选第i个物品时&#xff0c;f[i][j]f[i…

云计算生态该怎么做?阿里云计算巢打了个样

2023 年 2 月 23 日至 24 日&#xff0c;由阿里云主办的「阿里云计算巢加速器」于杭州阿里云谷园区集结。 阿里云计算巢加速器于 2022 年 8 月正式启动招募&#xff0c;最终百奥利盟、极智嘉、EMQ、KodeRover、MemVerge 等 30 家创新企业入选计算加速器&#xff0c;覆盖了人工智…

16N60-ASEMI高压MOS管16N60

编辑-Z 16N60在TO-220封装里的静态漏极源导通电阻&#xff08;RDS(ON)&#xff09;为0.2Ω&#xff0c;是一款N沟道高压MOS管。16N60的最大脉冲正向电流ISM为48A&#xff0c;零栅极电压漏极电流(IDSS)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。16N60功耗&#xf…

Blazor_WASM之4:路由

Blazor_WASM之4&#xff1a;路由 路由模板 通过 Router组件可在 Blazor 应用中路由到 Razor 组件。 Router 组件在 Blazor 应用的 App 组件中使用。App组件模板如下 <Router AppAssembly"typeof(Program).Assembly"><Found Context"routeData"…

致敬我的C++启蒙老师,跟着他学计算机编程就对了 (文末赠书5本)

致敬我的C启蒙老师&#xff0c;跟着他学计算机编程就对了 摘要 讲述了一个故事&#xff0c;介绍了一位良师&#xff0c;一段因C而续写的回忆&#xff0c;希望对各位看官有所帮助和启发。 文章目录1 写在前面2 我的C启蒙老师3 谈谈老师给我的启发4 友情推荐5 文末福利1 写在前面…

Python 模块之 CSV 读取

1、CSV 文件存储 1.1 写入 简单示例 import csvwith open(data.csv, a) as csvfile:writer csv.writer(csvfile) # 初始化写入对象&#xff0c;传入文件句柄writer.writerow([id, name, age]) # 调用 writerow() 方法传入每行的数据writer.writerow([1, rose, 1…

python安装好了某个包但是仍报错ImportError: No module named xxx的解决方法及思路

这是一个应该比较常见的问题&#xff0c;下面首先讲一下该类问题的一般解决 思路&#xff0c;然后再进行一个自我debug的过程描述。 1 解决思路 出现该问题的根本原因是&#xff0c;当前使用的python环境&#xff0c;和已经安装了包的python环境不是同一个。解决问题一般可以根…

数组、指针总结【面试题】

文章目录0. 补充知识数组笔试题1. 一维数组1.1 字符数组1.1.1 sizeof1.1.2 strlen1.2 二维数组2. 指针笔试题0. 补充知识 在进入数组与指针的练习时&#xff0c;我们先来复习以下以下的知识点&#xff0c;这可以帮助我们更好的理解下面练习 数组是一组能存放相同类型的类型的元…

数据库的查询

数据库的查询 一、知识要点&#xff1a; 1.SELECT语句的一般格式 SELECT [ ALL|DISTINCT ] [TOP N [PERCENT] ] <目标列表达式> [别名] [&#xff0c;<目标列表达式> [别名] ]… FROM <表名或视图名> [ 别名 ][&#xff0c;<表名或视图名> [ 别名 ] ]…

JavaScript DOM API的使用

文章目录一. 什么是DOM二. 最常用的DOM API1. 选中页面元素2. 操作元素的属性2.1 事件概念2.2 获取/修改元素内容计数器2.4 获取/修改元素属性点击图片切换2.5 获取/修改表单元素属性表单计数器全选/取消全选按钮2.6 获取修改样式属性点击文字放大实现夜间/日间模式的切换3. 操…

【C++】反向迭代器

文章目录一、什么是反向迭代器二、STL 源码中反向迭代器的实现三、reverse_iterator 的模拟实现四、vector 和 list 反向迭代器的实现一、什么是反向迭代器 C 中一共有四种迭代器 – iterator、const_iterator、reverse_iterator 以及 const_reverse_iterator&#xff0c;其中…