Analyse des sentiments avec BERT a l’aide de Hugging Face

mostefa sihamdi
9 min readJul 31, 2021

Source https://explore.mathworks.com/

Dans cet article, on va apprendre à entrainer BERT pour l’analyse des sentiments. Vous effectuerez le prétraitement de texte requis (tokens , padding et masques d’attention),aussi on va construire un classifieur de sentiments en utilisant l’incroyable bibliothèque Transformers de Hugging Face !

Vous apprendrez à :

  1. Comprendre intuitivement ce qu’est BERT.
  2. traiter les données textuelles pour BERT et construire un ensemble de données PyTorch (dataset).
  3. Utiliser le Transfer Learning pour construire un classifieur de sentiments en utilisant la bibliothèque Transformers de Hugging Face.
  4. Évaluer le modèle sur des données de test.
  5. Prédire le sentiment sur du texte brut

Qu’est-ce que BERT ?

BERT (Bidirectional Encoder Representations from Transformers) est un modèle qui représente un bloc encodeur d’un transformateur et qui est pré-entraîné avec un énorme corpus de texte non étiqueté, La puissante de BERT est que le même modèle pré-entraîné peut être utilisé, simplement en ajoutant une couche de sortie supplémentaire, dans une variété de tâches avec de très bons résultats pour chacune d’entre elles.

Pour entraîner BERT, deux méthodes sont utilisées : masked language modeling et next sentence prediction.

Modélisation du langage masqué : L’idée principale est de remplacer aléatoirement un pourcentage des token d’entrée par un token [MASK] et de prédire le token original.. Cette méthode est assez similaire à celle utilisée pour entrainer les modèles Word2Vec.

Source : https://www.researchgate.net

Prédiction de la phrase suivante : le principe de cette méthode est que deux phrases aléatoires sont sélectionnées dans le corpus, et avec une probabilité de 50%, la phrase B est la phrase réelle qui suit la phrase A. Le modèle doit alors prédire si la phrase B suit la phrase A ou non.

Source : https://lbourdois.github.io/blog/nlp/ALBERT/

Installation

Nous aurons besoin de la bibliothèque Transformers de Hugging Face :

Data Exploration

We’ll load the Google Play app reviews dataset

df = pd.read_csv("reviews.csv")
df.shape
#(15746, 11)

Nous avons environ 16 000 exemples. Vérifions les valeurs manquantes :

df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 15746 entries, 0 to 15745
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 userName 15746 non-null object
1 userImage 15746 non-null object
2 content 15746 non-null object
3 score 15746 non-null int64
4 thumbsUpCount 15746 non-null int64
5 reviewCreatedVersion 13533 non-null object
6 at 15746 non-null object
7 replyContent 7367 non-null object
8 repliedAt 7367 non-null object
9 sortOrder 15746 non-null object
10 appId 15746 non-null object
dtypes: int64(2), object(9)
memory usage: 1.3+ MB

Super, pas de valeurs manquantes dans les textes de score et de revue ! Avons-nous un déséquilibre de classe ?

sns.countplot(df.score)
plt.xlabel('review score');

C’est très déséquilibré, mais c’est bon. Nous allons convertir l’ensemble des données en sentiments négatifs, neutres et positifs (3 ensembles):

L’équilibre a été (en grande partie) rétabli.

Préparer les données

Vous savez peut-être déjà que les modèles d’apprentissage automatique ne fonctionnent pas avec du texte brut. Vous devez convertir le texte en chiffres (d’une certaine manière).BERT requiert encore plus d’attention . Voici les exigences :

  1. Ajouter des tokens spéciaux pour séparer les phrases et faire de la classification.
  2. Passer des séquences de longueur constante (introduire le padding).
  3. Créer un tableau de 0 et de 1 appelé attention mask

La bibliothèque Transformers fournit une grande variété de modèles de Transformers (y compris BERT).Il fonctionne avec TensorFlow et PyTorch .Il inclut également des tokenizers pré-construits qui font le gros du travail pour nous !

PRE_TRAINED_MODEL_NAME = 'bert-base-cased'

Vous pouvez utiliser une version cased et uncased de BERT et de tokenizer. J’ai expérimenté les deux. La version cased fonctionne mieux.

Chargeons un BertTokenizer pré-entraîné :

tokenizer = BertTokenizer.from_pretrained(PRE_TRAINED_MODEL_NAME)

Nous allons utiliser ce texte pour comprendre le processus de tokenisation :

