El proyecto consiste en la competencia de Kaggle (www.kaggle.com) “What’s cooking?”. El objetivo es , a partir de una lista de ingredientes, crear un modelo que pueda predecir el tipo de comida, entre varias recetas de comida del mundo entero : Italiana, Mexicana, Hindu, …,etc.

Para poder trabajar este proyecto se necesitan los paquetes de R orientados a mineria de texto (text mining). Con la diferentes herramientas de mineria de texto se puede establecer una base de datos de los ingredientes, su ocurrencia en cada comida y “entrenar” un modelo que permita categorizar las diferentes comidas.

Librerias a utilizar

Cargamos las librerias necesarias. Las librerias jsonlite, dplyr, ggplot2 y tm nos sirven para manipular los datos. Las librerias caret, rpart y randomForest nos sirven para la creacion de los modelos de prediccion.

## 
## Attaching package: 'dplyr'
## 
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## 
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
## 
## Loading required package: NLP
## 
## Attaching package: 'NLP'
## 
## The following object is masked from 'package:ggplot2':
## 
##     annotate
## 
## Loading required package: lattice
## randomForest 4.6-12
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine

Carga de datos

El paso inicial es la carga de datos. Para esta competencia los archivos de “train” y “test” estan dados en formato JSON en la URL: https://www.kaggle.com/c/whats-cooking/data.

library(jsonlite)
setwd("~/Documents/R folder/Cooking")
train <- fromJSON("train.json", flatten = TRUE)
test <- fromJSON("test.json", flatten = TRUE)

Existen un total de 39774 recetas en el archivo “train” con 6716 ingredientes en total. Cada receta corresponde a una comida o “cuisine” y tiene un identificador (id) y un conjunto de ingredientes (ingredients). Los ingredientes son una lista de texto:

head(train)[1:4,1:3]
##      id     cuisine
## 1 10259       greek
## 2 25693 southern_us
## 3 20130    filipino
## 4 22213      indian
##                                                                                                                                          ingredients
## 1                       romaine lettuce, black olives, grape tomatoes, garlic, pepper, purple onion, seasoning, garbanzo beans, feta cheese crumbles
## 2                plain flour, ground pepper, salt, tomatoes, ground black pepper, thyme, eggs, green tomatoes, yellow corn meal, milk, vegetable oil
## 3 eggs, pepper, salt, mayonaise, cooking oil, green chilies, grilled chicken breasts, garlic powder, yellow onion, soy sauce, butter, chicken livers
## 4                                                                                                                  water, vegetable oil, wheat, salt

Creacion del Cuerpo de Texto (Corpus)

Un cuerpo o “Corpus” es basicamente una coleccion de listas de texto. En este caso las listas contienen todos los ingredientes de cada una de las 39774 comidas. Esto nos sirve para analizar los ingredientes que conforman cada una de las recetas:

ingredients <- Corpus(VectorSource(train$ingredients))
ingredients_test <- Corpus(VectorSource(test$ingredients))
ingredients
## <<VCorpus>>
## Metadata:  corpus specific: 0, document level (indexed): 0
## Content:  documents: 39774

Pre-procesamiento de los ingredientes

La etapa de pre-procesamiento nos permite hacer un poco de limpieza a los datos correspondientes a los ingredientes. En este caso nos interesa remover razgos comunes de las palabras de los ingredientes (e.g., thigh, thighs). Es decir removemos palabras que corresponden al mismo ingrediente: pechuga = pechugas, dejando uno solo de los ingredientes necesarios.

ingredients <- tm_map(ingredients, stemDocument)
ingredients_test <- tm_map(ingredients_test,stemDocument)

Creacion de la Matriz de Ingredientes (Document Term Matrix)

La matrix de terminos es basicamente una matriz de ingredientes que corresponden a cada una de las recetas. Nos indica si el ingrediente aparece o no en cada receta, es decir podemos ver un “1” en la columna si el ingrediente se incluye en la receta correspondiente. La matriz tiene 39774 lineas y 249 columnas que corresponden a ingredientes. A continuacion una muestra de la matriz de ingredientes:

ingredientsDTM <- DocumentTermMatrix(ingredients)
ingredientsDTM_test <- DocumentTermMatrix(ingredients_test)
inspect(ingredientsDTM[1:20, 319:330])
## <<DocumentTermMatrix (documents: 20, terms: 12)>>
## Non-/sparse entries: 4/236
## Sparsity           : 98%
## Maximal term length: 11
## Weighting          : term frequency (tf)
## 
##     Terms
## Docs broccoli broccolini broil broiler broiler-fry broth brown browni
##   1         0          0     0       0           0     0     0      0
##   2         0          0     0       0           0     0     0      0
##   3         0          0     0       0           0     0     0      0
##   4         0          0     0       0           0     0     0      0
##   5         0          0     0       0           0     0     0      0
##   6         0          0     0       0           0     0     0      0
##   7         0          0     0       0           0     0     0      0
##   8         0          0     0       0           0     0     0      0
##   9         0          0     0       0           0     0     0      0
##   10        0          0     0       0           0     0     0      0
##   11        0          0     0       0           0     0     0      0
##   12        0          0     0       0           0     0     0      0
##   13        0          0     0       0           0     0     0      0
##   14        0          0     0       0           0     0     0      0
##   15        0          0     0       0           0     1     0      0
##   16        0          0     0       0           0     0     0      0
##   17        0          0     0       0           0     0     0      0
##   18        0          0     0       0           1     0     0      0
##   19        0          0     0       0           0     0     0      0
##   20        0          1     0       0           0     1     0      0
##     Terms
## Docs brussel bucatini buckwheat bud
##   1        0        0         0   0
##   2        0        0         0   0
##   3        0        0         0   0
##   4        0        0         0   0
##   5        0        0         0   0
##   6        0        0         0   0
##   7        0        0         0   0
##   8        0        0         0   0
##   9        0        0         0   0
##   10       0        0         0   0
##   11       0        0         0   0
##   12       0        0         0   0
##   13       0        0         0   0
##   14       0        0         0   0
##   15       0        0         0   0
##   16       0        0         0   0
##   17       0        0         0   0
##   18       0        0         0   0
##   19       0        0         0   0
##   20       0        0         0   0

