MindSpore异常数据检查

异常数据检查

1)问题场景(背景,常见的问题)
2)问题解决思路(遇到这种问题,应该如何分析与解决)
3)实操指导(给出一些具体的步骤)

概述

在对神经网络进行训练之前,一般需要准备好大量、丰富的可训练数据,使模型得到充分的训练,以提升其准确率、泛化能力。

数据的来源是多样的,在准备训练数据时,有可能从本地读取数据,也可能远端服务器拉取数据。当读取到的数据丰富度不够时,可能会使用一些通用的数据增强方法对数据的样式和种类进行扩展,或者根据具体的需要对数据进行预处理计算(如token to id等),以满足神经网络的输入需要。

在对数据进行处理的过程中,可能会因为计算错误、数值溢出等因素,产生了异常的结果数值,从而导致训练网络时算子计算溢出、权重更新异常等问题。本章节将介绍如何基于MindSpore的脚本检查异常的数据行为/数据结果。

检查思路

关闭混洗,固定随机种子,确保可重现性

在一些数据处理的场景中,我们会使用随机的函数,作为数据运算的一部分。由于随机运算本身的特性,每一次运行的数据结果都不相同,这样很有可能会出现上一次运行的结果中存在异常的数值,但在下一次运行的时候,却没有检查到异常的数值,那么很有可能是因为随机索引/随机计算的影响。这种情况下,可以关闭数据集的混洗选项,并固定不同的随机种子,通过多次运行寻找可能引入的随机问题。

以下例子把一个随机值当做是一个除数,在偶然的情况下会出现除0的情况。

	import numpy as np
	import mindspore as ms
	 
	class Gen():
	    def __init__(self):
	        self.data = [np.array(i) / np.random.randint(0, 3) for i in range(1, 5)]
	    def __getitem__(self, index):
	        data = self.data[index]
	        return data
	    def __len__(self):
	        return len(self.data)
	 
	dataset = ms.dataset.GeneratorDataset(Gen(), ["data"])
	 
	for data in dataset:
	    print(data)复制

通过set_seed控制随机结果的可重现性,可以进一步排查代码中随机运算是否符合预期。

	ms.set_seed(1)
	ms.dataset.GeneratorDataset(Gen(), ["data"], shuffle=False)复制

多次运行结果均一致,可以看到第2条数据和第3条数据出现的除零的结果。间接可以说明,在第2条和第3条数据的计算上存在异常导致出现了inf的数值。

	[Tensor(shape=[], dtype=Float64, value= 1)]
	[Tensor(shape=[], dtype=Float64, value= inf)]
	[Tensor(shape=[], dtype=Float64, value= inf)]
	[Tensor(shape=[], dtype=Float64, value= 4)]复制

利用NumPy等工具快速校验结果

上一个例子中的数据量较少,基本上可以通过检查代码发现出现异常数值的位置。对于一些大型的高维数组,代码检查或者打印数值就不太方便了。这个时候,可以配置MindSpoer的数据集以NumPy的形式返回数据,并借助NumPy的一些常用检查数组内容的手段去检查数组中是否存在异常数值。

以下例子构造了一个大型的高维数组,并对其中的数值进行随机的运算。

	import numpy as np
	import mindspore as ms
	 
	class Gen():
	    def __init__(self):
	        self.data = np.random.randint(0, 255, size=(16, 50, 50))
	    def __getitem__(self, index):
	        data = self.data[index] / np.random.randint(0, 2)
	        return data
	    def __len__(self):
	        return 16
	 
	dataset = ms.dataset.GeneratorDataset(Gen(), ["data"])
	 
	for data in dataset:
	    print(data)复制

为了检查在数据运算时存在异常数值如nan、inf等,可以在遍历数据集对象时,指定其输出为NumPy类型。

指定了输出类型后,打印的data对象中各个元素均为NumPy类型,基于此可以采用NumPy中的一些非常方便的函数校验其中的数值是否异常

	for data_index, data in enumerate(dataset.create_tuple_iterator(output_numpy=True)):
	    print(data_index)           # 第N条数据。如果期望固定数据顺序,可以参考上一节关闭混洗与固定随机种子
	    print(data)                 # NumPy形式的数据
	 
	    print(np.isnan(data).any()) # 检查是否存在nan数值
	    print(np.isnan(data))       # 检查nan数值的位置,改函数返回一个mask标记,True为nan位置
	 
	    print(np.isinf(data).any()) # 检查是否存在inf数值
	    print(np.isinf(data))       # 检查nan数值的位置,改函数返回一个mask标记,True为inf位置复制