sample_txt = 'When was I last outside? I am stuck at home for 2 weeks.'

Certaines opérations de base permettent de convertir le texte en tokens et les tokens en entiers uniques (ids) :

tokens = tokenizer.tokenize(sample_txt)
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print(f' Sentence: {sample_txt}')
print(f' Tokens: {tokens}')
print(f'Token IDs: {token_ids}')
Sentence: When was I last outside? I am stuck at home for 2 weeks.
Tokens: ['When', 'was', 'I', 'last', 'outside', '?', 'I', 'am', 'stuck', 'at', 'home', 'for', '2', 'weeks', '.']
Token IDs: [1332, 1108, 146, 1314, 1796, 136, 146, 1821, 5342, 1120, 1313, 1111, 123, 2277, 119]

Tokens spéciaux

[SEP] : marqueur de fin de phrase

tokenizer.sep_token, tokenizer.sep_token_id
('[SEP]', 102)

[CLS] : nous devons ajouter ce token au début de chaque phrase, afin que BERT sache que nous faisons une classification.

tokenizer.cls_token, tokenizer.cls_token_id
('[CLS]', 101)

Il existe également un token spécial pour le padding :

tokenizer.pad_token, tokenizer.pad_token_id('[PAD]', 0)

Tout ce travail peut être effectué à l’aide de la méthode encode_plus() :

Les identifiants des token sont maintenant stockés dans un Tensor et paddés à une longueur de 32 :

print(len(encoding['input_ids'][0]))
encoding['input_ids'][0]
32
tensor([101,1332,1108,146,1314,1796,136,146,1821,5342,1120, 1313,1111,123,2277,119,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])

Le masque d’attention a la même longueur :

print(len(encoding['attention_mask'][0]))
encoding['attention_mask']
32
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

Nous pouvons inverser la tokénisation pour voir les tokens spéciaux :

tokenizer.convert_ids_to_tokens(encoding['input_ids'][0])['[CLS]',
'When',
'was',
'I',
'last',
'outside',
'?',
'I',
'am',
'stuck',
'at',
'home',
'for',
'2',
'weeks',
'.',
'[SEP]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]',
'[PAD]']

Choix de la longueur de la séquence

BERT fonctionne avec des séquences de longueur fixe. Nous allons utiliser une stratégie simple pour choisir la longueur maximale. Stockons la longueur du token de chaque revue :

token_lens = []
for txt in df.content:
tokens = tokenizer.encode(txt, max_length=512)
token_lens.append(len(tokens))

et dessinez la distribution :

sns.distplot(token_lens)
plt.xlim([0, 256])
plt.xlabel('Token count')

La plupart des revues semblent contenir moins de 128 tokens, mais nous serons prudents et choisirons une longueur maximale de 160.

MAX_LEN = 160

Nous avons tous les éléments nécessaires pour créer un jeu de données PyTorch.

Le tokenizer fait le gros travail pour nous. Nous retournons également les textes des revues, afin qu’il soit plus facile d’évaluer les prédictions de notre modèle. Divisons les données :

df_train, df_test = train_test_split(
df,
test_size=0.1,
random_state=RANDOM_SEED
)
df_val, df_test = train_test_split(
df_test,
test_size=0.5,
random_state=RANDOM_SEED
)

Nous devons également créer un couple de loaders de données. Voici une fonction d’aide pour le faire :

Classification de sentiments avec BERT et Hugging Face

Il y a beaucoup de fonctions qui facilitent l’utilisation de BERT avec la bibliothèque des Transformers.En fonction de la tâche à accomplir, vous pouvez utiliser BertForSequenceClassification,BertForQuestionAnswering ou autre chose.

Nous allons utiliser le BertModel de base et construire notre classifieur de sentiments. Chargeons le modèle :

bert_model = BertModel.from_pretrained(PRE_TRAINED_MODEL_NAME)

Et essayez de l’utiliser sur l’encodage de notre exemple de texte :

last_hidden_state, pooled_output = bert_model(
input_ids=encoding['input_ids'],
attention_mask=encoding['attention_mask']
)

le last_hidden_state est une séquence d’états cachés de la dernière couche du modèle.L’obtention du pooled_output se fait par l’application du BertPooler sur last_hidden_state .

last_hidden_state.shape
torch.Size([1, 32, 768])

Nous avons un hidden state pour chacun de nos 32 tokens (la longueur de notre exemple de séquence).

Mais pourquoi 768 ? C’est le nombre de cellules cachées dans les réseaux feedforward. Nous pouvons le vérifier en contrôlant la configuration :

