Tensorflow vs Tensorflow JS 浮点算术计算的不同结果

Tensorflow vs Tensorflow JS different results for floating point arithmetic computations

我已经将 Tensorflow 模型转换为 Tensorflow JS 并尝试在浏览器中使用。在输入到模型进行推理之前,需要对输入执行一些预处理步骤。我已经实现了与 Tensorflow 相同的这些步骤。问题是与 TensorFlow 相比,TF JS 的推理结果不同。所以我开始调试代码,发现TF JS预处理中的浮点算术运算结果与Tensorflow不同,后者是运行 on Docker container with GPU。 TF JS中使用的代码如下。

       var tensor3d = tf.tensor3d(image,[height,width,1],'float32')

        var pi= PI.toString();
        if(bs == 14 && pi.indexOf('1') != -1 ) {

          tensor3d =  tensor3d.sub(-9798.6993999999995).div(7104.607118190255)

        }
        else if(bs == 12 && pi.indexOf('1') != -1) {

          tensor3d = tensor3d.sub(-3384.9893000000002).div(1190.0708513300835)
        }
        else if(bs == 12 && pi.indexOf('2') != -1) {

          tensor3d =  tensor3d.sub(978.31200000000001).div(1092.2426342420442)

        }
        var resizedTensor = tensor3d.resizeNearestNeighbor([224,224]).toFloat()
        var copiedTens = tf.tile(resizedTensor,[1,1,3])
        return copiedTens.expandDims();

Python 使用了代码块

ds = pydicom.dcmread(input_filename, stop_before_pixels=True)
if (ds.BitsStored == 12) and '1' in ds.PhotometricInterpretation:
    normalize_mean = -3384.9893000000002
    normalize_std = 1190.0708513300835
elif (ds.BitsStored == 12) and '2' in ds.PhotometricInterpretation:
    normalize_mean = 978.31200000000001
    normalize_std = 1092.2426342420442
elif (ds.BitsStored == 14) and '1' in ds.PhotometricInterpretation:
    normalize_mean = -9798.6993999999995
    normalize_std = 7104.607118190255
else:
    error_response = "Unable to read required metadata, or metadata invalid. 
    BitsStored: {}. PhotometricInterpretation: {}".format(ds.BitsStored, 
    ds.PhotometricInterpretation)
    error_json = {'code': 500, 'message': error_response}
    self._set_headers(500)
    self.wfile.write(json.dumps(error_json).encode())
    return

    normalization = Normalization(mean=normalize_mean, std=normalize_std)
    resize = ResizeImage()
    copy_channels = CopyChannels()
    inference_data_collection.append_preprocessor([normalization, resize, 
    copy_channels])

标准化代码

    def normalize(self, normalize_numpy, mask_numpy=None):

        normalize_numpy = normalize_numpy.astype(float)

        if mask_numpy is not None:
            mask = mask_numpy > 0
        elif self.mask_zeros:
            mask = np.nonzero(normalize_numpy)
        else:
            mask = None

        if mask is None:
            normalize_numpy = (normalize_numpy - self.mean) / self.std
        else:
            raise NotImplementedError

        return normalize_numpy

ResizeImage代码

   from skimage.transform import resize

   def Resize(self, data_group):

        input_data = data_group.preprocessed_case

        output_data = resize(input_data, self.output_dim)

        data_group.preprocessed_case = output_data
        self.output_data = output_data

复制频道代码

    def CopyChannels(self, data_group):

        input_data = data_group.preprocessed_case

        if self.new_channel_dim:
            output_data = np.stack([input_data] * self.channel_multiplier, -1)
        else:
            output_data = np.tile(input_data, (1, 1, self.channel_multiplier))

        data_group.preprocessed_case = output_data
        self.output_data = output_data

示例输出左边是 Docker 上的 Tensorflow with GPU,右边是 TF JS:

每一步的结果其实都不一样

可能有多种可能导致此问题。

1- python中使用的ops在js和python中的使用方式不同。如果是这样的话,使用完全相同的操作将解决这个问题。

2- python 库和浏览器 canvas 读取张量图像的方式可能不同。实际上,由于 anti-aliasing 等操作,canvas 像素在浏览器中并不总是具有相同的值...如本 answer 中所述。所以操作的结果可能会有一些细微的差异。为确保这是问题的根本原因,首先尝试打印 python 和 js 数组 image 并查看它们是否相似。很可能js和python.

中的3d张量不一样
tensor3d = tf.tensor3d(image,[height,width,1],'float32')

在这种情况下,可以使用 python 库将图像转换为张量数组,而不是直接在浏览器中读取图像。并使用 tfjs 直接读取此数组而不是图像。这样,在 js 和 python.

中的输入张量将是相同的

3 - 这是一个 float32 精度问题。 tensor3d 是使用 dtype float32 创建的,根据所使用的操作,可能存在精度问题。考虑这个操作:

tf.scalar(12045, 'int32').mul(tf.scalar(12045, 'int32')).print(); // 145082032 instead of 145082025

在 python 中会遇到相同的精度问题,具体如下:

a = tf.constant([12045], dtype='float32') * tf.constant([12045], dtype='float32')
tf.print(a) // 145082032

在 python 中,这可以通过使用 int32 dtype 来解决。然而,由于 webgl float32 的限制,使用 tfjs 上的 webgl 后端无法完成同样的事情。在神经网络中,这个精度问题不是什么大问题。要摆脱它,可以使用 setBackend('cpu') 更改后端,例如这要慢得多。