CoolFace
Modelpublic

ChrisMoe/handwriting-v5

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes23downloads
Model Card

Chinese Handwriting Recognition — HSK1 v5 (ResNet CNN + Embedding Verification)

A ResNet-style CNN trained on HWDB1.0 to recognise 178 Chinese characters + Unknown, with an embedding-template verification head for handwriting-practice apps.

What's new in v5

Featurev4v5
ModeClassification only (which char is this?)+ Verification (is this a correct rendering of TARGET?)
Detects wrong charOnly via low confidenceYes — explicit cosine similarity to target template
Detects missing strokesNo (classifier picks closest)Yes — low sim to target template even if classifier picks target
New artifacttemplates_v5.npz (mean embedding per Chinese class)

What's new in v4

Featurev3v4
EMNIST polaritySame as raw (bright strokes on dark bg)Inverted to match HWDB (dark strokes on bright bg)
Brightness shortcutMean ratio 5.2xMean ratio <1.2x

Model details

ItemValue
Input40×40 grayscale image
Classes179 (178 Chinese characters + Unknown)
Embedding dim512
Templates178 (one per Chinese char)
FrameworkKeras / TensorFlow
Confidence threshold (classify)0.3
Similarity threshold (verify)0.9
OOD training dataEMNIST Balanced (8% of training set, polarity-inverted)

Quick start — verification mode

python
import numpy as np, json
import tensorflow as tf
from tensorflow import keras

model           = keras.models.load_model('chinese_hsk1_model_v5.keras')
embedding_model = keras.Model(model.input, model.layers[-2].output)

tpl_npz   = np.load('templates_v5.npz')
templates = dict(zip(tpl_npz['chars'].tolist(), tpl_npz['embeddings']))

def verify(img_gray, target_char, sim_threshold=0.65):
    x = img_gray.astype('float32') / 255.0
    x = x.reshape(1, 40, 40, 1)
    emb = embedding_model.predict(x, verbose=0)[0]
    emb = emb / (np.linalg.norm(emb) + 1e-8)
    if target_char not in templates:
        return 'invalid_target', 0.0
    sim = float(np.dot(emb, templates[target_char]))
    return ('correct' if sim >= sim_threshold else 'incomplete_or_unclear'), sim