如何才能信任你的深度学习代码?
深度学习是一门很难评估代码正确性的学科。随机初始化、庞大的数据集和权重的有限可解释性意味着,要找到模型为什么不能训练的确切问题,大多数时候都需要反复试验。在传统的软件开发中,自动化单元测试是确定代码是否完成预期任务的面包和黄油。它帮助开发人员信任他们的代码,并在引入更改时更加自信。一个破坏性的更改将会被单元测试检测到。
从GitHub上许多研究库的情况来看,深度学习的实践者们还不喜欢这种方法。从业者不知道他们的代码是否正常工作,他们能接受吗?通常,由于上述三个原因,学习系统的每个组件的预期行为并不容易定义。然而,我相信实践者和研究人员应该重新考虑他们对单元测试的厌恶,因为它可以帮助研究过程更加顺利。你只需要学习如何信任你的代码。
显然,我不是第一个,也不是最后一个谈论用于深度学习的单元测试的人。如果你对这个话题感兴趣,你可以看看这里:
- A Recipe for Training Neural Networks by Andrej Karpathy
- How to Unit Test Deep Learning by Sergios Karagiannakos
这篇文章的灵感来自于上面提到的,可能还有很多我现在想不起来的。为了在讨论中增加一些内容,我们将重点关注如何编写可重用的单元测试,这样就可以“不去自己重复自己“。
我们的例子将测试用PyTorch编写的系统的组件,该系统在MNIST 上训练可变自动编码器(VAE)。你可以在github.com/tilman151/unittest_dl上找到本文中的所有代码。
什么是单元测试?
如果您熟悉单元测试,可以跳过此部分。对于其他人,我们将看到Python中的单元测试是什么样子的。为了简单起见,我们将使用内置的包unittest,而不是其他花哨的包。
一般来说,单元测试的目的是检查代码是否正确地运行。通常(我也为此感到内疚很长一段时间),你会看到这样的东西在一个文件的结尾:
- if __name__ == 'main':
- net = Network()
- x = torch.randn(4, 1, 32, 32)
- y = net(x)
- print(y.shape)
如果直接执行该文件,则代码片段将构建一个网络,执行前向传递并打印输出的形状。这样,我们就可以看到向前传播是否会抛出错误,以及输出的形状是否可信。如果将代码分发到不同的文件中,则必须手动运行每个文件,并检查打印到控制台的内容。更糟糕的是,这个代码片段有时会在运行后被删除,当有变化时被重写。
原则上,这已经是一个基本的单元测试。我们所要做的就是将它形式化一点,使它能够轻松地自动运行。它看起来是这样的:
- import unittest
- class MyFirstTest(unittest.TestCase):
- def test_shape(self):
- net = Network()
- x = torch.randn(4, 1, 32, 32)
- y = net(x)
- self.assertEqual(torch.Size((10,)), y.shape)
unittest包的主要组件是类TestCase。单个单元测试是TestCase子类的成员函数。在我们的例子中,包将自动检测类MyFirstTest并运行函数'test_shape。如果满足assertEqual调用的条件,则测试成功。否则,或者如果它崩溃,测试将失败。
我需要测试些什么?
现在我们已经了解了单元测试是如何工作的,下一个问题是我们应该测试什么。下面你可以看到我们的例子的代码结构:
- |- src
- |- dataset.py
- |- model.py
- |- trainer.py
- |- run.py
我们将测试每个文件中的功能除了run.py,因为它只是我们程序的入口点。
Dataset
我们在例子中使用的数据集是torchvisionMNIST类。因此,我们可以假设像加载图像和训练/测试分割这样的基本功能可以正常工作。然而,MNIST类为配置提供了充足的机会,因此我们应该测试是否正确配置了所有内容。dataset.py文件包含一个名为MyMNIST的类,它有两个成员变量。成员train_data有torchvisionMNIST类的一个实例,该实例被配置为加载数据的训练部分,而test_data 中的实例加载测试部分。两种方法都将每幅图像每边填充2个像素,并将像素值归一化在[- 1,1]之间。此外,train_data 对每个图像应用随机旋转来增强数据。
数据的形状
为了继续使用上面的代码片段,我们将首先测试数据集是否输出了我们想要的形状。图像的填充意味着,它们现在的大小应该是32x32像素。我们的测试看起来是这样的:
- def test_shape(self):
- dataset = MyMNIST()
- sample, _ = dataset.train_data[0]
- self.assertEqual(torch.Shape((1, 32, 32)), sample.shape)
现在我们可以确定我们的padding是我们想要的。这可能看起来很琐碎,你们中的一些人可能会认为我在测试这个方面很迂腐,但是我不知道我有多少次因为我搞不清楚填充函数是如何工作的而导致了形状错误。像这样的简单测试编写起来很快,并且可以为你以后省去许多麻烦。
数据的缩放
我们配置的下一件事是数据的缩放。在我们的例子中,这非常简单。我们希望确保每个图像的像素值在[- 1,1]之间。与之前的测试相反,我们将对数据集中的所有图像进行测试。通过这种方式,我们可以确定我们关于如何缩放数据的假设对于整个数据集是有效的。
- def test_scaling(self):
- dataset = MyMNIST()
- for sample, _ in dataset.train_data:
- self.assertGreaterEqual(1, sample.max())
- self.assertLessEqual(-1, sample.min())
- self.assertTrue(torch.any(sample < 0))
- self.assertTrue(torch.any(sample > 0))
如你所见,我们不仅要测试每个图像的最大值和最小值是否在范围内。我们还通过断言测试是否存在大于零和小于零的值,我们将值缩放到[0,1]。这个测试之所以有效,是因为我们可以假设MNIST中的每个图像都覆盖了整个范围的值。对于更复杂的数据,比如自然图像,我们需要一个更复杂的测试条件。如果你的缩放基于数据的统计信息,那么测试一下是否只使用训练部分来计算这些统计信息也是一个好主意。
数据增强
增加训练数据可以极大地帮助提高模型的性能,特别是在数据量有限的情况下。另一方面,我们不会增加我们的测试数据,因为我们想要保持我们的模型的评估确定性。这意味着,我们应该测试我们的训练数据是否增加了,而我们的测试数据没有。敏锐的读者会在这一点上注意到一些重要的东西。到目前为止,我们的测试只涵盖了训练数据。这是需要强调的一点:
始终在训练和测试数据上运行测试
仅仅因为你的代码在数据的一个部分上工作,并不能保证在另一个部分上不存在未检测到的bug。对于数据增强,我们甚至希望为每个部分断言代码的不同行为。
对于我们的增强问题,一个简单的测试现在是加载一个样本两次,然后检查两个版本是否相等。简单的解决方案是为我们的每一个部分写一个测试函数:
- def test_augmentation_active_train_data(self):
- dataset = MyMNIST()
- are_same = []
- for i in range(len(dataset.train_data)):
- sample_1, _ = dataset.train_data[i]
- sample_2, _ = dataset.train_data[i]
- are_same.append(0 == torch.sum(sample_1 - sample_2))
- self.assertTrue(not all(are_same))
- def test_augmentation_inactive_test_data(self):
- dataset = MyMNIST()
- are_same = []
- for i in range(len(dataset.test_data)):
- sample_1, _ = dataset.test_data[i]
- sample_2, _ = dataset.test_data[i]
- are_same.append(0 == torch.sum(sample_1 - sample_2))
- self.assertTrue(all(are_same))
这些函数测试我们想要测试的内容,但是,正如你所看到的,它们几乎就是重复的。这有两个主要的缺点。首先,如果在测试中需要更改某些内容,我们必须记住在两个函数中都要更改。其次,如果我们想添加另一个部分,例如一个验证部分,我们将不得不第三次复制测试。要解决这个问题,我们应该将测试功能提取到一个单独的函数中,然后由真正的测试函数调用两次。重构后的测试看起来像这样:
- def test_augmentation(self):
- dataset = MyMNIST()
- self._check_augmentation(dataset.train_data, active=True)
- self._check_augmentation(dataset.test_data, active=False)
- def _check_augmentation(self, data, active):
- are_same = []
- for i in range(len(data)):
- sample_1, _ = data[i]
- sample_2, _ = data[i]
- are_same.append(0 == torch.sum(sample_1 - sample_2))
- if active:
- self.assertTrue(not all(are_same))
- else:
- self.assertTrue(all(are_same))
_check_augmentation函数断言给定的数据集是否进行了增强,并有效地删除代码中的重复。函数本身不会由unittest包自动运行,因为它不是以test_开头的。因为我们的测试函数现在真的很短,我们把它们合并成一个组合函数。它们测试了增强是如何工作的这一单一的概念,因此应该属于相同的测试函数。但是,通过这个组合,我们引入了另一个问题。如果测试失败了,现在很难直接看到哪一个部分失败了。这个包只告诉我们组合函数的名称。进入subTest函数。TestCase类有一个成员函数subTest,它可以在一个测试函数中标记不同的测试组件。这样,包就可以准确地告诉我们测试的哪一部分失败了。最后的函数是这样的:
- def test_augmentation(self):
- dataset = MyMNIST()
- with self.subTest(split='train'):
- self._check_augmentation(dataset.train_data, active=True)
- with self.subTest(split='test'):
- self._check_augmentation(dataset.test_data, active=False)
现在我们有了一个无重复、精确定位、可重用的测试功能。我们在此所使用的核心原则可以应用到我们在前面几节中编写的所有其他单元测试中。你可以在附带的存储库中看到结果测试。
数据的加载
数据集的最后一种类型的单元测试与我们的例子并不完全相关,因为我们使用的是内置数据集。无论如何我们都会把它包括进来,因为它涵盖了我们学习系统的一个重要部分。通常,你将在dataloader类中使用数据集,该类处理批处理并可以并行化加载。因此,测试你的数据集在单进程和多进程模式下是否与dataloader一起工作是一个好主意。考虑到我们所学到的增强测试,测试函数如下所示:
- def test_single_process_dataloader(self):
- dataset = MyMNIST()
- with self.subTest(split='train'):
- self._check_dataloader(dataset.train_data, num_workers=0)
- with self.subTest(split='test'):
- self._check_dataloader(dataset.test_data, num_workers=0)
- def test_multi_process_dataloader(self):
- dataset = MyMNIST()
- with self.subTest(split='train'):
- self._check_dataloader(dataset.train_data, num_workers=2)
- with self.subTest(split='test'):
- self._check_dataloader(dataset.test_data, num_workers=2)
- def _check_dataloader(self, data, num_workers):
- loader = DataLoader(data, batch_size=4, num_workers=num_workers)
- for _ in loader:
- pass
函数_check_dataloader不会对加载的数据进行任何测试。我们只是想检查加载过程是否没有抛出错误。理论上,ni 也可以检查诸如正确的批大小或填充的序列数据的不同长度。因为我们为dataloader使用了最基本的配置,所以可以省略这些检查。
同样,这个测试可能看起来琐碎而没有必要,但是让我给你一个例子,在这个简单的检查中节省了我的时间。这个项目需要从pandas的dataframes中加载序列数据,并从这些datafames上的滑动窗口中构造样本。我们的数据集太大了,无法装入内存,所以我们必须按需加载数据模型,并从中剪切出所请求的序列。为了提高加载速度,我们决定用一个LRU cache来缓存一些数据文件。它在我们早期的单进程实验中如预期的那样工作,因此我们决定将它包含在代码库中。结果是,这个缓存不能很好地用于多进程,但是我们的单元测试提前发现了这个问题。在使用多进程时,我们停用了缓存,避免了以后出现令人不快的意外。
最后要注意的
有些人可能已经在我们的单元测试中看到了另一个重复的模式。每个测试对训练数据运行一次,对测试数据运行一次,产生相同的四行代码:
- with self.subTest(split='train'):
- self._check_something(dataset.train_data)
- with self.subTest(split='test'):
- self._check_dataloader(dataset.test_data)
也完全有理由消除这种重复。不幸的是,这将涉及到创建一个高阶函数,以函数_check_something作为参数。有时,例如对于增强测试,我们还需要向_check_something函数传递额外的参数。最后,所需的编程构造将引入更多的复杂性,并模糊要测试的概念。一般的规则是,为了可读性和可重用性,让你的测试代码尽可能在需要的范围内变复杂。
Model
模型可以说是学习系统的核心组件,通常需要是完全可配置的。这意味着,还有很多东西需要测试。幸运的是,PyTorch中用于神经网络模型的API非常简洁,大多数实践者都非常严格地使用它。这使得为模型编写可重用的单元测试相当容易。
我们的模型是一个简单的VAE,由一个全连接的编码器和解码器组成。前向函数接受输入图像,对其进行编码,执行重新参数化操作,然后将隐编码解码为图像。虽然相对简单,但这种变换可以演示几个值得进行单元测试的方面。
模型的输出形状
我们在本文开头看到的第一段代码是几乎每个人都要做的测试。我们也已经知道这个测试是如何写成单元测试的。我们要做的唯一一件事就是添加要测试的正确形状。对于一个自动编码器,就简单的判断和输入的形状是否相同:
- @torch.nograd()
- def test_shape(self):
- net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
- inputs = torch.randn(4, 1, 32, 32)
- outputs = net(x)
- self.assertEqual(inputs.shape, outputs.shape)
同样,这很简单,但有助于找到一些最恼人的bug。例如,在将模型输出从拉平的表示中reshape时忘记添加通道维度。
我们最后增加的测试是torch.nograd 。它告诉PyTorch这个函数不需要记录梯度,并给我们一个小的加速。对于每个测试来说,它可能不是很多,但是你永远不知道需要编写多少。同样,这是另一个可引用的单元测试智慧:
让你的测试更快。否则,没有人会想要运行它们。
单元测试应该在开发期间非常频繁地运行。如果你的测试运行时间很长,那么你可以跳过它们。
模型的移动
在CPU上训练深度神经网络在大多数时候都非常慢。这就是为什么我们使用GPU来加速它。为此,我们所有的模型参数必须驻留在GPU上。因此,我们应该断言我们的模型可以在设备(CPU和多个GPU)之间正确地移动。
我们可以用一个常见的错误来说明我们的例子VAE中的问题。这里你可以看到bottleneck函数,执行重新参数化的技巧:
- def bottleneck(self, mu, log_sigma):
- noise = torch.randn(mu.shape)
- latent_code = log_sigma.exp() * noise + mu
- return latent_code
它取隐先验的参数,从标准高斯分布中采样一个噪声张量,并使用参数对其进行变换。这在CPU上运行没有问题,但当模型移动到GPU时失败。问题是噪音张量是在CPU内存中创建的,因为它是默认的,并没有移动到模型所在的设备上。一个简单的错误和一个简单的解决方案。我们用noise = torch.randn_like(mu)替换了这行有问题的代码。这就产生了一个与张量mu相同形状和在相同设备上的噪声张量。
帮助我们尽早捕获这些bug的测试:
- @torch.no_grad()
- @unittest.skipUnless(torch.cuda.is_available(), 'No GPU was detected')
- def test_device_moving(self):
- net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
- net_on_gpu = net.to('cuda:0')
- net_back_on_cpu = net_on_gpu.cpu()
- inputs = torch.randn(4, 1, 32, 32)
- torch.manual_seed(42)
- outputs_cpu = net(inputs)
- torch.manual_seed(42)
- outputs_gpu = net_on_gpu(inputs.to('cuda:0'))
- torch.manual_seed(42)
- outputs_back_on_cpu = net_back_on_cpu(inputs)
- self.assertAlmostEqual(0., torch.sum(outputs_cpu - outputs_gpu.cpu()))
- self.assertAlmostEqual(0., torch.sum(outputs_cpu - outputs_back_on_cpu))
我们把网络从一个CPU移动到另一个CPU,然后再移动回来,只是为了确保正确。现在我们有了网络的三份拷贝(移动网络复制了它们),并使用相同的输入张量向前传递。如果网络被正确移动,前向传递应该在不抛出错误的情况下运行,并且每次产生相同的输出。
为了运行这个测试,我们显然需要一个GPU,但也许我们想在笔记本电脑上做一些快速测试。如果PyTorch没有检测到GPU,unittest.skipUnless 可以跳过测试。这样可以避免将测试结果与失败的测试混淆。
你还可以看到,我们在每次通过之前固定了torch的随机种子。我们必须这样做,因为VAEs是非确定性的,否则我们会得到不同的结果。这说明了深度学习代码单元测试的另一个重要概念:
在测试中控制随机性。
如果你不能确保你的模型能到边界情况,你如何测试你的模型的一个罕见边界条件?如何确保模型的输出是确定性的?你如何知道一个失败的测试是由于随机的偶然还是由于你引入的bug ?通过手动设置深度学习框架的种子,可以消除函数中的随机性。此外,还应该将CuDNN设置为确定性模式。这主要影响卷积,但无论如何是一个好主意。
注意确定正在使用的所有框架的种子。Numpy和内置的Python随机数生成器有它们自己的种子,必须分别设置。有一个这样的函数是很有用的:
- def make_deterministic(seed=42):
- # PyTorch
- torch.manual_seed(seed)
- if torch.cuda.is_available():
- torch.backends.cudnn.deterministic = True
- torch.backends.cudnn.benchmark = False
- # Numpy
- np.random.seed(seed)
- # Built-in Python
- random.seed(seed)
模型到采样独立性
在99。99%的情况下,你都想用随机梯度下降的方式来训练你的模型。你给你的模型一个minibatch的样本,并计算他们的平均损失。批量处理训练样本假设你的模型可以处理每个样本,也就是你可以独立的把样本喂给模型。换句话说,你的batch中的样本在你的模型处理时不会相互影响。这个假设是很脆弱的,如果在一个错误的张量维度上进行错误的reshape或aggregation,就会打破这个假设。
下面的测试通过执行与输入相关的前向和后向传递来检查样本的独立性。在对这个batch做平均损失之前,我们把损失乘以零。如果我们的模型保持样本独立性,这将导致一个零梯度。唯一的事情,我们必须断言,如果只有masked的样本梯度是零:
- def test_batch_independence(self):
- inputs = torch.randn(4, 1, 32, 32)
- inputs.requires_grad = True
- net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
- # Compute forward pass in eval mode to deactivate batch norm
- net.eval()
- outputs = net(inputs)
- net.train()
- # Mask loss for certain samples in batch
- batch_size = inputs[0].shape[0]
- mask_idx = torch.randint(0, batch_size, ())
- mask = torch.ones_like(outputs)
- mask[mask_idx] = 0
- outputs = outputs * mask
- # Compute backward pass
- loss = outputs.mean()
- loss.backward()
- # Check if gradient exists and is zero for masked samples
- for i, grad in enumerate(inputs.grad):
- if i == mask_idx:
- self.assertTrue(torch.all(grad == 0).item())
- else:
- self.assertTrue(not torch.all(grad == 0))
如果你准确地阅读了代码片段,你会注意到我们将模型设置为evaluation模式。这是因为batch normalization违反了我们上面的假设。进程均值和标准差的处理交叉污染了我们batch中的样本,所以我们通过evaluation模式停止了对样本的更新。我们可以这样做,因为我们的模型在训练和评估模式中表现相同。如果你的模型不是这样的,你将不得不找到另一种方法来禁用它进行测试。一个选项是用instance normalization临时替换它。
上面的测试函数非常通用,可以按原样复制。例外情况是,如果你的模型接受多个输入。处理这个问题的附加代码是必要的。
模型的参数更新
下一个测试也与梯度有关。当你的网络架构变得更加复杂时,比如初始化,很容易构建死子图。死子图是网络中包含可学习参数的一部分,前向传递、后向传递或两者都不使用。这就像在构造函数中构建一个网络层,然后忘记在forward函数中应用它一样简单。
找到这些死子图可以通过运行优化步骤并检查梯度你的网络参数:
- def test_all_parameters_updated(self):
- net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
- optim = torch.optim.SGD(net.parameters(), lr=0.1)
- outputs = net(torch.randn(4, 1, 32, 32))
- loss = outputs.mean()
- loss.backward()
- optim.step()
- for param_name, param in self.net.named_parameters():
- if param.requires_grad:
- with self.subTest(name=param_name):
- self.assertIsNotNone(param.grad)
- self.assertNotEqual(0., torch.sum(param.grad ** 2))
参数函数返回的模型的所有参数在优化步骤后都应该有一个梯度张量。此外,对于我们所使用的损失,它不应该是零。测试假设模型中的所有参数都需要梯度。即使是那些不应该被更新的参数也会首先检查requires_grad标志。如果任何参数在测试中失败,子测试的名称将提示你在哪里查找。
提高重用性
现在我们已经写出了模型的所有测试,我们可以将它们作为一个整体进行分析。我们将注意到这些测试有两个共同点。所有测试都从创建模型和定义示例输入批处理开始。与以往一样,这种冗余级别有可能导致拼写错误和不一致。此外,你不希望在更改模型的构造函数时分别更新每个测试。
幸运的是,unittest为我们提供了一个简单的解决方案,即setUp函数。这个函数在执行TestCase中的每个测试函数之前被调用,通常为空。通过在setUp中将模型和输入定义为TestCase的成员变量,我们可以在一个地方初始化测试的组件。
- class TestVAE(unittest.TestCase):
- def setUp(self):
- self.net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
- self.test_input = torch.random(4, 1, 32, 32)
- ... # Test functions
现在我们用各自的成员变量替换出现的net和inputs,这样就完成了。如果你想更进一步,对所有测试使用相同的模型实例,您可以使用setUpClass。这个函数在构造TestCase时被调用一次。如果构建速度很慢,并且你不想多次进行构建,那么这是非常有用的。
在这一点上,我们有一个整洁的系统来测试我们的VAE模型。我们可以轻松地添加测试,并确保每次都测试模型的相同版本。但是如果你想引入一种新的卷积层,会发生什么呢?它将在相同的数据上运行,也应该具有相同的行为,因此将应用相同的测试。
仅仅复制整个TestCase 显然不是首选的解决方案,但是通过使用setUp,我们已经在正确的轨道上了。我们将所有测试函数转移到一个基类中,而将setUp保留为一个抽象函数。
- class AbstractTestVAE(unittest.TestCase):
- def setUp(self):
- raise NotImplementedError
- ... # Test functions
你的IDE会提示类没有成员变量net 和test_inputs,但是Python并不关心。只要子类添加了它们,它就可以工作。对于我们想要测试的每个模型,我们创建这个抽象类的一个子类,并在其中实现setUp。为多个模型或同一个模型的多个配置创建TestCases 就像:
- class TestCNNVAE(AbstractTestVAE):
- def setUp(self):
- self.test_inputs = torch.randn(4, 1, 32, 32)
- self.net = model.CNNVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
- class TestMLPVAE(AbstractTestVAE):
- def setUp(self):
- self.test_inputs = torch.randn(4, 1, 32, 32)
- self.net = model.MLPVAE(input_shape=(1, 32, 32), bottleneck_dim=16)
只剩下一个问题了。unittest包发现并运行unittest.TestCase的所有子元素。因为这包括不能实例化的抽象基类,所以我们总是会有一个失败的测试。
解决方案是由一个流行的设计模式提出的。通过删除TestCase作为AbstractTestVAE的父类,它就不再被发现了。相反,我们让我们的具体测试有两个父类, TestCase和AbstractTestVAE。抽象类和具体类之间的关系不再是父类和子类之间的关系。相反,具体类使用抽象类提供的共享功能。这个模式称为MixIn。
- class AbstractTestVAE:
- ...
- class TestCNNVAE(unittest.TestCase, AbstractTestVAE):
- ...
- class TestMLPVAE(unittest.TestCase, AbstractTestVAE):
- ...
父类的顺序很重要,因为方法查找是从左到右进行的。这意味着TestCase将覆盖AbstractTestVAE的共享方法。在我们的例子中,这不是一个问题,但无论如何知道都是好的。
Trainer
我们的学习系统的最后一部分是trainer类。它将你所有的组件(数据集、优化器和模型)放在一起,并使用它们来训练模型。此外,它还实现了一个评估函数,输出测试数据的平均损失。在训练时,所有的损失和指标都被写入一个TensorBoard event文件中以便可视化。
在这一部分中,编写可重用测试是最困难的,因为它允许最大程度的自由实现。有些人只在脚本文件中使用简单的代码进行训练,有些人将其封装在函数中,还有一些人试图保持更面向对象的风格。我不会判断你喜欢哪种方式。我唯一要说的是,在我的经验中,整洁封装的trainer类使单元测试变得最舒适。
然而,我们会发现我们之前学过的一些原则在这里也适用。
trainer的损失
大多数时候,你只需要从torch上选择一个预先实现的损失函数就可以了。但话说回来,你所选择的损失函数可能无法实现。这种情况可能是由于实现相对简单,函数太小众或者太新。无论如何,如果你自己实现了它,你也应该测试它。
我们的例子使用Kulback-Leibler (KL)散度作为整体损失函数的一部分,这在PyTorch中是不存在的(现在的版本里有了)。我们的实现是这样的:
- def _kl_divergence(log_sigma, mu):
- return 0.5 * torch.sum((2 * log_sigma).exp() + mu ** 2 - 1 - 2 * log_sigma)
函数取多变量高斯分布的标准偏差和平均值的对数,并计算在封闭形式中的标准高斯分布的KL散度。
检查这种损失的一种方法是手工计算,然后硬编码以便比较。更好的方法是在另一个包中找到一个参考实现,并根据它的输出检查代码。幸运的是,scipy包有一个离散KL散度的实现,我们可以使用:
- @torch.no_grad()
- def test_kl_divergence(self):
- mu = np.random.randn(10) * 0.25 # means around 0.
- sigma = np.random.randn(10) * 0.1 + 1. # stds around 1.
- standard_normal_samples = np.random.randn(100000, 10)
- transformed_normal_sample = standard_normal_samples * sigma + mu
- bins = 1000
- bin_range = [-2, 2]
- expected_kl_div = 0
- for i in range(10):
- standard_normal_dist, _ = np.histogram(standard_normal_samples[:, i], bins, bin_range)
- transformed_normal_dist, _ = np.histogram(transformed_normal_sample[:, i], bins, bin_range)
- expected_kl_div += scipy.stats.entropy(transformed_normal_dist, standard_normal_dist)
- actual_kl_div = self.vae_trainer._kl_divergence(torch.tensor(sigma).log(), torch.tensor(mu))
- self.assertAlmostEqual(expected_kl_div, actual_kl_div.numpy(), delta=0.05)
我们首先从标准高斯函数和一个不同均值和标准差的高斯函数中抽取一个足够大的样本。然后我们用np.histogram函数,得到基本pdf的离散逼近。有了这些,我们就可以用scipy.stats.entropy得到一个KL散度来比较。我们使用一个相对较大的delta来进行比较,因为scipy.stats.entropy只是一个近似值。
你可能已经注意到,我们没有创建Trainer对象,而是使用TestCase的成员。我们在这里使用了与模型测试相同的技巧,并在setUp函数中创建了它。我们还固定了PyTorch和NumPy的种子。因为我们这里不需要任何梯度,所以我们用@torch.no_grad来装饰函数。
trainer的日志记录
我们使用TensorBoard来记录我们的训练过程的损失和度量。为此,我们希望确保按预期写入所有日志。一种方法是在训练后打开event文件,查找正确的event。同样,这也是一个有效的选项,但我们将以另一种方式来看看unittest包的一个有趣功能:mock。
mock允许你用一个监视其自身是如何调用的函数来打包一个函数或对象。我们将替换summary writer的add_scalar 函数,并确保以这种方式记录我们关心的所有损失和指标。
- def test_logging(self):
- with mock.patch.object(self.vae_trainer.summary, 'add_scalar') as add_scalar_mock:
- self.vae_trainer.train(1)
- expected_calls = [mock.call('train/recon_loss', mock.ANY, 0),
- mock.call('train/kl_div_loss', mock.ANY, 0),
- mock.call('train/loss', mock.ANY, 0),
- mock.call('test/loss', mock.ANY, 0)]
- add_scalar_mock.assert_has_calls(expected_calls)
assert_has_calls 函数匹配预期调用列表和实际记录的调用。mock.ANY 表示我们不关心记录的标量的值,因为无论如何我们都不知道它。
因为我们不需要对整个数据集执行完一个epoch,所以我们在setUp 中将训练数据配置为只有一个batch。这样,我们可以显著地加快我们的测试速度。
trainer的拟合
最后一个问题也是最难回答的。我的训练最终会收敛吗?要确切地回答这个问题,我们需要用我们所有的数据进行一次全面的训练并对其打分。
由于这非常耗时,我们将使用一种更快的方法。我们将看看我们的训练是否能使模型对单个batch的数据进行过拟合。测试函数相当简单:
- def test_overfit_on_one_batch(self):
- self.vae_trainer.train(500)
- self.assertGreaterEqual(30, self.vae_trainer.eval())
如前一节所述,setUp函数创建一个只包含一个batch的数据集的trainer。此外,我们也使用训练数据作为测试数据。通过这种方式,我们可以从 eval函数中获得训练batch的损失,并将其与我们预期的损失进行比较。
对于一个分类问题,当我们完全过拟合时,我们期望损失为零。“VAE”的问题是,它是一个非确定性的生成模型,零损失是不现实的。这就是为什么我们预期的损失是30,这等于每像素的误差为0.04。
这是迄今为止运行时间最长的测试,它可以运行500 epochs。最后,在我的笔记本电脑上用1.5分钟左右就可以了,这仍然是合理的。为了在不降低对没有GPU的机器的支持的情况下进一步加速,我们可以简单地在setUp中添加这一行:
- device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
这样一来,如果我们有GPU,我们就可以利用它,如果没有,就利用CPU进行训练。
最后要注意的
在我们进行日志记录时,你可能会注意到,针对trainer的单元测试往往会使你的文件夹充满event文件。为了避免这种情况,我们使用tempfile 包为trainer创建一个临时日志目录。测试结束后,我们只需要再次删除它和它的内容。为此,我们使用了孪生函数setUp,和tearDown。在每个测试函数后调用此函数,清理过程简单如下:
- def tearDown(self):
- shutil.rmtree(self.log_dir)
总结
我们看完了这篇文章。让我们评估一下我们从整个磨难中得到了什么。
我们为我们的小例子编写的测试套件包含58个单元测试,整个运行大约需要3.5分钟。对于这58个测试,我们只编写了20个函数。所有测试都可以确定地、独立地运行。如果有GPU,我们可以运行额外的测试。大多数测试,例如数据集和模型测试,可以在其他项目中轻松重用。我们可以通过使用:
- 子测试为我们的数据集的多种配置运行一个测试
- setUp和tearDown函数一致地初始化和清理我们的测试
- 抽象测试类来测试VAE的不同实现
- torch.no_grad装饰器在可能的情况下禁用梯度计算
- mock模块检查函数是否被正确调用
最后,我希望我能够说服至少有人在他们的深度学习项目中使用单元测试。本文的配套git仓库可以作为起点。
【编辑推荐】
- 人工智能如何创建自动驾驶数据中心
- 人工智能:技术本无罪,善恶在人心
- 人工智能和基于机器学习软件解决方案如何改变零售业局面
- 人工智能市场收入今年将达到1560亿美元
- 6大人工智能应用关键技术,终于有人讲明白了
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 人工智能和机器学习技术推动企业发展
IT主管们已经开始收获人工智能和机器学习技术所带来的回报。最近的一项调查显示,随着经济遭遇重创,有一半的主管正在考虑加大投资能够带来收益的人工智能和机器学习技术。到目前为止,我们大多数人都知道,在当今时代,人工智能及其子领域机器学习技术与人类智能没什么关系。…...
2024/4/11 19:54:27 - 八十个3DMAX常见的问题大盘点,小白珍藏系列!让你遇见问题不犯愁
1. 为什么3dmax的页面上显现奇怪的字体,只有最上部菜单的字体显示是正常的?答:这是机器上Windows字体安装过多的缘故,尤其是安装了一些特别的中文字体等。为此可以将【HKEY_CURRENT_CONFIG/Display/Settings/】文件下的【fonts.fon】的值修改为【vgafix.fon】或者其他的【*…...
2024/4/27 19:19:12 - Java:双色球彩票训练
规则: “双色球” 每注投注号码由 6 个红色球号码和 1 个蓝色球号码组成。 红色球号码从 1—33 中选择, 蓝色球号码从 1—16 中选择。球的数字匹配数量和颜色决定了是否中奖。package day4case;import java.util.Random; import java.util.Scanner;public class ShaungSeQiu …...
2024/5/6 7:09:45 - 为什么不建议使用免费IP转换软件?
IP转换软件在我们网络生活中的用途数不胜数,我们使用IP转换软件的本身目的就是想要快速方便的访问某些国外网站、打游戏还有办公等。打开百度,搜索“IP转换”,你会发现一大堆“高速稳定”、“不限流量”、“永久免费”等等的IP转换软件广告总是那么能吸引你注意。但广告就是…...
2024/4/24 11:29:13 - 防火墙学习笔记
一、定义 位于网络中不同区域之间,由安全策略建立起来的一个安全硬件系统。 二、作用 流量控制,防止黑客对内网的攻击,保护内网资源。 三、工作模式 防火墙有三种工作模式,分别为路由模式,透明模式,混合模式。 1.路由模式 定义:如果防火墙以第三层对外连接(接口具有IP …...
2024/4/21 18:45:32 - 图像识别技术文档
图像识别技术文档 GitHub传送门项目概述项目名称:面部表情识别项目背景:在与客户交流的过程中,通过客户的面部表情来判断用户对话题是否感兴趣,营销人员或者沟通人员可以从中找到客户感兴趣的方面,或者判断客户的购买欲望图像识别是指利用计算机对图像进行处理、分析、理解…...
2024/4/25 23:31:19 - 朝阳行业高薪职业的选择-RHC@注册健康管理师
中调网(年红) 2020年8月8日第十二个“全民健身日”来临之时,RHC注册健康管理师考评管理中心在京成立,该中心是由北京西商国际心理医学研究院联合国内外有关资深健康管理专家,共同成立的一个非赢利性的考评管理机构。RHC注册健康管理师考评管理中心是根据2020年7月20日国家…...
2024/4/25 1:45:36 - 真Unity3d_分享一下UI
谈一下很久没谈的UI,虽然之前也分享过一些UI的Unity插件好像一直以来很不屑UI,包括我,还有其他很多很多人有些同事工作2年,写的一手好UI,分享项目也是纯客户端,纯UI,连逻辑都省了看到某些面试官问UGUI和NGUI就烦,都5年没用过NGUI了,你还问??当然这5年也肯定不少团队…...
2024/4/24 22:27:26 - Netty——经典面试题
TCP和UDP的根本区别TCP面向连接,如打电话要先拨号建立连接;UDP是无连接的,即发送数据之前不需要建立连接 TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 TCP通过校验和、重传控制、序号标…...
2024/4/11 19:54:24 - 个人身份认证接口介绍
个人身份认证接口主要是用于互联网核验用户信息的真实性,比如在各类网站发布信息需要实名、注册域名需要实名、办理固定电话入网需要实名、线上购买金融产品需要实名等,总之,在很多业务场景中都会需要用户进行实名,这不仅仅是业务需求还是法规要求,同时要想确保实名信息的…...
2024/4/24 5:40:30 - Hive源码阅读--导读
总述 Hive的执行流程大致分为两部分,即任务的提交与返回,命令的编译与执行。 前者在CliDriver类中流转,后者主要在Driver与ParseDriver类,核心编译在BaseSemanticAnalyzer和QueryPlan类中。 任务的提交与返回 调用顺序:main --(程序的开始)–> run --(任务的开始,读取…...
2024/4/24 12:46:38 - oracle学习笔记,完整目录结构
Oracle学习笔记 1、sql执行顺序 1、常见的select、from、where的顺序from -> where -> select 2、完整的select、from、where、group by、having、order by的顺序from -> where -> group by -> having -> select -> order by2、oracle组成 1、参数文件,口…...
2024/5/1 7:45:23 - 【JAVA】安装jdk和集成开发环境MyEclipse\Eclipse\Intellij IDEA (64位 Window10)并运行代码检验实验环境安装成功,Hello World!
JAVA实验一 Hello World! (一)实验任务: 1、安装jdk和对应的编辑器; 2、在编辑器里写入:Hello World!生成Java文件,并运行结果。 (二)实验方法 参考官方文档,进行按步骤的下载和安装,熟悉实验环境。 我的电脑环境: Windows10 64位操作系统 建议每次下载时,新建一个…...
2024/4/24 17:44:40 - 使用POI给word文档加水印
在网上收罗了半天 发现大多数解决方案在word含有页眉的时候会报错 我整理了如下代码 希望以后不要在遇到这样的坑。 本文使用的poi版本为 poi-4.1.2, poi-scratchpad-4.1.2, poi-ooxml-schemas-4.1.2, poi-ooxml-4.1.2 import java.io.FileNotFoundException; import java.io.F…...
2024/4/11 19:54:18 - 格式化json字符串
格式化以后的效果1.上代码/*** 格式化** @param jsonStr* @return* @author peng2.li(李鹏1203962)* @Date 2015-10-14 下午1:17:35* @Modified 2017-04-28 下午8:55:35*/public String formatJson(String jsonStr) {if (null == jsonStr || "".equals(jsonStr))ret…...
2024/5/5 3:29:58 - Springboot整合Shiro 完整版
1.shiro是什么? Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。 官方架构图如下:2.主要功能 shiro主要有三大功能模块:…...
2024/4/11 19:54:16 - 面试精选
面试精选数据类型面向对象集合框架多线程ServletJSPJDBCMySQLSpringSpringMVCMybatisSpringBootSpringCloudRedisSolrElasticSearchNginxDubbo&Zookeeper**Zookeeper同步流程**FastDFSLinuxDocker 数据类型java中基本数据类型各占多少字节? byte:1字节 short:2字节 int:…...
2024/4/25 13:02:30 - 怎么给自己Linux远程登录设置:SSH证书登录
SSH 是服务器登录工具,提供密码登录和密钥登录。但是,SSH 还有第三种登录方法,那就是证书登录。很多情况下,它是更合理、更安全的登录方法,本文就介绍这种登录方法。 一、非证书登录的缺点密码登录和密钥登录,都有各自的缺点。密码登录需要输入服务器密码,这非常麻烦,也…...
2024/4/11 19:54:14 - 如何刷提高直播间真实观看人数人气及点击获得更高收入
到现在直播模式已经深入各个领域中,尤其是偶像明星的加入,让不少直播平台都充满着生机,热闹非凡,而直播的形式也逐渐引领了互联网的又一个高潮,不少网红主播就出自于直播间中,他们依靠所获得的在线人数以及人气,每次直播都可以赚取不少的酬劳。 随着全民直播时代的到来,…...
2024/4/25 3:17:01 - rtable
对于创建路由表项,系统着会再调用rt_new来继续工作rt_new函数会对我们传入的参数进行判断,看是否符合创建路由表项的条件。首先,函数先从传入的rt.rt_dev来判断要创建路由表项的设备是否存在,如果不存在则退出,因为创建一个路由表项,其实就是要对路由表项结构体的各个成员…...
2024/4/11 19:54:13
最新文章
- Python | Leetcode Python题解之第71题简化路径
题目: 题解: class Solution:def simplifyPath(self, path: str) -> str:names path.split("/")stack list()for name in names:if name "..":if stack:stack.pop()elif name and name ! ".":stack.append(name)re…...
2024/5/6 9:42:58 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/5/6 9:38:23 - redis 集群 (主从复制 哨兵模式 cluster)
目录 一 主从复制 (一)相关理论 1,主从复制定义 2,主从复制的作用 3,主从复制架构图 4 sync 同步过程 5,主从复制流程 (二) 实验模拟 1, 实验环境 2, 修…...
2024/5/5 8:33:53 - 学习鸿蒙基础(11)
目录 一、Navigation容器 二、web组件 三、video视频组件 四、动画 1、属性动画 .animation() 2、 转场动画 transition() 配合animateTo() 3、页面间转场动画 一、Navigation容器 Navigation组件一般作为页面的根容器,包括单页面、分栏和自适应三种显示模式…...
2024/5/4 11:54:31 - 416. 分割等和子集问题(动态规划)
题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义:dp[i][j]表示当背包容量为j,用前i个物品是否正好可以将背包填满ÿ…...
2024/5/5 18:19:03 - 【Java】ExcelWriter自适应宽度工具类(支持中文)
工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...
2024/5/5 12:22:20 - Spring cloud负载均衡@LoadBalanced LoadBalancerClient
LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon…...
2024/5/5 19:59:54 - TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案
一、背景需求分析 在工业产业园、化工园或生产制造园区中,周界防范意义重大,对园区的安全起到重要的作用。常规的安防方式是采用人员巡查,人力投入成本大而且效率低。周界一旦被破坏或入侵,会影响园区人员和资产安全,…...
2024/5/6 7:24:07 - VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...
2024/5/5 15:25:47 - 【Objective-C】Objective-C汇总
方法定义 参考:https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...
2024/5/6 6:01:13 - 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】
👨💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】🌏题目描述🌏输入格…...
2024/5/6 7:24:06 - 【ES6.0】- 扩展运算符(...)
【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数࿰…...
2024/5/6 1:08:53 - 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?
文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕,各大品牌纷纷晒出优异的成绩单,摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称,在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁,多个平台数据都表现出极度异常…...
2024/5/5 18:50:00 - Go语言常用命令详解(二)
文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令,这些命令可以帮助您在Go开发中进行编译、测试、运行和…...
2024/5/6 0:27:44 - 用欧拉路径判断图同构推出reverse合法性:1116T4
http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b,我们在 a i a_i ai 和 a i 1 a_{i1} ai1 之间连边, b b b 同理,则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然࿰…...
2024/5/6 7:24:04 - 【NGINX--1】基础知识
1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息,并安装一些有助于配置官方 NGINX 软件包仓库的软件包: apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...
2024/5/6 7:24:04 - Hive默认分割符、存储格式与数据压缩
目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限(ROW FORMAT)配置标准HQL为: ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...
2024/5/5 13:14:22 - 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法
文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中,传感器和控制器产生大量周…...
2024/5/6 7:24:03 - --max-old-space-size=8192报错
vue项目运行时,如果经常运行慢,崩溃停止服务,报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中,通过JavaScript使用内存时只能使用部分内存(64位系统&…...
2024/5/5 17:03:52 - 基于深度学习的恶意软件检测
恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞,例如可以被劫持的合法软件(例如浏览器或 Web 应用程序插件)中的错误。 恶意软件渗透可能会造成灾难性的后果,包括数据被盗、勒索或网…...
2024/5/5 21:10:50 - JS原型对象prototype
让我简单的为大家介绍一下原型对象prototype吧! 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象…...
2024/5/6 7:24:02 - C++中只能有一个实例的单例类
C中只能有一个实例的单例类 前面讨论的 President 类很不错,但存在一个缺陷:无法禁止通过实例化多个对象来创建多名总统: President One, Two, Three; 由于复制构造函数是私有的,其中每个对象都是不可复制的,但您的目…...
2024/5/6 7:24:01 - python django 小程序图书借阅源码
开发工具: PyCharm,mysql5.7,微信开发者工具 技术说明: python django html 小程序 功能介绍: 用户端: 登录注册(含授权登录) 首页显示搜索图书,轮播图࿰…...
2024/5/5 17:03:21 - 电子学会C/C++编程等级考试2022年03月(一级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...
2024/5/5 15:25:31 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57