[IA Aprendizaje] Probabilidad: El método clásico II Clasificación bayesiana ingenua

En primer lugar pido disculpas por haber dejado tanto espacio entre una entrada y otra, pero es que estoy en un momento de mi vida bastante complicado, no he parado de estudiar (en el ámbito universitario, que todos los que andamos por aquí siempre estamos estudiando nuevos frameworks constantemente) y por otra parte ha sido un verano muy ajetreado porque además, he vuelto a emprender 😀
Hoy vamos a hablar de la clasificación bayesiana ingenua, que no es más que un clasificador probabilístico basado en la aplicación del teorema de Bayes. La coletilla de ingenuo viene porque al crear el clasificador asumimos que las variables que utilizaremos son totalmente independientes unas de otras (esto es, recordemos,  ausencia de correlación o P(A,B)=P(A)*P(B)). Como anécdota mencionar que una de las razones por las que se abandonó el método clásico de probabilidad en IA fue, en parte, por la introducción de esta hipótesis. Quizá más adelante (en función de mi tiempo y vuestra aceptación, feedback por favor), veremos que con las redes bayesianas no tenemos este inconveniente.

Teorema de Bayes



Recordemos que el teorema de bayes nos dice que P(A|B) = P(A|B)*P(A) / P(B) donde A se puede interpretar como “causa” y B como “efecto”, es decir, A es una reacción a la acción de B o lo que es lo mismo, A tendría dependencia causal de B.
Por ejemplo, en el caso de los diagnósticos clínicos del campo de Medicina Basada en le Evidencia, tenemos que: P(síntoma|enfermedad) = 0.6 P(síntoma) = 0.15 y P(enfermedad) =0.05
A partir de estos datos podemos preguntarnos por la probabilidad de que la causa del síntoma de un paciente sea la enfermedad, y vendría dada por (teorema de Bayes):

P(enfermedad|síntoma)=P(sintoma|enfermedad)*P(enfermedad)/P(síntoma)  = 0.7*0.05/0.15 = 0.233.
Como se puede ver en el ejemplo, dada una serie de efectos se puede clasificar y, por definición, encontrar la causa que los provoca. Un ejemplo típico (aunque hay muchísimas aplicaciones,
sobre todo en el campo de la minería de datos) es el filtrado de Spam o correo basura. Así que nos ponemos manos a la obra para construir nuestro propio y simple, filtro de Spam.

Filtro de Spam


