ajouts de commentaires

This commit is contained in:
François Pelletier 2019-09-30 12:00:16 -04:00
parent eb62721320
commit 1e59a902fb
4 changed files with 87 additions and 67 deletions

View file

@ -18,20 +18,20 @@ Comme on souhaite garder la forme la plus générale possible pour ces expressio
L'approche utilisée a été de trouver d'abord les mots interrogatifs, ensuite la structure générale de la phrase, dont le verbe principal et les noms à remplacer, le cas échéant. Puis, on a identifié les différents types de réponses requises, et lorsqu'un même motif de question nécessitait deux types de réponses différentes, on a séparé en deux expressions consécutives. Ensuite, on a construit quelques règles qui appliquent des corrections mineures au niveau des signes et de la ponctuation.
Toutes ces règles sont enchainées les unes à la suite des autres. Comme la conversion élimine le point d'interrogation à la fin de la phrase, les motifs subséquents au premier qui a donné une correspondance sont ainsi ignorés. Il n'y a donc aucun traitement conditionnel en dehors de ceux inclus dans les expressions régulières.
Toutes ces règles sont enchainées les unes à la suite des autres. Comme la conversion élimine le point d'interrogation à la fin de la phrase, les motifs subséquents au premier qui a donné une correspondance sont ainsi ignorés. Il n'y a donc aucun traitement conditionnel en dehors de ceux inclus dans les expressions régulières. Si on n'est pas parvenu à transformer l'affirmation, on retourne une réponse par défaut. Ce qui ne se produit pas avec les questions de cet exercice.
### Description des motifs utilisés
Les motifs suivent cet ordre. Sauf indication contraire, le groupe affirmatif à convertir est situé après le motif recherché :
1. Mot interrogatif **qui**, suivi d'un verbe auxiliaire, sous forme d'énumération partielle ;
2. Mot interrogatif **qu'est-ce que **;
2. Mot interrogatif **qu'est-ce que**;
3. Mot interrogatif **que** suivi d'un verbe d'état ou d'action. On a ici utilisé une énumération partielle pour identifier les verbes présents dans les questions en échantillon ;
4. Mot interrogatif **où** ou de la forme **dans quelle** suivi d'une catégorie géographique, sous forme d'énumération partielle. On a aussi capturé la forme particulière **quelle est la capitale** où on remplace le mot **quelle** par où, afin de correspondre au motif précédent. Ceci permet d'éviter une règle additionnelle ;
5. Mot interrogatif **quel** suivi soit du mot âge et du verbe avoir, soit du verbe auxiliaire et d'un nom représentant une quantité, sous forme d'énumération ;
6. Mot interrogatif **quel** suivi du verbe être (forme générique du motif précédent) ;
7. mot interrogatif **quand** ou groupe **en quelle année **;
8. mot interrogatif **pourquoi **;
8. mot interrogatif **pourquoi**;
9. mot interrogatif **combien** suivi du mot **de **;
10. mot interrogatif **combien** suivi de **y a-t-il**. La différence ici est que la réponse conserve l'ordre de la question et qu'il faut transformer **y a-t-il** en **il y a **;
11. Mot interrogatif **quel** à la fin de la question. Ce motif sert à attraper une forme moins fréquente de questions où la question est posée après le groupe affirmatif.

View file

