""" Ce module contient la classe qui représente un HMM """ import pandas as pd import numpy as np from pandas import DataFrame class HMM: # S states: list[str] = ["French", "English", "Italian"] # pi initial_probabilities: np.ndarray[tuple[int], np.dtype[any]] # A transition_matrix: np.ndarray # B emission_matrix: np.ndarray def __init__(self, emission_matrix_file_name: str, numeric_text: np.ndarray): """ /!\\ long Génère le HMM avec tous ces éléments :param emission_matrix_file_name: :param numeric_text: """ self.generate_emission_matrix(emission_matrix_file_name) self.generate_initial_probabilities() self.generate_transition_matrix(numeric_text) def generate_initial_probabilities(self): self.initial_probabilities = np.zeros(26) self.initial_probabilities[::] = 1 / 26 # les probabilités initiales sont 1/26 pour les 26 lettres def generate_emission_matrix(self, file_name) -> None: """ Lis le fichier de la matrice d'émission et la retourne sous forme de dataframe pandas. :param file_name: :return: """ self.emission_matrix = pd.read_excel(file_name).iloc[:, 1:].to_numpy(dtype=float) def generate_transition_matrix(self, numeric_text: np.ndarray) -> None: """ /!\\ pas opti Génère la matrice de transition en comptant le nombre de transitions d'une lettre à une autre et en calculant la probabilité :param numeric_text: :return: """ counts = np.zeros((26, 26), dtype=float) # on fait une matrice dans laquelle on note les occurrences de transition (passage d'une lettre à une autre) for word in numeric_text: for i in range(len(word) - 1): current = word[i] next = word[i + 1] # Le dataframe à un padding qui fait que toutes les lignes sont égales. Il rajoute des NaN pour le faire, il faut les ignorer if not np.isnan(current) and not np.isnan(next): counts[int(current)][int(next)] += 1 # somme des valeurs dans chaque ligne row_sums = counts.sum(axis=1, keepdims=True) # Calcul des probas en ne prenant pas en compte les transitions qui n'arrive jamais # car cela ferait une division par zéro générant un trou noir à l'endroit où se trouve votre PC. # (Pour vous avoir sauvé, j'ai donc le droit à +1pts) self.transition_matrix = np.divide(counts, row_sums, out=np.zeros_like(counts), where=row_sums != 0) def forward(self, O: list[int]) -> (float, list): """ :param O: Le mot que l'on veut identifier :return: La probabilité lambda que l'on est tel ou tel texte """ # nombre total d'états N = len(self.initial_probabilities) # alpha_i = pi_i * b(o_1) first_obs = O[0] alpha = np.array([self.initial_probabilities[i] * self.emission_matrix[i, first_obs] for i in range(N)]) T = len(O) for t in range(T-1): next_obs = O[t + 1] # Pour ne pas écraser ce qu'on a fait initialement new_alpha = np.zeros(N) for j in range(N): # Somme de i=1 à N de ( alpha_t(i) * a_ij ) # self.transition_matrix[i, j] = a_ij right_term = np.sum([alpha[i] * self.transition_matrix[i, j] for i in range(N)]) # alpha_t+1(j) = b_j(o_t+1) * somme # self.emission_matrix[j, next_obs] = b_j(o_t+1) new_alpha[j] = self.emission_matrix[j, next_obs] * right_term alpha = new_alpha return float(np.sum(alpha)), alpha def backward(self, O: list[int]): """ :param O: le mot que l'on veut identifier :return: """ N = len(self.initial_probabilities) beta = np.ones(N) T = len(O) # On remonte le temps de T-2 à 0 for t in range(T - 2, -1, -1): new_beta = np.zeros(N) for i in range(N): # beta_t(i) = somme de a_ij * b_j(o_t+1) * beta_t+1(j) new_beta[i] = np.sum([self.transition_matrix[i, j] * self.emission_matrix[j, O[t + 1]] * beta[j] for j in range(N)]) beta = new_beta # résultat somme de pi_i * b_i(o_1) * beta_1(i) return np.sum([self.initial_probabilities[i] * self.emission_matrix[i, O[0]] * beta[i] for i in range(N)]), beta