Naive Bayes Classifier – Construction en SQL

Naive Bayes Classifier – Construction en SQL


Nous aimons parler de la façon dont Snowflake est construit et de ce qui fait que Snowflake se démarque de la masse des entrepôts de données, mais nous aimons aussi montrer comment vous pouvez utiliser Snowflake pour relever des défis intéressants en matière de Big Data. Dans cet article de blog, nous montrons comment créer un classificateur Naive Bayes d’apprentissage automatique au-dessus de Snowflake en utilisant SQL et flocon de neige JSON extensions. Le classificateur apprendra à séparer les émotions heureuses des émotions tristes dans de courts extraits de texte. En tant que source de données, nous utilisons le flux de données Twitter accessible au public qui peut être téléchargé au format JSON brut.

Twitter est une source inépuisable d’actualités, de liens, de commentaires et de conversations personnelles. Une partie de cette communication est très émotionnelle : c’est joyeux, c’est triste, ce n’est ni l’un ni l’autre, ou les deux à la fois. En chargeant les données Twitter dans Snowflake, nous pouvons créer un classificateur d’apprentissage automatique qui détecte automatiquement ces émotions. Le classificateur d’apprentissage automatique peut ensuite être utilisé pour détecter des émotions dans n’importe quel extrait de texte de la taille d’un tweet, tel que « Le soleil brille, le temps est de nouveau doux » (heureux), « J’ai dû appeler mille fois » (triste) , et « Make America Great Again » (heureux).

Ou, en SQL :

select * from result order by id;
----+------------------------------------------------+-------+
 ID |                      TEXT                      | LABEL |
----+------------------------------------------------+-------+
 1  | The sun is shining, the weather is sweet again | happy |
 2  | I must've called a thousand times              | sad   |
 3  | Make America Great Again                       | happy |
----+------------------------------------------------+-------+

Dans cet article de blog, nous allons créer un classificateur d’apprentissage automatique Naive Bayes à l’aide de SQL. Nous procédons en trois étapes :

  1. Nous créons un ensemble de données d’entraînement en étiquetant les tweets comme joyeux ou tristes à l’aide d’emojis.
  2. Nous calculons un modèle d’apprentissage automatique à l’aide d’un classificateur Naive Bayes.
  3. Nous validons notre modèle à l’aide d’un ensemble de données de test et détectons les émotions dans d’autres extraits de texte.

Voyons rapidement à quoi ressemblent les données Twitter, ce qu’il faut pour créer un modèle d’apprentissage automatique et ce qu’est un classificateur Naive Bayes de toute façon.

Données Twitter

Twitter vous permet de télécharger un échantillon d’un pour cent de tous les tweets publics gratuitement. Le format de ces données est JSON. Chaque tweet est un objet JSON distinct qui ressemble à ceci :

{
  "created_at": "Tue Feb 02 21:09:01 +0000 2016",
  "entities": {
    "hashtags": [ ... ],
    "media": [],
    "symbols": [],
    "trends": [],
    "urls": [],
    "user_mentions": [ ... ]
  },
  "id": 694751783172739072,
  "lang": "en",
  "retweeted_status": { ... },
  "text": "We are delighted to inform you that your submission 900: 
           The Snowflake Elastic Data Warehouse has been accepted. 
           #SIGMOD @SnowflakeDB",
  "user": { "screen_name": "hemasail" ... }
  ...
}

Ce tweet a été envoyé par l’utilisateur @hemasail— qui est moi, par coïncidence — le 2 février 2016. Chaque objet JSON représentant un tweet a des champs obligatoires et des champs facultatifs. Les champs obligatoires incluent l’identifiant du tweet, l’utilisateur qui a envoyé le tweet et la langue dans laquelle le tweet a été écrit (ce qui est apparemment détecté par le classificateur de langue de Twitter). Les champs facultatifs incluent « retweeted_status », contenant des informations sur le tweet retweeté (le cas échéant) ; et les « entités », contenant des informations sur les hashtags, les médias, les URL, etc. qui ont été trouvés dans le tweet. Il y a beaucoup plus d’informations dans le JSON d’un tweet qui ne sont pas montrées dans l’exemple ci-dessus. Il est toujours intéressant de voir la quantité complète d’informations raffinées à partir de « 140 caractères ou moins ».

Les données Twitter formatées en JSON conviennent parfaitement à Snowflake. Flocon de neige supporte nativement JSON: le chargement, l’interrogation et le déchargement des données JSON sont pris en charge sans conversion. Snowflake prend également en charge les fonctions définies par l’utilisateur en JavaScript qui peuvent fonctionner directement sur les données JSON.

Apprentissage automatique

