Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lexiconfree beam search #101

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open

Lexiconfree beam search #101

wants to merge 34 commits into from

Conversation

SimBe195
Copy link
Collaborator

@SimBe195 SimBe195 commented Feb 19, 2025

Simple time synchronous beam search algorithm based on the new SearchAlgorithmV2 interface. Does not use (proper) pronunciation lexicon, word-level LM or transition model. Performs special handling of blank if a blank index is set. Main purpose is open vocabulary search with CTC/Neural Transducer (or similar) models.

Supports global pruning by max beam-size and by score difference to the best hypothesis. Uses a LabelScorer to context initialization/extension and scoring.

The search requires a lexicon that represents the vocabulary. Each lemma is viewed as a token with its index in the lexicon corresponding to the associated output index of the LabelScorer.

Depends on #103 and #104.

Simon Berger added 2 commits February 19, 2025 19:10

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@larissakl
Copy link
Contributor

Two general points:

  1. How should this search algorithm be used? Are you planning to put the enum SearchTypeV2, the related ParameterChoice and Module_::createSearchAlgorithm() to Module.hh/.cc later? Or do you have a different plan?
  2. Would it maybe be a good idea to factor out the struct TimeStatistic and its related code so that it can be reused in other search algorithms?

@SimBe195
Copy link
Collaborator Author

SimBe195 commented Feb 20, 2025

1. How should this search algorithm be used? Are you planning to put the `enum SearchTypeV2`, the related `ParameterChoice` and `Module_::createSearchAlgorithm()` to Module.hh/.cc later? Or do you have a different plan?

With this PR alone, the search algorithm is not usable yet. I will make PR's for an Flf node and python bindings separately. But I can include the createSearchAlgorithm() function already here.

2. Would it maybe be a good idea to factor out the `struct TimeStatistic` and its related code so that it can be reused in other search algorithms?

Yeah, probably. Maybe even into Core?

log() << extensions.size() << " candidates survived beam pruning";
}

std::sort(extensions.begin(), extensions.end());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they actually have to be sorted if no score pruning is done? If not, I would put that in the if-statement below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the recombination also requires the hypotheses to be sorted and we also want beam.front() to be the single-best in the end.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you can find the first-best hypotheses faster than in N * log(N). So I would recommend to just compute the minimum for that. Pruning can also be done in linear time. In recombination you don't need order either, you can just check the scores, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order is needed in the recombination because it is assumed that the best hypothesis is first and if there is a hypothesis with the same scoring context later, we know that its score is worse.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can rewrite the pruning and recombination functions to not depend on sorted hypotheses.

@SimBe195 SimBe195 changed the base branch from master to lattice_traces March 5, 2025 14:15
Comment on lines 80 to 81
static const Core::ParameterBool paramUseSentenceEnd;
static const Core::ParameterBool paramSentenceEndIndex;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two are currently not used. I don't know if you want keep them if at some point you introduce sentence-end handling or if you want to remove them for now.

Copy link
Contributor

@curufinwe curufinwe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global comment: It would be nice if the order of method definitions would match the order of declarations in the class.

Comment on lines +79 to +80
SearchAlgorithmV2* Module_::createSearchAlgorithm(const Core::Configuration& config) const {
SearchAlgorithmV2* searchAlgorithm = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SearchAlgorithmV2* Module_::createSearchAlgorithm(const Core::Configuration& config) const {
SearchAlgorithmV2* searchAlgorithm = 0;
SearchAlgorithmV2* Module_::createSearchAlgorithmV2(const Core::Configuration& config) const {
SearchAlgorithmV2* searchAlgorithm = nullptr;

SearchAlgorithm* createRecognizer(SearchType type, const Core::Configuration& config) const;
LatticeHandler* createLatticeHandler(const Core::Configuration& c) const;
SearchAlgorithm* createRecognizer(SearchType type, const Core::Configuration& config) const;
SearchAlgorithmV2* createSearchAlgorithm(const Core::Configuration& config) const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SearchAlgorithmV2* createSearchAlgorithm(const Core::Configuration& config) const;
SearchAlgorithmV2* createSearchAlgorithmV2(const Core::Configuration& config) const;

Comment on lines 16 to 21
#include "Traceback.hh"
#include <Lattice/LatticeAdaptor.hh>
#include <Speech/Types.hh>
#include <stack>

#include <stack>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "Traceback.hh"
#include <Lattice/LatticeAdaptor.hh>
#include <Speech/Types.hh>
#include <stack>
#include <stack>
#include "Traceback.hh"
#include <stack>
#include <Lattice/LatticeAdaptor.hh>
#include <Speech/Types.hh>

@@ -46,6 +46,7 @@ CHECK_O = $(OBJDIR)/check.o \
../Mm/libSprintMm.$(a) \
../Mc/libSprintMc.$(a) \
../Search/libSprintSearch.$(a) \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better: use LIBS_SEARCH here and also for all other Makefiles below.

@@ -12,6 +12,7 @@ TARGETS = archiver$(exe)
ARCHIVER_O = $(OBJDIR)/Archiver.o \
../../Speech/libSprintSpeech.$(a) \
../../Search/libSprintSearch.$(a) \
../../Search/LexiconfreeTimesyncBeamSearch/libSprintLexiconfreeTimesyncBeamSearch.$(a) \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct indentation please.

/*
* Collect all possible extensions for all hypotheses in the beam.
*/
std::vector<ExtensionCandidate> extensions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In AdvancedTreeSearch the vector for hypotheses expansion is also part of the class and not reallocated in every step. Maybe you could consider doing the same.

log() << extensions.size() << " candidates survived beam pruning";
}

std::sort(extensions.begin(), extensions.end());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you can find the first-best hypotheses faster than in N * log(N). So I would recommend to just compute the minimum for that. Pruning can also be done in linear time. In recombination you don't need order either, you can just check the scores, right?

/*
* Create new beam from surviving extensions.
*/
std::vector<LabelHypothesis> newBeam;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above on preallocating vectors.

scorePruning(extensions);

if (debugLogging_) {
log() << extensions.size() << " candidates survived score pruning";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could compute the min/avg/max statistics and log them at the end of the segment like in advanced-tree-search.

* Create scoring requests for the label scorer.
* Each extension candidate makes up a request.
*/
std::vector<Nn::LabelScorer::Request> requests;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid filling that extensions vector above and directly fill out the requests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could maybe combine filling the requests and the extensions vector in the same loop? But I don't think we can completely avoid the extensions vector.

Base automatically changed from lattice_traces to master March 21, 2025 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants