<jupyter_start><jupyter_text>Viterbi decodingThis notebook demonstrates how to use Viterbi decoding to impose temporalsmoothing on frame-wise state predictions.Our working example will be the problem of silence/non-silence detection.<jupyter_code># Code source: <NAME>
# License: ISC

##################
# Standard imports
import numpy as np
import matplotlib.pyplot as plt
import librosa

import librosa.display<jupyter_output><empty_output><jupyter_text>Load an example signal<jupyter_code>y, sr = librosa.load(librosa.ex('trumpet'))


# And compute the spectrogram magnitude and phase
S_full, phase = librosa.magphase(librosa.stft(y))


###################
# Plot the spectrum
fig, ax = plt.subplots()
img = librosa.display.specshow(librosa.amplitude_to_db(S_full, ref=np.max),
                               y_axis='log', x_axis='time', sr=sr, ax=ax)
fig.colorbar(img, ax=ax);<jupyter_output><empty_output><jupyter_text>As you can see, there are periods of silence andnon-silence throughout this recording.<jupyter_code># As a first step, we can plot the root-mean-square (RMS) curve
rms = librosa.feature.rms(y=y)[0]

times = librosa.frames_to_time(np.arange(len(rms)))

fig, ax = plt.subplots()
ax.plot(times, rms)
ax.axhline(0.02, color='r', alpha=0.5)
ax.set(xlabel='Time', ylabel='RMS');<jupyter_output><empty_output><jupyter_text>The red line at 0.02 indicates a reasonable threshold for silence detection.However, the RMS curve occasionally dips below the threshold momentarily,and we would prefer the detector to not count these brief dips as silence.This is where the Viterbi algorithm comes in handy! As a first step, we will convert the raw RMS scoreinto a likelihood (probability) by logistic mapping  $P[V=1 | x] = \frac{\exp(x - \tau)}{1 + \exp(x - \tau)}$where $x$ denotes the RMS value and $\tau=0.02$ is our threshold.The variable $V$ indicates whether the signal is non-silent (1) or silent (0).We'll normalize the RMS by its standard deviation to expand therange of the probability vector<jupyter_code>r_normalized = (rms - 0.02) / np.std(rms)
p = np.exp(r_normalized) / (1 + np.exp(r_normalized))<jupyter_output><empty_output><jupyter_text>We can plot the probability curve over time:<jupyter_code>fig, ax = plt.subplots()
ax.plot(times, p, label='P[V=1|x]')
ax.axhline(0.5, color='r', alpha=0.5, label='Descision threshold')
ax.set(xlabel='Time')
ax.legend();<jupyter_output><empty_output><jupyter_text>which looks much like the first plot, but with the decision thresholdshifted to 0.5.  A simple silence detector would classify each frameindependently of its neighbors, which would result in the following plot:<jupyter_code>plt.figure(figsize=(12, 6))
fig, ax = plt.subplots(nrows=2, sharex=True)
librosa.display.specshow(librosa.amplitude_to_db(S_full, ref=np.max),
                         y_axis='log', x_axis='time', sr=sr, ax=ax[0])
ax[0].label_outer()
ax[1].step(times, p>=0.5, label='Non-silent')
ax[1].set(ylim=[0, 1.05])
ax[1].legend()<jupyter_output><empty_output><jupyter_text>We can do better using the Viterbi algorithm.We'll use state 0 to indicate silent, and 1 to indicate non-silent.We'll assume that a silent frame is equally likely to be followedby silence or non-silence, but that non-silence is slightlymore likely to be followed by non-silence.This is accomplished by building a self-loop transition matrix,where `transition[i, j]` is the probability of moving from state`i` to state `j` in the next frame.<jupyter_code>transition = librosa.sequence.transition_loop(2, [0.5, 0.6])
print(transition)<jupyter_output><empty_output><jupyter_text>Our `p` variable only indicates the probability of non-silence,so we need to also compute the probability of silence as its complement.<jupyter_code>full_p = np.vstack([1 - p, p])
print(full_p)<jupyter_output><empty_output><jupyter_text>Now, we're ready to decode!We'll use `viterbi_discriminative` here, since the inputs arestate likelihoods conditional on data (in our case, data is rms).<jupyter_code>states = librosa.sequence.viterbi_discriminative(full_p, transition)

# sphinx_gallery_thumbnail_number = 5
fig, ax = plt.subplots(nrows=2, sharex=True)
librosa.display.specshow(librosa.amplitude_to_db(S_full, ref=np.max),
                         y_axis='log', x_axis='time', sr=sr, ax=ax[0])
ax[0].label_outer()
ax[1].step(times, p>=0.5, label='Frame-wise')
ax[1].step(times, states, linestyle='--', color='orange', label='Viterbi')
ax[1].set(ylim=[0, 1.05])
ax[1].legend()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1293045989780049, 'fraction_numerical': 0.014219062430570984, 'mean_word_length': 5.790346907993967, 'pattern_counts': {'":': 0, '<': 38, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '572991', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 1583, 'n_tokens_neox': 1529, 'n_words': 428, 'path': 'main/_downloads/900de063700e120f6dbbcfecafadbec6/plot_viterbi.ipynb', 'repo_name': 'librosa/doc', 'size': '4506'}