Ya tenemos los datos separados. El problema ahora es que un modelo no sabe qué hacer con la cadena "female" ni entiende que la primera clase sea mejor que la tercera. Necesita números, y necesita que esos números signifiquen lo que queremos que signifiquen.
Recuperamos el punto donde lo dejamos en el capítulo anterior
La columna Pclass se almacena como Int64 y MLJ la interpreta como Count, un conteo. Pero Pclass no cuenta nada: es la clase del billete, una categoría con tres valores posibles. Si la dejamos como conteo, el modelo asumirá que la distancia entre primera y tercera es el doble que entre primera y segunda, y que tiene sentido calcular su media.
Estos son los tipos científicos que más nos van a interesar:
Scitype
Qué representa
Ejemplo en nuestros datos
Continuous
Magnitud real
Age, Fare
Count
Conteo entero no negativo
SibSp, Parch
Multiclass
Categoría sin orden
Sex
OrderedFactor
Categoría con orden
Pclass
TipPor qué esta distinción y no simplemente el tipo de Julia
Porque el tipo de almacenamiento es ambiguo. Un Int64 puede ser una edad, un código postal o un número de hermanos, y cada uno exige un tratamiento distinto. En lugar de que cada modelo adivine, MLJ obliga a declararlo una vez y comprueba la compatibilidad antes de entrenar. Si le pasas un Multiclass a un modelo que solo admite Continuous, te avisa en lugar de producir un resultado silenciosamente absurdo.
10.2 Ajustar los tipos
coerce nos permite corregir la interpretación columna a columna.
Pclass la declaramos OrderedFactor porque sus categorías sí tienen un orden natural, igual que hicimos en el análisis exploratorio. Conviene comprobar en qué orden han quedado los niveles:
unwrap.(levels(Xtrain.Pclass))
3-element Vector{Int64}:
1
2
3
Están en orden ascendente 1 < 2 < 3, que es el orden del número pero el inverso del prestigio: la primera clase es la mejor. Lo invertimos para que “mayor” signifique “mejor clase”, que es como lo interpretará el modelo.
Sex en cambio es Multiclass: no hay ningún orden que justifique decir que un sexo es “mayor” que otro.
10.3 De categorías a números
Con los tipos bien declarados podemos codificar. La transformación habitual para una variable sin orden es la codificación one-hot: crear una columna binaria por categoría.
Escribimos MLJ.transform con el prefijo porque DataFrames también exporta una función transform (la que usamos en el análisis preliminar) y Julia no sabría a cuál nos referimos. Cuando dos paquetes exportan el mismo nombre, cualificarlo es la forma de resolver la ambigüedad.
Fijaos en drop_last = true. Con dos categorías (male, female) bastan una columna: si Sex__male vale 0, necesariamente es female. Mantener las dos columnas introduce redundancia perfecta entre ellas, lo que en modelos lineales produce un sistema sin solución única. Descartar la última categoría evita el problema.
Observad también el argumento ordered_factor = false. Por defecto OneHotEncoder desdobla también las variables ordenadas, y aquí no nos interesa: al desactivarlo, Pclass sobrevive intacta como OrderedFactor y más adelante se convertirá en un único número que conserva el orden, en lugar de en varias columnas independientes. Desdoblarla habría tirado por la borda justo la información que nos molestamos en declarar.
10.4 Escalado
Age va de 0 a 80; Fare llega hasta 512. Para modelos que miden distancias o que optimizan por descenso de gradiente, esa diferencia de escala hace que la variable de mayor rango domine artificialmente. El escalado estándar lo corrige restando la media y dividiendo por la desviación típica.
Aquí se ve con toda claridad el punto del capítulo anterior: esas medias y desviaciones son parámetros aprendidos, y se han aprendido del conjunto de entrenamiento. Son exactamente el tipo de cantidad que no debe calcularse con los datos de test.
La media del conjunto de test no es exactamente cero, y eso es señal de que lo hemos hecho bien. Si lo fuera, significaría que hemos vuelto a calcular la media sobre el test, es decir, que hemos vuelto a filtrar información. El test se transforma con los parámetros del entrenamiento, y el resultado es el que sea.
ImportanteEl patrón que se repite
Todas las transformaciones que hemos visto siguen el mismo ciclo:
machine(modelo, datos_entrenamiento): se prepara la transformación.
fit!(mach): se aprenden los parámetros, solo con entrenamiento.
MLJ.transform(mach, datos): se aplican a lo que haga falta.
Ajustar (fit!) y aplicar (transform) son operaciones distintas a propósito. La fuga de información aparece casi siempre cuando alguien las confunde y ajusta sobre el conjunto completo.
10.5 Lo que hemos conseguido
Nuestras columnas ya están en un formato que un modelo puede consumir: categorías codificadas, magnitudes escaladas y tipos científicos declarados.
También hemos acumulado un problema. Llevamos dos objetos machine, cada uno con sus parámetros, y hay que acordarse de aplicarlos en el mismo orden y sobre el conjunto correcto. Con dos transformaciones es manejable; con seis, y metidas en una validación cruzada, es cuestión de tiempo que se cuele un error. En el capítulo siguiente veremos cómo encadenarlas en un único objeto que se ajusta y se aplica de una vez.