import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import tiktoken import joblib import json from transformers import pipeline from extra_streamlit_components import tab_bar, TabBarItemData from sklearn.decomposition import PCA import matplotlib.pyplot as plt import seaborn as sns from sklearn import naive_bayes from translate_app import tr import requests from tabs.read_api_url import read_api_url title = "Identification de langue" sidebar_name = "Identification de langue" dataPath = st.session_state.DataPath # CountVectorizer a une liste de phrase en entrée. # Cette fonction met les données d'entrée dans le bon format def format_to_vectorize(data): X_tok = [] if "DataFrame" in str(type(data)):sentences = data.tolist() elif "str" in str(type(data)): sentences =[data] else: sentences = data for sentence in sentences: X_tok.append(sentence) return X_tok def create_BOW(data): global vectorizer X_tok = format_to_vectorize(data) X = vectorizer.transform(X_tok) return X def load_vectorizer(tokenizer): global dict_token, dict_ids, nb_token path = dataPath+'/vectorizer_tiktoken_big.pkl' vectorizer = joblib.load(path) dict_token = {tokenizer.decode([cle]): cle for cle, valeur in vectorizer.vocabulary_.items()} dict_ids = {cle: tokenizer.decode([cle]) for cle, valeur in vectorizer.vocabulary_.items()} #dict_ids.items()} nb_token = len(vectorizer.vocabulary_) return vectorizer def lang_id_nb(sentences): global lan_to_language if "str" in str(type(sentences)): return lan_to_language[clf_nb.predict(create_BOW(sentences))[0]] else: return [lan_to_language[l] for l in clf_nb.predict(create_BOW(sentences))] @st.cache_resource def init_nb_identifier(): tokenizer = tiktoken.get_encoding("cl100k_base") # Chargement du classificateur sauvegardé clf_nb = joblib.load(dataPath+"/id_lang_tiktoken_nb_sparse_big.pkl") vectorizer = load_vectorizer(tokenizer) # Lisez le contenu du fichier JSON with open(dataPath+'/multilingue/lan_to_language.json', 'r') as fichier: lan_to_language = json.load(fichier) return tokenizer, dict_token, dict_ids, nb_token, lan_to_language, clf_nb, vectorizer @st.cache_resource def init_lang_id_external(): lang_id_model_ext = pipeline('text-classification',model="papluca/xlm-roberta-base-language-detection") dict_xlmr = {"ar":"ara", "bg":"bul", "de":"deu", "el": "ell", "en":"eng", "es":"spa", "fr":"fra", "hi": "hin","it":"ita","ja":"jpn", \ "nl":"nld", "pl":"pol", "pt":"por", "ru":"rus", "sw":"swh", "th":"tha", "tr":"tur", "ur": "urd", "vi":"vie", "zh":"cmn"} sentence_test = pd.read_csv(dataPath+'//multilingue/sentence_test_extract.csv') sentence_test = sentence_test[:4750] # Instanciation d'un exemple exemples = ["Er weiß überhaupt nichts über dieses Buch", # Phrase 0 "Umbrellas sell well", # Phrase 1 "elle adore les voitures très luxueuses, et toi ?", # Phrase 2 "she loves very luxurious cars, don't you?", # Phrase 3 "Vogliamo visitare il Colosseo e nuotare nel Tevere", # Phrase 4 "vamos a la playa", # Phrase 5 "Te propongo un trato", # Phrase 6 "she loves you much, mais elle te hait aussi and das ist traurig", # Phrase 7 # Attention à cette phrase trilingue "Elle a de belles loches" # Phrase 8 ] lang_exemples = ['deu','eng','fra','eng','ita','spa','spa','fra','fra'] return lang_id_model_ext, dict_xlmr, sentence_test, lang_exemples, exemples @st.cache_data def display_acp(title, comment): data = np.load(dataPath+'/data_lang_id_acp.npz') X_train_scaled = data['X_train_scaled'] y_train_pred = data['y_train_pred'] label_arrow = ['.', ',', '?', ' a', ' de', ' la', ' que', 'Tom', ' un', ' the', ' in', \ ' to', 'I', "'", 'i', ' le', ' en', ' es', 'é', ' l', '!', 'o', ' ist', \ ' pas', ' Tom', ' me', ' di', 'Ich', ' is', 'Je', ' nicht', ' you', \ ' die', ' à', ' el', ' est', 'a', 'en', ' d', ' è', ' ne', ' se', ' no', \ ' una', ' zu', 'Il', '¿', ' of', ' du', "'t", 'ato', ' der', ' il', \ ' n', 'El', ' non', ' che', 'are', ' con', 'ó', ' was', 'La', 'No', \ ' ?', 'es', 'le', 'L', ' and', ' des', ' s', ' ich', 'as', 'S', ' per', \ ' das', ' und', ' ein', 'e', "'s", 'u', ' y', 'He', 'z', 'er', ' m', \ 'st', ' les', 'Le', ' I', 'ar', 'te', 'Non', 'The', ' er', 'ie', ' v', \ ' c', "'est", ' ha', ' den'] pca = PCA(n_components=2) X_new = pca.fit_transform(X_train_scaled) coeff = pca.components_.transpose() xs = X_new[:, 0] ys = X_new[:, 1] scalex = 1.0/(xs.max() - xs.min()) scaley = 1.0/(ys.max() - ys.min()) principalDf = pd.DataFrame({'PC1': xs*scalex, 'PC2': ys * scaley}) finalDF = pd.concat([principalDf, pd.Series(y_train_pred, name='Langue')], axis=1) sns.set_context("poster") # Valeur possible:"notebook", "talk", "poster", ou "paper" plt.rc("axes", titlesize=32,titleweight='bold') # Taille du titre de l'axe plt.rc("axes", labelsize=18,labelweight='bold') # Taille des étiquettes de l'axe plt.rc("xtick", labelsize=14) # Taille des étiquettes de l'axe des x plt.rc("ytick", labelsize=14) # Taille des étiquettes de l'axe des y st.write(comment) st.write("") fig = plt.figure(figsize=(20, 15)) sns.scatterplot(x='PC1', y='PC2', hue='Langue', data=finalDF, alpha=0.5) for i in range(50): plt.arrow(0, 0, coeff[i, 0]*1.5, coeff[i, 1]*0.8,color='k', alpha=0.08, head_width=0.01, ) plt.text(coeff[i, 0]*1.5, coeff[i, 1] * 0.8, label_arrow[i], color='k', weight='bold') plt.title(title) plt.xlim(-0.4, 0.45) plt.ylim(-0.15, 0.28); st.pyplot(fig) return @st.cache_data def read_BOW_examples(): return pd.read_csv(dataPath+'/lang_id_small_BOW.csv') def analyse_nb(sel_phrase): global lang_exemples,exemples def create_small_BOW(s): encodage = tokenizer.encode(s) sb = [0] * (df_BOW.shape[1]-1) nb_unique_token = 0 for i in range(df_BOW.shape[1]-1): for t in encodage: if df_BOW.columns[i]==str(t): sb[i] += 1 if sb[i] > 0: nb_unique_token +=1 return sb, nb_unique_token st.write("#### **"+tr("Probabilité d'appartenance de la phrase à une langue")+" :**") st.image("./assets/formule_proba_naive_bayes.png") st.write(tr("où **C** est la classe (lan_code), **Fi** est la caractéristique i du BOW, **Z** est l'\"evidence\" servant à regulariser la probabilité")) st.write("") nb_lang = 5 lan_code = ['deu','eng','fra','spa','ita'] lan_color = {'deu':'violet','eng':'green','fra':'red','spa':'blue','ita':'orange'} df_BOW = read_BOW_examples() clf_nb2 = naive_bayes.MultinomialNB() clf_nb2.fit(df_BOW.drop(columns='lan_code').values.tolist(), df_BOW['lan_code'].values.tolist()) nb_phrases_lang =[] for l in lan_code: nb_phrases_lang.append(sum(df_BOW['lan_code']==l)) st.write(tr("Phrase à analyser")+" :",'**:'+lan_color[lang_exemples[sel_phrase]]+'['+lang_exemples[sel_phrase],']** - **"'+exemples[sel_phrase]+'"**') # Tokenisation et encodage de la phrase encodage = tokenizer.encode(exemples[sel_phrase]) # Création du vecteur BOW de la phrase bow_exemple, nb_unique_token = create_small_BOW(exemples[sel_phrase]) st.write(tr("Nombre de tokens retenus dans le BOW")+": "+ str(nb_unique_token)) masque_tokens_retenus = [(1 if token in list(dict_ids.keys()) else 0) for token in encodage] str_token = " " for i in range(len(encodage)): if masque_tokens_retenus[i]==1: if (i%2) ==0: str_token += "**:red["+tokenizer.decode([encodage[i]])+"]** " else: str_token += "**:violet["+tokenizer.decode([encodage[i]])+"]** " else: str_token += ":green["+tokenizer.decode([encodage[i]])+"] " st.write(tr("Tokens se trouvant dans le modèle (en")+" :red["+tr("rouge")+"] "+tr("ou")+" :violet["+tr("violet")+"]) :"+str_token+" ") st.write("") # Afin de continuer l'analyse on ne garde que les token de la phrase disponibles dans le BOW token_used = [str(encodage[i]) for i in range(len(encodage)) if (masque_tokens_retenus[i]==1)] # Calcul du nombre d'apparition de ces tokens dans le BOW pour chaque langue, et stockage dans un DataFrame df_count def compter_non_zero(colonne): return (colonne != 0).sum() votes = [] for i in range(nb_lang): #votes.append(list(df_BOW[token_used].loc[df_BOW['lan_code']==lan_code[i]].sum(axis=0))) votes.append(list(df_BOW[token_used].loc[df_BOW['lan_code']==lan_code[i]].apply(compter_non_zero))) col_name = [str(i+1)+'-'+tokenizer.decode([int(token_used[i])]) for i in range(len(token_used))] df_count = pd.DataFrame(data=votes,columns=token_used, index=lan_code) df_count.columns = col_name st.write("\n**"+tr("Nombre d'apparitions des tokens, dans chaque langue")+"**") # Lissage de Laplace n°1 (Laplace smoothing ) # df_count = df_count+1 st.dataframe(df_count) ######### ######### 3. Calcul de la probabilité d'apparition de chaque token dans chaque langue df_proba = df_count.div(nb_phrases_lang, axis = 0) # Lissage de Laplace n°2 (Laplace smoothing ) df_proba = df_proba.replace(0.0,0.0010) # Initialisation de df_proba: Calcul de la probabilité conditionnelle d'appartenance de la phrase à une langue df_proba['Proba'] = 1 # Itérer sur les colonnes et effectuez la multiplication pour chaque ligne for col in df_count.columns: df_proba['Proba'] *= df_proba[col] ######### ######### 4. Calcul (par multiplication) de la probabilité d'appartenance de la phrase à une langue # Multiplication par la probabilité de la classe p_classe = [(nb_phrases_lang[i]/df_BOW.shape[0]) for i in range(len(nb_phrases_lang))] df_proba['Proba'] *= p_classe # Diviser par l'evidence evidence = df_proba['Proba'].sum(axis=0) df_proba['Proba'] *= 1/evidence df_proba['Proba'] = df_proba['Proba'].round(3) # Affichage de la matrice des probabilités st.write("**"+tr("Probabilités conditionnelles d'apparition des tokens retenus, dans chaque langue")+":**") st.dataframe(df_proba) str_token = "Lang proba max: "# "*20 for i,token in enumerate(df_proba.columns[:-1]): str_token += '*'+token+'*:**:'+lan_color[df_proba[token].idxmax()]+'['+df_proba[token].idxmax()+']**'+" "*2 #8 st.write(str_token) st.write("") st.write(tr("Langue réelle de la phrase")+" "*35+": **:"+lan_color[lang_exemples[sel_phrase]]+'['+lang_exemples[sel_phrase]+']**') st.write(tr("Langue dont la probabilité est la plus forte ")+": **:"+lan_color[df_proba['Proba'].idxmax()]+'['+df_proba['Proba'].idxmax(),"]** (proba={:.2f}".format(max(df_proba['Proba']))+")") prediction = clf_nb2.predict([bow_exemple]) st.write(tr("Langue prédite par Naiva Bayes")+" "*23+": **:"+lan_color[prediction[0]]+'['+prediction[0]+"]** (proba={:.2f}".format(max(clf_nb2.predict_proba([bow_exemple])[0]))+")") st.write("") fig, axs = plt.subplots(1, 2, figsize=(10, 6)) df_proba_sorted =df_proba.sort_index(ascending=True) axs[0].set_title(tr("Probabilités calculée manuellement"), fontsize=12) axs[0].barh(df_proba_sorted.index, df_proba_sorted['Proba']) axs[1].set_title(tr("Probabilités du classifieur Naive Bayes"), fontsize=12) axs[1].barh(df_proba_sorted.index, clf_nb2.predict_proba([bow_exemple])[0]); st.pyplot(fig) return #@st.cache_data def find_exemple(lang_sel): global exemples return exemples[lang_sel] def display_shapley(lang_sel): st.write("**"+tr("Analyse de l'importance de chaque token dans l'identification de la langue")+"**") st.image('assets/fig_schapley'+str(lang_sel)+'.png') st.write("**"+tr("Recapitulatif de l'influence des tokens sur la selection de la langue")+"**") st.image('assets/fig_schapley_recap'+str(lang_sel)+'.png') return def run(): global tokenizer, vectorizer, dict_token, dict_ids, nb_token, lan_to_language, clf_nb global toggle_val, custom_sentence, lan_identified global lang_exemples, exemples, url_base url_base = read_api_url() tokenizer, dict_token, dict_ids, nb_token, lan_to_language, clf_nb, vectorizer = init_nb_identifier() lan_identified = requests.get( url_base[0]+"/lan_identified").json() lang_id_model_ext, dict_xlmr, sentence_test, lang_exemples, exemples= init_lang_id_external() st.write("") st.title(tr(title)) st.write("## **"+tr("Explications")+" :**\n") st.markdown(tr( """ Afin de mettre en oeuvre cette fonctionnalité nous avons utilisé un jeu d'entrainement multilinge de 9.757.778 phrases dans 95 langues. Les 95 langues identifiées sont: """) , unsafe_allow_html=True) st.selectbox(label="Lang",options=sorted(lan_identified),label_visibility="hidden") st.markdown(tr( """ Nous avons utilisé 2 méthodes pour identifier la langue d'un texte: 1. un classificateur **Naïve Bayes** 2. un modèle de **Deep Learning** """) , unsafe_allow_html=True) st.markdown(tr( """ Les 2 modèles ont un accuracy similaire sur le jeu de test: **:red[96% pour NB et 97,5% pour DL]**
""") , unsafe_allow_html=True) chosen_id = tab_bar(data=[ TabBarItemData(id="tab1", title=tr("Id. Naïve Bayes"), description=tr("avec le Bag Of Words")), TabBarItemData(id="tab2", title=tr("Id. Deep Learning"), description=tr(" avec Keras")), TabBarItemData(id="tab3", title=tr("Interpretabilité"), description=tr("du modèle Naïve Bayes "))], default="tab1") if (chosen_id == "tab1") or (chosen_id == "tab2"): st.write("## **"+tr("Paramètres")+" :**\n") toggle_val = st.toggle(tr('Phrase à saisir/Phrase test'), value=True, help=tr("Off = phrase à saisir, On = selection d'une phrase test parmi 9500 phrases")) if toggle_val: custom_sentence= st.selectbox(tr("Selectionnez une phrases test à identifier")+":", sentence_test['sentence'] ) else: custom_sentence = st.text_area(label=tr("Saisir le texte dont vous souhaitez identifier la langue:")) st.button(label=tr("Validez"), type="primary") if custom_sentence!='': url = url_base[0]+"/lang_id_dl" params = {"sentence": custom_sentence} response = requests.get(url, params=params) st.write("## **"+tr("Résultats")+" :**\n") md = """ |"""+tr("Identifieur")+""" |"""+tr("Langue identifiée")+"""| |-------------------------------------|---------------|""" md1 = "" if toggle_val: lan_reelle = sentence_test['lan_code'].loc[sentence_test['sentence']==custom_sentence].tolist()[0] md1 = """ |"""+tr("Langue réelle")+""" |**:blue["""+lan_to_language[lan_reelle]+"""]**|""" md2 = """ |"""+tr("Classificateur Naïve Bayes")+""" |**:red["""+lang_id_nb(custom_sentence)+"""]**| |"""+tr("Modèle de Deep Learning")+""" |**:red["""+str(response.json())+"""]**|""" md3 = """ |XLM-RoBERTa (Hugging Face) |**:red["""+lan_to_language[dict_xlmr[lang_id_model_ext(custom_sentence)[0]['label']]]+"""]**|""" if toggle_val: if not (lan_reelle in list(dict_xlmr.values())): md3="" st.markdown(md+md1+md2+md3, unsafe_allow_html=True) st.write("## **"+tr("Details sur la méthode")+" :**\n") if (chosen_id == "tab1"): st.markdown(tr( """ Afin d'utiliser le classificateur Naïve Bayes, il nous a fallu:""")+"\n"+ "* "+tr("Créer un Bag of Words de token..")+"\n"+ "* "+tr("..Tokeniser le texte d'entrainement avec CountVectorizer et un tokenizer 'custom', **Tiktoken** d'OpenAI. ")+"\n"+ "* "+tr("Utiliser des matrices creuses (Sparse Matrix), car notre BOW contenait 10 Millions de lignes x 59122 tokens. ")+"\n"+ "* "+tr("Sauvegarder le vectorizer (non serialisable) et le classificateur entrainé. ") , unsafe_allow_html=True) st.markdown(tr( """ L'execution de toutes ces étapes est assez rapide: une dizaine de minutes
Le résultat est très bon: L'Accuracy sur le jeu de test est = **:red[96%]** sur les 95 langues, et **:red[99,1%]** sur les 5 langues d'Europe de l'Ouest (en,fr,de,it,sp)
""") , unsafe_allow_html=True) st.markdown(tr( """ **Note 1:** Les 2 modèles ont un accuracy similaire sur le jeu de test: **:red[96% pour NB et 97,5% pour DL]** **Note 2:** Le modèle *XLM-RoBERTa* de Hugging Face (qui identifie 20 langues seulement) a une accuracy, sur notre jeu de test = **97,8%**, versus **99,3% pour NB** et **99,2% pour DL** sur ces 20 langues. """) , unsafe_allow_html=True) else: st.markdown(tr( """ Nous avons mis en oeuvre un modèle Keras avec une couche d'embedding et 4 couches denses (*Voir architecture ci-dessous*). Nous avons utilisé le tokeniser Tiktoken d'OpenAI. La couche d'embedding accepte 250 tokens, ce qui signifie que la détection de langue s'effectue sur approximativement les 200 premiers mots.
""") , unsafe_allow_html=True) st.markdown(tr( """ L'entrainement a duré plus de 10 heures.. Finalement, le résultat est très bon: L'Accuracy sur le jeu de test est = **:red[97,5%]** sur les 95 langues, et **:red[99,1%]** sur les 5 langues d'Europe de l'Ouest (en,fr,de,it,sp). Néanmoins, la durée pour une prédiction est relativement longue: approximativement 5/100 de seconde
""") , unsafe_allow_html=True) st.markdown(tr( """ **Note 1:** Les 2 modèles ont un accuracy similaire sur le jeu de test: **:red[96% pour NB et 97,5% pour DL]**""")+"
"+ tr(""" **Note 2:** Le modèle *XLM-RoBERTa* de Hugging Face (qui identifie 20 langues seulement) a une accuracy, sur notre jeu de test = 97,8%, versus **99,3% pour NB** et **99,2% pour DL** sur ces 20 langues.
""") , unsafe_allow_html=True) st.write("
"+tr("Architecture du modèle utilisé")+":
", unsafe_allow_html=True) col1, col2, col3 = st.columns([0.15,0.7,0.15]) with col2: st.image(url_base[0]+"e/small_vocab/plot_model?&model_type=lang_id",use_column_width="auto") elif (chosen_id == "tab3"): st.write("### **"+tr("Interpretabilité du classifieur Naïve Bayes sur 5 langues")+"**") st.write("##### "+tr("..et un Training set réduit (15000 phrases et 94 tokens)")) st.write("") chosen_id2 = tab_bar(data=[ TabBarItemData(id="tab1", title=tr("Analyse en Compos. Princ."), description=""), TabBarItemData(id="tab2", title=tr("Simul. calcul NB"), description=""), TabBarItemData(id="tab3", title=tr("Shapley"), description="")], default="tab1") if (chosen_id2 == "tab1"): display_acp(tr("Importance des principaux tokens dans \n l'identification de langue par l'algorithme Naive Bayes"),tr("Affichage de 10 000 phrases (points) et des 50 tokens les + utilisés (flèches)")) if (chosen_id2 == "tab2") or (chosen_id2 == "tab3"): sel_phrase = st.selectbox(tr('Selectionnez une phrase à "interpréter"')+':', range(9), format_func=find_exemple) if (chosen_id2 == "tab2"): analyse_nb(sel_phrase) if (chosen_id2 == "tab3"): display_shapley(sel_phrase)