xbe.at

NLP and transformers / NLP core

Hidden Markov Models for Automatic Speech Recognition in Python

7 min read, 14 sections

Contents
  1. Introduction to Hidden Markov Models for ASR
  2. Components of an HMM
  3. Feature Extraction for ASR
  4. Training HMMs for ASR
  5. Decoding in ASR
  6. Handling Continuous Speech
  7. Real-life Example: Voice Command Recognition
  8. Handling Noise and Variability
  9. Integrating Language Models
  10. Evaluating ASR Systems
  11. Advanced HMM Techniques
  12. Real-life Example: Multilingual ASR
  13. Future Directions in ASR
  14. Additional Resources

Introduction to Hidden Markov Models for ASR

Hidden Markov Models (HMMs) are statistical models widely used in Automatic Speech Recognition (ASR) systems. They represent speech as a sequence of hidden states, each emitting observable features. In ASR, these states typically correspond to phonemes or sub-phoneme units, while the observations are acoustic features extracted from the speech signal.

import numpy as np
from hmmlearn import hmm

# Create a simple HMM for speech recognition
model = hmm.GaussianHMM(n_components=3, covariance_type="full")
model.startprob_ = np.array([0.6, 0.3, 0.1])
model.transmat_ = np.array([[0.7, 0.2, 0.1],
                            [0.3, 0.5, 0.2],
                            [0.3, 0.3, 0.4]])
model.means_ = np.array([[0.0, 0.0], [3.0, -3.0], [5.0, 10.0]])
model.covars_ = np.tile(np.identity(2), (3, 1, 1))

# Generate sample data
X, Z = model.sample(100)

Components of an HMM

An HMM consists of three main components: states, transitions, and emissions. States represent the underlying phonetic units, transitions model the probability of moving from one state to another, and emissions define the probability of observing specific acoustic features given a state.

import matplotlib.pyplot as plt

# Visualize the HMM components
plt.figure(figsize=(10, 6))
plt.scatter(X[:, 0], X[:, 1], c=Z, cmap=plt.cm.Set1)
plt.title("HMM States and Emissions")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.colorbar(ticks=range(model.n_components), label="State")
plt.show()

# Print transition matrix
print("Transition Matrix:")
print(model.transmat_)

Feature Extraction for ASR

Before applying HMMs, we need to extract relevant features from the speech signal. Common features include Mel-Frequency Cepstral Coefficients (MFCCs), which capture the spectral envelope of the speech signal.

import librosa
import librosa.display

# Load audio file
audio_file = "speech_sample.wav"
y, sr = librosa.load(audio_file)

# Extract MFCCs
mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)

# Visualize MFCCs
plt.figure(figsize=(10, 4))
librosa.display.specshow(mfccs, x_axis='time')
plt.colorbar()
plt.title('MFCC')
plt.tight_layout()
plt.show()

Training HMMs for ASR

Training HMMs for ASR involves estimating the model parameters (transition probabilities, emission probabilities) using a large corpus of labeled speech data. The Baum-Welch algorithm, a variant of Expectation-Maximization, is commonly used for this purpose.

from sklearn.model_selection import train_test_split

# Assume we have a dataset of MFCCs and their corresponding labels
X_train, X_test, y_train, y_test = train_test_split(mfccs.T, labels, test_size=0.2)

# Train the HMM
model = hmm.GaussianHMM(n_components=5, covariance_type="diag", n_iter=100)
model.fit(X_train)

# Print the converged log-likelihood
print(f"Model converged with log-likelihood: {model.score(X_train)}")

Decoding in ASR

Decoding is the process of finding the most likely sequence of states (phonemes) given the observed acoustic features. The Viterbi algorithm is commonly used for this purpose in HMM-based ASR systems.

# Assuming we have a trained HMM model and a new observation sequence
new_observation = X_test[0]

# Perform Viterbi decoding
log_prob, state_sequence = model.decode(new_observation.reshape(1, -1))

print("Most likely state sequence:")
print(state_sequence)
print(f"Log probability: {log_prob}")

Handling Continuous Speech

In continuous speech recognition, we need to deal with connected words and sentences. This involves modeling word transitions and using language models to improve recognition accuracy.

import networkx as nx

