mirror of
https://github.com/qurator-spk/eynollah.git
synced 2026-07-11 22:29:29 +02:00
training.models for cnn-rnn-ocr: make ONNX convertible…
- `training.models.CTCDecoder`: switch back from `tf.nn.ctc_beam_search_decoder()` to `tf.nn.ctc_greedy_decoder()` (because ONNX only implements `CTCGreedyDecoder`) - `training.models.cnn_rnn_ocr_model(inference=True)` and `training.models.cnn_rnn_ocr_model4inference`: drop layer `tf.io.decode_raw()` (because ONNX does not implement `DecodePaddedRaw`) - `Eynollah_ocr.run_cnn()`: expect bytes arrays from predictor instead of uint8 - `predictor`: to prevent segfaults when sending `tf.string` results via `shared_memory`, convert `np.object` to `np.bytes_` directly
This commit is contained in:
parent
45168178dc
commit
948d841a7d
4 changed files with 18 additions and 21 deletions
|
|
@ -296,13 +296,11 @@ class Eynollah_ocr(Eynollah):
|
||||||
probs[ver_index > 0][flipped_ver_is_better] = probs_ver[flipped_ver_is_better]
|
probs[ver_index > 0][flipped_ver_is_better] = probs_ver[flipped_ver_is_better]
|
||||||
|
|
||||||
def nooov(x):
|
def nooov(x):
|
||||||
return x != b'[UNK]'
|
if x == b'[UNK]':
|
||||||
|
return b''
|
||||||
|
return x
|
||||||
for pred, prob in zip(preds, probs):
|
for pred, prob in zip(preds, probs):
|
||||||
text = b''.join(
|
text = b''.join(map(nooov, pred.tolist())).decode('utf-8')
|
||||||
filter(nooov,
|
|
||||||
map(bytes,
|
|
||||||
(filter(None, char)
|
|
||||||
for char in pred.tolist())))).decode('utf-8')
|
|
||||||
extracted_texts.append(text)
|
extracted_texts.append(text)
|
||||||
extracted_confs.append(prob)
|
extracted_confs.append(prob)
|
||||||
del cropped_lines_rgb
|
del cropped_lines_rgb
|
||||||
|
|
|
||||||
|
|
@ -191,13 +191,19 @@ class Predictor(mp.context.SpawnProcess):
|
||||||
#result = self.model.predict(data, verbose=0)
|
#result = self.model.predict(data, verbose=0)
|
||||||
# faster, less VRAM
|
# faster, less VRAM
|
||||||
result = self.model.predict_on_batch(data)
|
result = self.model.predict_on_batch(data)
|
||||||
if isinstance(result, tuple):
|
def make_shareable(x):
|
||||||
|
# convert tf.string/np.object to fixed-length bytes
|
||||||
|
# (because object segfaults in shm)
|
||||||
|
if x.dtype is np.dtype(object):
|
||||||
|
return x.astype(bytes)
|
||||||
|
return x
|
||||||
|
if isinstance(result, (list, tuple)):
|
||||||
multi_output = True
|
multi_output = True
|
||||||
results = zip(*(np.split(result0, len(jobs))
|
results = zip(*(np.split(make_shareable(result0), len(jobs))
|
||||||
for result0 in result))
|
for result0 in result))
|
||||||
else:
|
else:
|
||||||
multi_output = False
|
multi_output = False
|
||||||
results = np.split(result, len(jobs))
|
results = np.split(make_shareable(result), len(jobs))
|
||||||
#self.logger.debug("sharing result array for '%d'", jobid)
|
#self.logger.debug("sharing result array for '%d'", jobid)
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
for jobid, result in zip(jobs, results):
|
for jobid, result in zip(jobs, results):
|
||||||
|
|
|
||||||
|
|
@ -82,18 +82,17 @@ class CTCDecoder(Layer):
|
||||||
inputs = tf.math.log(
|
inputs = tf.math.log(
|
||||||
tf.transpose(inputs, perm=[1, 0, 2]) + tf.keras.backend.epsilon()
|
tf.transpose(inputs, perm=[1, 0, 2]) + tf.keras.backend.epsilon()
|
||||||
)
|
)
|
||||||
# tf.nn.ctc_greedy_decoder() is not as precise
|
|
||||||
# tf.compat.v1.nn.ctc_beam_search_decoder() also needs merge_repeated=False
|
# tf.compat.v1.nn.ctc_beam_search_decoder() also needs merge_repeated=False
|
||||||
decoded, logits = tf.nn.ctc_beam_search_decoder(
|
# tf.nn.ctc_beam_search_decoder() is not supported by ONNX, yet
|
||||||
|
# tf.nn.ctc_greedy_decoder() is not as precise, though:
|
||||||
|
decoded, logits = tf.nn.ctc_greedy_decoder(
|
||||||
inputs,
|
inputs,
|
||||||
lengths,
|
lengths,
|
||||||
beam_width=10,
|
|
||||||
top_paths=2,
|
|
||||||
)
|
)
|
||||||
# get top path for all sequences in batch
|
# get top path for all sequences in batch
|
||||||
decoded = decoded[0]
|
decoded = decoded[0]
|
||||||
logits = logits[:, 0] - logits[:, 1]
|
logits = logits[:, 0]
|
||||||
probs = tf.exp(-logits)
|
probs = tf.exp(-logits / n_steps)
|
||||||
# convert to dense
|
# convert to dense
|
||||||
outputs = tf.SparseTensor(decoded.indices, decoded.values,
|
outputs = tf.SparseTensor(decoded.indices, decoded.values,
|
||||||
(n_samples, n_steps))
|
(n_samples, n_steps))
|
||||||
|
|
@ -555,8 +554,6 @@ def cnn_rnn_ocr_model(input_height=None, input_width=None, n_classes=None, max_l
|
||||||
voc = char2num.get_vocabulary()
|
voc = char2num.get_vocabulary()
|
||||||
num2char = StringLookup(vocabulary=voc, invert=True)
|
num2char = StringLookup(vocabulary=voc, invert=True)
|
||||||
output = num2char(out)
|
output = num2char(out)
|
||||||
# avoid output tf.dtype=string → np.dtype=object (which cannot be shm-ed)
|
|
||||||
output = tf.io.decode_raw(output, tf.uint8, fixed_length=max(map(len, voc)))
|
|
||||||
|
|
||||||
return Model((inputs, inputs_bin), (output, prob))
|
return Model((inputs, inputs_bin), (output, prob))
|
||||||
|
|
||||||
|
|
@ -585,8 +582,6 @@ def cnn_rnn_ocr_model4inference(model, model_path):
|
||||||
voc = char2num.get_vocabulary()
|
voc = char2num.get_vocabulary()
|
||||||
num2char = StringLookup(vocabulary=voc, invert=True)
|
num2char = StringLookup(vocabulary=voc, invert=True)
|
||||||
output = num2char(output)
|
output = num2char(output)
|
||||||
# avoid output tf.dtype=string → np.dtype=object (which cannot be shm-ed)
|
|
||||||
output = tf.io.decode_raw(output, tf.uint8, fixed_length=max(map(len, voc)))
|
|
||||||
inputs = (inputs, inputs_bin)
|
inputs = (inputs, inputs_bin)
|
||||||
outputs = (output, prob)
|
outputs = (output, prob)
|
||||||
return Model(inputs, outputs)
|
return Model(inputs, outputs)
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,6 @@ $(MODELS_DST)/%.h5: $(MODELS_SRC)/%
|
||||||
$(MODELS_DST)/%.onnx: $(MODELS_SRC)/%
|
$(MODELS_DST)/%.onnx: $(MODELS_SRC)/%
|
||||||
if jq -e '.task == "segmentation" and .backbone_type == "transformer"' $</config.json &>/dev/null; then \
|
if jq -e '.task == "segmentation" and .backbone_type == "transformer"' $</config.json &>/dev/null; then \
|
||||||
echo skipping $@: vision transformer architecture currently does not work with ONNX; \
|
echo skipping $@: vision transformer architecture currently does not work with ONNX; \
|
||||||
elif jq -e '.task == "cnn-rnn-ocr"' $</config.json &>/dev/null || test x$(findstring _ocr,$@) = x_ocr; then \
|
|
||||||
echo skipping $@: OCR CTC decoder does not work with ONNX; \
|
|
||||||
else \
|
else \
|
||||||
eynollah-training convert \
|
eynollah-training convert \
|
||||||
$(and $(wildcard $</config.json),--rebuild) \
|
$(and $(wildcard $</config.json),--rebuild) \
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue