|
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 |
|
|
|
|
|
|
|
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()} |
|
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") |
|
|
|
|
|
clf_nb = joblib.load(dataPath+"/id_lang_tiktoken_nb_sparse_big.pkl") |
|
vectorizer = load_vectorizer(tokenizer) |
|
|
|
|
|
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] |
|
|
|
exemples = ["Er weiß überhaupt nichts über dieses Buch", |
|
"Umbrellas sell well", |
|
"elle adore les voitures très luxueuses, et toi ?", |
|
"she loves very luxurious cars, don't you?", |
|
"Vogliamo visitare il Colosseo e nuotare nel Tevere", |
|
"vamos a la playa", |
|
"Te propongo un trato", |
|
"she loves you much, mais elle te hait aussi and das ist traurig", |
|
"Elle a de belles loches" |
|
] |
|
|
|
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") |
|
plt.rc("axes", titlesize=32,titleweight='bold') |
|
plt.rc("axes", labelsize=18,labelweight='bold') |
|
plt.rc("xtick", labelsize=14) |
|
plt.rc("ytick", labelsize=14) |
|
|
|
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]+'"**') |
|
|
|
|
|
encodage = tokenizer.encode(exemples[sel_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("") |
|
|
|
token_used = [str(encodage[i]) for i in range(len(encodage)) if (masque_tokens_retenus[i]==1)] |
|
|
|
|
|
|
|
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]].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")+"**") |
|
|
|
|
|
|
|
|
|
st.dataframe(df_count) |
|
|
|
|
|
|
|
df_proba = df_count.div(nb_phrases_lang, axis = 0) |
|
|
|
|
|
df_proba = df_proba.replace(0.0,0.0010) |
|
|
|
|
|
df_proba['Proba'] = 1 |
|
|
|
for col in df_count.columns: |
|
df_proba['Proba'] *= df_proba[col] |
|
|
|
|
|
|
|
|
|
|
|
p_classe = [(nb_phrases_lang[i]/df_BOW.shape[0]) for i in range(len(nb_phrases_lang))] |
|
df_proba['Proba'] *= p_classe |
|
|
|
|
|
evidence = df_proba['Proba'].sum(axis=0) |
|
df_proba['Proba'] *= 1/evidence |
|
df_proba['Proba'] = df_proba['Proba'].round(3) |
|
|
|
|
|
st.write("**"+tr("Probabilités conditionnelles d'apparition des tokens retenus, dans chaque langue")+":**") |
|
st.dataframe(df_proba) |
|
str_token = "Lang proba max: " |
|
for i,token in enumerate(df_proba.columns[:-1]): |
|
str_token += '*'+token+'*:**:'+lan_color[df_proba[token].idxmax()]+'['+df_proba[token].idxmax()+']**'+" "*2 |
|
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 |
|
|
|
|
|
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 <b> 9.757.778 phrases dans 95 langues</b>. |
|
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]** |
|
<br> |
|
""") |
|
, 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 |
|
<br> |
|
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) |
|
<br> |
|
""") |
|
, 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 <b>Tiktoken</b> 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. |
|
<br> |
|
""") |
|
, 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 |
|
<br> |
|
""") |
|
, 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]**""")+"<br>"+ |
|
tr(""" |
|
**Note 2:** Le modèle *XLM-RoBERTa* de Hugging Face (qui identifie 20 langues seulement) a une accuracy, sur notre jeu de test = <b>97,8%</b>, |
|
versus **99,3% pour NB** et **99,2% pour DL** sur ces 20 langues. |
|
<br> |
|
""") |
|
, unsafe_allow_html=True) |
|
st.write("<center><h5>"+tr("Architecture du modèle utilisé")+":</h5></center>", unsafe_allow_html=True) |
|
col1, col2, col3 = st.columns([0.15,0.7,0.15]) |
|
with col2: |
|
st.image(url_base[0]+"/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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|