L’apprentissage automatique est une méthode informatique permettant d’apprendre automatiquement les caractéristiques des données existantes et d’appliquer les résultats à de nouvelles données. Dans ce blog, nous utilisons l’apprentissage automatique supervisé. L’apprentissage automatique supervisé est une branche de l’apprentissage automatique où l’ensemble de données existant doit être étiqueté. Autrement dit, chaque élément de l’ensemble de données, chaque tweet par exemple, se voit attribuer une étiquette : celui-ci est un tweet joyeux, celui-ci est un tweet triste. Il existe également un apprentissage automatique non supervisé où l’ensemble de données n’a pas besoin d’être étiqueté, et l’algorithme d’apprentissage automatique essaie de trouver des étiquettes lui-même. Cependant, nous utiliserons un ensemble de données étiquetées.

Il existe de nombreuses façons de générer des étiquettes. Habituellement, les étiquettes sont générées par des humains. Dans notre cas, cela signifierait que nous nous asseyons et étiquetons beaucoup de tweets comme joyeux ou tristes. Cela prendrait beaucoup de temps. Nous pourrions également sous-traiter la génération d’étiquettes à des services tels qu’Amazon Mechanical Turk, mais cela prendrait beaucoup d’argent. Nous ne ferons donc rien non plus. Au lieu de cela, nous attribuerons des étiquettes aux tweets à l’aide d’emojis. Si un tweet contient un emoji heureux, nous attribuerons une étiquette « heureux » à ce tweet. Si un tweet contient un emoji triste, nous attribuerons une étiquette « triste » à ce tweet. S’il n’y a pas d’emojis ou les deux types d’emojis dans un tweet, nous attribuerons des étiquettes « aucun » ou « les deux », respectivement.

Étant donné un ensemble de données étiqueté, nous le diviserons en un ensemble de données d’apprentissage et un ensemble de données de test. L’ensemble de données de formation sera utilisé pour former le classificateur d’apprentissage automatique. L’ensemble de données de test sera utilisé pour tester la qualité de notre classificateur. Nous diviserons notre ensemble de données en 80 % de données d’entraînement et 20 % de données de test.

Classificateur naïf de Bayes

Un classificateur Naive Bayes est un type simple de modèle d’apprentissage automatique basé sur des probabilités. Dans notre cas, un classificateur Naive Bayes utilise des probabilités de mots pour classer un tweet comme joyeux ou triste. En termes simples, un classificateur Naive Bayes compte tous les mots et calcule les probabilités de leur fréquence d’apparition dans une catégorie ou une autre. Par exemple, le mot « génial » apparaît plus souvent dans les tweets joyeux que dans les tweets tristes. Le mot « must’ve » apparaît plus souvent dans les tweets tristes que dans les tweets joyeux. En faisant la moyenne de toutes les probabilités de mots ensemble, nous obtenons une probabilité globale qu’un texte appartienne à une catégorie ou à une autre. Par exemple, en faisant la moyenne de toutes les probabilités de mots dans « Make America Great Again », nous voyons qu’il est plus heureux que triste. Au moins, nos données de formation de Twitter nous le disent.

Une bien meilleure explication du classificateur Naive Bayes peut être trouvée dans les diapositives de Stanford ici.

Construire un classificateur Naive Bayes en SQL

Nous créons un ensemble de données d’entraînement en étiquetant d’abord les tweets comme joyeux ou tristes. Les étiquettes seront attribuées à l’aide d’emojis. Si un tweet a un emoji heureux, nous attribuons l’étiquette « heureux ». Si un tweet contient un emoji triste, nous attribuons l’étiquette « triste ». Si le tweet ne contient pas d’emoji, ou s’il contient les deux types d’emojis, nous attribuons respectivement les étiquettes « aucun » ou « les deux ».

Pour calculer les étiquettes, nous utiliserons les éléments suivants fonction définie par l’utilisateur (UDF) en JavaScript. La fonction prend une chaîne en entrée et génère une étiquette basée sur le type d’emojis trouvé. Pour cette tâche, il divise la chaîne en mots et compare chaque mot avec un emoji heureux ou un emoji triste. Nous appelons cette fonction « happyLabel »:

