在 C# 代码中使用 fastai (pytorch),如何用 mean 和 std 规范化位图?

Using fastai (pytorch) in c# code, how to normalize Bitmap with mean and std?

几天前,我的 C# 项目从 tensorflow 切换到了 fastai。但现在我正面临正常化的问题。对于两者,我都使用 onnx 管道来加载模型和数据。

var onnxPipeline = mLContext.Transforms.ResizeImages(resizing: ImageResizingEstimator.ResizingKind.Fill, outputColumnName: inputName,
                                                                 imageWidth: ImageSettings.imageWidth, imageHeight: ImageSettings.imageHeight,
                                                                 inputColumnName: nameof(ImageInputData.Image)) 
                .Append(mLContext.Transforms.ExtractPixels(outputColumnName: inputName, interleavePixelColors: true, scaleImage: 1 / 255f))
                .Append(mLContext.Transforms.ApplyOnnxModel(outputColumnName: outputName, inputColumnName: inputName, modelFile: onnxModelPath));

var emptyData = mLContext.Data.LoadFromEnumerable(new List<ImageInputData>());
var onnxModel = onnxPipeline.Fit(emptyData);

    class ImageInputData
    {
        [ImageType(ImageSettings.imageHeight, ImageSettings.imageWidth)]
        public Bitmap Image { get; set; }

        public ImageInputData(byte[] image)
        {
            using (var ms = new MemoryStream(image))
            {
                Image = new Bitmap(ms);
            }
        }
        public ImageInputData(Bitmap image)
        {
            Image = image;
        }
    }

在使用 fastai 之后我了解到,如果使用特定的均值和标准差对数据进行归一化,模型会获得更好的准确性(因为我使用了 resnet34 模型,它应该是均值 { 0.485, 0.456, 0.406 } stds = {分别为 0.229、0.224、0.225})。 因此,像素值(对于 c 的每种颜色)必须使用这些值进行转换以匹配训练图像。但是我怎样才能在 C# 中实现这个呢? 到目前为止我尝试的是:

int imageSize = 256;
double[] means = new double[] { 0.485, 0.456, 0.406 }; // used in fastai model
double[] stds = new double[] { 0.229, 0.224, 0.225 };
Bitmap bitmapImage = inputBitmap;
Image image = bitmapImage;

Color[] pixels = new Color[imageSize * imageSize];
for (int x = 0; x < bitmapImage.Width; x++)
{
   for (int y = 0; y < bitmapImage.Height; y++)
   {
      Color pixel = bitmapImage.GetPixel(x, y);
      pixels[x + y] = pixel;
      double red = (pixel.R - (means[0] * 255)) / (stds[0] * 255); // *255 to scale the mean and std values to the Bitmap
      double gre = (pixel.G - (means[1] * 255)) / (stds[1] * 255);
      double blu = (pixel.B - (means[2] * 255)) / (stds[2] * 255);
      Color pixel_n = Color.FromArgb(pixel.A, (int)red, (int)gre, (int)blu);
      bitmapImage.SetPixel(x, y, pixel_n);

   }
}

当然它不起作用,因为颜色值不能为负(我后来才意识到)。 但是,我如何使用 onnx 模型在 C# 中为我的模型实现 -1 和 1 之间的标准化?

是否有不同的方法来喂养模型或处理标准化?

如有任何帮助,我们将不胜感激!

解决这个问题的一种方法是从 onnx 管道切换到 onnx Inferencesession,在我看来这更简单也更容易理解:

public List<double> UseOnnxSession(Bitmap image, string onnxModelPath)
{
      double[] means = new double[] { 0.485, 0.456, 0.406 };
      double[] stds = new double[] { 0.229, 0.224, 0.225 };

      using (var session = new InferenceSession(onnxModelPath))
      {
          List<double> scores = new List<double>();
          Tensor<float> t1 = ConvertImageToFloatData(image, means, stds);
          List<float> fl = new List<float>();

          var inputMeta = session.InputMetadata;
          var inputs = new List<NamedOnnxValue>()
          {
             NamedOnnxValue.CreateFromTensor<float>("input_1", t1)
          };
          using (var results = session.Run(inputs))
          {
              foreach (var r in results)
              {
                  var x = r.AsTensor<float>().First();
                  var y = r.AsTensor<float>().Last();
                  var softmaxScore = Softmax(new double[] { x, y });
                  scores.Add(softmaxScore[0]);
                  scores.Add(softmaxScore[1]);
              }
           }
           return scores;
       }
}

// Create your Tensor and add transformations as you need.
public static Tensor<float> ConvertImageToFloatData(Bitmap image, double[] means, double[] std)
{
      Tensor<float> data = new DenseTensor<float>(new[] { 1, 3, image.Width, image.Height });
      for (int x = 0; x < image.Width; x++)
      {
           for (int y = 0; y < image.Height; y++)
           {
               Color color = image.GetPixel(x, y);
               var red = (color.R - (float)means[0] * 255) / ((float)std[0] * 255);
               var gre = (color.G - (float)means[1] * 255) / ((float)std[1] * 255);
               var blu = (color.B - (float)means[2] * 255) / ((float)std[2] * 255);
               data[0, 0, x, y] = red;
               data[0, 1, x, y] = gre;
               data[0, 2, x, y] = blu;
            }
       }
       return data;
}

此外,我必须在这些分数上使用我自己的 Softmax 方法来从我的模型中获取真实概率:

        public double[] Softmax(double[] values)
        {
            double[] ret = new double[values.Length];
            double maxExp = values.Select(Math.Exp).Sum();
            for (int i = 0; i < values.Length; i++)
            {
                ret[i] = Math.Round((Math.Exp(values[i]) / maxExp), 4);
            }
            return ret;
        }

希望这对遇到类似问题的人有所帮助。