Para comenzar, utilizaremos la fórmula de Bayes de la siguiente manera: P(palabra|categoria)=P(categoria|palabra) * P(palabra) / P(categoria)
Donde la categoría puede tomar los siguientes valores: SPAM y NO_SPAM. Cuando tenemos calculada la probabilidad de cada palabra condicionada, faltaría multiplicar sus probabilidades para cada categoría: P(categoria|palabra1,palabra2,…,palabran) = P(categoria) SIGMA_POR P(palabra|categoria) Es decir, multiplicamos cada probabilidad P(palabrai|categoria) del texto que queremos
clasificar y además, también P(categoria).
Antes de poder clasificar un texto, tendremos que poder analizar una serie de datos previos, algo así como un entrenamiento. Necesitaremos ejemplos de correos clasificados como SPAM o NO_SPAM, cuantos más ejemplos proveamos, más probabilidad de acertar tendrá nuestro clasificador.
En el próximo ejemplo utilizaremos la función training() para realizar el entrenamiento. La función espera una lista con un campo para el texto y otro que indica si es SPAM o NO_SPAM:
[“Esto es un ejemplo de frase”,NO_SPAM]
Se utilizará la función auxiliar wordList() que devuelve una lista con todas las palabras del texto mayores de dos caracteres, en minúscula y sin repeticiones.
La función training genera cuatro variables que almacenan los parámetros para su posterior clasificación.
La variable cWords es una lista con las palabras del texto y el número de veces que aparece en cada categoría. La variable cCategories almacena las categorías con el número de textos que pertenecen a ella. {“SPAM”:2,”NO_SPAM”:3} y por último, las variables cTexts y cTotalWords guardan el número de textos totales que hay en el ejemplo y el número de palabras totales.
La función Classify() es la que hace los cálculos y determina a qué categoría pertenece el texto que le pasamos como parámetro. También espera que le pasemos las cuatro variables anteriores.

 

   1:  namespace Clasificador_de_Spam
   2:  {
   3:      class Program
   4:      {
   5:   
   6:          class Message
   7:          {
   8:              public string Text { get; set; }
   9:              public string CategoryName { get; set; }
  10:          }
  11:          class Category
  12:          {
  13:              public string Name { get; set; }
  14:              public int Total { get; set; }
  15:   
  16:          }
  17:          class WordPerCategory
  18:          {
  19:             
  20:              public string Word { get; set; }
  21:              public List<Category>CategoryPerWord { get; set; }
  22:          
  23:              
  24:              public Category this[string value]
  25:              {
  26:                  get
  27:                  {
  28:                      return this.CategoryPerWord.FirstOrDefault(c => c.Name.Equals(value));
  29:                  }
  30:              }
  31:          }
  32:   
  33:   
  34:          class Classify
  35:          {
  36:              public Classify()
  37:              {
  38:                  cWords = new List<WordPerCategory>();
  39:                  cCategories = new List<Category>();
  40:                  cTotalTexts = 0;
  41:                  cTotalWords = 0;
  42:              }
  43:              public List<WordPerCategory> cWords { get; set; }
  44:              public List<Category> cCategories { get; set; }
  45:              public int cTotalTexts { get; set; }
  46:              public int cTotalWords { get; set; }
  47:   
  48:   
  49:   
  50:              public List<string> WordList(string text)
  51:              {
  52:                  var wordsTemp = text.ToLower().Split(' ');
  53:                  var words = new List<string>();
  54:                  foreach (var word in wordsTemp)
  55:                      if (word.Count() > 2 && !words.Contains(word))
  56:                          words.Add(word);
  57:   
  58:                  return words;
  59:              }
  60:   
  61:              public void Training(List<Message> texts)
  62:              {
  63:   
  64:   
  65:                  foreach (var message in texts)
  66:                  {
  67:                      cTotalTexts++;
  68:                      if (cCategories.Any(c => c.Name.Equals(message.CategoryName)))
  69:                          cCategories.First(c => c.Name.Equals(message.CategoryName)).Total++;
  70:                      else
  71:                          cCategories.Add(new Category() { Name = message.CategoryName, Total = 1 });
  72:   
  73:                  }
  74:   
  75:                  foreach (var message in texts)
  76:                  {
  77:                      var words = WordList(message.Text);
  78:                      foreach (var word in words)
  79:                      {
  80:                         
  81:                          if (!cWords.Any(w => w.Word.Equals(word)))
  82:                          {
  83:                              cTotalWords++;
  84:                              var wordPerCategory = new WordPerCategory() { Word = word, CategoryPerWord = new List<Category>()};
  85:                              foreach (var cat in cCategories)                     
  86:                                  wordPerCategory.CategoryPerWord.Add(new Category(){Name = cat.Name, Total = 0});
  87:                          
  88:                              cWords.Add(wordPerCategory);
  89:                          }
  90:                          cWords.First(w => w.Word.Equals(word))[message.CategoryName].Total++;
  91:   
  92:                      }
  93:   
  94:                  }
  95:   
  96:   
  97:              }
  98:   
  99:   
 100:              public Category Classifier(string texto)
 101:              {
 102:                  Category category = new Category();
 103:                  double probCategory = 0.0;
 104:   
 105:                  foreach (var c in cCategories)
 106:                  {
 107:                      //Prob of the category
 108:                      double probC = (double) c.Total / cTotalTexts;
 109:                      var words = WordList(texto);
 110:                      double probTotal = probC;
 111:                      foreach (var w in words)
 112:                      {
 113:                          //Prob of the  word
 114:                          if (cWords.Any(word => word.Word.Equals(w)))
 115:                          {
 116:                              var word = cWords.First(wrd => wrd.Word.Equals(w));
 117:                              double probWord = (double)word[c.Name].Total / cTotalWords;
 118:                              //P(cat|word)
 119:                              double probCond = (double)probWord / probC;
 120:                              //P(word|cat)
 121:                              double prob = (double)(probCond * probWord) / probC;
 122:                              probTotal *= (double)prob;
 123:                          }
 124:                      }
 125:                      if (probCategory < probTotal)
 126:                      {
 127:                          category = c;
 128:                          probCategory = probTotal;
 129:                      }
 130:                  }
 131:   
 132:                  return category;
 133:              }
 134:          }
 135:   
 136:          static void Main(string[] args)
 137:          {
 138:             List<Message> text = new List<Message>()
 139:                                       {
 140:                                           new Message(){CategoryName = "SPAM",Text = "Juega al poker online"},
 141:                                           new Message(){CategoryName = "NO_SPAM",Text = "Mañana vamos al cine"},
 142:                                           new Message(){CategoryName = "SPAM",Text = "Juega en los mejores casinos de poker"},
 143:                                           new Message(){CategoryName = "SPAM",Text = "Felicidades usted ha sido seleccionado para ganar un Microsoft Surface"},
 144:                                           new Message(){CategoryName = "NO_SPAM",Text = "La IA es una gran disciplina, es el punto de convergencia perfecto entre las mates y la program"},
 145:                                       };
 146:              Classify antiSpam = new Classify();
 147:              antiSpam.Training(text);
 148:              Category category = antiSpam.Classifier("Juega al poker y gana");
 149:              Console.WriteLine("Se trata de {0}.",category.Name);
 150:   
 151:              Console.Read();
 152:          }
 153:      }
 154:  }

 

 

Pues ya tenemos nuestro filtro anti spam definido. Hay que tener en cuenta que el entrenamiento ha sido con muy pocos datos, aunque aun así es relativamente fiable. Por otra parte, lo hemos definido para N categorías, cambiando dichas categorías podríamos clasificar cualquier otra cosa (por ejemplo qué palabras pertenecen a qué lenguaje).

 
Bibliografía:

Francisco Javier Díez, Probabilidad y teoría de la decisión en medicina

Alberto Garcia Serrano, Inteligencia Artificial Fundamentos, práctica y aplicaciones.

Stuart Russell, Peter Norvig Inteligencia Artificial: Un enfoque moderno.

Referencias

[IA Aprendizaje] Probabilidad: El método clásico I

2 Comentarios

  1. Muy interesante, Juan, espero que haya en el futuro más artículos de esta temática. Saludos.

  2. jmgomez

    18 Septiembre, 2012 at 6:48 pm

    Muchas gracias por el feedback :).
    A corto plazo no creo que pueda publicar nada más de este estilo (demasiado tiempo), pero sin duda lo haré en el futuro.

Deja un comentario

Tu dirección de correo electrónico no será publicada.

*