decode method
Implementation
Future<List<KeywordDetection>> decode(KwsEncoderOutput encoder) async {
if (encoder.values.length != encoder.frameCount * joinerDimension) {
throw StateError('Invalid encoder output shape');
}
final detections = <KeywordDetection>[];
for (var frame = 0; frame < encoder.frameCount; frame++) {
final previous = _hypotheses.values.toList(growable: false);
final contexts = Int32List(previous.length * contextSize);
for (var i = 0; i < previous.length; i++) {
final tokens = previous[i].tokens;
contexts[i * 2] = tokens[tokens.length - 2];
contexts[i * 2 + 1] = tokens.last;
}
final decoderVectors = await _backend.runDecoder(contexts);
final encoderVectors = Float32List(previous.length * joinerDimension);
final frameStart = frame * joinerDimension;
final frameVector = encoder.values.sublist(
frameStart,
frameStart + joinerDimension,
);
for (var i = 0; i < previous.length; i++) {
encoderVectors.setRange(
i * joinerDimension,
(i + 1) * joinerDimension,
frameVector,
);
}
final logits = await _backend.runJoiner(encoderVectors, decoderVectors);
if (logits.length != previous.length * vocabSize) {
throw StateError('Invalid joiner output shape');
}
_logSoftmaxRows(logits, vocabSize);
final candidates = <_Candidate>[];
for (
var hypothesisIndex = 0;
hypothesisIndex < previous.length;
hypothesisIndex++
) {
final base = hypothesisIndex * vocabSize;
for (var token = 0; token < vocabSize; token++) {
candidates.add(
_Candidate(
hypothesisIndex,
token,
logits[base + token] + previous[hypothesisIndex].logProbability,
logits[base + token],
),
);
}
}
candidates.sort((left, right) => right.combined.compareTo(left.combined));
final next = <String, KwsHypothesis>{};
final count = math.min(maxActivePaths, candidates.length);
for (var i = 0; i < count; i++) {
final candidate = candidates[i];
final hypothesis = previous[candidate.hypothesisIndex].copy();
var contextScore = 0.0;
if (candidate.token != blankId && candidate.token != unknownId) {
hypothesis.tokens.add(candidate.token);
hypothesis.timestamps.add(frame + _frameOffset);
hypothesis.tokenProbabilities.add(math.exp(candidate.acoustic));
hypothesis.trailingBlanks = 0;
final step = _graph.forward(hypothesis.contextState, candidate.token);
contextScore = step.score;
hypothesis.contextState = step.state;
if (hypothesis.contextState.token == -1) {
hypothesis.tokens
..clear()
..addAll(const [-1, blankId]);
hypothesis.timestamps.clear();
hypothesis.tokenProbabilities.clear();
}
} else {
hypothesis.trailingBlanks++;
}
hypothesis.logProbability = candidate.combined + contextScore;
_addOrMerge(next, hypothesis);
}
final best = _best(next.values);
final matched = _graph.matched(best.contextState);
if (matched != null &&
best.trailingBlanks > trailingBlankFrames &&
best.tokenProbabilities.length >= matched.level) {
final probabilities = best.tokenProbabilities.sublist(
best.tokenProbabilities.length - matched.level,
);
final mean = probabilities.reduce((a, b) => a + b) / matched.level;
if (mean >= matched.acousticThreshold) {
final timestamps = best.timestamps.sublist(
best.timestamps.length - matched.level,
);
detections.add(
KeywordDetection(
phrase: matched.phrase,
detectedAt: Duration(milliseconds: (frame + _frameOffset) * 40),
tokenTimestamps: timestamps
.map((frame) => Duration(milliseconds: frame * 40))
.toList(growable: false),
meanTokenProbability: mean,
),
);
final empty = KwsHypothesis(
tokens: <int>[-1, blankId],
logProbability: 0,
contextState: _graph.root,
);
next
..clear()
..[empty.key] = empty;
}
}
_hypotheses = next;
}
_frameOffset += encoder.frameCount;
return detections;
}