bert_model.config.hidden_size768

Nous pouvons utiliser toutes ces informations pour créer un classifieur qui utilise le modèle BERT :

Notre classifieur laisse la plus grande partie du travail au BertModel.Nous utilisons un dropout pour une certaine régularisation et une couche entièrement connectée pour notre sortie.

Créons une instance et déplaçons-la vers le GPU.

model = SentimentClassifier(len(class_names))
model = model.to(device)

Training

Pour reproduire la procédure de training de l’article de BERT, nous utiliserons l’optimiseur AdamW fourni par Hugging Face.

Nous utiliserons également un linear scheduler

Comment trouver tous les hyperparamètres ? Les auteurs de BERT ont quelques recommandations pour un réglage fin :

  • Batch size: 16, 32
  • Learning rate (Adam): 5e-5, 3e-5, 2e-5
  • Number of epochs: 2, 3, 4

Nous allons ignorer la recommandation du nombre des epochs mais conservons le reste. Notez que l’augmentation de la taille du batch réduit le temps de training de manière significative, mais vous donne une accuracy plus faible.

Continuons avec l’écriture d’une fonction d’aide pour entraîner notre modèle pendant une époque :

Écrivons-en un autre qui nous aide à évaluer le modèle sur un loader de données :

En utilisant ces deux méthodes, nous pouvons écrire notre boucle d’entraînement. On va aussi stocker l’historique de l’entraînement :

Notez que nous stockons l’état du meilleur modèle, indiqué par la plus haute précision de validation.

plt.plot(history['train_acc'], label='train accuracy')
plt.plot(history['val_acc'], label='validation accuracy')
plt.title('Training history')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend()
plt.ylim([0, 1]);

La précision de l’apprentissage commence à approcher les 100% après environ 10 époques. Vous pouvez essayer d’ajuster les paramètres un peu plus, mais cela sera suffisant pour nous.

model = SentimentClassifier(len(class_names))
model.load_state_dict(torch.load('best_model_state.bin'))
model = model.to(device)

Évaluation

Alors, quelle est la qualité de notre modèle pour prédire le sentiment ? Commençons par calculer la précision sur les données de test :

test_acc, _ = eval_model(
model,
test_data_loader,
loss_fn,
device,
len(df_test)
)
test_acc.item()0.883248730964467

La précision est inférieure d’environ 1% sur l’ensemble de test. Notre modèle semble bien.

Nous allons définir une fonction d’aide pour obtenir les prédictions de notre modèle :

Cette fonction est similaire à la fonction d’évaluation, sauf que nous stockons le texte des revues et les probabilités prédites

y_review_texts, y_pred, y_pred_probs, y_test = get_predictions(
model,
test_data_loader
)

Jetons un coup d’œil au rapport de classification

print(classification_report(y_test, y_pred, target_names=class_names))
precision recall f1-score support
negative 0.89 0.87 0.88 245
neutral 0.83 0.85 0.84 254
positive 0.92 0.93 0.92 289
accuracy 0.88 788
macro avg 0.88 0.88 0.88 788
weighted avg 0.88 0.88 0.88 788

Il semble qu’il soit très difficile de classer les avis neutres (3 étoiles). Et je peux vous dire par expérience, en regardant de nombreux avis, que ceux-ci sont difficiles à classer.

Nous allons continuer avec la matrice de confusion :

Cela confirme que notre modèle a du mal à classer les avis neutres. Il les confond avec les négatifs et les positifs à une fréquence à peu près égale.

Prédire sur du texte brut

Utilisons notre modèle pour prédire le sentiment d’un texte brut :

review_text = "I love completing my todos! Best app ever!!!"

Nous devons utiliser le tokenizer pour encoder le texte :

encoded_review = tokenizer.encode_plus(
review_text,
max_length=MAX_LEN,
add_special_tokens=True,
return_token_type_ids=False,
pad_to_max_length=True,
return_attention_mask=True,
return_tensors='pt',
)

Récupérons les prédictions de notre modèle :

input_ids = encoded_review['input_ids'].to(device)
attention_mask = encoded_review['attention_mask'].to(device)
output = model(input_ids, attention_mask)
_, prediction = torch.max(output, dim=1)
print(f'Review text: {review_text}')
print(f'Sentiment : {class_names[prediction]}')
Review text: I love completing my todos! Best app ever!!!
Sentiment : positive

Références

Responses (1)

Write a response

very good article,thank you

--