几天前,我在我的 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 },标准差分别是 { 0.229, 0.224, 0.225 })。因此,每个颜色的像素值必须使用这些值进行转换,以匹配训练图像。但在 C# 中如何实现这一点呢?我尝试过的方法是:
int imageSize = 256;double[] means = new double[] { 0.485, 0.456, 0.406 }; // 在 fastai 模型中使用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 将均值和标准差值缩放到 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); }}
当然,这不起作用,因为颜色值不能为负(我后来才意识到这一点)。但如何在我的 C# 代码中使用 onnx 模型将归一化值调整到 -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; }}// 创建您的 Tensor 并根据需要添加转换。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; }
希望这能帮助到有类似问题的人。