# Simple language model represented as a graph
G = nx.DiGraph()
G.add_edges_from([("START", "THE"), ("THE", "CAT"), ("THE", "DOG"),
                  ("CAT", "IS"), ("DOG", "IS"), ("IS", "ON"),
                  ("ON", "THE"), ("THE", "MAT"), ("MAT", "END")])

# Visualize the language model
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=500, arrowsize=20)
plt.title("Simple Language Model for ASR")
plt.axis('off')
plt.show()

Real-life Example: Voice Command Recognition

HMMs can be used to build a simple voice command recognition system for home automation. We'll train separate HMMs for different commands like "Turn on lights", "Set temperature", and "Play music".

# Assume we have extracted MFCCs for different voice commands
commands = ["lights_on", "set_temp", "play_music"]
models = {}

for command in commands:
    model = hmm.GaussianHMM(n_components=5, covariance_type="diag", n_iter=100)
    model.fit(command_data[command])
    models[command] = model

# Function to recognize a new command
def recognize_command(audio):
    mfccs = extract_mfccs(audio)
    scores = {cmd: model.score(mfccs) for cmd, model in models.items()}
    recognized_command = max(scores, key=scores.get)
    return recognized_command

# Example usage
new_audio = record_audio(duration=3)
result = recognize_command(new_audio)
print(f"Recognized command: {result}")

Handling Noise and Variability

Real-world speech recognition must handle various challenges such as background noise, speaker variability, and channel effects. Techniques like noise reduction and speaker adaptation can improve robustness.

import numpy as np

def add_noise(signal, snr_db):
    signal_power = np.sum(signal ** 2) / len(signal)
    noise_power = signal_power / (10 ** (snr_db / 10))
    noise = np.random.normal(0, np.sqrt(noise_power), len(signal))
    return signal + noise

# Example usage
clean_signal = np.random.randn(1000)
noisy_signal = add_noise(clean_signal, snr_db=10)

plt.figure(figsize=(10, 4))
plt.subplot(211)
plt.title("Clean Signal")
plt.plot(clean_signal)
plt.subplot(212)
plt.title("Noisy Signal (SNR = 10 dB)")
plt.plot(noisy_signal)
plt.tight_layout()
plt.show()

Integrating Language Models

Language models help improve ASR accuracy by incorporating prior knowledge about word sequences. N-gram models are commonly used to estimate the probability of word sequences.

from collections import defaultdict

# Simple bigram language model
class BigramModel:
    def __init__(self):
        self.bigrams = defaultdict(lambda: defaultdict(int))
        self.word_counts = defaultdict(int)

    def train(self, corpus):
        words = corpus.split()
        for i in range(len(words) - 1):
            self.bigrams[words[i]][words[i+1]] += 1
            self.word_counts[words[i]] += 1

    def probability(self, word1, word2):
        return self.bigrams[word1][word2] / self.word_counts[word1]

# Example usage
corpus = "the cat sat on the mat the dog ran in the park"
model = BigramModel()
model.train(corpus)

print(f"P(dog|the) = {model.probability('the', 'dog'):.3f}")
print(f"P(cat|the) = {model.probability('the', 'cat'):.3f}")

Evaluating ASR Systems

ASR systems are typically evaluated using metrics such as Word Error Rate (WER) and Phoneme Error Rate (PER). These metrics compare the recognized text to the ground truth transcription.

def word_error_rate(reference, hypothesis):
    # Levenshtein distance implementation
    def levenshtein(s1, s2):
        if len(s1) < len(s2):
            return levenshtein(s2, s1)
        if len(s2) == 0:
            return len(s1)
        previous_row = range(len(s2) + 1)
        for i, c1 in enumerate(s1):
            current_row = [i + 1]
            for j, c2 in enumerate(s2):
                insertions = previous_row[j + 1] + 1
                deletions = current_row[j] + 1
                substitutions = previous_row[j] + (c1 != c2)
                current_row.append(min(insertions, deletions, substitutions))
            previous_row = current_row
        return previous_row[-1]

    r = reference.split()
    h = hypothesis.split()
    distance = levenshtein(r, h)
    return distance / len(r)

# Example usage
reference = "the cat sat on the mat"
hypothesis = "the cat sat on the hat"
wer = word_error_rate(reference, hypothesis)
print(f"Word Error Rate: {wer:.2f}")

