Upload folder using huggingface_hub
Browse files- .gitattributes +8 -0
- DataLoader_Hindi_v2.py +107 -0
- Model_Hindi_v2.py +364 -0
- SamplePreprocessor_Hindi_v2.py +103 -0
- build_charlist_hindi.py +119 -0
- main_hindi_v2.py +337 -0
- model_hindi/accuracy.txt +4 -0
- model_hindi/charList.txt +1 -0
- model_hindi/checkpoint +5 -0
- model_hindi/metrics.json +44 -0
- model_hindi/snapshot-1.data-00000-of-00001 +3 -0
- model_hindi/snapshot-1.index +0 -0
- model_hindi/snapshot-1.meta +3 -0
- model_hindi/snapshot-2.data-00000-of-00001 +3 -0
- model_hindi/snapshot-2.index +0 -0
- model_hindi/snapshot-2.meta +3 -0
- model_hindi/snapshot-4.data-00000-of-00001 +3 -0
- model_hindi/snapshot-4.index +0 -0
- model_hindi/snapshot-4.meta +3 -0
- model_hindi/snapshot-5.data-00000-of-00001 +3 -0
- model_hindi/snapshot-5.index +0 -0
- model_hindi/snapshot-5.meta +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
model_hindi/snapshot-1.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
model_hindi/snapshot-1.meta filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
model_hindi/snapshot-2.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
model_hindi/snapshot-2.meta filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
model_hindi/snapshot-4.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
model_hindi/snapshot-4.meta filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
model_hindi/snapshot-5.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
model_hindi/snapshot-5.meta filter=lfs diff=lfs merge=lfs -text
|
DataLoader_Hindi_v2.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
from __future__ import division
|
| 3 |
+
from __future__ import print_function
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import gc
|
| 7 |
+
import numpy as np
|
| 8 |
+
import cv2
|
| 9 |
+
from collections import namedtuple
|
| 10 |
+
from datasets import load_dataset
|
| 11 |
+
|
| 12 |
+
from SamplePreprocessor_Hindi_v2 import preprocess
|
| 13 |
+
|
| 14 |
+
Batch = namedtuple("Batch", ["imgs", "gtTexts"])
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DataLoader:
|
| 18 |
+
|
| 19 |
+
def __init__(self,
|
| 20 |
+
dataset_name: str,
|
| 21 |
+
images_dir: str,
|
| 22 |
+
batchSize: int,
|
| 23 |
+
imgSize: tuple,
|
| 24 |
+
maxTextLen: int,
|
| 25 |
+
charList: str,
|
| 26 |
+
dataAugmentation: bool = False,
|
| 27 |
+
val_split: float = 0.1):
|
| 28 |
+
|
| 29 |
+
self.batchSize = batchSize
|
| 30 |
+
self.imgSize = imgSize
|
| 31 |
+
self.maxTextLen = maxTextLen
|
| 32 |
+
self.charList = charList
|
| 33 |
+
self.dataAugmentation = dataAugmentation
|
| 34 |
+
self.images_dir = images_dir
|
| 35 |
+
self.charSet = set(charList)
|
| 36 |
+
|
| 37 |
+
print(f"[DataLoader] Loading dataset: {dataset_name}")
|
| 38 |
+
|
| 39 |
+
dataset = load_dataset(dataset_name, split='train')
|
| 40 |
+
dataset = dataset.shuffle(seed=42)
|
| 41 |
+
|
| 42 |
+
split_idx = int(len(dataset) * (1 - val_split))
|
| 43 |
+
|
| 44 |
+
self.train_dataset = dataset.select(range(split_idx))
|
| 45 |
+
self.val_dataset = dataset.select(range(split_idx, len(dataset)))
|
| 46 |
+
|
| 47 |
+
print(f"[DataLoader] Train: {len(self.train_dataset)}")
|
| 48 |
+
print(f"[DataLoader] Val: {len(self.val_dataset)}")
|
| 49 |
+
|
| 50 |
+
self._current_dataset = None
|
| 51 |
+
self._idx = 0
|
| 52 |
+
|
| 53 |
+
def _load_and_preprocess_image(self, img_path, augment):
|
| 54 |
+
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
|
| 55 |
+
if img is None:
|
| 56 |
+
img = np.zeros([self.imgSize[1], self.imgSize[0]], dtype=np.uint8)
|
| 57 |
+
return preprocess(img, self.imgSize, dataAugmentation=augment)
|
| 58 |
+
|
| 59 |
+
def trainSet(self, subset_size=None):
|
| 60 |
+
dataset = self.train_dataset.shuffle(seed=None)
|
| 61 |
+
|
| 62 |
+
if subset_size is not None:
|
| 63 |
+
subset_size = min(subset_size, len(dataset))
|
| 64 |
+
indices = np.random.choice(len(dataset), subset_size, replace=False)
|
| 65 |
+
dataset = dataset.select(indices)
|
| 66 |
+
print(f"[DataLoader] Training on {subset_size} samples")
|
| 67 |
+
else:
|
| 68 |
+
print(f"[DataLoader] Training on FULL dataset")
|
| 69 |
+
|
| 70 |
+
self._current_dataset = dataset
|
| 71 |
+
self._idx = 0
|
| 72 |
+
|
| 73 |
+
def validationSet(self):
|
| 74 |
+
self._current_dataset = self.val_dataset
|
| 75 |
+
self._idx = 0
|
| 76 |
+
|
| 77 |
+
def hasNext(self):
|
| 78 |
+
return self._idx < len(self._current_dataset)
|
| 79 |
+
|
| 80 |
+
def getIteratorInfo(self):
|
| 81 |
+
"""Return current batch index and total batches."""
|
| 82 |
+
total = max(1, (len(self._current_dataset) + self.batchSize - 1) // self.batchSize)
|
| 83 |
+
current = self._idx // self.batchSize + 1
|
| 84 |
+
return (current, total)
|
| 85 |
+
|
| 86 |
+
def getNext(self):
|
| 87 |
+
end = min(self._idx + self.batchSize, len(self._current_dataset))
|
| 88 |
+
batch_indices = range(self._idx, end)
|
| 89 |
+
self._idx = end
|
| 90 |
+
|
| 91 |
+
imgs = []
|
| 92 |
+
gtTexts = []
|
| 93 |
+
|
| 94 |
+
for idx in batch_indices:
|
| 95 |
+
sample = self._current_dataset[idx]
|
| 96 |
+
|
| 97 |
+
augment = (self._current_dataset != self.val_dataset)
|
| 98 |
+
|
| 99 |
+
img = np.array(sample["image"].convert("L"))
|
| 100 |
+
img = preprocess(img, self.imgSize, dataAugmentation=augment)
|
| 101 |
+
|
| 102 |
+
imgs.append(img)
|
| 103 |
+
gtTexts.append(sample['text'])
|
| 104 |
+
return Batch(imgs=imgs, gtTexts=gtTexts)
|
| 105 |
+
|
| 106 |
+
def cleanup(self):
|
| 107 |
+
gc.collect()
|
Model_Hindi_v2.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
Model_Hindi_v2.py — FAST & BALANCED VERSION
|
| 4 |
+
--------------------------------------------------------------
|
| 5 |
+
Optimized for speed with balanced regularization.
|
| 6 |
+
|
| 7 |
+
KEY CHANGES:
|
| 8 |
+
✅ Larger batch size (16-32) - No gradient accumulation
|
| 9 |
+
✅ Label smoothing (0.05 instead of 0.1) - Gentler
|
| 10 |
+
✅ Moderate dropout (0.3) - Not too strong
|
| 11 |
+
✅ Faster training
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import division
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import sys
|
| 18 |
+
import numpy as np
|
| 19 |
+
import tensorflow as tf
|
| 20 |
+
from tensorflow.keras.layers import LSTM, Dense, Bidirectional
|
| 21 |
+
|
| 22 |
+
tf.compat.v1.disable_eager_execution()
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class DecoderType:
|
| 26 |
+
BestPath = 0
|
| 27 |
+
BeamSearch = 1
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class Model:
|
| 31 |
+
"""CRNN+CTC optimized for fast training."""
|
| 32 |
+
|
| 33 |
+
# ✅ LARGER batch size for speed
|
| 34 |
+
batchSize = 16 # Can increase to 32 if GPU allows
|
| 35 |
+
|
| 36 |
+
imgSize = (900, 64)
|
| 37 |
+
maxTextLen = 128
|
| 38 |
+
CNN_TIME_STEPS = 225
|
| 39 |
+
|
| 40 |
+
def __init__(self, charList, decoderType=DecoderType.BestPath,
|
| 41 |
+
mustRestore=False, lastEpoch=0):
|
| 42 |
+
|
| 43 |
+
self.charList = charList
|
| 44 |
+
self.decoderType = decoderType
|
| 45 |
+
self.mustRestore = mustRestore
|
| 46 |
+
self.snapID = 0
|
| 47 |
+
self.lastEpoch = lastEpoch
|
| 48 |
+
|
| 49 |
+
self.SPACE_IDX = charList.index(' ') if ' ' in charList else 1
|
| 50 |
+
|
| 51 |
+
# Placeholders
|
| 52 |
+
self.inputImgs = tf.compat.v1.placeholder(
|
| 53 |
+
tf.float32,
|
| 54 |
+
shape=(None, Model.imgSize[0], Model.imgSize[1]),
|
| 55 |
+
name='input_imgs')
|
| 56 |
+
|
| 57 |
+
self.isTraining = tf.compat.v1.placeholder_with_default(
|
| 58 |
+
False, shape=(), name='is_training')
|
| 59 |
+
|
| 60 |
+
self.dropout_rate = tf.compat.v1.placeholder_with_default(
|
| 61 |
+
0.0, shape=(), name='dropout_rate')
|
| 62 |
+
|
| 63 |
+
self.setupCNN()
|
| 64 |
+
self.setupRNN()
|
| 65 |
+
self.setupCTC()
|
| 66 |
+
|
| 67 |
+
self.learningRate = tf.compat.v1.placeholder(
|
| 68 |
+
tf.float32, shape=[], name='learning_rate')
|
| 69 |
+
|
| 70 |
+
self.setupOptimizer()
|
| 71 |
+
(self.sess, self.saver) = self.setupTF()
|
| 72 |
+
|
| 73 |
+
# ── BatchNorm ─────────────────────────────────────────────────────────────
|
| 74 |
+
def _batch_norm(self, x, name):
|
| 75 |
+
"""BatchNorm layer."""
|
| 76 |
+
channels = x.shape[-1]
|
| 77 |
+
|
| 78 |
+
gamma = tf.Variable(tf.ones([channels]), trainable=True, name=f'{name}_gamma')
|
| 79 |
+
beta = tf.Variable(tf.zeros([channels]), trainable=True, name=f'{name}_beta')
|
| 80 |
+
moving_mean = tf.Variable(tf.zeros([channels]), trainable=False, name=f'{name}_moving_mean')
|
| 81 |
+
moving_var = tf.Variable(tf.ones([channels]), trainable=False, name=f'{name}_moving_var')
|
| 82 |
+
|
| 83 |
+
decay = 0.99
|
| 84 |
+
eps = 1e-5
|
| 85 |
+
|
| 86 |
+
def train_mode():
|
| 87 |
+
mean, var = tf.nn.moments(x, axes=[0, 1, 2])
|
| 88 |
+
update_mean = tf.compat.v1.assign(
|
| 89 |
+
moving_mean, moving_mean * decay + mean * (1 - decay))
|
| 90 |
+
update_var = tf.compat.v1.assign(
|
| 91 |
+
moving_var, moving_var * decay + var * (1 - decay))
|
| 92 |
+
with tf.control_dependencies([update_mean, update_var]):
|
| 93 |
+
return tf.nn.batch_normalization(x, mean, var, beta, gamma, eps)
|
| 94 |
+
|
| 95 |
+
def infer_mode():
|
| 96 |
+
return tf.nn.batch_normalization(
|
| 97 |
+
x, moving_mean, moving_var, beta, gamma, eps)
|
| 98 |
+
|
| 99 |
+
return tf.cond(self.isTraining, train_mode, infer_mode)
|
| 100 |
+
|
| 101 |
+
# ── CNN ───────────────────────────────────────────────────────────────────
|
| 102 |
+
def setupCNN(self):
|
| 103 |
+
"""5-block CNN with BatchNorm."""
|
| 104 |
+
cnnIn4d = tf.expand_dims(self.inputImgs, axis=3)
|
| 105 |
+
|
| 106 |
+
kernelVals = [5, 5, 3, 3, 3]
|
| 107 |
+
featureVals = [1, 64, 128, 256, 256, 384]
|
| 108 |
+
poolVals = [(2, 2), (2, 2), (1, 2), (1, 2), (1, 4)]
|
| 109 |
+
strideVals = poolVals
|
| 110 |
+
|
| 111 |
+
pool = cnnIn4d
|
| 112 |
+
for i in range(len(poolVals)):
|
| 113 |
+
kernel = tf.Variable(
|
| 114 |
+
tf.random.truncated_normal(
|
| 115 |
+
[kernelVals[i], kernelVals[i],
|
| 116 |
+
featureVals[i], featureVals[i + 1]],
|
| 117 |
+
stddev=0.1),
|
| 118 |
+
name=f'cnn_kernel_{i}')
|
| 119 |
+
|
| 120 |
+
conv = tf.nn.conv2d(pool, kernel, padding='SAME', strides=(1, 1, 1, 1))
|
| 121 |
+
conv = self._batch_norm(conv, name=f'bn_{i}')
|
| 122 |
+
relu = tf.nn.relu(conv)
|
| 123 |
+
|
| 124 |
+
pool = tf.nn.max_pool(
|
| 125 |
+
relu,
|
| 126 |
+
ksize = (1, poolVals[i][0], poolVals[i][1], 1),
|
| 127 |
+
strides = (1, strideVals[i][0], strideVals[i][1], 1),
|
| 128 |
+
padding = 'VALID')
|
| 129 |
+
|
| 130 |
+
self.cnnOut4d = pool
|
| 131 |
+
print('[Model] CNN output shape:', pool.get_shape())
|
| 132 |
+
|
| 133 |
+
# ── RNN ───────────────────────────────────────────────────────────────────
|
| 134 |
+
def setupRNN(self):
|
| 135 |
+
"""
|
| 136 |
+
Two stacked BiLSTMs with MODERATE dropout.
|
| 137 |
+
|
| 138 |
+
✅ Dropout 0.3 (not 0.5) - Balanced regularization
|
| 139 |
+
"""
|
| 140 |
+
rnnIn3d = tf.squeeze(self.cnnOut4d, axis=[2])
|
| 141 |
+
|
| 142 |
+
numHidden = 384
|
| 143 |
+
|
| 144 |
+
lstm1 = Bidirectional(
|
| 145 |
+
LSTM(numHidden, return_sequences=True),
|
| 146 |
+
name='bilstm_1')
|
| 147 |
+
|
| 148 |
+
lstm2 = Bidirectional(
|
| 149 |
+
LSTM(numHidden, return_sequences=True),
|
| 150 |
+
name='bilstm_2')
|
| 151 |
+
|
| 152 |
+
x = lstm1(rnnIn3d)
|
| 153 |
+
# ✅ Moderate dropout (0.3, not 0.5)
|
| 154 |
+
x = tf.nn.dropout(x, rate=self.dropout_rate)
|
| 155 |
+
x = lstm2(x)
|
| 156 |
+
x = tf.nn.dropout(x, rate=self.dropout_rate)
|
| 157 |
+
|
| 158 |
+
dense = Dense(len(self.charList) + 1, name='output_dense')
|
| 159 |
+
self.rnnOut3d = dense(x)
|
| 160 |
+
print('[Model] RNN output shape:', self.rnnOut3d.shape)
|
| 161 |
+
|
| 162 |
+
# ── CTC ───────────────────────────────────────────────────────────────────
|
| 163 |
+
def setupCTC(self):
|
| 164 |
+
"""CTC loss with GENTLE label smoothing."""
|
| 165 |
+
num_classes = len(self.charList) + 1
|
| 166 |
+
init_bias = np.zeros(num_classes, dtype=np.float32)
|
| 167 |
+
init_bias[self.SPACE_IDX] = 1.0
|
| 168 |
+
|
| 169 |
+
self.space_logit_bias = tf.Variable(
|
| 170 |
+
initial_value=init_bias,
|
| 171 |
+
trainable=True,
|
| 172 |
+
name='space_logit_bias',
|
| 173 |
+
dtype=tf.float32)
|
| 174 |
+
|
| 175 |
+
self.ctcIn3dTBC_raw = tf.transpose(self.rnnOut3d, [1, 0, 2])
|
| 176 |
+
self.ctcIn3dTBC = self.ctcIn3dTBC_raw + self.space_logit_bias
|
| 177 |
+
|
| 178 |
+
self.gtTexts = tf.SparseTensor(
|
| 179 |
+
tf.compat.v1.placeholder(tf.int64, shape=[None, 2], name='gt_indices'),
|
| 180 |
+
tf.compat.v1.placeholder(tf.int32, shape=[None], name='gt_values'),
|
| 181 |
+
tf.compat.v1.placeholder(tf.int64, shape=[2], name='gt_shape'))
|
| 182 |
+
|
| 183 |
+
self.seqLen = tf.compat.v1.placeholder(
|
| 184 |
+
tf.int32, shape=[None], name='seq_len')
|
| 185 |
+
|
| 186 |
+
# ✅ GENTLE label smoothing (0.05 instead of 0.1)
|
| 187 |
+
self.label_smoothing = 0.05
|
| 188 |
+
|
| 189 |
+
ctc_loss_per_sample = tf.compat.v1.nn.ctc_loss(
|
| 190 |
+
labels = self.gtTexts,
|
| 191 |
+
inputs = self.ctcIn3dTBC,
|
| 192 |
+
sequence_length = self.seqLen,
|
| 193 |
+
ctc_merge_repeated = True,
|
| 194 |
+
ignore_longer_outputs_than_inputs = True)
|
| 195 |
+
|
| 196 |
+
# Space weighting
|
| 197 |
+
SPACE_WEIGHT = 2.0
|
| 198 |
+
batch_size_dyn = tf.shape(ctc_loss_per_sample)[0]
|
| 199 |
+
|
| 200 |
+
gt_values = self.gtTexts.values
|
| 201 |
+
gt_indices = self.gtTexts.indices
|
| 202 |
+
batch_idx = gt_indices[:, 0]
|
| 203 |
+
|
| 204 |
+
is_space = tf.cast(tf.equal(gt_values, self.SPACE_IDX), tf.float32)
|
| 205 |
+
|
| 206 |
+
spaces_per_sample = tf.math.unsorted_segment_sum(
|
| 207 |
+
is_space, batch_idx, num_segments=batch_size_dyn)
|
| 208 |
+
labels_per_sample = tf.math.unsorted_segment_sum(
|
| 209 |
+
tf.ones_like(is_space), batch_idx, num_segments=batch_size_dyn)
|
| 210 |
+
|
| 211 |
+
space_fraction = spaces_per_sample / tf.maximum(labels_per_sample, 1.0)
|
| 212 |
+
sample_weights = 1.0 + (SPACE_WEIGHT - 1.0) * space_fraction
|
| 213 |
+
|
| 214 |
+
# Apply gentle label smoothing
|
| 215 |
+
smoothed_loss = ctc_loss_per_sample * (1.0 - self.label_smoothing) + \
|
| 216 |
+
self.label_smoothing * tf.ones_like(ctc_loss_per_sample)
|
| 217 |
+
|
| 218 |
+
self.loss = tf.reduce_mean(smoothed_loss * sample_weights)
|
| 219 |
+
|
| 220 |
+
# Saved-CTC path
|
| 221 |
+
self.savedCtcInput = tf.compat.v1.placeholder(
|
| 222 |
+
tf.float32,
|
| 223 |
+
shape=[Model.CNN_TIME_STEPS, None, len(self.charList) + 1],
|
| 224 |
+
name='saved_ctc_input')
|
| 225 |
+
|
| 226 |
+
self.lossPerElement = tf.compat.v1.nn.ctc_loss(
|
| 227 |
+
labels = self.gtTexts,
|
| 228 |
+
inputs = self.savedCtcInput,
|
| 229 |
+
sequence_length = self.seqLen,
|
| 230 |
+
ctc_merge_repeated = True)
|
| 231 |
+
|
| 232 |
+
# Decoder
|
| 233 |
+
if self.decoderType == DecoderType.BestPath:
|
| 234 |
+
self.decoder = tf.nn.ctc_greedy_decoder(
|
| 235 |
+
inputs=self.ctcIn3dTBC, sequence_length=self.seqLen)
|
| 236 |
+
elif self.decoderType == DecoderType.BeamSearch:
|
| 237 |
+
self.decoder = tf.nn.ctc_beam_search_decoder(
|
| 238 |
+
inputs = self.ctcIn3dTBC,
|
| 239 |
+
sequence_length= self.seqLen,
|
| 240 |
+
beam_width = 100,
|
| 241 |
+
merge_repeated = False)
|
| 242 |
+
|
| 243 |
+
# ── Optimizer ─────────────────────────────────────────────────────────────
|
| 244 |
+
def setupOptimizer(self):
|
| 245 |
+
"""Simple Adam optimizer (no gradient accumulation needed)."""
|
| 246 |
+
optimizer = tf.compat.v1.train.AdamOptimizer(self.learningRate)
|
| 247 |
+
|
| 248 |
+
# Get gradients
|
| 249 |
+
tvars = tf.compat.v1.trainable_variables()
|
| 250 |
+
grads = tf.gradients(self.loss, tvars)
|
| 251 |
+
|
| 252 |
+
# Clip gradients
|
| 253 |
+
grads, _ = tf.clip_by_global_norm(grads, 5.0)
|
| 254 |
+
|
| 255 |
+
# Apply gradients
|
| 256 |
+
update_ops = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.UPDATE_OPS)
|
| 257 |
+
with tf.control_dependencies(update_ops):
|
| 258 |
+
self.optimizer = optimizer.apply_gradients(zip(grads, tvars))
|
| 259 |
+
|
| 260 |
+
# ── TF session ────────────────────────────────────────────────────────────
|
| 261 |
+
def setupTF(self):
|
| 262 |
+
print('Python: ', sys.version)
|
| 263 |
+
print('TensorFlow: ', tf.__version__)
|
| 264 |
+
|
| 265 |
+
config = tf.compat.v1.ConfigProto()
|
| 266 |
+
config.gpu_options.allow_growth = True
|
| 267 |
+
|
| 268 |
+
sess = tf.compat.v1.Session(config=config)
|
| 269 |
+
saver = tf.compat.v1.train.Saver(max_to_keep=5)
|
| 270 |
+
|
| 271 |
+
modelDir = 'model_hindi/'
|
| 272 |
+
latestSnapshot = tf.train.latest_checkpoint(modelDir)
|
| 273 |
+
|
| 274 |
+
if self.mustRestore and not latestSnapshot:
|
| 275 |
+
raise Exception('No saved model found in: ' + modelDir)
|
| 276 |
+
|
| 277 |
+
if latestSnapshot:
|
| 278 |
+
print('[Model] Restoring from:', latestSnapshot)
|
| 279 |
+
saver.restore(sess, latestSnapshot)
|
| 280 |
+
self.lastEpoch = int(latestSnapshot.split('-')[-1])
|
| 281 |
+
else:
|
| 282 |
+
print('[Model] Initialising fresh weights')
|
| 283 |
+
sess.run(tf.compat.v1.global_variables_initializer())
|
| 284 |
+
|
| 285 |
+
return (sess, saver)
|
| 286 |
+
|
| 287 |
+
# ── Helpers ───────────────────────────────────────────────────────────────
|
| 288 |
+
def toSparse(self, texts):
|
| 289 |
+
indices = []
|
| 290 |
+
values = []
|
| 291 |
+
shape = [len(texts), 0]
|
| 292 |
+
for (batchElement, text) in enumerate(texts):
|
| 293 |
+
labelStr = [self.charList.index(c) for c in text]
|
| 294 |
+
if len(labelStr) > shape[1]:
|
| 295 |
+
shape[1] = len(labelStr)
|
| 296 |
+
for (i, label) in enumerate(labelStr):
|
| 297 |
+
indices.append([batchElement, i])
|
| 298 |
+
values.append(label)
|
| 299 |
+
return (indices, values, shape)
|
| 300 |
+
|
| 301 |
+
def decoderOutputToText(self, ctcOutput, batchSize):
|
| 302 |
+
encodedLabelStrs = [[] for _ in range(batchSize)]
|
| 303 |
+
decoded = ctcOutput[0][0]
|
| 304 |
+
for (idx, idx2d) in enumerate(decoded.indices):
|
| 305 |
+
label = decoded.values[idx]
|
| 306 |
+
batchElement = idx2d[0]
|
| 307 |
+
encodedLabelStrs[batchElement].append(label)
|
| 308 |
+
return [str().join([self.charList[c] for c in labelStr])
|
| 309 |
+
for labelStr in encodedLabelStrs]
|
| 310 |
+
|
| 311 |
+
# ── Train ─────────────────────────────────────────────────────────────────
|
| 312 |
+
def trainBatch(self, batch, learning_rate=0.0001):
|
| 313 |
+
"""Train on one batch (NO gradient accumulation)."""
|
| 314 |
+
numBatchElements = len(batch.imgs)
|
| 315 |
+
sparse = self.toSparse(batch.gtTexts)
|
| 316 |
+
feedDict = {
|
| 317 |
+
self.inputImgs : batch.imgs,
|
| 318 |
+
self.gtTexts : sparse,
|
| 319 |
+
self.seqLen : [Model.CNN_TIME_STEPS] * numBatchElements,
|
| 320 |
+
self.learningRate: learning_rate,
|
| 321 |
+
self.isTraining : True,
|
| 322 |
+
self.dropout_rate: 0.3, # ✅ Moderate dropout
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
_, lossVal = self.sess.run([self.optimizer, self.loss], feedDict)
|
| 326 |
+
return lossVal
|
| 327 |
+
|
| 328 |
+
# ── Infer ─────────────────────────────────────────────────────────────────
|
| 329 |
+
def inferBatch(self, batch, calcProbability=False, probabilityOfGT=False):
|
| 330 |
+
"""Inference with dropout disabled."""
|
| 331 |
+
numBatchElements = len(batch.imgs)
|
| 332 |
+
evalList = [self.decoder] + ([self.ctcIn3dTBC] if calcProbability else [])
|
| 333 |
+
feedDict = {
|
| 334 |
+
self.inputImgs : batch.imgs,
|
| 335 |
+
self.seqLen : [Model.CNN_TIME_STEPS] * numBatchElements,
|
| 336 |
+
self.isTraining: False,
|
| 337 |
+
self.dropout_rate: 0.0,
|
| 338 |
+
}
|
| 339 |
+
evalRes = self.sess.run(evalList, feedDict)
|
| 340 |
+
decoded = evalRes[0]
|
| 341 |
+
texts = self.decoderOutputToText(decoded, numBatchElements)
|
| 342 |
+
|
| 343 |
+
probs = None
|
| 344 |
+
if calcProbability:
|
| 345 |
+
sparse = (self.toSparse(batch.gtTexts)
|
| 346 |
+
if probabilityOfGT else self.toSparse(texts))
|
| 347 |
+
ctcInput = evalRes[1]
|
| 348 |
+
feedDict = {
|
| 349 |
+
self.savedCtcInput : ctcInput,
|
| 350 |
+
self.gtTexts : sparse,
|
| 351 |
+
self.seqLen : [Model.CNN_TIME_STEPS] * numBatchElements,
|
| 352 |
+
self.isTraining : False,
|
| 353 |
+
self.dropout_rate : 0.0,
|
| 354 |
+
}
|
| 355 |
+
lossVals = self.sess.run(self.lossPerElement, feedDict)
|
| 356 |
+
probs = np.exp(-lossVals)
|
| 357 |
+
|
| 358 |
+
return (texts, probs)
|
| 359 |
+
|
| 360 |
+
# ── Save ───────────────────────────────────────────────────────────���──────
|
| 361 |
+
def save(self, epoch):
|
| 362 |
+
self.snapID += 1
|
| 363 |
+
self.saver.save(self.sess, 'model_hindi/snapshot', global_step=epoch)
|
| 364 |
+
print(f'[Model] Checkpoint saved (epoch {epoch})')
|
SamplePreprocessor_Hindi_v2.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
SamplePreprocessor_Hindi.py — MODERATE AUGMENTATION VERSION
|
| 4 |
+
----------------------------------------------------------
|
| 5 |
+
Balanced augmentation that prevents overfitting WITHOUT making images unreadable.
|
| 6 |
+
|
| 7 |
+
KEY PRINCIPLE: Model must be able to LEARN from augmented images!
|
| 8 |
+
- Apply only 1-2 augmentations per image (not 5+)
|
| 9 |
+
- Lower probabilities (20-30% instead of 40-60%)
|
| 10 |
+
- Gentler strength (±3° instead of ±5°)
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import division
|
| 14 |
+
from __future__ import print_function
|
| 15 |
+
|
| 16 |
+
import random
|
| 17 |
+
import numpy as np
|
| 18 |
+
import cv2
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def preprocess(img: np.ndarray,
|
| 22 |
+
imgSize: tuple,
|
| 23 |
+
dataAugmentation: bool = False) -> np.ndarray:
|
| 24 |
+
"""
|
| 25 |
+
Preprocess with MODERATE augmentation.
|
| 26 |
+
|
| 27 |
+
Strategy: Apply 1-2 augmentations per image, not 5+
|
| 28 |
+
This prevents overfitting while keeping images learnable.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
if img is None:
|
| 32 |
+
img = np.zeros([imgSize[1], imgSize[0]], dtype=np.uint8)
|
| 33 |
+
|
| 34 |
+
# ── MODERATE DATA AUGMENTATION ───────────────────────────────────────────
|
| 35 |
+
if dataAugmentation:
|
| 36 |
+
|
| 37 |
+
# Choose 1-2 augmentations randomly (not all of them!)
|
| 38 |
+
num_augs = random.choice([0, 1, 1, 2]) # Mostly 1, sometimes 2, rarely 0
|
| 39 |
+
available_augs = ['rotate', 'brightness', 'blur', 'noise', 'scale', 'contrast']
|
| 40 |
+
selected_augs = random.sample(available_augs, min(num_augs, len(available_augs)))
|
| 41 |
+
|
| 42 |
+
# 1. ROTATION - More gentle (±3° instead of ±5°)
|
| 43 |
+
if 'rotate' in selected_augs:
|
| 44 |
+
angle = random.uniform(-3.0, 3.0) # Reduced from ±5°
|
| 45 |
+
h, w = img.shape
|
| 46 |
+
M = cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0)
|
| 47 |
+
img = cv2.warpAffine(img, M, (w, h), borderMode=cv2.BORDER_REPLICATE)
|
| 48 |
+
|
| 49 |
+
# 2. BRIGHTNESS - Moderate variation
|
| 50 |
+
if 'brightness' in selected_augs:
|
| 51 |
+
delta = random.uniform(-20, 20) # Reduced from ±30
|
| 52 |
+
img = np.clip(img.astype(np.float32) + delta, 0, 255).astype(np.uint8)
|
| 53 |
+
|
| 54 |
+
# 3. CONTRAST - Gentle variation
|
| 55 |
+
if 'contrast' in selected_augs:
|
| 56 |
+
factor = random.uniform(0.8, 1.2) # Reduced from 0.7-1.3
|
| 57 |
+
img = np.clip((img.astype(np.float32) - 127.5) * factor + 127.5,
|
| 58 |
+
0, 255).astype(np.uint8)
|
| 59 |
+
|
| 60 |
+
# 4. BLUR - Mild blur only
|
| 61 |
+
if 'blur' in selected_augs:
|
| 62 |
+
kernel_size = 3 # Only size 3
|
| 63 |
+
sigma = random.uniform(0.3, 1.0) # Reduced from 0.5-2.0
|
| 64 |
+
img = cv2.GaussianBlur(img, (kernel_size, kernel_size), sigma)
|
| 65 |
+
|
| 66 |
+
# 5. NOISE - Light noise
|
| 67 |
+
if 'noise' in selected_augs:
|
| 68 |
+
std = random.uniform(3, 8) # Reduced from 5-15
|
| 69 |
+
noise = np.random.normal(0, std, img.shape).astype(np.float32)
|
| 70 |
+
img = np.clip(img.astype(np.float32) + noise, 0, 255).astype(np.uint8)
|
| 71 |
+
|
| 72 |
+
# 6. SCALING - Small scale variation
|
| 73 |
+
if 'scale' in selected_augs:
|
| 74 |
+
scale = random.uniform(0.9, 1.1) # Reduced from 0.85-1.15
|
| 75 |
+
h, w = img.shape
|
| 76 |
+
new_h = max(1, int(h * scale))
|
| 77 |
+
new_w = max(1, int(w * scale))
|
| 78 |
+
img = cv2.resize(img, (new_w, new_h))
|
| 79 |
+
|
| 80 |
+
# ── Resize to target canvas ──────────────────────────────────────────────
|
| 81 |
+
(wt, ht) = imgSize
|
| 82 |
+
(h, w) = img.shape
|
| 83 |
+
|
| 84 |
+
f = max(w / wt, h / ht)
|
| 85 |
+
new_w = max(1, min(wt, int(w / f)))
|
| 86 |
+
new_h = max(1, min(ht, int(h / f)))
|
| 87 |
+
|
| 88 |
+
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
| 89 |
+
|
| 90 |
+
# White canvas
|
| 91 |
+
canvas = np.ones([ht, wt], dtype=np.uint8) * 255
|
| 92 |
+
canvas[0:new_h, 0:new_w] = img
|
| 93 |
+
|
| 94 |
+
# ── Transpose for TF ──────────────────────────────────────────────────────
|
| 95 |
+
transposed = cv2.transpose(canvas)
|
| 96 |
+
|
| 97 |
+
# ── Normalization ─────────────────────────────────────────────────────────
|
| 98 |
+
(m, s) = cv2.meanStdDev(transposed)
|
| 99 |
+
m = m[0][0]
|
| 100 |
+
s = s[0][0]
|
| 101 |
+
normalised = (transposed - m) / (s + 1e-8)
|
| 102 |
+
|
| 103 |
+
return normalised.astype(np.float32)
|
build_charlist_hindi.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
build_charlist_hindi.py
|
| 4 |
+
-----------------------
|
| 5 |
+
Build charList.txt from Hindi OCR dataset.
|
| 6 |
+
|
| 7 |
+
Extracts all unique characters from the 'text' column in data.csv
|
| 8 |
+
and creates a character list for CTC training.
|
| 9 |
+
|
| 10 |
+
Usage:
|
| 11 |
+
python build_charlist_hindi.py --csv path/to/data.csv
|
| 12 |
+
|
| 13 |
+
Output:
|
| 14 |
+
../model_hindi/charList.txt
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import argparse
|
| 19 |
+
import pandas as pd
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def build_charlist(csv_path: str, output_path: str):
|
| 23 |
+
"""
|
| 24 |
+
Extract all unique characters from CSV and build charList.txt.
|
| 25 |
+
|
| 26 |
+
Format:
|
| 27 |
+
Index 0: '-' (CTC blank token)
|
| 28 |
+
Index 1: ' ' (space - critical for word boundaries)
|
| 29 |
+
Index 2+: All other characters in unicode order
|
| 30 |
+
"""
|
| 31 |
+
print(f"Loading dataset from: {csv_path}")
|
| 32 |
+
|
| 33 |
+
# Load CSV
|
| 34 |
+
df = pd.read_csv(csv_path)
|
| 35 |
+
print(f"Total samples: {len(df)}")
|
| 36 |
+
|
| 37 |
+
# Extract all unique characters
|
| 38 |
+
all_chars = set()
|
| 39 |
+
|
| 40 |
+
for text in df['text']:
|
| 41 |
+
all_chars.update(set(text))
|
| 42 |
+
|
| 43 |
+
# Remove empty string if present
|
| 44 |
+
all_chars.discard("")
|
| 45 |
+
|
| 46 |
+
print(f"\nFound {len(all_chars)} unique characters")
|
| 47 |
+
|
| 48 |
+
# Separate space from other characters
|
| 49 |
+
space_char = " "
|
| 50 |
+
other_chars = [c for c in all_chars if c != space_char]
|
| 51 |
+
|
| 52 |
+
# Sort other characters by unicode value
|
| 53 |
+
other_chars.sort(key=lambda c: ord(c))
|
| 54 |
+
|
| 55 |
+
# Build final charlist:
|
| 56 |
+
# Index 0 = '-' (CTC blank token) — MUST be first
|
| 57 |
+
# Index 1 = ' ' (space — critical for word boundaries)
|
| 58 |
+
# Index 2+ = all other characters in unicode order
|
| 59 |
+
final_charlist = "-" + space_char + "".join(other_chars)
|
| 60 |
+
|
| 61 |
+
# Make output directory if needed
|
| 62 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 63 |
+
|
| 64 |
+
# Save
|
| 65 |
+
with open(output_path, "w", encoding="utf-8") as f:
|
| 66 |
+
f.write(final_charlist)
|
| 67 |
+
|
| 68 |
+
print(f"\n✅ charList.txt saved to: {output_path}")
|
| 69 |
+
print(f" Total characters: {len(final_charlist)}")
|
| 70 |
+
print(f" Index 0 (CTC blank): '-'")
|
| 71 |
+
print(f" Index 1 (space): ' '")
|
| 72 |
+
print(f"\n First 50 characters:")
|
| 73 |
+
print(f" {repr(final_charlist[:50])}")
|
| 74 |
+
print(f"\n Sample Devanagari characters:")
|
| 75 |
+
|
| 76 |
+
# Show some sample Devanagari characters
|
| 77 |
+
devanagari_chars = [c for c in other_chars if '\u0900' <= c <= '\u097F']
|
| 78 |
+
print(f" {repr(''.join(devanagari_chars[:30]))}")
|
| 79 |
+
|
| 80 |
+
# Statistics
|
| 81 |
+
print(f"\n📊 Character set statistics:")
|
| 82 |
+
print(f" Devanagari consonants/vowels: {len(devanagari_chars)}")
|
| 83 |
+
print(f" Digits: {sum(c.isdigit() for c in other_chars)}")
|
| 84 |
+
print(f" Punctuation: {sum(not c.isalnum() for c in other_chars)}")
|
| 85 |
+
|
| 86 |
+
# Show character distribution
|
| 87 |
+
print(f"\n Unicode ranges:")
|
| 88 |
+
ranges = {
|
| 89 |
+
'Devanagari': (0x0900, 0x097F),
|
| 90 |
+
'ASCII': (0x0000, 0x007F),
|
| 91 |
+
'Latin Extended': (0x0080, 0x00FF),
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
for name, (start, end) in ranges.items():
|
| 95 |
+
count = sum(start <= ord(c) <= end for c in other_chars)
|
| 96 |
+
if count > 0:
|
| 97 |
+
print(f" {name}: {count} characters")
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def main():
|
| 101 |
+
parser = argparse.ArgumentParser(
|
| 102 |
+
description="Build character list for Hindi OCR dataset")
|
| 103 |
+
parser.add_argument("--csv", type=str, required=True,
|
| 104 |
+
help="Path to data.csv file")
|
| 105 |
+
parser.add_argument("--output", type=str,
|
| 106 |
+
default="../model_hindi/charList.txt",
|
| 107 |
+
help="Output path for charList.txt")
|
| 108 |
+
|
| 109 |
+
args = parser.parse_args()
|
| 110 |
+
|
| 111 |
+
if not os.path.exists(args.csv):
|
| 112 |
+
print(f"❌ Error: CSV file not found: {args.csv}")
|
| 113 |
+
return
|
| 114 |
+
|
| 115 |
+
build_charlist(args.csv, args.output)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
main()
|
main_hindi_v2.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
main_hindi_v2.py — FAST TRAINING VERSION
|
| 4 |
+
-------------------------------------------------------------
|
| 5 |
+
Optimized training with proper learning rate schedule.
|
| 6 |
+
|
| 7 |
+
KEY IMPROVEMENTS:
|
| 8 |
+
✅ Warmup + cosine decay learning rate
|
| 9 |
+
✅ Larger batch size = faster training
|
| 10 |
+
✅ Less verbose logging
|
| 11 |
+
✅ Better early stopping
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import division
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import sys
|
| 19 |
+
import argparse
|
| 20 |
+
import codecs
|
| 21 |
+
import gc
|
| 22 |
+
import json
|
| 23 |
+
import time
|
| 24 |
+
from datetime import datetime
|
| 25 |
+
import math
|
| 26 |
+
|
| 27 |
+
import cv2
|
| 28 |
+
import numpy as np
|
| 29 |
+
import tensorflow as tf
|
| 30 |
+
import editdistance
|
| 31 |
+
|
| 32 |
+
from DataLoader_Hindi_v2 import DataLoader, Batch
|
| 33 |
+
from Model_Hindi_v2 import Model, DecoderType
|
| 34 |
+
from SamplePreprocessor_Hindi_v2 import preprocess
|
| 35 |
+
|
| 36 |
+
tf.compat.v1.disable_eager_execution()
|
| 37 |
+
|
| 38 |
+
# ── Paths ─────────────────────────────────────────────────────────────────────
|
| 39 |
+
class FilePaths:
|
| 40 |
+
fnCharList = "model_hindi/charList.txt"
|
| 41 |
+
fnAccuracy = "model_hindi/accuracy.txt"
|
| 42 |
+
fnMetrics = "model_hindi/metrics.json"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# ── Learning rate schedule ────────────────────────────────────────────────────
|
| 46 |
+
def get_learning_rate(epoch, total_epochs=50):
|
| 47 |
+
"""
|
| 48 |
+
Warmup + Cosine decay learning rate.
|
| 49 |
+
|
| 50 |
+
Better than step decay for convergence.
|
| 51 |
+
"""
|
| 52 |
+
warmup_epochs = 3
|
| 53 |
+
base_lr = 0.001
|
| 54 |
+
min_lr = 0.00001
|
| 55 |
+
|
| 56 |
+
if epoch < warmup_epochs:
|
| 57 |
+
# Linear warmup
|
| 58 |
+
return base_lr * (epoch + 1) / warmup_epochs
|
| 59 |
+
else:
|
| 60 |
+
# Cosine decay
|
| 61 |
+
progress = (epoch - warmup_epochs) / (total_epochs - warmup_epochs)
|
| 62 |
+
return min_lr + (base_lr - min_lr) * 0.5 * (1 + math.cos(math.pi * progress))
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# ── Metrics tracking ──────────────────────────────────────────────────────────
|
| 66 |
+
class MetricsTracker:
|
| 67 |
+
"""Track training metrics."""
|
| 68 |
+
|
| 69 |
+
def __init__(self, filepath):
|
| 70 |
+
self.filepath = filepath
|
| 71 |
+
self.metrics = {
|
| 72 |
+
'train_loss': [],
|
| 73 |
+
'val_cer': [],
|
| 74 |
+
'val_word_acc': [],
|
| 75 |
+
'learning_rate': [],
|
| 76 |
+
'epochs': [],
|
| 77 |
+
'epoch_times': []
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
if os.path.exists(filepath):
|
| 81 |
+
try:
|
| 82 |
+
with open(filepath, 'r') as f:
|
| 83 |
+
self.metrics = json.load(f)
|
| 84 |
+
except:
|
| 85 |
+
pass
|
| 86 |
+
|
| 87 |
+
def add_epoch(self, epoch, train_loss, val_cer, val_word_acc, lr, epoch_time):
|
| 88 |
+
"""Add metrics for an epoch."""
|
| 89 |
+
self.metrics['epochs'].append(epoch)
|
| 90 |
+
self.metrics['train_loss'].append(float(train_loss))
|
| 91 |
+
self.metrics['val_cer'].append(float(val_cer))
|
| 92 |
+
self.metrics['val_word_acc'].append(float(val_word_acc))
|
| 93 |
+
self.metrics['learning_rate'].append(float(lr))
|
| 94 |
+
self.metrics['epoch_times'].append(float(epoch_time))
|
| 95 |
+
self.save()
|
| 96 |
+
|
| 97 |
+
def save(self):
|
| 98 |
+
"""Save metrics."""
|
| 99 |
+
with open(self.filepath, 'w') as f:
|
| 100 |
+
json.dump(self.metrics, f, indent=2)
|
| 101 |
+
|
| 102 |
+
def get_best_cer(self):
|
| 103 |
+
"""Get best CER."""
|
| 104 |
+
if not self.metrics['val_cer']:
|
| 105 |
+
return float('inf')
|
| 106 |
+
return min(self.metrics['val_cer'])
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# ── Training ──────────────────────────────────────────────────────────────────
|
| 110 |
+
def train(model: Model, loader: DataLoader, total_epochs=50):
|
| 111 |
+
"""Train with early stopping."""
|
| 112 |
+
epoch = model.lastEpoch
|
| 113 |
+
|
| 114 |
+
metrics_tracker = MetricsTracker(FilePaths.fnMetrics)
|
| 115 |
+
bestCharErrorRate = metrics_tracker.get_best_cer()
|
| 116 |
+
|
| 117 |
+
noImprovementSince = 0
|
| 118 |
+
earlyStopping = 10
|
| 119 |
+
|
| 120 |
+
while epoch < total_epochs:
|
| 121 |
+
epoch += 1
|
| 122 |
+
epoch_start = time.time()
|
| 123 |
+
|
| 124 |
+
print(f"\n{'='*80}")
|
| 125 |
+
print(f"Epoch {epoch}/{total_epochs}")
|
| 126 |
+
print(f"{'='*80}")
|
| 127 |
+
|
| 128 |
+
lr = get_learning_rate(epoch, total_epochs)
|
| 129 |
+
print(f"Learning rate: {lr:.6f}")
|
| 130 |
+
|
| 131 |
+
# ── Train ─────────────────────────────────────────────────────────────
|
| 132 |
+
print("Training...")
|
| 133 |
+
loader.trainSet(subset_size=10000)
|
| 134 |
+
epoch_loss = []
|
| 135 |
+
|
| 136 |
+
batch_count = 0
|
| 137 |
+
while loader.hasNext():
|
| 138 |
+
batch = loader.getNext()
|
| 139 |
+
loss = model.trainBatch(batch, learning_rate=lr)
|
| 140 |
+
epoch_loss.append(loss)
|
| 141 |
+
batch_count += 1
|
| 142 |
+
|
| 143 |
+
# Print progress every 50 batches
|
| 144 |
+
if batch_count % 50 == 0:
|
| 145 |
+
iterInfo = loader.getIteratorInfo()
|
| 146 |
+
print(f" Batch {iterInfo[0]:>4}/{iterInfo[1]} loss={loss:.4f}")
|
| 147 |
+
|
| 148 |
+
mean_loss = np.mean(epoch_loss)
|
| 149 |
+
epoch_time = time.time() - epoch_start
|
| 150 |
+
print(f" Epoch loss: {mean_loss:.4f} Time: {epoch_time/60:.1f}min")
|
| 151 |
+
|
| 152 |
+
loader.cleanup()
|
| 153 |
+
gc.collect()
|
| 154 |
+
|
| 155 |
+
# ── Validate ──────────────────────────────────────────────────────────
|
| 156 |
+
charErrorRate, wordAccuracy = validate(model, loader)
|
| 157 |
+
|
| 158 |
+
# Track metrics
|
| 159 |
+
metrics_tracker.add_epoch(epoch, mean_loss, charErrorRate,
|
| 160 |
+
wordAccuracy, lr, epoch_time)
|
| 161 |
+
|
| 162 |
+
# Checkpoint
|
| 163 |
+
if charErrorRate < bestCharErrorRate:
|
| 164 |
+
improvement = (bestCharErrorRate - charErrorRate) * 100
|
| 165 |
+
print(f"✅ CER improved by {improvement:.2f}pp → Saving model")
|
| 166 |
+
|
| 167 |
+
bestCharErrorRate = charErrorRate
|
| 168 |
+
noImprovementSince = 0
|
| 169 |
+
model.save(epoch)
|
| 170 |
+
|
| 171 |
+
with open(FilePaths.fnAccuracy, "w", encoding="utf-8") as f:
|
| 172 |
+
f.write(f"Epoch: {epoch}\n")
|
| 173 |
+
f.write(f"CER: {charErrorRate*100:.2f}%\n")
|
| 174 |
+
f.write(f"Word Acc: {wordAccuracy*100:.2f}%\n")
|
| 175 |
+
f.write(f"Loss: {mean_loss:.4f}\n")
|
| 176 |
+
else:
|
| 177 |
+
print(f" No improvement ({charErrorRate*100:.2f}% vs {bestCharErrorRate*100:.2f}%)")
|
| 178 |
+
noImprovementSince += 1
|
| 179 |
+
|
| 180 |
+
# Save every 5 epochs
|
| 181 |
+
if epoch % 5 == 0:
|
| 182 |
+
model.save(epoch)
|
| 183 |
+
|
| 184 |
+
gc.collect()
|
| 185 |
+
|
| 186 |
+
# Early stopping
|
| 187 |
+
if noImprovementSince >= earlyStopping:
|
| 188 |
+
print(f"\nEarly stopping: No improvement for {earlyStopping} epochs")
|
| 189 |
+
print(f"Best CER: {bestCharErrorRate*100:.2f}%")
|
| 190 |
+
break
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# ── Validation ────────────────────────────────────────────────────────────────
|
| 194 |
+
def validate(model: Model, loader: DataLoader):
|
| 195 |
+
"""Run validation."""
|
| 196 |
+
print("Validating...")
|
| 197 |
+
loader.validationSet()
|
| 198 |
+
|
| 199 |
+
numCharErr = 0
|
| 200 |
+
numCharTotal = 0
|
| 201 |
+
numWordOK = 0
|
| 202 |
+
numWordTotal = 0
|
| 203 |
+
|
| 204 |
+
batch_count = 0
|
| 205 |
+
|
| 206 |
+
while loader.hasNext():
|
| 207 |
+
batch = loader.getNext()
|
| 208 |
+
(recognized, _) = model.inferBatch(batch)
|
| 209 |
+
|
| 210 |
+
for i in range(len(recognized)):
|
| 211 |
+
gt = batch.gtTexts[i]
|
| 212 |
+
pred = recognized[i]
|
| 213 |
+
|
| 214 |
+
numWordOK += 1 if gt == pred else 0
|
| 215 |
+
numWordTotal += 1
|
| 216 |
+
|
| 217 |
+
dist = editdistance.eval(pred, gt)
|
| 218 |
+
numCharErr += dist
|
| 219 |
+
numCharTotal += len(gt)
|
| 220 |
+
|
| 221 |
+
# Print first few predictions
|
| 222 |
+
if batch_count == 0 and i < 3:
|
| 223 |
+
status = "[✓]" if dist == 0 else f"[✗ {dist}]"
|
| 224 |
+
print(f" {status} GT: '{gt[:60]}'")
|
| 225 |
+
print(f" PRD: '{pred[:60]}'")
|
| 226 |
+
|
| 227 |
+
batch_count += 1
|
| 228 |
+
|
| 229 |
+
charErrorRate = numCharErr / max(numCharTotal, 1)
|
| 230 |
+
wordAccuracy = numWordOK / max(numWordTotal, 1)
|
| 231 |
+
|
| 232 |
+
print(f"\n{'='*80}")
|
| 233 |
+
print(f"VALIDATION: CER={charErrorRate*100:.2f}% Word Acc={wordAccuracy*100:.2f}%")
|
| 234 |
+
print(f"{'='*80}")
|
| 235 |
+
|
| 236 |
+
loader.cleanup()
|
| 237 |
+
gc.collect()
|
| 238 |
+
|
| 239 |
+
return charErrorRate, wordAccuracy
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
# ── Inference ─────────────────────────────────────────────────────────────────
|
| 243 |
+
def infer(model: Model, img_path: str) -> str:
|
| 244 |
+
"""Inference on single image."""
|
| 245 |
+
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
|
| 246 |
+
if img is None:
|
| 247 |
+
raise FileNotFoundError(f"Cannot read: {img_path}")
|
| 248 |
+
|
| 249 |
+
processed = preprocess(img, Model.imgSize, dataAugmentation=False)
|
| 250 |
+
batch = Batch(imgs=[processed], gtTexts=None)
|
| 251 |
+
|
| 252 |
+
(recognized, probability) = model.inferBatch(batch, calcProbability=True)
|
| 253 |
+
|
| 254 |
+
print(f"Recognised: '{recognized[0]}'")
|
| 255 |
+
print(f"Probability: {probability[0]:.4f}")
|
| 256 |
+
|
| 257 |
+
return recognized[0]
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
| 261 |
+
def load_char_list() -> str:
|
| 262 |
+
if not os.path.exists(FilePaths.fnCharList):
|
| 263 |
+
raise FileNotFoundError(f"charList.txt not found at {FilePaths.fnCharList}")
|
| 264 |
+
with codecs.open(FilePaths.fnCharList, encoding="utf-8") as f:
|
| 265 |
+
return f.read()
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
| 269 |
+
def main():
|
| 270 |
+
parser = argparse.ArgumentParser(description="Hindi OCR - Fast Training")
|
| 271 |
+
|
| 272 |
+
parser.add_argument("--train", action="store_true", help="Train")
|
| 273 |
+
parser.add_argument("--validate", action="store_true", help="Validate")
|
| 274 |
+
parser.add_argument("--infer", action="store_true", help="Infer")
|
| 275 |
+
|
| 276 |
+
parser.add_argument("--dataset", type=str,
|
| 277 |
+
default="rajesh-1902/hindi-ocr-dataset",
|
| 278 |
+
help="HuggingFace dataset name")
|
| 279 |
+
parser.add_argument("--images", type=str, default="images/",
|
| 280 |
+
help="Path to images directory")
|
| 281 |
+
parser.add_argument("--image", type=str, default=None,
|
| 282 |
+
help="Image for inference")
|
| 283 |
+
|
| 284 |
+
parser.add_argument("--epochs", type=int, default=50,
|
| 285 |
+
help="Total epochs to train")
|
| 286 |
+
parser.add_argument("--beamsearch", action="store_true",
|
| 287 |
+
help="Use beam search decoder")
|
| 288 |
+
|
| 289 |
+
args = parser.parse_args()
|
| 290 |
+
|
| 291 |
+
decoderType = DecoderType.BeamSearch if args.beamsearch else DecoderType.BestPath
|
| 292 |
+
charList = load_char_list()
|
| 293 |
+
|
| 294 |
+
print(f"\nCharacter list: {len(charList)} characters")
|
| 295 |
+
print(f"Index 0: {repr(charList[0])} (blank)")
|
| 296 |
+
print(f"Index 1: {repr(charList[1])} (space)")
|
| 297 |
+
|
| 298 |
+
os.makedirs("model_hindi/", exist_ok=True)
|
| 299 |
+
|
| 300 |
+
if args.train or args.validate:
|
| 301 |
+
loader = DataLoader(
|
| 302 |
+
dataset_name=args.dataset,
|
| 303 |
+
images_dir=args.images,
|
| 304 |
+
batchSize=Model.batchSize,
|
| 305 |
+
imgSize=Model.imgSize,
|
| 306 |
+
maxTextLen=Model.maxTextLen,
|
| 307 |
+
charList=charList,
|
| 308 |
+
dataAugmentation=args.train,
|
| 309 |
+
val_split=0.1
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
# Save corpus
|
| 313 |
+
|
| 314 |
+
if args.train:
|
| 315 |
+
model = Model(charList, decoderType, mustRestore=False, lastEpoch=0)
|
| 316 |
+
try:
|
| 317 |
+
train(model, loader, total_epochs=args.epochs)
|
| 318 |
+
except KeyboardInterrupt:
|
| 319 |
+
print(f"\nTraining interrupted at epoch {model.lastEpoch}")
|
| 320 |
+
|
| 321 |
+
elif args.validate:
|
| 322 |
+
model = Model(charList, decoderType, mustRestore=True)
|
| 323 |
+
validate(model, loader)
|
| 324 |
+
|
| 325 |
+
elif args.infer:
|
| 326 |
+
if not args.image:
|
| 327 |
+
print("Error: --infer requires --image")
|
| 328 |
+
sys.exit(1)
|
| 329 |
+
model = Model(charList, decoderType, mustRestore=True)
|
| 330 |
+
infer(model, args.image)
|
| 331 |
+
|
| 332 |
+
else:
|
| 333 |
+
parser.print_help()
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
if __name__ == "__main__":
|
| 337 |
+
main()
|
model_hindi/accuracy.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Epoch: 4
|
| 2 |
+
CER: 0.62%
|
| 3 |
+
Word Acc: 84.03%
|
| 4 |
+
Loss: 1.6097
|
model_hindi/charList.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
- !"#$%'()*+,-./0123456789:;=?@_`|ँंःअआइईउऊऋएऐऑओऔकखगघचछजझञटठडढणतथदधनपफबभमयरऱलवशषसह़ािीुूृॅेैॉोौ्ख़ज़ड़ढ़फ़–‘’“”…₹
|
model_hindi/checkpoint
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_checkpoint_path: "snapshot-5"
|
| 2 |
+
all_model_checkpoint_paths: "snapshot-1"
|
| 3 |
+
all_model_checkpoint_paths: "snapshot-2"
|
| 4 |
+
all_model_checkpoint_paths: "snapshot-4"
|
| 5 |
+
all_model_checkpoint_paths: "snapshot-5"
|
model_hindi/metrics.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"train_loss": [
|
| 3 |
+
92.00146484375,
|
| 4 |
+
4.817220211029053,
|
| 5 |
+
2.3719639778137207,
|
| 6 |
+
1.6097475290298462,
|
| 7 |
+
1.2168760299682617
|
| 8 |
+
],
|
| 9 |
+
"val_cer": [
|
| 10 |
+
0.0749026983742714,
|
| 11 |
+
0.012815881115104564,
|
| 12 |
+
0.018309465725618725,
|
| 13 |
+
0.006242201903201177,
|
| 14 |
+
0.007158420082310658
|
| 15 |
+
],
|
| 16 |
+
"val_word_acc": [
|
| 17 |
+
0.1515625,
|
| 18 |
+
0.72609375,
|
| 19 |
+
0.62203125,
|
| 20 |
+
0.8403125,
|
| 21 |
+
0.81859375
|
| 22 |
+
],
|
| 23 |
+
"learning_rate": [
|
| 24 |
+
0.0006666666666666666,
|
| 25 |
+
0.001,
|
| 26 |
+
0.001,
|
| 27 |
+
0.0009509795896116975,
|
| 28 |
+
0.0008136274519200731
|
| 29 |
+
],
|
| 30 |
+
"epochs": [
|
| 31 |
+
1,
|
| 32 |
+
2,
|
| 33 |
+
3,
|
| 34 |
+
4,
|
| 35 |
+
5
|
| 36 |
+
],
|
| 37 |
+
"epoch_times": [
|
| 38 |
+
1097.182646036148,
|
| 39 |
+
1089.5408215522766,
|
| 40 |
+
1088.228083372116,
|
| 41 |
+
1079.393277645111,
|
| 42 |
+
1083.2388920783997
|
| 43 |
+
]
|
| 44 |
+
}
|
model_hindi/snapshot-1.data-00000-of-00001
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4291c4d3442d1caa732a2509144b5843896b2e424855009a89813b5ce4a5801a
|
| 3 |
+
size 95642112
|
model_hindi/snapshot-1.index
ADDED
|
Binary file (4.06 kB). View file
|
|
|
model_hindi/snapshot-1.meta
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a8513619f1dadb01b917437682f67dd532324fd8998745c5c658058d01cced60
|
| 3 |
+
size 765975
|
model_hindi/snapshot-2.data-00000-of-00001
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:02bffa6061034b19754288469c499d40338ecf59438f1ad33866309cd6a450e0
|
| 3 |
+
size 95642112
|
model_hindi/snapshot-2.index
ADDED
|
Binary file (4.06 kB). View file
|
|
|
model_hindi/snapshot-2.meta
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a8513619f1dadb01b917437682f67dd532324fd8998745c5c658058d01cced60
|
| 3 |
+
size 765975
|
model_hindi/snapshot-4.data-00000-of-00001
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dd04849088828eb6dcdf214571cd6645feb10c3aece45d460403e1f0c70c7a7d
|
| 3 |
+
size 95642112
|
model_hindi/snapshot-4.index
ADDED
|
Binary file (4.06 kB). View file
|
|
|
model_hindi/snapshot-4.meta
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a8513619f1dadb01b917437682f67dd532324fd8998745c5c658058d01cced60
|
| 3 |
+
size 765975
|
model_hindi/snapshot-5.data-00000-of-00001
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b9bc0e778b55ece9933bb335fa75c8933e9a224e58ec218ddd12b65f873619cf
|
| 3 |
+
size 95642112
|
model_hindi/snapshot-5.index
ADDED
|
Binary file (4.06 kB). View file
|
|
|
model_hindi/snapshot-5.meta
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a8513619f1dadb01b917437682f67dd532324fd8998745c5c658058d01cced60
|
| 3 |
+
size 765975
|