Reduccion de la dispersion

La matriz de ingredientes tiene varias columnas que corresponden a ingredientes que tienen baja ocurrencia, es decir ingredientes que se encuentran muy “dispersos”. Reducimos la dispersion de manera que mantenemos los ingredientes que estan incluidos en el 99% de las recetas.

sparse <- removeSparseTerms(ingredientsDTM, 0.99)
sparse_test <- removeSparseTerms(ingredientsDTM_test, 0.99)
sparse
## <<DocumentTermMatrix (documents: 39774, terms: 249)>>
## Non-/sparse entries: 646139/9257587
## Sparsity           : 93%
## Maximal term length: 13
## Weighting          : term frequency (tf)

Conversion a Data Frame

Convertimos los datos a un data frame antes de poder correr los modelos de prediccion. El set de pruebas “test” puede tener algunos ingredientes que no estan en el set de “train” y por lo tanto comparamos sus columnas e incluimos solo las que se intersectan.

ingredientsDTM <- as.data.frame(as.matrix(sparse))
ingredientsDTM_test <- as.data.frame(as.matrix(sparse_test))

# Hay ingredientes en el train data set , que no estan en el test data set. Por lo tanto hay que 
# dejar los que son comunes a ambos sets:

train_columns <- names(ingredientsDTM)
test_columns <- names(ingredientsDTM_test)

intersection <- intersect(train_columns,test_columns)

ingredientsDTM <- ingredientsDTM[,c(intersection)]
ingredientsDTM_test <- ingredientsDTM_test[,c(intersection)]

ingredientsDTM$cuisine <- as.factor(train$cuisine)

Modelo 1: Arbol de Decision (Decision Tree)

El primer modelo de prediccion es un modelo de arbol de decision CART (Classification and regression trees) usando la libreria rpart de R. Nos interesa poder predecir las 20 categorias, basados en el contenido de los ingredientes. La variable a predecir es “cuisine” y el metodo a utilizar es clasificacion:

set.seed(9347)
cartModelFit <- rpart(cuisine ~ ., data = ingredientsDTM, method = "class")
prp(cartModelFit)

Corremos la prediccion sobre el “test” data set y generamos el archivo .csv para subir a Kaggle:

# Correr prediccion sobre "test""

cartPredict <- predict(cartModelFit, newdata = ingredientsDTM_test, type = "class")

# Generar archivo CSV

submit <- data.frame(id=test$id,cuisine=cartPredict)
write.csv(submit, file = "project_LD_CART.csv", row.names = FALSE)

La primera entrega genero un valor de 0.40 en Kaggle. La segunda entrega genero un valor de 0.68. Basicamente esta diferencia se obtuvo jugando con los valores de dispersion “Sparse” variando entre 0.995 y 0.99 es decir tratando de modificar la dispersion incluyendo mas ingredientes.

alt text

alt text

Modelo 2: Random Forest

Para la tercera entrega usamos un modelo de random forest. Este modelo utiliza multiples arboles de decision en lugar de un solo arbol. Una particularidad de este modelo es que genera una medicion de “importancia” la cual puede servir para ajustes de las variables.

La generacion del modelo, la prediccion y el resultado obtenido en Kaggle se muestra a continuacion:

# El modelo random forest da error al detectar "-"

ingredientsDTM_RF <- ingredientsDTM
names(ingredientsDTM_RF) <- gsub("-","",names(ingredientsDTM_RF))
ingredientsDTM_test_RF <- ingredientsDTM_test
names(ingredientsDTM_test_RF) <- gsub("-","",names(ingredientsDTM_test_RF))

# Modelo con 10 arboles y prediccion sobre "test"

ForestModel <- randomForest(cuisine ~., data = ingredientsDTM_RF, importance = TRUE, ntree = 10)
ForestPredict <- predict(ForestModel, newdata = ingredientsDTM_test_RF, type = "class")

submit_2 <- data.frame(id=test$id,cuisine=ForestPredict)
write.csv(submit_2,file = "project_LD_RF.csv", row.names = FALSE)

Este ultimo modelo genero una mejora significativa llegando a 0.72395 ubicandome en el puesto 709 de 1013 participantes:

alt text

Es posible aun mejorar estos resultados. Algunas opciones son: 1) Jugar con valores de dispersion o “sparsity” para incluir mas ingredientes 2) En lugar de suprimir ingredientes no comunes a “test” y “train” tratar de incluirlos en el modelo o 3) Utilizar tecnicas mas avanzadas de clasificacion.