Advanced HMM Techniques

Advanced techniques can further improve HMM-based ASR systems. These include context-dependent phoneme models, discriminative training, and the use of deep neural networks for acoustic modeling.

import torch
import torch.nn as nn

# Simple DNN-HMM hybrid model
class DNNAcousticModel(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim),
            nn.LogSoftmax(dim=1)
        )

    def forward(self, x):
        return self.layers(x)

# Example usage
model = DNNAcousticModel(input_dim=39, hidden_dim=256, output_dim=42)
dummy_input = torch.randn(1, 39)
output = model(dummy_input)
print(f"Output shape: {output.shape}")

Real-life Example: Multilingual ASR

HMMs can be adapted for multilingual ASR by training models on multiple languages and using language identification as a preprocessing step.

import numpy as np
from sklearn.mixture import GaussianMixture

# Simplified language identification using GMMs
class LanguageIdentifier:
    def __init__(self, languages):
        self.languages = languages
        self.models = {lang: GaussianMixture(n_components=5) for lang in languages}

    def train(self, data):
        for lang, features in data.items():
            self.models[lang].fit(features)

    def identify(self, features):
        scores = {lang: model.score(features) for lang, model in self.models.items()}
        return max(scores, key=scores.get)

# Example usage
languages = ["english", "spanish", "french"]
identifier = LanguageIdentifier(languages)

# Assume we have training data for each language
training_data = {lang: np.random.randn(1000, 13) for lang in languages}
identifier.train(training_data)

# Test on new data
test_features = np.random.randn(1, 13)
identified_language = identifier.identify(test_features)
print(f"Identified language: {identified_language}")

Future Directions in ASR

While HMMs have been fundamental in ASR development, recent advancements in deep learning have led to end-to-end neural models that can outperform traditional HMM-based systems. These include architectures like Connectionist Temporal Classification (CTC) and Transformer-based models.

import torch
import torch.nn as nn

class SimpleCTCModel(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.rnn = nn.LSTM(input_dim, hidden_dim, batch_first=True, bidirectional=True)
        self.fc = nn.Linear(hidden_dim * 2, output_dim)

    def forward(self, x):
        rnn_out, _ = self.rnn(x)
        return self.fc(rnn_out)

# Example usage
model = SimpleCTCModel(input_dim=39, hidden_dim=128, output_dim=29)  # 29 for 26 letters + blank + space + apostrophe
dummy_input = torch.randn(1, 100, 39)  # Batch size 1, 100 time steps, 39 features
output = model(dummy_input)
print(f"Output shape: {output.shape}")

Additional Resources

For further exploration of Hidden Markov Models in Automatic Speech Recognition, consider the following resources:

  1. "Speech and Language Processing" by Daniel Jurafsky and James H. Martin ArXiv: https://arxiv.org/abs/0712.4360
  2. "Fundamentals of Speech Recognition" by Lawrence Rabiner and Biing-Hwang Juang (Not available on ArXiv, but widely recognized in the field)
  3. "A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition" by Lawrence R. Rabiner ArXiv: https://arxiv.org/abs/1709.04819

These resources provide in-depth coverage of HMMs and their applications in speech recognition, offering both theoretical foundations and practical insights.

More in NLP core

  1. Machine Learning Models for Sentiment Analysis in Python12 min

    Sentiment analysis is a natural language processing task that involves determining the emotional tone behind a piece of text. It's widely used in various...

  2. Exploring Word Embeddings in NLP with Python9 min

    Word embeddings are dense vector representations of words that capture semantic meanings and relationships. They are fundamental to many NLP tasks.

  3. Named Entity Recognition with Python13 min

    Named Entity Recognition (NER) is a natural language processing task that identifies and classifies named entities in text into predefined categories such as...

  4. Document Understanding Transformer for Optical Character Recognition10 min

    Document Understanding Transformer is a powerful deep learning model designed to comprehend and extract information from various types of documents. It...

  5. Natural Language Processing with NLTK and Pandas:4 min
  6. Deep Speech 2 Python-Powered Speech Recognition10 min

    Deep Speech 2 is an advanced speech recognition system developed by Baidu Research. It builds upon the original Deep Speech model, offering improved accuracy...