#include "recodebeam.h"
#include "networkio.h"
#include "pageres.h"
#include "unicharcompress.h"
#include <algorithm>
namespace tesseract {
const int RecodeBeamSearch::kBeamWidths[RecodedCharID::kMaxCodeLen + 1] = {
5, 10, 16, 16, 16, 16, 16, 16, 16, 16,
};
static const char *kNodeContNames[] = {"Anything", "OnlyDup", "NoDup"};
void RecodeNode::Print(int null_char, const UNICHARSET &unicharset,
int depth) const {
if (code == null_char) {
tprintf("null_char");
} else {
tprintf("label=%d, uid=%d=%s", code, unichar_id,
unicharset.debug_str(unichar_id).c_str());
}
tprintf(" score=%g, c=%g,%s%s%s perm=%d, hash=%" PRIx64, score, certainty,
start_of_dawg ? " DawgStart" : "", start_of_word ? " Start" : "",
end_of_word ? " End" : "", permuter, code_hash);
if (depth > 0 && prev != nullptr) {
tprintf(" prev:");
prev->Print(null_char, unicharset, depth - 1);
} else {
tprintf("\n");
}
}
RecodeBeamSearch::RecodeBeamSearch(const UnicharCompress &recoder,
int null_char, bool simple_text, Dict *dict)
: recoder_(recoder),
beam_size_(0),
top_code_(-1),
second_code_(-1),
dict_(dict),
space_delimited_(true),
is_simple_text_(simple_text),
null_char_(null_char) {
if (dict_ != nullptr && !dict_->IsSpaceDelimitedLang()) {
space_delimited_ = false;
}
}
RecodeBeamSearch::~RecodeBeamSearch() {
for (auto data : beam_) {
delete data;
}
for (auto data : secondary_beam_) {
delete data;
}
}
void RecodeBeamSearch::Decode(const NetworkIO &output, double dict_ratio,
double cert_offset, double worst_dict_cert,
const UNICHARSET *charset, int lstm_choice_mode) {
beam_size_ = 0;
int width = output.Width();
if (lstm_choice_mode) {
timesteps.clear();
}
for (int t = 0; t < width; ++t) {
ComputeTopN(output.f(t), output.NumFeatures(), kBeamWidths[0]);
DecodeStep(output.f(t), t, dict_ratio, cert_offset, worst_dict_cert,
charset);
if (lstm_choice_mode) {
SaveMostCertainChoices(output.f(t), output.NumFeatures(), charset, t);
}
}
}
void RecodeBeamSearch::Decode(const GENERIC_2D_ARRAY<float> &output,
double dict_ratio, double cert_offset,
double worst_dict_cert,
const UNICHARSET *charset) {
beam_size_ = 0;
int width = output.dim1();
for (int t = 0; t < width; ++t) {
ComputeTopN(output[t], output.dim2(), kBeamWidths[0]);
DecodeStep(output[t], t, dict_ratio, cert_offset, worst_dict_cert, charset);
}
}
void RecodeBeamSearch::DecodeSecondaryBeams(
const NetworkIO &output, double dict_ratio, double cert_offset,
double worst_dict_cert, const UNICHARSET *charset, int lstm_choice_mode) {
for (auto data : secondary_beam_) {
delete data;
}
secondary_beam_.clear();
if (character_boundaries_.size() < 2) {
return;
}
int width = output.Width();
unsigned bucketNumber = 0;
for (int t = 0; t < width; ++t) {
while ((bucketNumber + 1) < character_boundaries_.size() &&
t >= character_boundaries_[bucketNumber + 1]) {
++bucketNumber;
}
ComputeSecTopN(&(excludedUnichars)[bucketNumber], output.f(t),
output.NumFeatures(), kBeamWidths[0]);
DecodeSecondaryStep(output.f(t), t, dict_ratio, cert_offset,
worst_dict_cert, charset);
}
}
void RecodeBeamSearch::SaveMostCertainChoices(const float *outputs,
int num_outputs,
const UNICHARSET *charset,
int xCoord) {
std::vector<std::pair<const char *, float>> choices;
for (int i = 0; i < num_outputs; ++i) {
if (outputs[i] >= 0.01f) {
const char *character;
if (i + 2 >= num_outputs) {
character = "";
} else if (i > 0) {
character = charset->id_to_unichar_ext(i + 2);
} else {
character = charset->id_to_unichar_ext(i);
}
size_t pos = 0;
while (choices.size() > pos && choices[pos].second > outputs[i]) {
pos++;
}
choices.insert(choices.begin() + pos,
std::pair<const char *, float>(character, outputs[i]));
}
}
timesteps.push_back(choices);
}
void RecodeBeamSearch::segmentTimestepsByCharacters() {
for (unsigned i = 1; i < character_boundaries_.size(); ++i) {
std::vector<std::vector<std::pair<const char *, float>>> segment;
for (int j = character_boundaries_[i - 1]; j < character_boundaries_[i];
++j) {
segment.push_back(timesteps[j]);
}
segmentedTimesteps.push_back(segment);
}
}
std::vector<std::vector<std::pair<const char *, float>>>
RecodeBeamSearch::combineSegmentedTimesteps(
std::vector<std::vector<std::vector<std::pair<const char *, float>>>>
*segmentedTimesteps) {
std::vector<std::vector<std::pair<const char *, float>>> combined_timesteps;
for (auto &segmentedTimestep : *segmentedTimesteps) {
for (auto &j : segmentedTimestep) {
combined_timesteps.push_back(j);
}
}
return combined_timesteps;
}
void RecodeBeamSearch::calculateCharBoundaries(std::vector<int> *starts,
std::vector<int> *ends,
std::vector<int> *char_bounds_,
int maxWidth) {
char_bounds_->push_back(0);
for (unsigned i = 0; i < ends->size(); ++i) {
int middle = ((*starts)[i + 1] - (*ends)[i]) / 2;
char_bounds_->push_back((*ends)[i] + middle);
}
char_bounds_->pop_back();
char_bounds_->push_back(maxWidth);
}
void RecodeBeamSearch::ExtractBestPathAsLabels(
std::vector<int> *labels, std::vector<int> *xcoords) const {
labels->clear();
xcoords->clear();
std::vector<const RecodeNode *> best_nodes;
ExtractBestPaths(&best_nodes, nullptr);
int t = 0;
int width = best_nodes.size();
while (t < width) {
int label = best_nodes[t]->code;
if (label != null_char_) {
labels->push_back(label);
xcoords->push_back(t);
}
while (++t < width && !is_simple_text_ && best_nodes[t]->code == label) {
}
}
xcoords->push_back(width);
}
void RecodeBeamSearch::ExtractBestPathAsUnicharIds(
bool debug, const UNICHARSET *unicharset, std::vector<int> *unichar_ids,
std::vector<float> *certs, std::vector<float> *ratings,
std::vector<int> *xcoords) const {
std::vector<const RecodeNode *> best_nodes;
ExtractBestPaths(&best_nodes, nullptr);
ExtractPathAsUnicharIds(best_nodes, unichar_ids, certs, ratings, xcoords);
if (debug) {
DebugPath(unicharset, best_nodes);
DebugUnicharPath(unicharset, best_nodes, *unichar_ids, *certs, *ratings,
*xcoords);
}
}
void RecodeBeamSearch::ExtractBestPathAsWords(const TBOX &line_box,
float scale_factor, bool debug,
const UNICHARSET *unicharset,
PointerVector<WERD_RES> *words,
int lstm_choice_mode) {
words->truncate(0);
std::vector<int> unichar_ids;
std::vector<float> certs;
std::vector<float> ratings;
std::vector<int> xcoords;
std::vector<const RecodeNode *> best_nodes;
std::vector<const RecodeNode *> second_nodes;
character_boundaries_.clear();
ExtractBestPaths(&best_nodes, &second_nodes);
if (debug) {
DebugPath(unicharset, best_nodes);
ExtractPathAsUnicharIds(second_nodes, &unichar_ids, &certs, &ratings,
&xcoords);
tprintf("\nSecond choice path:\n");
DebugUnicharPath(unicharset, second_nodes, unichar_ids, certs, ratings,
xcoords);
}
ExtractPathAsUnicharIds(best_nodes, &unichar_ids, &certs, &ratings, &xcoords,
&character_boundaries_);
int num_ids = unichar_ids.size();
if (debug) {
DebugUnicharPath(unicharset, best_nodes, unichar_ids, certs, ratings,
xcoords);
}
int word_end = 0;
float prev_space_cert = 0.0f;
for (int word_start = 0; word_start < num_ids; word_start = word_end) {
for (word_end = word_start + 1; word_end < num_ids; ++word_end) {
if (unichar_ids[word_end] == UNICHAR_SPACE) {
break;
}
int index = xcoords[word_end];
if (best_nodes[index]->start_of_word) {
break;
}
if (best_nodes[index]->permuter == TOP_CHOICE_PERM &&
(!unicharset->IsSpaceDelimited(unichar_ids[word_end]) ||
!unicharset->IsSpaceDelimited(unichar_ids[word_end - 1]))) {
break;
}
}
float space_cert = 0.0f;
if (word_end < num_ids && unichar_ids[word_end] == UNICHAR_SPACE) {
space_cert = certs[word_end];
}
bool leading_space =
word_start > 0 && unichar_ids[word_start - 1] == UNICHAR_SPACE;
WERD_RES *word_res =
InitializeWord(leading_space, line_box, word_start, word_end,
std::min(space_cert, prev_space_cert), unicharset,
xcoords, scale_factor);
for (int i = word_start; i < word_end; ++i) {
auto *choices = new BLOB_CHOICE_LIST;
BLOB_CHOICE_IT bc_it(choices);
auto *choice = new BLOB_CHOICE(unichar_ids[i], ratings[i], certs[i], -1,
1.0f, static_cast<float>(INT16_MAX), 0.0f,
BCC_STATIC_CLASSIFIER);
int col = i - word_start;
choice->set_matrix_cell(col, col);
bc_it.add_after_then_move(choice);
word_res->ratings->put(col, col, choices);
}
int index = xcoords[word_end - 1];
word_res->FakeWordFromRatings(best_nodes[index]->permuter);
words->push_back(word_res);
prev_space_cert = space_cert;
if (word_end < num_ids && unichar_ids[word_end] == UNICHAR_SPACE) {
++word_end;
}
}
}
struct greater_than {
inline bool operator()(const RecodeNode *&node1, const RecodeNode *&node2) const {
return (node1->score > node2->score);
}
};
void RecodeBeamSearch::PrintBeam2(bool uids, int num_outputs,
const UNICHARSET *charset,
bool secondary) const {
std::vector<std::vector<const RecodeNode *>> topology;
std::unordered_set<const RecodeNode *> visited;
const std::vector<RecodeBeam *> &beam = !secondary ? beam_ : secondary_beam_;
for (int step = beam.size() - 1; step >= 0; --step) {
std::vector<const RecodeNode *> layer;
topology.push_back(layer);
}
for (int step = beam.size() - 1; step >= 0; --step) {
std::vector<tesseract::RecodePair> &heaps = beam.at(step)->beams_->heap();
for (auto &&node : heaps) {
int backtracker = 0;
const RecodeNode *curr = &node.data();
while (curr != nullptr && !visited.count(curr)) {
visited.insert(curr);
topology[step - backtracker].push_back(curr);
curr = curr->prev;
++backtracker;
}
}
}
int ct = 0;
unsigned cb = 1;
for (const std::vector<const RecodeNode *> &layer : topology) {
if (cb >= character_boundaries_.size()) {
break;
}
if (ct == character_boundaries_[cb]) {
tprintf("***\n");
++cb;
}
for (const RecodeNode *node : layer) {
const char *code;
int intCode;
if (node->unichar_id != INVALID_UNICHAR_ID) {
code = charset->id_to_unichar(node->unichar_id);
intCode = node->unichar_id;
} else if (node->code == null_char_) {
intCode = 0;
code = " ";
} else {
intCode = 666;
code = "*";
}
int intPrevCode = 0;
const char *prevCode;
float prevScore = 0;
if (node->prev != nullptr) {
prevScore = node->prev->score;
if (node->prev->unichar_id != INVALID_UNICHAR_ID) {
prevCode = charset->id_to_unichar(node->prev->unichar_id);
intPrevCode = node->prev->unichar_id;
} else if (node->code == null_char_) {
intPrevCode = 0;
prevCode = " ";
} else {
prevCode = "*";
intPrevCode = 666;
}
} else {
prevCode = " ";
}
if (uids) {
tprintf("%x(|)%f(>)%x(|)%f\n", intPrevCode, prevScore, intCode,
node->score);
} else {
tprintf("%s(|)%f(>)%s(|)%f\n", prevCode, prevScore, code, node->score);
}
}
tprintf("-\n");
++ct;
}
tprintf("***\n");
}
void RecodeBeamSearch::extractSymbolChoices(const UNICHARSET *unicharset) {
if (character_boundaries_.size() < 2) {
return;
}
std::vector<RecodeBeam *> ¤tBeam =
secondary_beam_.empty() ? beam_ : secondary_beam_;
character_boundaries_[0] = 0;
for (unsigned j = 1; j < character_boundaries_.size(); ++j) {
std::vector<int> unichar_ids;
std::vector<float> certs;
std::vector<float> ratings;
std::vector<int> xcoords;
int backpath = character_boundaries_[j] - character_boundaries_[j - 1];
std::vector<tesseract::RecodePair> &heaps =
currentBeam.at(character_boundaries_[j] - 1)->beams_->heap();
std::vector<const RecodeNode *> best_nodes;
std::vector<const RecodeNode *> best;
for (auto &&entry : heaps) {
bool validChar = false;
int backcounter = 0;
const RecodeNode *node = &entry.data();
while (node != nullptr && backcounter < backpath) {
if (node->code != null_char_ &&
node->unichar_id != INVALID_UNICHAR_ID) {
validChar = true;
break;
}
node = node->prev;
++backcounter;
}
if (validChar) {
best.push_back(&entry.data());
}
}
if (!best.empty()) {
std::sort(best.begin(), best.end(), greater_than());
ExtractPath(best[0], &best_nodes, backpath);
ExtractPathAsUnicharIds(best_nodes, &unichar_ids, &certs, &ratings,
&xcoords);
}
if (!unichar_ids.empty()) {
int bestPos = 0;
for (unsigned i = 1; i < unichar_ids.size(); ++i) {
if (ratings[i] < ratings[bestPos]) {
bestPos = i;
}
}
#if 0
int bestCode = -10;
for (auto &node : best_nodes) {
if (node->unichar_id == unichar_ids[bestPos]) {
bestCode = node->code;
}
}
#endif
std::unordered_set<int> excludeCodeList;
for (auto &best_node : best_nodes) {
if (best_node->code != null_char_) {
excludeCodeList.insert(best_node->code);
}
}
if (j - 1 < excludedUnichars.size()) {
for (auto elem : excludeCodeList) {
excludedUnichars[j - 1].insert(elem);
}
} else {
excludedUnichars.push_back(excludeCodeList);
}
if (j - 1 < ctc_choices.size()) {
int id = unichar_ids[bestPos];
const char *result = unicharset->id_to_unichar_ext(id);
float rating = ratings[bestPos];
ctc_choices[j - 1].push_back(
std::pair<const char *, float>(result, rating));
} else {
std::vector<std::pair<const char *, float>> choice;
int id = unichar_ids[bestPos];
const char *result = unicharset->id_to_unichar_ext(id);
float rating = ratings[bestPos];
choice.emplace_back(result, rating);
ctc_choices.push_back(choice);
}
} else {
if (j - 1 >= excludedUnichars.size()) {
std::unordered_set<int> excludeCodeList;
excludedUnichars.push_back(excludeCodeList);
}
if (j - 1 >= ctc_choices.size()) {
std::vector<std::pair<const char *, float>> choice;
ctc_choices.push_back(choice);
}
}
}
for (auto data : secondary_beam_) {
delete data;
}
secondary_beam_.clear();
}
void RecodeBeamSearch::DebugBeams(const UNICHARSET &unicharset) const {
for (int p = 0; p < beam_size_; ++p) {
for (int d = 0; d < 2; ++d) {
for (int c = 0; c < NC_COUNT; ++c) {
auto cont = static_cast<NodeContinuation>(c);
int index = BeamIndex(d, cont, 0);
if (beam_[p]->beams_[index].empty()) {
continue;
}
tprintf("Position %d: %s+%s beam\n", p, d ? "Dict" : "Non-Dict",
kNodeContNames[c]);
DebugBeamPos(unicharset, beam_[p]->beams_[index]);
}
}
}
}
void RecodeBeamSearch::DebugBeamPos(const UNICHARSET &unicharset,
const RecodeHeap &heap) const {
std::vector<const RecodeNode *> unichar_bests(unicharset.size());
const RecodeNode *null_best = nullptr;
int heap_size = heap.size();
for (int i = 0; i < heap_size; ++i) {
const RecodeNode *node = &heap.get(i).data();
if (node->unichar_id == INVALID_UNICHAR_ID) {
if (null_best == nullptr || null_best->score < node->score) {
null_best = node;
}
} else {
if (unichar_bests[node->unichar_id] == nullptr ||
unichar_bests[node->unichar_id]->score < node->score) {
unichar_bests[node->unichar_id] = node;
}
}
}
for (auto &unichar_best : unichar_bests) {
if (unichar_best != nullptr) {
const RecodeNode &node = *unichar_best;
node.Print(null_char_, unicharset, 1);
}
}
if (null_best != nullptr) {
null_best->Print(null_char_, unicharset, 1);
}
}
void RecodeBeamSearch::ExtractPathAsUnicharIds(
const std::vector<const RecodeNode *> &best_nodes,
std::vector<int> *unichar_ids, std::vector<float> *certs,
std::vector<float> *ratings, std::vector<int> *xcoords,
std::vector<int> *character_boundaries) {
unichar_ids->clear();
certs->clear();
ratings->clear();
xcoords->clear();
std::vector<int> starts;
std::vector<int> ends;
int t = 0;
int width = best_nodes.size();
while (t < width) {
double certainty = 0.0;
double rating = 0.0;
while (t < width && best_nodes[t]->unichar_id == INVALID_UNICHAR_ID) {
double cert = best_nodes[t++]->certainty;
if (cert < certainty) {
certainty = cert;
}
rating -= cert;
}
starts.push_back(t);
if (t < width) {
int unichar_id = best_nodes[t]->unichar_id;
if (unichar_id == UNICHAR_SPACE && !certs->empty() &&
best_nodes[t]->permuter != NO_PERM) {
if (certainty < certs->back()) {
certs->back() = certainty;
}
ratings->back() += rating;
certainty = 0.0;
rating = 0.0;
}
unichar_ids->push_back(unichar_id);
xcoords->push_back(t);
do {
double cert = best_nodes[t++]->certainty;
if (cert < certainty || (unichar_id == UNICHAR_SPACE &&
best_nodes[t - 1]->permuter == NO_PERM)) {
certainty = cert;
}
rating -= cert;
} while (t < width && best_nodes[t]->duplicate);
ends.push_back(t);
certs->push_back(certainty);
ratings->push_back(rating);
} else if (!certs->empty()) {
if (certainty < certs->back()) {
certs->back() = certainty;
}
ratings->back() += rating;
}
}
starts.push_back(width);
if (character_boundaries != nullptr) {
calculateCharBoundaries(&starts, &ends, character_boundaries, width);
}
xcoords->push_back(width);
}
WERD_RES *RecodeBeamSearch::InitializeWord(bool leading_space,
const TBOX &line_box, int word_start,
int word_end, float space_certainty,
const UNICHARSET *unicharset,
const std::vector<int> &xcoords,
float scale_factor) {
C_BLOB_LIST blobs;
C_BLOB_IT b_it(&blobs);
for (int i = word_start; i < word_end; ++i) {
if (static_cast<unsigned>(i + 1) < character_boundaries_.size()) {
TBOX box(static_cast<int16_t>(
std::floor(character_boundaries_[i] * scale_factor)) +
line_box.left(),
line_box.bottom(),
static_cast<int16_t>(
std::ceil(character_boundaries_[i + 1] * scale_factor)) +
line_box.left(),
line_box.top());
b_it.add_after_then_move(C_BLOB::FakeBlob(box));
}
}
WERD *word = new WERD(&blobs, leading_space, nullptr);
auto *word_res = new WERD_RES(word);
word_res->end = word_end - word_start + leading_space;
word_res->uch_set = unicharset;
word_res->combination = true;
word_res->space_certainty = space_certainty;
word_res->ratings = new MATRIX(word_end - word_start, 1);
return word_res;
}
void RecodeBeamSearch::ComputeTopN(const float *outputs, int num_outputs,
int top_n) {
top_n_flags_.clear();
top_n_flags_.resize(num_outputs, TN_ALSO_RAN);
top_code_ = -1;
second_code_ = -1;
top_heap_.clear();
for (int i = 0; i < num_outputs; ++i) {
if (top_heap_.size() < top_n || outputs[i] > top_heap_.PeekTop().key()) {
TopPair entry(outputs[i], i);
top_heap_.Push(&entry);
if (top_heap_.size() > top_n) {
top_heap_.Pop(&entry);
}
}
}
while (!top_heap_.empty()) {
TopPair entry;
top_heap_.Pop(&entry);
if (top_heap_.size() > 1) {
top_n_flags_[entry.data()] = TN_TOPN;
} else {
top_n_flags_[entry.data()] = TN_TOP2;
if (top_heap_.empty()) {
top_code_ = entry.data();
} else {
second_code_ = entry.data();
}
}
}
top_n_flags_[null_char_] = TN_TOP2;
}
void RecodeBeamSearch::ComputeSecTopN(std::unordered_set<int> *exList,
const float *outputs, int num_outputs,
int top_n) {
top_n_flags_.clear();
top_n_flags_.resize(num_outputs, TN_ALSO_RAN);
top_code_ = -1;
second_code_ = -1;
top_heap_.clear();
for (int i = 0; i < num_outputs; ++i) {
if ((top_heap_.size() < top_n || outputs[i] > top_heap_.PeekTop().key()) &&
!exList->count(i)) {
TopPair entry(outputs[i], i);
top_heap_.Push(&entry);
if (top_heap_.size() > top_n) {
top_heap_.Pop(&entry);
}
}
}
while (!top_heap_.empty()) {
TopPair entry;
top_heap_.Pop(&entry);
if (top_heap_.size() > 1) {
top_n_flags_[entry.data()] = TN_TOPN;
} else {
top_n_flags_[entry.data()] = TN_TOP2;
if (top_heap_.empty()) {
top_code_ = entry.data();
} else {
second_code_ = entry.data();
}
}
}
top_n_flags_[null_char_] = TN_TOP2;
}
void RecodeBeamSearch::DecodeStep(const float *outputs, int t,
double dict_ratio, double cert_offset,
double worst_dict_cert,
const UNICHARSET *charset, bool debug) {
if (t == static_cast<int>(beam_.size())) {
beam_.push_back(new RecodeBeam);
}
RecodeBeam *step = beam_[t];
beam_size_ = t + 1;
step->Clear();
if (t == 0) {
ContinueContext(nullptr, BeamIndex(false, NC_ANYTHING, 0), outputs, TN_TOP2,
charset, dict_ratio, cert_offset, worst_dict_cert, step);
if (dict_ != nullptr) {
ContinueContext(nullptr, BeamIndex(true, NC_ANYTHING, 0), outputs,
TN_TOP2, charset, dict_ratio, cert_offset,
worst_dict_cert, step);
}
} else {
RecodeBeam *prev = beam_[t - 1];
if (debug) {
int beam_index = BeamIndex(true, NC_ANYTHING, 0);
for (int i = prev->beams_[beam_index].size() - 1; i >= 0; --i) {
std::vector<const RecodeNode *> path;
ExtractPath(&prev->beams_[beam_index].get(i).data(), &path);
tprintf("Step %d: Dawg beam %d:\n", t, i);
DebugPath(charset, path);
}
beam_index = BeamIndex(false, NC_ANYTHING, 0);
for (int i = prev->beams_[beam_index].size() - 1; i >= 0; --i) {
std::vector<const RecodeNode *> path;
ExtractPath(&prev->beams_[beam_index].get(i).data(), &path);
tprintf("Step %d: Non-Dawg beam %d:\n", t, i);
DebugPath(charset, path);
}
}
int total_beam = 0;
for (int tn = 0; tn < TN_COUNT && total_beam == 0; ++tn) {
auto top_n = static_cast<TopNState>(tn);
for (int index = 0; index < kNumBeams; ++index) {
for (int i = prev->beams_[index].size() - 1; i >= 0; --i) {
ContinueContext(&prev->beams_[index].get(i).data(), index, outputs,
top_n, charset, dict_ratio, cert_offset,
worst_dict_cert, step);
}
}
for (int index = 0; index < kNumBeams; ++index) {
if (ContinuationFromBeamsIndex(index) == NC_ANYTHING) {
total_beam += step->beams_[index].size();
}
}
}
for (int c = 0; c < NC_COUNT; ++c) {
if (step->best_initial_dawgs_[c].code >= 0) {
int index = BeamIndex(true, static_cast<NodeContinuation>(c), 0);
RecodeHeap *dawg_heap = &step->beams_[index];
PushHeapIfBetter(kBeamWidths[0], &step->best_initial_dawgs_[c],
dawg_heap);
}
}
}
}
void RecodeBeamSearch::DecodeSecondaryStep(
const float *outputs, int t, double dict_ratio, double cert_offset,
double worst_dict_cert, const UNICHARSET *charset, bool debug) {
if (t == static_cast<int>(secondary_beam_.size())) {
secondary_beam_.push_back(new RecodeBeam);
}
RecodeBeam *step = secondary_beam_[t];
step->Clear();
if (t == 0) {
ContinueContext(nullptr, BeamIndex(false, NC_ANYTHING, 0), outputs, TN_TOP2,
charset, dict_ratio, cert_offset, worst_dict_cert, step);
if (dict_ != nullptr) {
ContinueContext(nullptr, BeamIndex(true, NC_ANYTHING, 0), outputs,
TN_TOP2, charset, dict_ratio, cert_offset,
worst_dict_cert, step);
}
} else {
RecodeBeam *prev = secondary_beam_[t - 1];
if (debug) {
int beam_index = BeamIndex(true, NC_ANYTHING, 0);
for (int i = prev->beams_[beam_index].size() - 1; i >= 0; --i) {
std::vector<const RecodeNode *> path;
ExtractPath(&prev->beams_[beam_index].get(i).data(), &path);
tprintf("Step %d: Dawg beam %d:\n", t, i);
DebugPath(charset, path);
}
beam_index = BeamIndex(false, NC_ANYTHING, 0);
for (int i = prev->beams_[beam_index].size() - 1; i >= 0; --i) {
std::vector<const RecodeNode *> path;
ExtractPath(&prev->beams_[beam_index].get(i).data(), &path);
tprintf("Step %d: Non-Dawg beam %d:\n", t, i);
DebugPath(charset, path);
}
}
int total_beam = 0;
for (int tn = 0; tn < TN_COUNT && total_beam == 0; ++tn) {
auto top_n = static_cast<TopNState>(tn);
for (int index = 0; index < kNumBeams; ++index) {
for (int i = prev->beams_[index].size() - 1; i >= 0; --i) {
ContinueContext(&prev->beams_[index].get(i).data(), index, outputs,
top_n, charset, dict_ratio, cert_offset,
worst_dict_cert, step);
}
}
for (int index = 0; index < kNumBeams; ++index) {
if (ContinuationFromBeamsIndex(index) == NC_ANYTHING) {
total_beam += step->beams_[index].size();
}
}
}
for (int c = 0; c < NC_COUNT; ++c) {
if (step->best_initial_dawgs_[c].code >= 0) {
int index = BeamIndex(true, static_cast<NodeContinuation>(c), 0);
RecodeHeap *dawg_heap = &step->beams_[index];
PushHeapIfBetter(kBeamWidths[0], &step->best_initial_dawgs_[c],
dawg_heap);
}
}
}
}
void RecodeBeamSearch::ContinueContext(
const RecodeNode *prev, int index, const float *outputs,
TopNState top_n_flag, const UNICHARSET *charset, double dict_ratio,
double cert_offset, double worst_dict_cert, RecodeBeam *step) {
RecodedCharID prefix;
RecodedCharID full_code;
const RecodeNode *previous = prev;
int length = LengthFromBeamsIndex(index);
bool use_dawgs = IsDawgFromBeamsIndex(index);
NodeContinuation prev_cont = ContinuationFromBeamsIndex(index);
for (int p = length - 1; p >= 0 && previous != nullptr; --p) {
while (previous->duplicate || previous->code == null_char_) {
previous = previous->prev;
}
prefix.Set(p, previous->code);
full_code.Set(p, previous->code);
previous = previous->prev;
}
if (prev != nullptr && !is_simple_text_) {
if (top_n_flags_[prev->code] == top_n_flag) {
if (prev_cont != NC_NO_DUP) {
float cert =
NetworkIO::ProbToCertainty(outputs[prev->code]) + cert_offset;
PushDupOrNoDawgIfBetter(length, true, prev->code, prev->unichar_id,
cert, worst_dict_cert, dict_ratio, use_dawgs,
NC_ANYTHING, prev, step);
}
if (prev_cont == NC_ANYTHING && top_n_flag == TN_TOP2 &&
prev->code != null_char_) {
float cert = NetworkIO::ProbToCertainty(outputs[prev->code] +
outputs[null_char_]) +
cert_offset;
PushDupOrNoDawgIfBetter(length, true, prev->code, prev->unichar_id,
cert, worst_dict_cert, dict_ratio, use_dawgs,
NC_NO_DUP, prev, step);
}
}
if (prev_cont == NC_ONLY_DUP) {
return;
}
if (prev->code != null_char_ && length > 0 &&
top_n_flags_[null_char_] == top_n_flag) {
float cert =
NetworkIO::ProbToCertainty(outputs[null_char_]) + cert_offset;
PushDupOrNoDawgIfBetter(length, false, null_char_, INVALID_UNICHAR_ID,
cert, worst_dict_cert, dict_ratio, use_dawgs,
NC_ANYTHING, prev, step);
}
}
const std::vector<int> *final_codes = recoder_.GetFinalCodes(prefix);
if (final_codes != nullptr) {
for (int code : *final_codes) {
if (top_n_flags_[code] != top_n_flag) {
continue;
}
if (prev != nullptr && prev->code == code && !is_simple_text_) {
continue;
}
float cert = NetworkIO::ProbToCertainty(outputs[code]) + cert_offset;
if (cert < kMinCertainty && code != null_char_) {
continue;
}
full_code.Set(length, code);
int unichar_id = recoder_.DecodeUnichar(full_code);
if (length == 0 && code == null_char_) {
unichar_id = INVALID_UNICHAR_ID;
}
if (unichar_id != INVALID_UNICHAR_ID && charset != nullptr &&
!charset->get_enabled(unichar_id)) {
continue;
}
ContinueUnichar(code, unichar_id, cert, worst_dict_cert, dict_ratio,
use_dawgs, NC_ANYTHING, prev, step);
if (top_n_flag == TN_TOP2 && code != null_char_) {
float prob = outputs[code] + outputs[null_char_];
if (prev != nullptr && prev_cont == NC_ANYTHING &&
prev->code != null_char_ &&
((prev->code == top_code_ && code == second_code_) ||
(code == top_code_ && prev->code == second_code_))) {
prob += outputs[prev->code];
}
cert = NetworkIO::ProbToCertainty(prob) + cert_offset;
ContinueUnichar(code, unichar_id, cert, worst_dict_cert, dict_ratio,
use_dawgs, NC_ONLY_DUP, prev, step);
}
}
}
const std::vector<int> *next_codes = recoder_.GetNextCodes(prefix);
if (next_codes != nullptr) {
for (int code : *next_codes) {
if (top_n_flags_[code] != top_n_flag) {
continue;
}
if (prev != nullptr && prev->code == code && !is_simple_text_) {
continue;
}
float cert = NetworkIO::ProbToCertainty(outputs[code]) + cert_offset;
PushDupOrNoDawgIfBetter(length + 1, false, code, INVALID_UNICHAR_ID, cert,
worst_dict_cert, dict_ratio, use_dawgs,
NC_ANYTHING, prev, step);
if (top_n_flag == TN_TOP2 && code != null_char_) {
float prob = outputs[code] + outputs[null_char_];
if (prev != nullptr && prev_cont == NC_ANYTHING &&
prev->code != null_char_ &&
((prev->code == top_code_ && code == second_code_) ||
(code == top_code_ && prev->code == second_code_))) {
prob += outputs[prev->code];
}
cert = NetworkIO::ProbToCertainty(prob) + cert_offset;
PushDupOrNoDawgIfBetter(length + 1, false, code, INVALID_UNICHAR_ID,
cert, worst_dict_cert, dict_ratio, use_dawgs,
NC_ONLY_DUP, prev, step);
}
}
}
}
void RecodeBeamSearch::ContinueUnichar(int code, int unichar_id, float cert,
float worst_dict_cert, float dict_ratio,
bool use_dawgs, NodeContinuation cont,
const RecodeNode *prev,
RecodeBeam *step) {
if (use_dawgs) {
if (cert > worst_dict_cert) {
ContinueDawg(code, unichar_id, cert, cont, prev, step);
}
} else {
RecodeHeap *nodawg_heap = &step->beams_[BeamIndex(false, cont, 0)];
PushHeapIfBetter(kBeamWidths[0], code, unichar_id, TOP_CHOICE_PERM, false,
false, false, false, cert * dict_ratio, prev, nullptr,
nodawg_heap);
if (dict_ != nullptr &&
((unichar_id == UNICHAR_SPACE && cert > worst_dict_cert) ||
!dict_->getUnicharset().IsSpaceDelimited(unichar_id))) {
float dawg_cert = cert;
PermuterType permuter = TOP_CHOICE_PERM;
if (unichar_id == UNICHAR_SPACE) {
permuter = NO_PERM;
} else {
dawg_cert *= dict_ratio;
}
PushInitialDawgIfBetter(code, unichar_id, permuter, false, false,
dawg_cert, cont, prev, step);
}
}
}
void RecodeBeamSearch::ContinueDawg(int code, int unichar_id, float cert,
NodeContinuation cont,
const RecodeNode *prev, RecodeBeam *step) {
RecodeHeap *dawg_heap = &step->beams_[BeamIndex(true, cont, 0)];
RecodeHeap *nodawg_heap = &step->beams_[BeamIndex(false, cont, 0)];
if (unichar_id == INVALID_UNICHAR_ID) {
PushHeapIfBetter(kBeamWidths[0], code, unichar_id, NO_PERM, false, false,
false, false, cert, prev, nullptr, dawg_heap);
return;
}
float score = cert;
if (prev != nullptr) {
score += prev->score;
}
if (dawg_heap->size() >= kBeamWidths[0] &&
score <= dawg_heap->PeekTop().data().score &&
nodawg_heap->size() >= kBeamWidths[0] &&
score <= nodawg_heap->PeekTop().data().score) {
return;
}
const RecodeNode *uni_prev = prev;
while (uni_prev != nullptr &&
(uni_prev->unichar_id == INVALID_UNICHAR_ID || uni_prev->duplicate)) {
uni_prev = uni_prev->prev;
}
if (unichar_id == UNICHAR_SPACE) {
if (uni_prev != nullptr && uni_prev->end_of_word) {
PushInitialDawgIfBetter(code, unichar_id, uni_prev->permuter, false,
false, cert, cont, prev, step);
PushHeapIfBetter(kBeamWidths[0], code, unichar_id, uni_prev->permuter,
false, false, false, false, cert, prev, nullptr,
nodawg_heap);
}
return;
} else if (uni_prev != nullptr && uni_prev->start_of_dawg &&
uni_prev->unichar_id != UNICHAR_SPACE &&
dict_->getUnicharset().IsSpaceDelimited(uni_prev->unichar_id) &&
dict_->getUnicharset().IsSpaceDelimited(unichar_id)) {
return;
}
DawgPositionVector initial_dawgs;
auto *updated_dawgs = new DawgPositionVector;
DawgArgs dawg_args(&initial_dawgs, updated_dawgs, NO_PERM);
bool word_start = false;
if (uni_prev == nullptr) {
dict_->default_dawgs(&initial_dawgs, false);
word_start = true;
} else if (uni_prev->dawgs != nullptr) {
dawg_args.active_dawgs = uni_prev->dawgs;
word_start = uni_prev->start_of_dawg;
} else {
return;
}
auto permuter = static_cast<PermuterType>(dict_->def_letter_is_okay(
&dawg_args, dict_->getUnicharset(), unichar_id, false));
if (permuter != NO_PERM) {
PushHeapIfBetter(kBeamWidths[0], code, unichar_id, permuter, false,
word_start, dawg_args.valid_end, false, cert, prev,
dawg_args.updated_dawgs, dawg_heap);
if (dawg_args.valid_end && !space_delimited_) {
PushInitialDawgIfBetter(code, unichar_id, permuter, word_start, true,
cert, cont, prev, step);
PushHeapIfBetter(kBeamWidths[0], code, unichar_id, permuter, false,
word_start, true, false, cert, prev, nullptr,
nodawg_heap);
}
} else {
delete updated_dawgs;
}
}
void RecodeBeamSearch::PushInitialDawgIfBetter(int code, int unichar_id,
PermuterType permuter,
bool start, bool end, float cert,
NodeContinuation cont,
const RecodeNode *prev,
RecodeBeam *step) {
RecodeNode *best_initial_dawg = &step->best_initial_dawgs_[cont];
float score = cert;
if (prev != nullptr) {
score += prev->score;
}
if (best_initial_dawg->code < 0 || score > best_initial_dawg->score) {
auto *initial_dawgs = new DawgPositionVector;
dict_->default_dawgs(initial_dawgs, false);
RecodeNode node(code, unichar_id, permuter, true, start, end, false, cert,
score, prev, initial_dawgs,
ComputeCodeHash(code, false, prev));
*best_initial_dawg = node;
}
}
void RecodeBeamSearch::PushDupOrNoDawgIfBetter(
int length, bool dup, int code, int unichar_id, float cert,
float worst_dict_cert, float dict_ratio, bool use_dawgs,
NodeContinuation cont, const RecodeNode *prev, RecodeBeam *step) {
int index = BeamIndex(use_dawgs, cont, length);
if (use_dawgs) {
if (cert > worst_dict_cert) {
PushHeapIfBetter(kBeamWidths[length], code, unichar_id,
prev ? prev->permuter : NO_PERM, false, false, false,
dup, cert, prev, nullptr, &step->beams_[index]);
}
} else {
cert *= dict_ratio;
if (cert >= kMinCertainty || code == null_char_) {
PushHeapIfBetter(kBeamWidths[length], code, unichar_id,
prev ? prev->permuter : TOP_CHOICE_PERM, false, false,
false, dup, cert, prev, nullptr, &step->beams_[index]);
}
}
}
void RecodeBeamSearch::PushHeapIfBetter(int max_size, int code, int unichar_id,
PermuterType permuter, bool dawg_start,
bool word_start, bool end, bool dup,
float cert, const RecodeNode *prev,
DawgPositionVector *d,
RecodeHeap *heap) {
float score = cert;
if (prev != nullptr) {
score += prev->score;
}
if (heap->size() < max_size || score > heap->PeekTop().data().score) {
uint64_t hash = ComputeCodeHash(code, dup, prev);
RecodeNode node(code, unichar_id, permuter, dawg_start, word_start, end,
dup, cert, score, prev, d, hash);
if (UpdateHeapIfMatched(&node, heap)) {
return;
}
RecodePair entry(score, node);
heap->Push(&entry);
ASSERT_HOST(entry.data().dawgs == nullptr);
if (heap->size() > max_size) {
heap->Pop(&entry);
}
} else {
delete d;
}
}
void RecodeBeamSearch::PushHeapIfBetter(int max_size, RecodeNode *node,
RecodeHeap *heap) {
if (heap->size() < max_size || node->score > heap->PeekTop().data().score) {
if (UpdateHeapIfMatched(node, heap)) {
return;
}
RecodePair entry(node->score, *node);
heap->Push(&entry);
ASSERT_HOST(entry.data().dawgs == nullptr);
if (heap->size() > max_size) {
heap->Pop(&entry);
}
}
}
bool RecodeBeamSearch::UpdateHeapIfMatched(RecodeNode *new_node,
RecodeHeap *heap) {
std::vector<RecodePair> &nodes = heap->heap();
for (auto &i : nodes) {
RecodeNode &node = i.data();
if (node.code == new_node->code && node.code_hash == new_node->code_hash &&
node.permuter == new_node->permuter &&
node.start_of_dawg == new_node->start_of_dawg) {
if (new_node->score > node.score) {
node = *new_node;
i.key() = node.score;
heap->Reshuffle(&i);
}
return true;
}
}
return false;
}
uint64_t RecodeBeamSearch::ComputeCodeHash(int code, bool dup,
const RecodeNode *prev) const {
uint64_t hash = prev == nullptr ? 0 : prev->code_hash;
if (!dup && code != null_char_) {
int num_classes = recoder_.code_range();
uint64_t carry = (((hash >> 32) * num_classes) >> 32);
hash *= num_classes;
hash += carry;
hash += code;
}
return hash;
}
void RecodeBeamSearch::ExtractBestPaths(
std::vector<const RecodeNode *> *best_nodes,
std::vector<const RecodeNode *> *second_nodes) const {
const RecodeNode *best_node = nullptr;
const RecodeNode *second_best_node = nullptr;
const RecodeBeam *last_beam = beam_[beam_size_ - 1];
for (int c = 0; c < NC_COUNT; ++c) {
if (c == NC_ONLY_DUP) {
continue;
}
auto cont = static_cast<NodeContinuation>(c);
for (int is_dawg = 0; is_dawg < 2; ++is_dawg) {
int beam_index = BeamIndex(is_dawg, cont, 0);
int heap_size = last_beam->beams_[beam_index].size();
for (int h = 0; h < heap_size; ++h) {
const RecodeNode *node = &last_beam->beams_[beam_index].get(h).data();
if (is_dawg) {
const RecodeNode *dawg_node = node;
while (dawg_node != nullptr &&
(dawg_node->unichar_id == INVALID_UNICHAR_ID ||
dawg_node->duplicate)) {
dawg_node = dawg_node->prev;
}
if (dawg_node == nullptr ||
(!dawg_node->end_of_word &&
dawg_node->unichar_id != UNICHAR_SPACE)) {
continue;
}
}
if (best_node == nullptr || node->score > best_node->score) {
second_best_node = best_node;
best_node = node;
} else if (second_best_node == nullptr ||
node->score > second_best_node->score) {
second_best_node = node;
}
}
}
}
if (second_nodes != nullptr) {
ExtractPath(second_best_node, second_nodes);
}
ExtractPath(best_node, best_nodes);
}
void RecodeBeamSearch::ExtractPath(
const RecodeNode *node, std::vector<const RecodeNode *> *path) const {
path->clear();
while (node != nullptr) {
path->push_back(node);
node = node->prev;
}
std::reverse(path->begin(), path->end());
}
void RecodeBeamSearch::ExtractPath(const RecodeNode *node,
std::vector<const RecodeNode *> *path,
int limiter) const {
int pathcounter = 0;
path->clear();
while (node != nullptr && pathcounter < limiter) {
path->push_back(node);
node = node->prev;
++pathcounter;
}
std::reverse(path->begin(), path->end());
}
void RecodeBeamSearch::DebugPath(
const UNICHARSET *unicharset,
const std::vector<const RecodeNode *> &path) const {
for (unsigned c = 0; c < path.size(); ++c) {
const RecodeNode &node = *path[c];
tprintf("%u ", c);
node.Print(null_char_, *unicharset, 1);
}
}
void RecodeBeamSearch::DebugUnicharPath(
const UNICHARSET *unicharset, const std::vector<const RecodeNode *> &path,
const std::vector<int> &unichar_ids, const std::vector<float> &certs,
const std::vector<float> &ratings, const std::vector<int> &xcoords) const {
auto num_ids = unichar_ids.size();
double total_rating = 0.0;
for (unsigned c = 0; c < num_ids; ++c) {
int coord = xcoords[c];
tprintf("%d %d=%s r=%g, c=%g, s=%d, e=%d, perm=%d\n", coord, unichar_ids[c],
unicharset->debug_str(unichar_ids[c]).c_str(), ratings[c], certs[c],
path[coord]->start_of_word, path[coord]->end_of_word,
path[coord]->permuter);
total_rating += ratings[c];
}
tprintf("Path total rating = %g\n", total_rating);
}
}