create or replace function happyLabel(TEXT string)
returns string
language javascript
as ‘
 var happyRegex = /:-?\)/;
 var sadRegex = /:-?\(/;
 var happyEmoji = happyRegex.test(TEXT);
 var sadEmoji = sadRegex.test(TEXT);
 if (happyEmoji && sadEmoji) return “both”;
 if (happyEmoji) return “happy”;
 if (sadEmoji) return “sad”;
 return “none”;
‘;

Nous allons utiliser un autre UDF JavaScript qui nettoie un tweet et le divise en mots. Cette fonction prend une chaîne en entrée et génère une variante. Une variante est un type de données spécifique à Snowflake qui peut contenir des données semi-structurées telles que des objets JSON et des tableaux JSON, ainsi que des données natives telles que des chaînes et des nombres. Cette fonction génère une variante contenant un tableau JSON. Avant de diviser un tweet en mots, la fonction supprime toutes les entités HTML telles que & et <, toute mention d’autres noms Twitter et toute ponctuation. Il génère une liste de mots en minuscules.

create or replace function splitText(TEXT string)
returns variant
language javascript
as ‘
 var words = TEXT
   .replace(/&\w+;/g, ” “)         // remove HTML entities
   .replace(/@[^\s]+/g, “”)        // remove mentions
   .replace(/[^#’\w\s]|_/g, ” “) // remove punctuation
   .toLowerCase()
   .trim()
   .split(/\s+/g);                 // split on whitespace
 return words;
‘;

Nous allons maintenant écrire SQL pour générer l’ensemble de données étiqueté. Pour générer les étiquettes, nous appellerons les UDF JavaScript définies ci-dessus. Nous équilibrerons l’ensemble de données étiquetées de sorte qu’il y ait le même nombre de tweets heureux et de tweets tristes. À partir de cet ensemble de données équilibré, nous utiliserons 80 % des tweets pour entraîner le classifieur. Nous calculerons toutes les probabilités et tous les comptes nécessaires pour le classificateur Naive Bayes. Pour un résumé complet de toutes les formules nécessaires pour implémenter le classifieur, voir le diapositives de l’Université de Stanford. Pour toute question concernant le traitement JSON dans Snowflake, reportez-vous au Documentation sur les flocons de neige.

-- label tweets and only select happy and sad tweets
create or replace table labeledTweets as
select tweet,
      happyLabel(tweet:text) as label,
      splitText(tweet:text) as words,
      uniform(0::float, 1::float, random()) as rand
from twitter.public.tweets_1p
where created_at > ‘2016-05-01’::date       — tweets from May 2016
 and tweet:lang = ‘en’                     — english language only
 and array_size(tweet:entities:urls) = 0   — no tweets with links
 and tweet:entities:media is null          — no tweets with media (images etc.)
 and tweet:retweeted_status is null        — no retweets, only originals
 and (label = ‘happy’ or label = ‘sad’);

-- balance tweets to have same number of happy and sad tweets
create or replace table balancedLabeledTweets as
select *
from (select *,
       rank() over (partition by label order by tweet:id) as rnk
     from labeledTweets) x
where rnk <= (select min(cnt)
             from (select label,count(*) as cnt
                   from labeledTweets
                   group by label));
-- training set, pick random 80 percent of tweets
create or replace table training as
select * from balancedLabeledTweets where rand < 0.8;

-- training helper table: tweet id, word, label
create or replace table training_helper as
select tweet:id as id, value as word, label
from training,
    lateral flatten(words);
 
-- number of total docs
create or replace table docs as
select count(*) as docs from training;

-- number of docs per class j
create or replace table docsj as
select label,count(*) as docsj
from training
group by label;

-- number of distinct words = |Vocabulary|
create or replace table voc as
select count(distinct word) as voc
from training_helper;

-- number of words per class j
create or replace table wordsj as
select label,count(*) as wordsj
from training_helper
group by label;

-- count per word n and class j
create or replace table wordsnj as
select a.label,a.word,ifnull(wordsnj,0) as wordsnj
from
(select label,word
from (select distinct label from training_helper) c
 join
    (select distinct word from training_helper) w) a
left outer join
 (select label,word,count(*) as wordsnj
 from training_helper
 group by label,word) r
on a.label = r.label and a.word = r.word;

Pour tester notre classificateur Naive Bayes, nous utiliserons les 20 % restants des tweets étiquetés. Pour chaque tweet de l’ensemble de test, nous calculerons l’étiquette réelle que le classificateur attribue au tweet. Le classificateur ne regarde pas les emojis, bien sûr. Ce serait tricher. Nous comparerons ensuite les étiquettes réelles avec les étiquettes attendues. Cela nous donnera un pourcentage de tweets correctement classés.

-- test set
create or replace table test as
select * from balancedLabeledTweets where rand >= 0.8;

-- test helper table: tweet id, word, label
create or replace table test_helper as
select tweet:id as id,value as word,label
from test,
    lateral flatten(words);

-- classification probabilities
create or replace table probs as
select id,label,max(pc)+sum(pw) as p,expected
from (
select id,t.word,n.label,wordsnj,wordsj,docsj,docs,voc,
 log(10,docsj::real/docs) as pc,
 log(10,(wordsnj::real+0.1)/(wordsj+0.1*voc)) as pw,
 t.label as expected
from test_helper t
 inner join wordsnj n on t.word = n.word
 inner join wordsj j on n.label = j.label
 inner join docsj on docsj.label = n.label
 cross join docs
 cross join voc) x
group by id,label,expected;

-- classification result
create or replace table testResult as
select p1.id,t.tweet:text::string as text,p1.expected,p1.label as actual
from probs p1
 inner join (
   select id,max(p) as maxp
   from probs
   group by id) p2
 on p1.id = p2.id and p1.p = p2.maxp
 inner join test t on p1.id = t.tweet:id;

-- correctly classified tweets: “win probability”
select sum(win),count(*),sum(win)::real/count(*) as winprob
from (
select id,expected,actual,
 iff(expected = actual,1,0) as win
from testResult);

The output of the last query is the following:
---------+-----------+-----------------+
SUM(WIN) | COUNT(*)  |     WINPROB     |
---------+-----------+-----------------+
43926    | 56298     |   0.7802408611  |
---------+-----------+-----------------+

Cela signifie que notre classificateur Naive Bayes a correctement classé 78 % de tous les tweets de test. Ce n’est pas trop mal étant donné que nous n’avons pas fait beaucoup de nettoyage de données, de détection de spam et de recherche de mots. La probabilité de gain de base est de 50 %, car le nombre de tweets heureux est le même que le nombre de tweets tristes dans notre ensemble de données. Ainsi, notre classificateur nous donne un coup de pouce significatif.

Nous pouvons maintenant utiliser le classificateur formé pour étiqueter tous les extraits de texte. Par exemple, nous pouvons étiqueter des extraits de texte tels que « Le soleil brille, le temps est de nouveau doux », « J’ai dû appeler un millier de fois » et « Make America Great Again ». Pour ce faire, nous créons un nouveau tableau avec tous les extraits de texte que nous voulons classer. Pour calculer la classification, nous divisons chaque texte en mots et utilisons les probabilités de mots de notre ensemble d’apprentissage pour finalement attribuer une étiquette à chaque extrait de texte. Les résultats sont stockés dans une table appelée « résultat ».

-- create new table with any text snippets
create or replace table query(id int, text varchar(500));
insert into query values (1, ‘We are the champions’);
insert into query values (2, ‘I must’ve called a thousand times’);
insert into query values (3, ‘Make America Great Again’);
 
-- split texts into words
create or replace table query_helper as
select id,value as word
from query,
    lateral flatten(splitText(text));

-- compute probabilities using data from training set
create or replace table probs as
select id,label,max(pc)+sum(pw) as p
from (
select id,t.word,n.label,wordsnj,wordsj,docsj,docs,voc,
 log(10,docsj::real/docs) as pc,
 log(10,(wordsnj::real+0.1)/(wordsj+0.1*voc)) as pw
from query_helper t
 inner join wordsnj n on t.word = n.word
 inner join wordsj j on n.label = j.label
 inner join docsj on docsj.label = n.label
 cross join docs
 cross join voc) x
group by id,label;

-- assign labels to text snippets
create or replace table result as
select p1.id as id, text, p1.label as label
from probs p1
 inner join (
   select id,max(p) as maxp
   from probs
   group by id) p2
 on p1.id = p2.id and p1.p = p2.maxp
 inner join query q on p1.id = q.id;

Et, ta-dah, voici les résultats de notre classement :

select * from result order by id;
---+------------------------------------------------+-------+
ID |                      TEXT                      | LABEL |
---+------------------------------------------------+-------+
1  | The sun is shining, the weather is sweet again | happy |
2  | I must’ve called a thousand times              | sad   |
3  | Make America Great Again                       | happy |
---+------------------------------------------------+-------+

Sommaire

Dans cet article de blog, nous avons créé un classificateur Naive Bayes complet à l’aide de SQL. Le classificateur a appris à distinguer les tweets joyeux des tweets tristes. Notre classificateur a un taux de réussite de 78 %, ce qui représente une amélioration significative par rapport à la référence de 50 %. Nous avons utilisé le classificateur sur d’autres extraits de texte pour montrer son applicabilité au-delà des tweets. Bien sûr, il ne s’agit que d’une démonstration et un nettoyage supplémentaire des données, la détection du spam, la recherche de mots et d’autres astuces de traitement du langage naturel sont nécessaires pour augmenter le taux de réussite du classificateur.

Comme toujours, gardez un œil sur ce site de blog, notre flux Twitter Snowflake (@SnowflakeDB), et mon fil Twitter personnel (@hemasail) pour des mises à jour sur toutes les actions et activités ici à Snowflake Computing.

Liens supplémentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.