我掏出了一个简单的卷积神经网络,它能察觉到不对。单字的正确率高达 93%。如果一排有多个汉字,可想而知能非常可靠地判断了。
Module<Tensor, Tensor> model;
var w = 32;
var h = 32;
var chs = 1;
var categories = 6;
var c1k = 5;
var c2k = 5;
var c3k = 3;
var k1 = 64;
var k2 = 64;
var k3 = 64;
var (c0oh, c0ow) = (h + 1 - c1k, w + 1 - c1k);
var (c1oh, c1ow) = (c0oh / 1 + 1 - c2k, c0ow / 1 + 1 - c2k);
var (c2oh, c2ow) = (c1oh / 2 + 1 - c3k, c1ow / 2 + 1 - c3k);
var (rh, rw) = (c2oh / 2, c2ow / 2);
model = Sequential(
Conv2d(chs, k1, c1k),
ReLU6(),
// AvgPool2d(2),
Conv2d(k1, k2, c2k),
ReLU6(),
AvgPool2d(2),
Conv2d(k2, k3, c3k),
ReLU6(),
AvgPool2d(2),
Flatten(),
Linear(k3 * rh * rw, 96),
ReLU6(),
Linear(96, 96),
ReLU6(),
Linear(96, categories),
Softmax(1));
这个网络具体是这样,输入 32 x 32 的图片,分成 6 类——非文字、正、左右镜像、颠倒、上下镜像、横侧。 训练数据是用 Skia 和非衬线字体(例如微软雅黑),将 Unicode 编码在 0X4e00 至 0X9fff(即「基本汉字」的区间)的汉字,经过小幅度的随机的平移、缩放和旋转变换,再打印到位图生成的。
public static SKBitmap? GenerateImageFromGraphemeClusterWithFontMsyh(string ch) {
var random = Random.Shared;
SKBitmap bitmap = new SKBitmap(32, 32, SKColorType.Gray8, SKAlphaType.Opaque);
using SKCanvas canvas = new SKCanvas(bitmap);
var a = random.Next(1 + 60000);
canvas.RotateRadians((float)(Math.PI / 180 / 1000 * a - Math.PI / 6), 16, 16);
var v = Math.Pow(2, random.NextDouble() * 0.5 - 0.25);
canvas.Scale((float)v, (float)v, 16, 16);
canvas.Translate((float)(random.NextDouble() * 8 - 4), (float)(random.NextDouble() * 8 - 4));
if (!typeface.ContainsGlyphs(ch)) {
return null;
}
canvas.DrawText(ch, 4, 24, paint1msyh);
return bitmap;
}
循环生成,放进列表里:
for (int i = 0X4e00; i <= 0X9fff; ++i) {
using var image = GenerateImageFromGraphemeClusterWithFontMsyh(new string((char)i, 1));
if (image == null) {
continue;
}
using var view = CreateTensorViewFromImageData(image);
view.unsqueeze_(0);
using var converted = view.to(type, device);
var normalized = converted / 255;
list.Add(normalized);
}
训练的时候这样
// ...
foreach (var image in images) {
{
// 一批次 6 个,懒得优化了。
using var nai = GenerateRandom(mainScalarType, device); // |--> 0
// image |--> 1
using var hflipped = flip(image, 2); // |--> 2
using var rotatedpi = rot90(image, 2, (2, 3)); // |--> 3
using var vflipped = rot90(hflipped, 2, (2, 3)); // |--> 4
var ss = rr.Next(4);
var s0 = 0 != (1 & ss) ? image : hflipped;
var other = rot90(s0, 0 != (2 & ss) ? 1 : 3, (2, 3)); // |--> 5
tensor_object_buffer[0] = nai;
tensor_object_buffer[1] = image;
tensor_object_buffer[2] = hflipped;
tensor_object_buffer[3] = rotatedpi;
tensor_object_buffer[4] = vflipped;
tensor_object_buffer[5] = other;
using var inputs = cat(tensor_object_buffer);
optimizer.zero_grad();
using var outputs = model.forward(inputs);
using var loss = lossFunction.call(outputs, matrixI);
loss.backward();
using var r = optimizer.step();
using var results = outputs.argmax(1);
// ...
// 统计当前批次,保存模型数据等。略
}
// ...
}
// ...
// 训练完这一轮,保存模型数据等
10 分钟就练好了。 除了用扩展区的汉字测试,我还用画图捏造一些不存在汉字,卷积神经网络大多(> 90%)能正确判断。而且对训练中未出现过的新字体(甚至是衬线字体)也稳定。 这就说明,只要知道正确方向的汉字见着多了,就能判定汉字有没有颠倒。和他懂不懂汉语或知不知道汉字的含义是没关系的。也不需要理解汉字的造字法。 作为人类,只要看几小段正常的文字,很容易察觉到规律。即使那是陌生语言,也能判断出有没有颠倒。 对高赞答案提到缅文这种看似难以判断的文字也做了类似测试。结果与汉文的情况相似。但缅文很难按照汉字那样根据造字规则创造新的汉字来测试。所以在详细分析前,难说是神经网络是记得了这几个固定字形,还是习得了某种规律。 接下来可以通过拿一种文字的神经网络去判断另一种文字的图片,看看人类的不同种文字间是否存在某种能区分上下的共同特征。(目前倾向是否定的。有空再看)
|