@ -7,6 +7,7 @@ results_filename = "./t1_results.txt"
test_questions_fn = "./data/test-questions-t1.txt"
test_results_filename = "./test-t1_results.txt"
# Mettre dans cette partie les expressions régulières
# que vous utilisez pour analyser les questions
# et en faire la conversion en affirmations
@ -19,10 +20,11 @@ def run_task1(filename):
results = convert_all_questions(questions)
save_results(results)
def run_task_test(filename, test_results_filename):
def run_task_test(filename, res_filename):
questions = load_questions(filename)
results = convert_all_questions(questions)
save_results(results, test_results_filename)
save_results(results, res_filename)
def convert_all_questions(questions):
@ -50,22 +52,25 @@ def convert(question):
# En cas de doute, me consulter.
# Votre code...
# Mot interrogatif **qui**
regex_qui = re.compile(r"(qui)\s"
r"(est|sont|était|étaient|sera|seront)\s"
r"(.*)\?",
re.IGNORECASE)
modif1_0 = re.sub(regex_qui,
r"\3 \2 Luc Lamontagne.",
question)
r"\3 \2 Luc Lamontagne.",
question)
regex_qui_a = re.compile(r"(qui)\s"
r"(a)\s"
r"(.*)\?",
re.IGNORECASE)
r"(a)\s"
r"(.*)\?",
re.IGNORECASE)
modif1 = re.sub(regex_qui_a,
r"Luc Lamontagne \2 \3.",
modif1_0)
regex_quel_nom = re.compile(r"quel\s"
r"(est)\s"
r"(le nom du chef)"
@ -75,6 +80,7 @@ def convert(question):
r"\2 \3 \1 Luc Lamontagne.",
modif1)
# Mot interrogatif ** qu'est-ce que**
regex_questce = re.compile(r"(qu'est-ce\squ[ie'])\s?"
r"(.*)\?",
re.IGNORECASE)
@ -82,6 +88,7 @@ def convert(question):
r"\2 est X.",
modif1_1)
# Mot interrogatif **que**
regex_quesont = re.compile(r"(qu[e'])\s"
r"(est|sont|signifie|fait)-?(on)?"
r"(.*)\?",
@ -90,6 +97,7 @@ def convert(question):
r"\4 \3 \2 X.",
modif2)
# Mot interrogatif **où** ou de la forme **dans quelle**
regex_ou = re.compile(r"((dans\squel(le)?\s(ville|région|pays))|où)\s"
r"(est|sont)\s"
r"(.*)\?",
@ -101,8 +109,9 @@ def convert(question):
regex_capitale = re.compile(r"(quel[le]*)\s(est)\s(la\scapitale|l'emplacement)(.*)\?", re.IGNORECASE)
modif4_1 = re.sub(regex_capitale,
r"\3 \4 \2 Québec.", modif4)
r"\3 \4 \2 Québec.", modif4)
# Mot interrogatif **quel**
regex_mesure = re.compile(r"([aà]\s)?qu[el]+\s(âge\s)?"
r"(est|avait)?\s?"
r"(l[a']\s?)?"
@ -117,18 +126,21 @@ def convert(question):
re.IGNORECASE)
modif6 = re.sub(regex_quelle_est, r"\1\3 \2 X.", modif5)
# mot interrogatif **quand**
regex_quand = re.compile(r"(quand|en\squelle\sannée)\s(est\sné\s)?(.*)\?",
re.IGNORECASE)
modif7 = re.sub(regex_quand,
r"\3 \2en 2000.",
modif6)
# mot interrogatif **pourquoi **
regex_pourquoi = re.compile(r"pourquoi\s(.*)\?",
re.IGNORECASE)
modif8 = re.sub(regex_pourquoi,
r"\1 parce que X.",
modif7)
# mot interrogatif **combien**
regex_combien1 = re.compile(r"combien\sde\s(\w+)(.*)\?",
re.IGNORECASE)
modif9 = re.sub(regex_combien1,
@ -143,27 +155,31 @@ def convert(question):
r"\1 1000 \2.",
modif9))
# Mot interrogatif **quel** à la fin
regex_quelle_fin = re.compile(r"(.*?)(qu\w+)\s(\w+)\?",
re.IGNORECASE)
modif11 = re.sub(regex_quelle_fin,
r"\1 \3 X.",
modif10)
# Modifications non liées aux mots
# Inversions de pronoms
modif12 = re.sub(r"(\w+)(-t)?-(il|elle)", r"\1", modif11)
modif12 = re.sub(r"(\w+)(-t)?-(vous)", r"\3 \1", modif12)
modif12 = re.sub(r"\s{2,}", " ", modif12)
modif12 = re.sub(r"^\s", "", modif12)
modif12 = re.sub(r"\'\s", "'", modif12)
modif13 = re.sub(r"(\w+)(-t)?-(vous)", r"\3 \1", modif12)
# Suppression des espaces
modif14 = re.sub(r"\s{2,}", " ", modif13)
modif15 = re.sub(r"^\s", "", modif14)
modif16 = re.sub(r"\'\s", "'", modif15)
# Retourner la question seulement si on a réussi à la modifier en affirmation
if question != modif11:
return modif12
return modif16
else:
return "la terre est ronde."
def save_results(results, results_filename=results_filename):
with open(results_filename, 'w') as output_file:
def save_results(results, res_filename=results_filename):
with open(res_filename, 'w') as output_file:
for question, sentence in results:
output_file.write("Q: " + question + "\n")
output_file.write("A: " + sentence + "\n")
@ -171,7 +187,7 @@ def save_results(results, results_filename=results_filename):
if __name__ == '__main__':
# Vous pouvez modifier cette section
print("Conversion des questions du fichier {} ".format(questions_fn))
run_task1(questions_fn)
run_task_test(test_questions_fn, test_results_filename)

