naive_bayes (NAIVE_BAYES) classe le texte à l’aide d’un modèle Naive Bayes multinomial, le modèle d’événements standard pour le texte : elle attribue un score à chaque classe selon la fréquence d’apparition des n-grammes dans l’entrée. Vous lui fournissez une table de comptages de n-grammes par classe, qu’elle compile en modèle une seule fois, au moment du chargement, puis utilise pour classer tout texte que vous lui passez.
Elle convient à une classification de texte rapide et légère, comme l’analyse des sentiments, l’étiquetage thématique ou anti-spam, ainsi que la détection de langue ou d’écriture.
Vous interrogez le dictionnaire avec l’une des trois fonctions :
naiveBayesClassifierrenvoie l’identifiant de classe prédit.naiveBayesClassifierWithProbrenvoie la classe prédite avec sa probabilité.naiveBayesClassifierWithAllProbsrenvoie chaque classe avec sa probabilité.
dictGet permet aussi de classer (voir les Remarques). Une autre fonction, naiveBayesNgrams, ne classe pas — elle découpe le texte en n-grammes de la même manière que le dictionnaire, afin que vous puissiez créer les données d’entraînement à partir de texte brut (voir Créer des données d’entraînement à partir de texte brut).
Démarrage rapide
n = 1) en mode token pour l’analyse des sentiments.
1. Créez une table source contenant les décomptes de n-grammes par classe :
1) et négative (0) :
NAIVE_BAYES :
PRIMARY KEY ngram fait de la colonne ngram la clé — mais pour un dictionnaire NAIVE_BAYES, cette « clé » est le texte que vous soumettez à la classification, et non une valeur stockée que vous recherchez (voir Structure du dictionnaire). Le LAYOUT configure le modèle : class_attribute 'class_id' désigne class_id comme l’étiquette de classe (l’autre attribut, count, correspondant alors au nombre d’occurrences par classe), n 1 utilise des unigrammes, et mode 'token' découpe le texte en mots séparés par des espaces (voir Paramètres du layout).
4. Classifier — naiveBayesClassifier renvoie l’identifiant de classe :
1 correspond à la classe positive, d’après les données d’entraînement que nous avons insérées à l’étape 2.
0 correspond à la classe négative.
On obtient le même résultat avec dictGet :
0 (négative) avec une probabilité de 0.64.
naiveBayesClassifierWithAllProbs renvoie toutes les classes, triées de la plus probable à la moins probable, avec des probabilités dont la somme est égale à 1.0 — ici 0.64 pour la classe négative et 0.36 pour la classe positive.
Fonctionnement
(n-gram, class, count). Lorsque le dictionnaire se charge, les lignes sont compilées une seule fois dans le modèle. Les lignes (n-gram, class) en double sont additionnées, et les lignes avec count = 0 sont ignorées.
Classification (au moment de la requête). Pour classer une chaîne, le modèle :
- La découpe en n-grams selon
modeetn(voir Modes de tokenisation). - Attribue un score à chaque classe en combinant l’a priori de la classe avec la fréquence d’apparition des n-grams de l’entrée dans cette classe.
- Classe les classes par score. La classe ayant le score le plus élevé est la prédiction renvoyée par
naiveBayesClassifier;naiveBayesClassifierWithProbetnaiveBayesClassifierWithAllProbsrenvoient aussi des probabilités — pour cette classe ou pour l’ensemble des classes.
alpha, utilisé pour le lissage. Le lissage empêche le modèle d’attribuer à une classe un score nul simplement parce qu’un n-gram n’est pas apparu dans cette classe pendant l’entraînement. Un alpha plus faible fait davantage reposer le modèle sur les données d’entraînement, si bien qu’une classe peut obtenir un score beaucoup plus élevé que les autres, mais cela peut aussi rendre le modèle trop sensible lorsque les données d’entraînement sont peu nombreuses ou déséquilibrées. Un alpha plus élevé réduit l’importance des décomptes de n-grams, de sorte que les scores des différentes classes se rapprochent. Si alpha est très élevé, les informations apportées par les n-grams comptent à peine, et le score dépend surtout de l’a priori de la classe (décrit ci-après).
Le second est l’a priori de la classe — ce que le modèle suppose quant à la probabilité de chaque classe avant d’examiner le texte. Il agit comme un score initial attribué à chaque classe avant que les n-grams ne soient pris en compte, si bien qu’un a priori plus élevé rend une classe plus susceptible d’être prédite. Son paramétrage dépend de priors_mode. Par défaut (proportional), une classe dont le nombre total de n-grams est plus élevé dans les données d’entraînement commence avec un score plus élevé. Avec uniform, toutes les classes démarrent à égalité, si bien que seuls les n-grams font la différence. Avec explicit, vous définissez vous-même le point de départ de chaque classe. Voir Modes d’a priori.
Un n-gram qui n’a jamais été vu dans les données d’entraînement est ignoré : il ne fait pas partie du vocabulaire du modèle et n’aide ni ne pénalise donc aucune classe.
L’algorithme suit le modèle multinomial de Naive Bayes pour la classification de texte ; voir Manning, Raghavan & Schütze, Introduction to Information Retrieval, ch. 13 (Text Classification and Naive Bayes).
Structure du dictionnaire
NAIVE_BAYES a une structure fixe :
- La
PRIMARY KEYest une unique colonneString— le n-gram. Au moment de la requête, cette “clé” est le texte que vous fournissez pour la classification, et non une clé de lookup stockée. - À ses côtés, déclarez exactement deux attributs de type entier non signé : l’étiquette de classe et le nombre d’occurrences. Les identifiants de classe utilisent toujours
UInt32en interne ; une étiquette de classe doit donc tenir dansUInt32(au maximum4294967295), même si vous déclarez son attribut enUInt64. Une valeur plus grande est rejetée lors du chargement du dictionnaire, et non lors de sa création. Il en va de même pour les types déclarés : si un identifiant de classe source ou un nombre ne tient pas dans le type d’attribut déclaré, le chargement échoue au lieu d’être tronqué silencieusement. - Le paramètre
class_attributedu layout désigne l’attribut correspondant à l’étiquette de classe ; l’autre est automatiquement interprété comme le nombre. Les deux attributs peuvent être déclarés dans n’importe quel ordre.
(n-gram, class) avec le nombre de fois où ce n-gram est apparu dans cette classe. Vous produisez ces nombres en tokenisant votre corpus et en groupant le résultat, soit dans votre propre pipeline d’entraînement, soit dans ClickHouse à partir de texte brut labellisé (voir Build training data from raw text). Le dictionnaire se contente de les consommer.
Mise à jour du modèle. Comme le modèle est un dictionnaire adossé à une table, réentraînez-le en mettant à jour la table puis en le rechargeant :
Paramètres de layout
Vous pouvez définir le dictionnaire avec la DDL
CREATE DICTIONARY (comme dans le guide de démarrage rapide ci-dessus) ou dans un fichier de configuration XML ; voir layouts de dictionnaire pour savoir où placer ce fichier. L’exemple ci-dessous définit toutes les options de layout afin que vous puissiez toutes les voir — seuls class_attribute, n et mode sont obligatoires, et le tableau ci-dessus donne les valeurs par défaut des autres. Dans un fichier de configuration, les probabilités a priori s’écrivent sous forme d’éléments prior répétés (un par classe, comme illustré ci-dessous), les tokens de padding pour byte et codepoint sont des nombres (la config ne peut pas contenir d’octets bruts), et un littéral token est échappé en XML si nécessaire ; ainsi, <s> devient <s>.
- DDL
- Fichier de configuration
Modes de tokenisation
mode détermine ce qu’est un « token » et, par conséquent, la forme des n-grammes. Les n-grammes source doivent avoir été produits avec le même mode et le même n.
byte— chaque token est un octet unique ; aucune hypothèse n’est faite sur l’UTF-8. Avecn = 2,'abc'produit les bigrammes d’octets'ab','bc'. Adapté à la détection de la langue ou de l’encodage sur des séquences d’octets arbitraires, ainsi qu’à toute donnée où un signal sous-caractère est important. Généralement utilisé avecn >= 2.codepoint— chaque token correspond à un point de code Unicode ; l’entrée est interprétée comme de l’UTF-8. Avecn = 1,'café'produit les points de code'c','a','f','é'. Adapté à la détection du système d’écriture et de la langue, ainsi qu’aux textes courts ou en CJK, où les séparations entre mots par des espaces sont peu fiables. (Les n-grammes source doivent être en UTF-8 valide ; l’entrée de la requête est décodée de façon permissive — voir les Notes.)token— chaque token est un mot délimité par des espaces ASCII (espace, tabulation, saut de ligne, retour chariot, saut de page, tabulation verticale ; les séquences consécutives sont réduites à un seul séparateur). Les espaces Unicode non ASCII, tels queU+00A0(espace insécable) ouU+2003(espace cadratin), ne sont pas des séparateurs et restent à l’intérieur d’un token. Les espaces sont la seule chose qui segmente — rien n’est mis en minuscules ni supprimé — ainsi,'Hello, World!'devient les tokens'Hello,'et'World!'(la virgule, le!et les majuscules sont tous conservés), et avecn = 2ils forment l’unique bigramme'Hello, World!'. Adapté à la classification au niveau des mots pour les langues séparées par des espaces — sentiment, thème, spam, langue d’une phrase.
Modes de probabilité a priori
priors_mode détermine comment elle est définie.
-
proportional(par défaut) — la probabilité a priori de chaque classe est proportionnelle à son nombre total de n-grammes dans les données d’entraînement — c’est-à-dire la somme de la colonnecountpour cette classe, et non son nombre de lignes ou de documents d’entraînement — de sorte que les classes observées plus souvent sont initialement plus probables. Choisissez ce mode lorsque les proportions des classes dans l’entraînement (par nombre total de n-grammes) correspondent aux fréquences attendues au moment de la requête. Rien à fournir — elle est dérivée des comptes de la source. -
uniform— au départ, toutes les classes sont équiprobables, donc aucune ne part avec un avantage et la prédiction repose entièrement sur les n-grammes de l’entrée. Choisissez ce mode lorsque les classes sont équilibrées, ou lorsque les fréquences d’entraînement ne reflètent pas la fréquence d’apparition de chaque classe au moment de la requête. Rien à fournir. -
explicit— vous fournissez les probabilités a priori avecpriors [(0, 0.6), (1, 0.4)]: une paire(classe, probabilité)par classe, chaque probabilité étant strictement supérieure à 0 et inférieure ou égale à 1, le tout devant totaliser1.0. Choisissez ce mode lorsque vous connaissez les taux de base réels et qu’ils diffèrent de ceux de l’entraînement — par exemple, si seulement 1 % du trafic de production est du spam alors que l’ensemble d’entraînement était équilibré. Calculez-les à partir de la proportion réelle attendue pour chaque classe.
Tokens de délimitation (padding)
n > 1, où il peut améliorer la précision en permettant au modèle d’utiliser des signaux au début et à la fin du texte.
Pourquoi c’est utile. Avec n > 1, les n-grams au milieu du texte bénéficient d’un contexte complet à gauche et à droite, mais ce n’est pas le cas des premiers et derniers tokens. L’ajout de tokens de délimitation crée des n-grams qui marquent le « début du texte » et la « fin du texte », afin que le modèle puisse apprendre des motifs liés à la position — par exemple, un mot distinctif lorsqu’il ouvre un message, ou un caractère typique à la fin d’un mot.
Ce que vous devez faire :
- Décidez pour chaque côté.
start_tokenetend_tokensont indépendants — définissez l’un, les deux ou aucun des deux. Une valeur vide signifie qu’aucun padding n’est appliqué de ce côté. - Choisissez des valeurs rares qui n’entreront pas en collision avec des données réelles, par exemple
0x01/0xFFpourbyte,U+10FFFE/U+10FFFFpourcodepoint, ou<s>/</s>pourtoken. - Générez les n-grams d’entraînement avec le même padding. Le dictionnaire applique le padding à l’entrée de la requête, mais jamais à votre source ; les tokens de délimitation doivent donc déjà être intégrés aux n-grams que vous chargez. Le moyen le plus simple de garantir la correspondance consiste à construire la source avec
naiveBayesNgrams, en lui passant les mêmesstart_tokenetend_token(ainsi quenetmode) que pour le layout — la fonction émet exactement les n-grams paddés que le dictionnaire produit au moment de la requête.
-
byte— un nombre pour la valeur de l’octet, en décimal ou en hexadécimal0x(ainsi,'1'et'0x01'sont identiques) : -
codepoint— un nombre pour le point de code UTF-8, en décimal ou en hexadécimal0x(ainsi,'1114110'et'0x10FFFE'sont identiques) : -
token— la chaîne littérale du token :
Créer des données d’entraînement à partir de texte brut
naiveBayesNgrams pour le découper en n-grammes. Donnez-lui les mêmes valeurs n, mode, start_token et end_token que votre layout, et elle produira exactement les n-grammes attendus par le dictionnaire, afin que les données d’entraînement correspondent à ce que le modèle voit au moment de la requête.
À partir d’une table de lignes (class_id, text), construisez la source (ngram, class_id, count) avec un seul GROUP BY :
training_data est désormais une source valide pour un dictionnaire NAIVE_BAYES (ici, des unigrammes de tokens ; modifiez les arguments n et mode pour qu’ils correspondent à votre layout). Le dictionnaire tokenize l’entrée de la requête exactement telle qu’elle est fournie. Ainsi, si le texte d’entraînement est en minuscules mais pas le texte de requête, leurs n-grams ne correspondront pas et la précision du modèle s’en ressentira.
A priori et nombre de documentsL’a priori
proportional (par défaut) est pondéré par le nombre total de n-grams de chaque classe, et non par son nombre de documents. Si vous voulez l’a priori classique basé sur la fréquence des documents (documents_in_class / total_documents), calculez-le à partir de la table brute docs et transmettez-le avec priors_mode 'explicit' :training_data, en transmettant l’a priori explicite calculé ci-dessus, puis classez les nouveaux avis :
1 est positive et 0 est négative, donc les deux avis sont bien classés.
Autres exemples
n = 2, mode 'byte' ; classe 0 = chaînes de lettres a–d, classe 1 = lettres x–z) :
n = 1, mode 'codepoint'; class 0 = latin, 1 = cyrillique) :
store_source :
n = 2, mode 'codepoint' ; classe 0 = anglais, 1 = espagnol). Les n-grammes d’entraînement sont construits à partir de mots bruts avec naiveBayesNgrams, et les tokens de délimitation — transmis à la fois à la fonction et au layout — permettent au modèle d’utiliser la première et la dernière lettre de chaque mot :
Remarques
- Sémantique d’un dictionnaire de calcul. Il s’agit d’un dictionnaire de calcul :
dictGet(dict, '<class_attribute>', text)classetext(la clé est une entrée à classer, et non une clé stockée), l’attribut de décompte ne peut pas être interrogé, etdictHasrenvoie toujours1. - Validation de la source au chargement. Chaque n-gramme source doit correspondre aux valeurs configurées de
net demode(en modecodepoint, il doit aussi être en UTF-8 valide) ; à la moindre divergence, le chargement échoue. Comme les lignes dont le décompte est nul sont ignorées (voir Fonctionnement), une source vide ou ne contenant que des décomptes nuls n’offre aucune donnée d’entraînement et ne peut pas être chargée. - La tokenisation au moment de la requête est tolérante. Contrairement à la validation de la source, l’entrée de la requête n’est jamais rejetée. En mode
codepoint, les octets qui ne sont pas en UTF-8 valide sont décodés au mieux au lieu de faire échouer la requête ; en modetoken, seuls les espaces ASCII séparent les mots (les espaces Unicode commeU+00A0restent à l’intérieur d’un token). Une entrée mal formée est tout de même classée — généralement d’après les probabilités a priori, puisque ses n-grammes ne correspondront pas à ceux appris.