如何在不失去理智的情况下调试 TensorFlow 训练程序

一、说明
关于tensorflow的调试,是一个难啃的骨头,除了要有耐力,还需要方法;本文假设您是一个很有耐力的开发者,为您提供一些方法;这些方法也许不容易驾驭,但是依然强调您只要有耐力,没有不能攻克的难题。
二、关于tensorflow调试的说法
If debugging is the process of removing software bugs, then programming must be the process of putting them in.
Edsger Dijkstra. From https://www.azquotes.com/quote/561997
在我之前的一些帖子(这里,这里和这里)中,我告诉了你一些关于我在Mobileye的团队(正式名称为Mobileye,英特尔公司)如何使用TensorFlow,Amazon SageMaker和Amazon s3来训练我们的深度神经网络在大量数据上。在这篇文章中,我想谈谈TensorFlow中的调试。
众所周知,程序调试是软件开发的一个组成部分,并且花费在调试上的时间通常会超过编写原始程序所需的时间。
调试是困难的,关于如何设计和实现自己的程序以增加错误的可重复性并简化根本原因分析的过程已经写了很多。
在机器学习中,由于机器学习算法固有的随机性,以及算法通常在远程计算机上的专用硬件加速器上运行这一事实,调试任务变得复杂。
由于使用了符号执行(又名图模式),TensorFlow 中的调试变得更加复杂,这提高了训练会话的运行时性能,但同时限制了自由读取图中任意张量的能力,而这种能力对于调试很重要。
在这篇文章中,我将扩展调试TensorFlow训练程序的困难,并提供一些如何解决这些困难的建议。
出于法律目的,我想澄清一下,尽管我精心挑选了副标题,但我不保证我在这里写的任何内容都会阻止你气馁。相反,我认为我几乎可以保证,尽管我写了什么,但在调试TensorFlow程序时,你可能会气馁而失去理智。
在开始之前,让我们澄清一下讨论的范围。
三、调试的类型
在这篇文章的上下文中,调试是指识别代码或数据中的错误,导致培训课程突然崩溃的艺术。
另一种调试,超出了本文的范围,指的是修复或调整未收敛的模型的任务,或者对某一类输入产生不令人满意的预测(例如,无法识别粉红色汽车的车辆检测模型)。此过程可能涉及定义和评估模型指标、收集和统计分析模型工件(例如梯度、激活和权重)、使用 TensorBoard 和 Amazon Sagemaker 调试器等工具、超参数调优、重新架构或使用增强和提升等技术修改数据输入。调整模型可能是一项极具挑战性、耗时且经常令人沮丧的任务。
3.1 错误的类型
在解决代码或数据中的错误领域,我喜欢区分两类错误:错误和怪物错误。
通过错误,我指的是相对容易重现的问题。错误的示例包括假设输入张量的大小与训练数据不匹配的模型,尝试连接不匹配的张量,或对无效数据类型执行 tf 操作。这些通常不依赖于特定的模型状态和数据,并且通常相对容易重现。它们不一定容易修复,但与怪物虫子相比,它们是儿戏。
怪物虫是偶尔发生和不可预测的虫子。仅在模型的特定状态、特定数据样本或模型状态和数据输入的特定组合上重现的 bug 可能会带来严重的挑战,并可能构成怪物 bug。
下面是一个基于真实事件的场景示例,它肯定会增加您的血压:
现在是星期五下午,您的模型已经成功训练了几天。损失似乎正在很好地收敛,你开始想象一个放松的,发布后的周末假期,在你选择的度假地点。你回头看了一眼屏幕,注意到,突然之间,没有任何警告,你的损失变成了NaN。“当然”,你心想,“这一定是由于一些完全随机的、瞬间的、宏观的故障”,然后你立即从上一个有效的模型检查点恢复训练。又过了几个小时,它又发生了,然后又发生了。现在你开始恐慌,周末天堂的梦幻画面现在被需要解决怪物虫子的诱人努力的想法所取代。
我们一会儿再来看这个令人悲伤的例子。但首先,让我们勾选一些强制性的“调试”复选框。
四、在 Tensorflow 中调试的提示
很多笔墨已经洒在调试的艺术上,更重要的是,开发可调试代码的艺术。在本节中,我将提到一些技术,因为它们与TensorFlow应用程序有关。这份清单绝不是全面的。
4.1 保存模型检查点
这可能是我将在这篇文章中写的最重要的事情。始终配置训练会话,使其定期保存模型的快照。
编程错误并不是您的培训可能崩溃的唯一原因......如果您在云中运行,则可能会遇到 Spot 实例终止或遇到内部服务器错误。如果在本地运行,则可能会断电,或者 GPU 可能会爆炸。如果您已经训练了几天,没有存储中间检查点,那么损害可能是极端的。如果您每小时保存一个检查点,那么您最多只丢失一个小时。TensorFlow提供了用于存储检查点的实用程序,例如keras模型检查点回调。您需要做的就是通过权衡存储检查点的开销与培训课程中计划外故障的成本来决定捕获此类快照的频率。
4.2 接触者追踪
我向我的 Covid19 同时代人道歉,因为我为这个小节选择了标题,我只是无法抗拒。通过接触者追踪,我指的是跟踪输入到训练管道中的训练数据的能力。
假设您的训练数据在 tfrecord 文件中分为 100,000 个,并且其中一个文件存在导致程序崩溃或停止的格式错误。缩小对有问题文件的搜索范围的一种方法是记录输入到管道中的每个文件。一旦你遇到崩溃,你可以回顾你的日志,看看最近输入的文件是什么。正如我在之前的文章中提到的,我们使用 Amazon SageMaker 管道模式功能进行训练。管道模式最近新增的管道模式服务器端日志记录了输入到管道中的文件。
记录进入管道的数据可以帮助一个人重现错误的能力,这将我们带到下一点。
4.3 错误再现
重现错误的难易程度直接影响解决错误的难易程度。我们总是希望编写代码以确保可重复性。这在TensorFlow程序中并不容易。机器学习应用通常包括对随机变量使用的依赖。我们随机初始化模型权重,随机增加数据,随机分片数据以进行分布式训练,随机应用 dropout,在每个 epoch 之前对输入数据进行洗牌,然后在创建批处理之前再次对其进行洗牌(使用 tf.dataset.shuffle)。我们可以用我们记录的伪随机种子来播种所有的伪随机操作,但请记住,可能有许多不同的地方引入了随机化,跟踪所有这些很容易成为记账的噩梦。我无法告诉你有多少次我以为我已经删除了随机化的所有元素,却发现我错过了一个。此外,还有一些随机过程无法设定种子。如果您使用多个进程来导入训练数据,则可能无法控制数据记录的实际馈送顺序(例如,如果在 tf.data.Options() 中将experimental_deterministic设置为 false)。当然,您可以在每个样本进入管道时记录它们,但这会带来陡峭且可能令人望而却步的开销。
最重要的是,虽然构建可重复的训练程序绝对是可能的,但我认为更明智的做法是接受非确定性,接受训练的不可重复性,并找到克服这种调试限制的方法。
4.4 模块化编程
创建可调试程序的一个关键技术是以模块化方式构建应用程序。应用于 TensorFlow 训练循环,这意味着能够分别测试训练管道的不同子集,例如数据集、损失函数、不同的模型层和回调。这并不总是那么容易做到,因为一些训练模块(如损失函数)非常依赖于其他模块。但是有很大的创造力空间。例如,只需迭代数据集,同时应用数据集操作的子集,就可以在输入管道上测试不同的函数。可以通过创建仅运行损失函数或回调的应用程序来测试损失函数或回调。人们可以通过用虚拟损失函数代替损失函数来中和损失函数。我喜欢用多个输出点构建我的模型,即能够轻松修改模型中的层数,以测试不同层的影响。
在构建程序时,您对程序的模块化和可调试性投入的考虑越多,以后遭受的痛苦就越少。
4.5 急切的执行力
如果您是常规的 TensorFlow 用户,您可能遇到过“渴望执行模式”、“图形模式”和“tf 函数限定符”等术语。您可能听说过一些(有些误导性)语句,例如“在渴望执行模式下调试是小菜一碟”或“Tensorflow 2 在渴望执行模式下运行”。你可能像我一样,热切地投入到tensorflow源代码中,试图理解不同的执行模式,结果却在抽泣中崩溃了,你的自尊心终生破碎。为了全面了解它是如何工作的,我建议你阅读TensorFlow文档,并祝你好运。在这里,我们将只提及它与调试相关的要点。运行 TensorFlow 训练的最佳方法是在图形模式下运行它。图模式是一种符号执行模式,这意味着我们不能任意访问图张量。使用 tf.function 限定符包装的函数将在图形模式下运行。使用 tf.keras.model.fit 进行训练时,默认情况下,训练步骤在图形模式下执行。当然,无法访问任意图张量使得在图模式下进行调试变得困难。在预先执行模式下,您可以访问任意张量,甚至可以使用调试器进行调试(前提是您将断点放在 model.call() 函数中的适当位置)。当然,当您在渴望执行模式下运行时,您的训练会运行得更慢。若要对模型进行编程以在预先执行模式下进行训练,需要调用 model.compile() 函数,并将 run_eagerly 标志设置为 true。
底线是,当你在训练时,在图形模式下运行,当你在调试时,在预先执行模式下运行。不幸的是,某些错误仅在图形模式下而不是在急切执行模式下重现的情况并不少见,这真是令人沮丧。此外,在本地环境中调试时,预先执行很有帮助,而在云中则不那么有用。它在调试怪物错误时通常不是很有用......除非您首先找到一种在本地环境中重现该错误的方法(下面将对此进行详细介绍)。
4.6 TensorFlow Logging和Debugging Utility
尝试充分利用TensorFlow记录器。调试问题时,请将记录器设置为信息量最大的级别。
tf.debugging 模块提供了一堆断言实用程序以及数字检查函数。特别是,tf.debugging.enable_check_numerics实用程序有助于查明有问题的函数。
tf.print 函数可以打印出任意图形张量,这是一个额外的实用程序,我发现它对调试非常有用。
最后但并非最不重要的一点是,添加您自己的打印日志(在代码的非图形部分中),以便更好地了解程序故障的位置。
4.7 解密 TensorFlow 错误消息
有时,你会很幸运地收到TensorFlow错误消息。不幸的是,并不总是立即清楚如何使用它们。我经常收到同事的电子邮件,上面有神秘的TensorFlow消息,乞求帮助。当我看到消息时,例如:
tensorflow.python.framework.errors_impl.InvalidArgumentError: ConcatOp : Dimensions of inputs should match: shape[0] = [5,229376] vs. shape[2] = [3,1]
或
node DatasetToGraphV2 (defined at main.py:152) (1) Failed precondition: Failed to serialize the input pipeline graph: Conversion to GraphDef is not supported.
或
alueError: slice index -1 of dimension 0 out of bounds. for 'loss/strided_slice' (op: 'StridedSlice') with input shapes: [0], [1], [1], [1] and with computed input tensors: input[1] = <-1>, input[2] = <0>, input[3] = <1>.
我问自己(稍微修改一下,使帖子对孩子友好)“我应该用它发出什么嘟嘟声?”或者“为什么友好的TensorFlow工程师不能给我更多的东西来工作?”。但我很快就让自己平静下来(有时在酒精饮料的帮助下),并说:“Chaim,别再被宠坏了。回去工作,感谢你得到了任何信息。您应该做的第一件事是尝试在预先执行模式下和/或使用调试器重现错误。不幸的是,如上所述,这并不总是有帮助。
毫无疑问,上述消息不是很有帮助。但不要绝望。有时,在一些调查工作的帮助下,你会发现可能引导你走向正确方向的线索。浏览调用堆栈以查看它是否提供了任何提示。如果消息包含形状大小,请尝试将这些大小与图形中可能具有相同形状的张量进行匹配。当然,还可以在线搜索,看看其他人是否遇到过类似的问题以及在什么情况下遇到过。不要绝望。
4.8 在本地环境中运行
当然,在本地环境中调试比在远程计算机或云中调试更容易。首次创建模型时尤其如此。在开始远程培训之前,您的目标应该是在当地环境中解决尽可能多的问题。否则,您最终可能会浪费大量时间和金钱。
为了提高可重现性,应尝试使本地环境尽可能与远程环境相似。如果您在远程环境中使用 docker 映像或虚拟环境,请尝试在本地使用相同的映像或虚拟环境。(如果您的远程培训在 Amazon SageMaker 上进行,则可以拉取所使用的 docker 映像。
当然,远程培训环境中的某些元素可能无法在本地复制。例如,您可能遇到了仅在使用 Amazon SageMaker 管道模式时重现的错误,该模式目前仅在云中运行时受支持。(在这种情况下,您可以考虑从 s3 访问数据的替代方法。
我希望我能告诉你,这里描述的技术将解决你所有的问题。但可惜,事实并非如此。在下一节中,我们将回到上面演示的怪物错误场景,并介绍最后一种调试技术。
五、使用 TensorFlow 自定义训练循环进行调试
在我们上面描述的场景中,经过几天的训练,模型的特定状态和特定的训练批处理样本的组合突然导致损失变成 NaN。
让我们评估如何使用上面的调试技术来调试此问题。
- 如果我们仔细跟踪用于所有随机操作的种子,并且没有不受控制的非确定性事件,理论上我们可以通过从头开始训练来重现错误......但这需要几天时间。
- 在本地环境或预先执行模式下复制可能需要数周时间。
- 我们可以从最近的检查点恢复,但是如果我们可以从完全相同的样本恢复并且所有伪随机生成器的状态完全相同,我们只能重现相同的模型状态和批处理样本。
- 添加 tf.prints 会有所帮助,但会带来巨大的开销
- 添加tf.debugging.enable_check_numerics对于查明失败的功能将非常有帮助。如果函数中存在明显的错误,这可能就足够了。但它不能让我们重现该错误。
理想情况下,我们将能够在损失消失之前捕获输入和模型状态。然后,我们可以在受控(本地)环境中,以预先执行模式并使用调试器重现问题。
问题是,我们不知道这个问题即将发生,直到它真正发生。当损失报告为 NaN 时,模型已使用 NaN 权重进行了更新,并且导致错误的批处理样本已经迭代。
我想提出的解决方案是自定义训练循环,以便我们在每一步记录当前样本,并且仅在梯度有效时才更新模型权重。如果梯度无效,我们将停止训练并转储最后一个批次样本以及当前模型快照。这可以转移到本地环境,在那里加载模型,并在预先执行模式下输入捕获的数据样本,以便重现(和解决)错误。
我们稍后将介绍代码,但首先,简要介绍使用自定义训练循环的利弊。
5.1 自定义训练循环与高级 API
TensorFlow用户之间关于是编写自定义训练循环还是依赖高级API(如tf.keras.model.fit())存在一个由来已久的争议。
自定义培训循环的支持者预示着能够逐行控制培训的执行方式,以及发挥创造力的自由。高级 API 的支持者指出了它提供的许多便利,最值得注意的是内置的回调实用程序和分布式策略支持。还假定使用高级 API 可以确保您使用的是无错误且高度优化的训练循环实现。
从 2.2 版本开始,TensorFlow 引入了覆盖 tf.keras.model 类的 train_step 和make_train_function例程的功能。这使人们能够引入一定程度的自定义,同时继续享受 model.fit() 的便利。我们将演示如何以这样一种方式覆盖这些函数,使我们能够捕获有问题的示例输入和模型状态以进行本地调试。
5.2 自定义捕获循环
在下面的代码块中,我们使用train_step和make_train_functions例程的自定义实现扩展了 tf.keras.models.Model 对象。为了全面了解实现,我建议您将其与 github 中例程的默认实现进行比较。您会注意到,我已经删除了与指标计算和策略支持相关的所有逻辑,以使代码更具可读性。需要注意的主要变化是:
- 在将梯度应用于模型权重之前,我们测试 NaN 的梯度。仅当 NaN 未出现时,渐变才会应用于权重。否则,将向训练循环发送遇到错误的信号。信号的一个例子是将损耗设置为预定值,例如零或NaN。
- 训练循环在每个步骤中存储数据特征和标签(x 和 y)。请注意,为了做到这一点,我们已将数据集遍历(下一个(迭代器)调用)移到了 @tf.function 范围之外。
- 该类有一个布尔“crash”标志,用于向 main 函数发出是否遇到错误的信号。
class CustomKerasModel(tf.keras.models.Model):def __init__(self, **kwargs):super(CustomKerasModel, self).__init__(**kwargs)# boolean flag that will signal to main function that # an error was encounteredself.crash = False@tf.functiondef train_step(self, data):x, y = datawith tf.GradientTape() as tape:y_pred = self(x, training=True) # Forward pass# Compute the loss value# (the loss function is configured in `compile()`)loss = self.compiled_loss(y, y_pred, regularization_losses=self.losses)# Compute gradientstrainable_vars = self.trainable_variablesgradients = tape.gradient(loss, trainable_vars)# concatenate the gradients into a single tensor for testingconcat_grads = tf.concat([tf.reshape(g,[-1]) for g in gradients],0)# In this example, we test for NaNs, # but we can include other testsif tf.reduce_any(tf.math.is_nan(concat_grads)):# if any of the gradients are NaN, send a signal to the # outer loop and halt the training. We choose to signal# to the outer loop by setting the loss to 0.return {'loss': 0.}else:# Update weightsself.optimizer.apply_gradients(zip(gradients, trainable_vars))return {'loss': loss}def make_train_function(self):if self.train_function is not None:return self.train_functiondef train_function(iterator):data = next(iterator)# records the current sampleself.x, self.y = datares = self.train_step(data)if res['loss'] == 0.:self.crash = Trueraise Exception()return resself.train_function = train_functionreturn self.train_functionif __name__ == '__main__':# train_ds = # inputs = # outputs =# optimizer =# loss = # epochs =# steps_per_epoch = model = CustomKerasModel(inputs=inputs, outputs=outputs)opt = tf.keras.optimizers.Adadelta(1.0)model.compile(loss=loss, optimizer=optimizer)try:model.fit(train_ds, epochs=epochs, steps_per_epoch=steps_per_epoch)except Exception as e:# check for signalif model.crash:model.save_weights('model_weights.ckpt')# pickle dump model.x and model.yfeatures_dict = {}for n, v in model.x.items():features_dict[n] = v.numpy()with open('features.pkl','wb') as f:pickle.dump(features_dict,f)labels_dict = {}for n, v in model.y.items():labels_dict[n] = v.numpy()with open('labels.pkl', 'wb') as f:pickle.dump(labels_dict, f)raise e
请务必注意,此技术的训练运行时成本很小,它来自在预先执行模式(而不是图形模式)下从数据集读取数据。(没有免费的午餐。确切的成本将取决于模型的大小;模型越大,这种变化就越少。您应该在自己的模型上评估此技术的开销,然后决定是否以及如何使用它。
六、总结
只要我们人类参与人工智能应用程序的开发,编程错误的普遍性就几乎是有保证的。在设计代码时考虑到可调试性,并获得解决错误的工具和技术,可以防止一些严重的折磨。
最重要的是,不要气馁。
相关文章:
如何在不失去理智的情况下调试 TensorFlow 训练程序
一、说明 关于tensorflow的调试,是一个难啃的骨头,除了要有耐力,还需要方法;本文假设您是一个很有耐力的开发者,为您提供一些方法;这些方法也许不容易驾驭,但是依然强调您只要有耐力,…...
24. 图论 - 图的表示种类
Hi,你好。我是茶桁。 之前的一节课中,我们了解了图的来由和构成,简单的理解了一下图的一些相关概念。那么这节课,我们要了解一下图的表示,种类。相应的,我们中间需要穿插一些新的知识点用于更好的去理解图…...
C++ 读bin文件,部分代码。赚经验。
编号:1 Head: magicWord[0] 0x0102 magicWord[1] 0x0304 magicWord[2] 0x0506 magicWord[3] 0x0708 version 0x02010004 totalPacketLen 288 platform 0x000a1443 frameNumber 12 timeCpuCycles 172969774 numDetectedObj 99 numTLVs 2 subFrameNumber 0 TLV…...
vue3 父子组件传值
一,子传父 父组件 <script setup> import HelloWorld from ./components/HelloWorld.vue import { ref } from vue//直接赋值页面不会自动渲染,使用ref存储响应式数据 import { defineExpose } from "vue";父传子 let val ref(); con…...
【看懂MPLS LSP表项】
IP网络 R1根据路由表项去查FIB表 目的网络、出口、下一跳 MPLS网络 R1根据LFIB表现去查表, 路由,出口、(标签) 要实现MPLS网络全局可达性,R1应具有到每一个LSR、LSE的路由。 1、R1去FEC(转发等价类) /去往2.2.2.2的路由《路由方…...
代码随想录训练营 单调栈
代码随想录训练营 单调栈 84. 柱状图中最大的矩形🌸 最后一天~ 84. 柱状图中最大的矩形🌸 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最…...
Android MQTT
MQTT Android MQTT连接,重新编译Service-1.1.1兼容Android高版本服务 Paho Android Service-1.1.1 Paho Client Mqtt3-1.1.0 资源 名字资源AAR下载GitHub查看Gitee查看 Maven 1.build.grade allprojects {repositories {...maven { url https://jitpack.io }} }2./app/bu…...
Codeforces Round 823 (Div. 2)C
更好的阅读体验 C. Minimum Notation 思路:我们可以进行的操作时将一个位置的数删除然后在任意位置处添加一个比当前数大1并且小于9的数,所以我们的操作只会让一个数变大,我们统计一个最大值的后缀,贪心的考虑如果当前数的后面有…...
npm发布vue3自定义组件库--方法一
npm发布vue3自定义组件库 创建项目 vue create test-ui自定义组件 创建自定义组件,组件名称根据你的需求来,最好一个组件一个文件夹,下图是我的示例。 src/components 组件和你写页面一样,所谓组件就是方便实用,不…...
Centos7原生hadoop环境,搭建Impala集群和负载均衡配置
Centos7原生hadoop环境,搭建Impala集群和负载均衡配置 impala介绍 Impala集群包含一个Catalog Server (Catalogd)、一个Statestore Server (Statestored) 和若干个Impala Daemon (Impalad)。Catalogd主要负责元数据的获取和DDL的执行,Statestored主要负…...
如何在macOS上安装Go并搭建本地编程环境
引言 Go是一种诞生于挫折中的编程语言。在谷歌,开发人员厌倦了在为新项目选择语言时必须做出权衡。有些语言执行效率很高,但需要很长时间编译,而另一些语言易于编写,但在生产环境中运行效率很低。因此,谷歌发明了Go语…...
postgresql-存储过程
postgresql-存储过程 简述PL/pgSQL 代码块结构示例嵌套子块 声明与赋值控制结构IF 语句CASE 语句简单case语句搜索 CASE 语句 循环语句continuewhilefor语句遍历查询结果 foreach 游标游标传参 错误处理报告错误和信息检查断言 捕获异常自定义函数重载VARIADIC 存储过程示例事务…...
改造user ,使得userId相同视为一个对象,user是Key,User的username做value
如果您想要将具有相同userId的用户视为一个对象,其中User对象是键,而User对象的username是值,您可以使用Java的Map<User, String>数据结构来实现。以下是示例代码: java import java.util.*;class User {private int userI…...
力扣刷题-数组-滑动窗口法相关题目总结
209. 长度最小的子数组(最小滑窗) 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 示例: 输入&…...
Qt创建线程(线程池)
1.线程池可以创建线程统一的管理线程(统一创建、释放线程) 2.使用线程池方法实现点击开始按钮生成10000个随机数,然后分别使用冒泡排序和快速排序排序这10000个随机数,最后在窗口显示排序后的数字: mainwindow.h文件…...
【Java】泛型 之 使用泛型
使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是 Object: // 编译器警告: List list new ArrayList(); list.add("Hello"); list.add("World"); String first (String) list.get(0); String second (Strin…...
消费者NPS调查,帮您了解客户满意度!
随着市场竞争的日益激烈,了解消费者需求和对企业品牌的认知程度,对于企业的持续发展至关重要。您的客户对您的产品或服务有多满意?您是否想提升客户忠诚度,从而增加业务的持续增长?群狼调研(长沙产品包装测试)为您提供全新的消费者NPS调查服…...
Webpack监视文件修改,自动重新打包文件
方法一:使用watch监视文件变化 在终端中输入以下指令: npx webpack --watch 我们使用这种方法监听文件变化时只会监听我们计算机本地的文件变化,在开发场景中我们的项目是要部署到服务器中的,因此这种方式并不推荐。 方法二&…...
list容器排序案例
案例描述:将Perspn自定义数据类型进行排序,Person中属性有姓名、年龄、身高 排序规则:按照年龄进行升序,如果年龄相同按照身高进行降序 代码示例 #include <iostream> #include <string.h> #include <iterator> #include <vector…...
PHP使用Analysis中英文分词
1、下载Analysis,创建test.php测试 2、引入Analysis实现中文分词 <?php include "./Analysis/Analysis.php";$annew \WordAnalysis\Analysis(); $content"机器学习是一门重要的技术,可以用于数据分析和模式识别。"; //10分词数…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...