View file

@ -21,11 +21,9 @@ all_origins = [] # la liste des 18 langues d'origines de noms
BOS = "~" # character used to pad the beginning of a name
EOS = "!" # character used to pad the end of a name
v = 26
kgram_models = {}
V = 26
def find_files(path):
"""Retourne le nom des fichiers contenus dans un répertoire.
@ -52,8 +50,8 @@ def unicode_to_ascii(s):
def read_names(filename):
"""Retourne une liste de tous les noms contenus dans un fichier."""
with open(filename, encoding='utf-8') as f:
names = f.read().strip().split('\n')
with open(filename, encoding='utf-8') as file_handle:
names = file_handle.read().strip().split('\n')
return [unicode_to_ascii(name) for name in names]
@ -67,6 +65,7 @@ def load_names():
names_by_origin[origin] = names
# Cette fonction normalise les mots en les convertissant en ASCII, mettant en minuscules et supprimant les espaces
def normalize_word(word):
word = unicode_to_ascii(word)
word = word.lower()
@ -74,10 +73,14 @@ def normalize_word(word):
return word
# On génère les unigrammes à l'aide d'une liste
def make_unigram(word):
return list(word)
# On génère les k-grammes en ajoutant du padding et
# en traversant la chaîne de caractères de gauche à droite
# avec un pas de 1 et une largeur de k
def make_kgram(unigram_word, k):
# Ajouter padding
unigram_with_padding = [BOS for i in range(1, k)] + unigram_word + [EOS for i in range(1, k)]
@ -85,6 +88,7 @@ def make_kgram(unigram_word, k):
return kgrams
# Cette fonction crée un modèle de langue pour une origine en prenant en entrée des k-grammes
def make_count_dict(kgrams_origin):
kgram_count_dict = {}
for kgram_name in kgrams_origin:
@ -100,10 +104,6 @@ def train_models():
#
# Vous pouvez ajouter au fichier toutes les fonctions que vous jugerez nécessaire.
# Merci de ne pas modifier les fonctions présentes dans ce fichier.
#
# À compléter - Fonction pour la construction des modèles unigrammes, bigrammes et trigrammes.
#
# Votre code à partir d'ici...
# Construire un tableau de fréquence par langue. Chaque tableau est une entrée dans un dictionnaire
for origin in names_by_origin.keys():
@ -121,7 +121,6 @@ def train_models():
def most_probable_origin(name, n=3, use_logprob=True):
# Retourne la langue d'origine la plus probable du nom.
# n désigne la longueur des N-grammes. Par ex n=3 --> trigramme
# À compléter...
# Calculer les log-prob et les perplexités
log_prob_origin = {}
@ -143,25 +142,24 @@ def most_probable_origin(name, n=3, use_logprob=True):
def logprob(name, origin, n=3):
# Retourne la valeur du logprob d'un nom étant donné une origine
# Utilisez une fonction logarithme en base 2.
# À compléter...
unigram_name = make_unigram(name)
kgram_name = make_kgram(unigram_name, n)
k_counts = kgram_models[(origin, n)]
logprob_name = 0
if n == 1:
N = sum(k_counts.values())
prob_n = sum(k_counts.values())
for kgram in kgram_name:
C = k_counts.get(kgram, 0)
L = math.log((C + 1) / (N + V))
logprob_name = logprob_name + L
prob_c = k_counts.get(kgram, 0)
prob_l = math.log((prob_c + 1) / (prob_n + v))
logprob_name = logprob_name + prob_l
else:
k1_counts = kgram_models[(origin, n - 1)]
for kgram in kgram_name:
N = k1_counts.get(kgram[:-1], 0)
C = k_counts.get(kgram, 0)
L = math.log((C + 1) / (N + V))
logprob_name = logprob_name + L
prob_n = k1_counts.get(kgram[:-1], 0)
prob_c = k_counts.get(kgram, 0)
prob_l = math.log((prob_c + 1) / (prob_n + v))
logprob_name = logprob_name + prob_l
return logprob_name
@ -172,26 +170,24 @@ def prod(iterable):
def perplexity(name, origin, n=3):
# Retourne la valeur de perplexité d'un nom étant donné une origine
# À compléter...
unigram_name = make_unigram(name)
kgram_name = make_kgram(unigram_name, n)
length_name = len(kgram_name)
k_counts = kgram_models[(origin, n)]
V = 26
probs_name = []
if n == 1:
N = sum(k_counts.values())
prob_n = sum(k_counts.values())
for kgram in kgram_name:
C = k_counts.get(kgram, 0)
P = (C + 1) / (N + V)
probs_name.append(P)
prob_c = k_counts.get(kgram, 0)
prob_p = (prob_c + 1) / (prob_n + v)
probs_name.append(prob_p)
else:
k1_counts = kgram_models[(origin, n - 1)]
for kgram in kgram_name:
N = k1_counts.get(kgram[:-1], 0)
C = k_counts.get(kgram, 0)
P = (C + 1) / (N + V)
probs_name.append(P)
prob_n = k1_counts.get(kgram[:-1], 0)
prob_c = k_counts.get(kgram, 0)
prob_p = (prob_c + 1) / (prob_n + v)
probs_name.append(prob_p)
perp = math.exp(-sum([math.log(x) for x in probs_name]) / length_name)
@ -229,14 +225,14 @@ def evaluate_models(filename, n=3, use_logprob=True):
def compute_v():
global V
global v
vocab = {}
for origin in all_origins:
modele_courant = kgram_models[(origin, 1)]
for k in modele_courant.keys():
vocab[k] = vocab.get(k, 0) + modele_courant.get(k, 0)
V = len(vocab.keys()) + 1
print("la valeur de V est "+str(V))
v = len(vocab.keys()) + 1
print("la valeur de V est " + str(v))
if __name__ == '__main__':
@ -261,22 +257,25 @@ if __name__ == '__main__':
# print("\nLes données pour tester vos modèles sont:")
# for org, name_list in test_names.items():
# print("\t", org, name_list)
for n in range(1, 4):
res_logprob, succ_logprob = evaluate_models(test_filename, n, True)
res_perp, succ_perp = evaluate_models(test_filename, n, False)
for current_n in range(1, 4):
res_logprob, succes_logprob = evaluate_models(test_filename, current_n, True)
res_perp, succes_perp = evaluate_models(test_filename, current_n, False)
print(res_logprob)
# Résultats pour le rapport
df_succ_logprob = pd.DataFrame.from_dict(succ_logprob).fillna(0).transpose()
df_succ_logprob.columns = ['echec_logprob', 'succes_logprob']
df_succ_perp = pd.DataFrame.from_dict(succ_perp).fillna(0).transpose()
df_succ_perp.columns = ['echec_perp', 'succes_perp']
df_succ = df_succ_logprob.join(df_succ_perp)
df_succes_logprob = pd.DataFrame.from_dict(succes_logprob).fillna(0).transpose()
df_succes_logprob.columns = ['echec_logprob', 'succes_logprob']
df_succes_perp = pd.DataFrame.from_dict(succes_perp).fillna(0).transpose()
df_succes_perp.columns = ['echec_perp', 'succes_perp']
df_succ = df_succes_logprob.join(df_succes_perp)
f, ax = plt.subplots(figsize=(9, 9))
hm_echec = sn.heatmap(df_succ, annot=True, fmt="d", linewidths=.5, ax=ax, cmap='Oranges')
hm_echec.set(title="Performance de modèles "+{1:"uni",2:"bi",3:"tri"}[n]+"grammes \nsur les données de test")
hm_echec.set(
title="Performance de modèles " +
{1: "uni", 2: "bi", 3: "tri"}[current_n] +
"grammes \nsur les données de test")
plt.tight_layout()
hm_echec.get_figure().savefig("images/2_seaborn_hm_echec_"+str(n)+".png")
hm_echec.get_figure().savefig("images/2_seaborn_hm_echec_" + str(current_n) + ".png")

View file

@ -14,6 +14,7 @@ from sklearn.utils.multiclass import unique_labels
training_questions_fn = "./data/questions-t3.txt"
test_questions_fn = "./data/test-questions-t3.txt"
# Cette fonction provient de https://stackoverflow.com/a/7716358
def mean(numbers):
return float(sum(numbers)) / max(len(numbers), 1)
@ -25,6 +26,7 @@ def run_question_classification(training_fn, test_fn):
print("Accuracy on test set: {0:.4f}".format(accuracy_test))
# Fonction de décompte personnalisée avec les trois paramètres optimisés
def custom_count_vectorize_questions(min_df, max_df, use_stop_words):
stop_words = [None, 'english'][use_stop_words]
count_vec = CountVectorizer(analyzer='word',
@ -42,9 +44,7 @@ def train_and_test_classifier(training_fn, test_fn):
# test_questions, test_labels = load_dataset(test_questions_fn)
print("Nb questions de test:", len(test_questions))
# Insérer ici votre code pour la classification des questions.
# Votre code...
# Multinomial Naive Bayes
mnb = MultinomialNB()
# Sélection des meilleurs paramètres pour le CountVectorizer
@ -74,18 +74,22 @@ def train_and_test_classifier(training_fn, test_fn):
count_questions_train_opt = count_vec_opt.fit_transform(questions)
count_questions_test_opt = count_vec_opt.transform(test_questions)
# Optimisation de l'hyperparamètre alpha
print("\nRecherche de l'hyperparamètre alpha:")
gvc = GridSearchCV(mnb, param_grid={'alpha': [x / 10.0 for x in range(1, 21)]}, cv=5)
mnb_fit = gvc.fit(count_questions_train_opt, labels)
print("Score: {0:.4f}".format(str(mnb_fit.best_score_)))
print("Score: {0:.4f}".format(mnb_fit.best_score_))
print("Paramètres: " + str(mnb_fit.get_params()))
u_labels = unique_labels(labels, test_labels)
# Calcul des prédictions et de la performance
labels_predict_train = mnb_fit.predict(count_questions_train_opt)
labels_predict_test = mnb_fit.predict(count_questions_test_opt)
@ -112,7 +116,8 @@ def train_and_test_classifier(training_fn, test_fn):
f, ax4 = plt.subplots(figsize=(9, 9))
sn.reset_defaults()
sn.set(style="darkgrid")
sn_plot_count_category_test = sn.countplot(y='category', data=df_count_test_labels, palette="deep", ax=ax4, order=u_labels)
sn_plot_count_category_test = sn.countplot(y='category', data=df_count_test_labels, palette="deep", ax=ax4,
order=u_labels)
sn_plot_count_category_test.set(title="Fréquence des catégories de questions\ndans l'échantillon de test",
xlabel="Fréquence",
ylabel="Catégorie")