数据Pipeline分段检查数值输出

在一些情况下,数据集Pipeline的定义可能比上述例子要复杂的多,主要体现在多了map操作、shuffle操作、batch操作等。由于每个操作都会引入一些运算,用户的代码很可能在任意一个运算环节产生异常的数值。为了寻找可能是哪一个数据节点引入了异常数值,可以采用Pipeline分段的方式检查各个数据节点的输出值。

以下例子构造了一个较复杂的数据Pipeline,由GeneratorDataset -> map -> batch组成。

	import numpy as np
	import mindspore as ms
	 
	class Gen():
	    def __init__(self):
	        self.data = np.random.randint(0, 255, size=(16, 50, 50))
	    def __getitem__(self, index):
	        data = self.data[index]
	        return data
	    def __len__(self):
	        return 16
	 
	def map_fun(data):
	    data = data / np.random.randint(0, 2)
	    return data
	 
	dataset = ms.dataset.GeneratorDataset(Gen(), ["data"], shuffle=False)
	map_dataset = dataset.map(map_fun, ["data"])
	shuffle_dataset = map_dataset.shuffle(2)
	batch_dataset = shuffle_dataset.batch(4)
	 
	for data in batch_dataset.create_tuple_iterator(output_numpy=True):
	    print(np.isnan(data).any())
	    print(data)复制

此数据Pipeline的功能为:由GeneratorDataset产生数据,数据流转到map操作,对每一条数据执行map_fun的内容并返回,紧接着数据流转到shuffle操作,对数据进行混洗和采样,最后数据流转到batch操作,组合成batch size为4的高维数组。

运行上述代码是会发现np.isnan(data).any()返回的是True,也就是说数据处理流程中引入了异常数值。但由于存在4个数据节点,粗略一看难以检查异常数据究竟引入自哪一个数据节点。因此可以对不同的数据节点获取数据,以便进一步确定异常数值引入的起始点。数据Pipeline可以获取某一个数据节点的数据,如

	# 获取batch操作节点的输出数据,并指定输出数据类型为NumPy,校验其中的数值
	for data in batch_dataset.create_tuple_iterator(output_numpy=True):
	    print(np.isnan(data).any())
	    print(np.isinf(data).any())
	 
	# 获取shuffle操作节点的输出数据,并指定输出数据类型为NumPy,校验其中的数值
	for data in shuffle_dataset.create_tuple_iterator(output_numpy=True):
	    print(np.isnan(data).any())
	    print(np.isinf(data).any())
	 
	# 获取map操作节点的输出数据,并指定输出数据类型为NumPy,校验其中的数值
	for data in map_dataset.create_tuple_iterator(output_numpy=True):
	    print(np.isnan(data).any())
	    print(np.isinf(data).any())
	 
	# 获取GeneratorDataset节点的输出数据,并指定输出数据类型为NumPy,校验其中的数值
	for data in dataset.create_tuple_iterator(output_numpy=True):
	    print(np.isnan(data).any())
	    print(np.isinf(data).any())复制

由于上述代码是故意在map数据节点引入异常数值的,因此在获取batchshufflemap数据节点的数据时,均会发现存在nan或inf数据。而在GeneratorDataset数据节点,多次运行均无发现异常数值,即可判断异常数值的引入起始点是GeneratorDataset节点的下一个节点,即map数据节点。

当大致确定map操作引入了异常数据后,可以尝试在map操作所执行的函数map_fun前后增加一些日志打印,以确认具体哪一步操作计算出异常数值。

	def map_fun(data):
	    print("orginal data", data)
	    data = data / np.random.randint(0, 2)
	    print("processed data", data)
	    return data复制

结语

数据处理仅是神经网络训练的其中一个环节。在神经网络训练中途也可能因为各种数学运算、网络操作导致输出或梯度值异常。总的来说,可以优先从以下几个方面检查。

  • 数据问题:输入数据中可能存在NaN或Inf(无穷大)的值,这可能会导致计算结果为NaN。可以按照上文建议检查输入数据。

  • 模型结构问题:模型中的某些层或操作可能会导致NaN的输出。例如,当计算log或sqrt等函数的负数时,会出现NaN。建议检查模型结构,确保模型中的所有操作都是数值稳定的。

  • 梯度问题:当模型的梯度变得非常大时,会导致计算结果为NaN。这可能是由于学习率设置过高或梯度爆炸导致的。建议检查学习率设置,或尝试使用梯度剪裁来控制梯度范围。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值