Haopeng commited on
Commit
91abd6d
·
verified ·
1 Parent(s): 0e0d443

Add MyEncoderASR and fix CTC output dimension

Browse files
Files changed (1) hide show
  1. MyEncoderASR.py +209 -0
MyEncoderASR.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from speechbrain.inference.ASR import EncoderASR
2
+ from speechbrain.decoders.ctc import TorchAudioCTCPrefixBeamSearcher
3
+ from speechbrain.decoders.ctc import CTCHypothesis
4
+ import torch
5
+ import speechbrain
6
+ import functools
7
+ import matplotlib.pyplot as plt
8
+
9
+ class MyEncoderASR(EncoderASR):
10
+ def transcribe_batch(self, wavs, wav_lens):
11
+ """Transcribes the input audio into a sequence of words
12
+
13
+ The waveforms should already be in the model's desired format.
14
+ You can call:
15
+ ``normalized = EncoderASR.normalizer(signal, sample_rate)``
16
+ to get a correctly converted signal in most cases.
17
+
18
+ Arguments
19
+ ---------
20
+ wavs : torch.Tensor
21
+ Batch of waveforms [batch, time, channels] or [batch, time]
22
+ depending on the model.
23
+ wav_lens : torch.Tensor
24
+ Lengths of the waveforms relative to the longest one in the
25
+ batch, tensor of shape [batch]. The longest one should have
26
+ relative length 1.0 and others len(waveform) / max_length.
27
+ Used for ignoring padding.
28
+
29
+ Returns
30
+ -------
31
+ list
32
+ Each waveform in the batch transcribed.
33
+ tensor
34
+ Each predicted token id.
35
+ """
36
+ with torch.no_grad():
37
+ wav_lens = wav_lens.to(self.device)
38
+ encoder_out = self.encode_batch(wavs, wav_lens)
39
+ # frame level logits.
40
+ predictions = self.decoding_function(encoder_out, wav_lens)
41
+ is_ctc_text_encoder_tokenizer = isinstance(
42
+ self.tokenizer, speechbrain.dataio.encoder.CTCTextEncoder
43
+ )
44
+ if isinstance(self.hparams.decoding_function, functools.partial):
45
+ if is_ctc_text_encoder_tokenizer:
46
+ predicted_words = [
47
+ " ".join(self.tokenizer.decode_ndim(token_seq))
48
+ for token_seq in predictions
49
+ ]
50
+ else:
51
+ predicted_words = [
52
+ self.tokenizer.decode_ids(token_seq)
53
+ for token_seq in predictions
54
+ ]
55
+ else:
56
+ predicted_words = [hyp[0].text for hyp in predictions]
57
+ return predicted_words, predictions
58
+
59
+ class MyCTCPrefixBeamSearcher(TorchAudioCTCPrefixBeamSearcher):
60
+ def decode_beams(self, log_probs, wav_len):
61
+ """Decode log_probs using TorchAudio CTC decoder.
62
+
63
+ If `using_cpu_decoder=True` then log_probs and wav_len are moved to CPU before decoding.
64
+ When using CUDA CTC decoder, the timestep information is not available. Therefore, the timesteps
65
+ in the returned hypotheses are set to None.
66
+
67
+ Make sure that the input are in the log domain. The decoder will fail to decode
68
+ logits or probabilities. The input should be the log probabilities of the CTC output.
69
+
70
+ Arguments
71
+ ---------
72
+ log_probs : torch.Tensor
73
+ The log probabilities of the input audio.
74
+ Shape: (batch_size, seq_length, vocab_size)
75
+ wav_len : torch.Tensor, default: None
76
+ The speechbrain-style relative length. Shape: (batch_size,)
77
+ If None, then the length of each audio is assumed to be seq_length.
78
+
79
+ Returns
80
+ -------
81
+ list of list of CTCHypothesis
82
+ The decoded hypotheses. The outer list is over the batch dimension, and the inner list is over the topk dimension.
83
+ """
84
+ if wav_len is not None:
85
+ wav_len = log_probs.size(1) * wav_len
86
+ else:
87
+ wav_len = torch.tensor(
88
+ [log_probs.size(1)] * log_probs.size(0),
89
+ device=log_probs.device,
90
+ dtype=torch.int32,
91
+ )
92
+
93
+ if wav_len.dtype != torch.int32:
94
+ wav_len = wav_len.to(torch.int32)
95
+
96
+ if log_probs.dtype != torch.float32:
97
+ raise ValueError("log_probs must be float32.")
98
+
99
+ # When using CPU decoder, we need to move the log_probs and wav_len to CPU
100
+ if self.using_cpu_decoder and log_probs.is_cuda:
101
+ log_probs = log_probs.cpu()
102
+
103
+ if self.using_cpu_decoder and wav_len.is_cuda:
104
+ wav_len = wav_len.cpu()
105
+
106
+ if not log_probs.is_contiguous():
107
+ raise RuntimeError("log_probs must be contiguous.")
108
+
109
+ results = self._ctc_decoder(log_probs, wav_len)
110
+
111
+ tokens_preds = []
112
+ words_preds = []
113
+ scores_preds = []
114
+ timesteps_preds = []
115
+
116
+ # over batch dim
117
+ for i in range(len(results)):
118
+ if self.using_cpu_decoder:
119
+ preds = [
120
+ results[i][j].tokens.tolist()
121
+ for j in range(len(results[i]))
122
+ ]
123
+ preds = [
124
+ [self.tokens[token] for token in tokens] for tokens in preds
125
+ ]
126
+ tokens_preds.append(preds)
127
+
128
+ timesteps = [
129
+ results[i][j].timesteps.tolist()
130
+ for j in range(len(results[i]))
131
+ ]
132
+ timesteps_preds.append(timesteps)
133
+
134
+ else:
135
+ # no timesteps is available for CUDA CTC decoder
136
+ timesteps = [None for _ in range(len(results[i]))]
137
+ timesteps_preds.append(timesteps)
138
+
139
+ preds = [results[i][j].tokens for j in range(len(results[i]))]
140
+ preds = [
141
+ [self.tokens[token] for token in tokens] for tokens in preds
142
+ ]
143
+ tokens_preds.append(preds)
144
+
145
+ words = [results[i][j].words for j in range(len(results[i]))]
146
+ words_preds.append(words)
147
+
148
+ scores = [results[i][j].score for j in range(len(results[i]))]
149
+ scores_preds.append(scores)
150
+
151
+ hyps = []
152
+ for (
153
+ batch_index,
154
+ (batch_text, batch_score, batch_timesteps),
155
+ ) in enumerate(zip(tokens_preds, scores_preds, timesteps_preds)):
156
+ hyps.append([])
157
+ for text, score, timestep in zip(
158
+ batch_text, batch_score, batch_timesteps
159
+ ):
160
+ hyps[batch_index].append(
161
+ CTCHypothesis(
162
+ text=text,
163
+ last_lm_state=None,
164
+ score=score,
165
+ lm_score=score,
166
+ text_frames=timestep,
167
+ )
168
+ )
169
+ return hyps
170
+
171
+ def plot_alignments(waveform, emission, tokens, timesteps, sample_rate):
172
+ t = torch.arange(waveform.size(0)) / sample_rate
173
+ ratio = waveform.size(0) / emission.size(1) / sample_rate
174
+
175
+ chars = []
176
+ words = []
177
+ word_start = None
178
+ for token, timestep in zip(tokens, timesteps * ratio):
179
+ if token == "|":
180
+ if word_start is not None:
181
+ words.append((word_start, timestep))
182
+ word_start = None
183
+ else:
184
+ chars.append((token, timestep))
185
+ if word_start is None:
186
+ word_start = timestep
187
+
188
+ num_axes = len(waveform) // sample_rate + 1
189
+ plt.figure(figsize=[num_axes*10, 5])
190
+ fig, axes = plt.subplots(num_axes, 1)
191
+
192
+ def _plot(ax, xlim):
193
+ ax.plot(t, waveform)
194
+ for token, timestep in chars:
195
+ ax.annotate(token.upper(), (timestep, 0.5))
196
+ for word_start, word_end in words:
197
+ ax.axvspan(word_start, word_end, alpha=0.1, color="red")
198
+ ax.set_ylim(-0.6, 0.7)
199
+ ax.set_yticks([0])
200
+ ax.grid(True, axis="y")
201
+ ax.set_xlim(xlim)
202
+
203
+ for i in range(0, num_axes):
204
+ _plot(axes[i], (i, i+1))
205
+
206
+ axes[num_axes-1].set_xlabel("time (sec)")
207
+ fig.tight_layout()
208
+
209
+ return fig