# Sesiones de Capacitación: Experimentos en `triage`
**ITAM Centro de Ciencia de Datos**
**Elizabeth Rodriguez Sánchez - Sebastián Cadavid-Sánchez**
**Febrero 15, 2021**
### 0. Recordatorio
Para utilizar `triage`, es necesario que tengamos estructurados nuestros datos de acuerdo al _Schema_ _Semantic_. Es decir, con tablas de entidades (estudiantes, profesores, cursos) y las tablas de sus respectivos eventos.
En particular, las tablas deben poder relacionarse entre sí, para ello debemos tener un identificador de las entidades, y un identificador de los eventos que les ocurren a las mismas.
- Por ejemplo, si quisieramos ver los datos estáticos del el estudiante con identificador 116,862 en la base de datos: podríamos realizar el siguiente _query_:
```{SQL}
select * from semantic.alumnos a where alumno = 116862;
```
- Si quisieramos ver todos los eventos en la historia académica del mismo alumno:
```{SQL}
select * from semantic.eventos_alumnos where alumno = 116862;
```
**Nota:** `triage` requiere que los identificadores de entidades y alumnos sean de tipo `int`.
## 1. Crear `virtualenv` en bastión
Triage es compatible con `python 3.6+` , por lo que primero lo debemos instalar en `pyenv`:
```bash
pyenv install 3.7.9
```
Creamos un ambiente virtual para el proyecto:
```{bash}
pyenv virtualenv triage-ccd
```
Lo activamos:
```{bash}
pyenv activate triage-ccd
```
Instalar `triage`:
```bash
pip install triage
```
O bien, si queremos la versión más actualizada (recomendado):
```bash
pip install git+https://github.com/dssg/triage
```
**Nota: Spot Instances AWS**
En la sección de AMIs de AWS del CCD creamos la imagen `proyecto_itam_triage` con un`pyenv` estable ( _name:_`triage_itam`, _AMI name:_ triage-ccd-official-v2) para correr los experimentos en `SPOT instances`.
## 2. Crear configuración del experimento
La configuración de un experimento en `triage` es un archivo `.yaml` y tiene las siguientes secciones:
- Información del experimento
- Configuración temporal (_temporal_config_)
- Definición de cohortes (_cohort_config_)
- Configuración del la etiqueta (_label_config_)
- Variables explicativas (_features_)
- Metricas a optimziar (_scoring_)
- _Grid_ de modelos (grid_config)
Por medio de un ejemplo, donde el `config_file` del experimento se llamaŕa `reprobados.yaml` explicaremos cada una de las secciones y como ejecutar el experimento en una `SPOT instance`.
### 2.1 Información del experimento
Utilizando la notación de archivos `yaml`, podemos definir las siguientes claves con sus valores asociados, las cuales tienen como objetivo enriquecer los metadatos del experimento ejecutado.
```{yaml}
config_version: 'v7'
random_seed: 6174
model_comment: 'primera_prueba'
user_metadata:
label_definition: 'perderan_materias'
experiment_type: 'baseline'
file_name: 'reprobados.yaml'
description: |
Primer experimento con triage - modelos básicos
purpose: 'baseline'
org: 'ITAM'
team: 'Centro de Ciencia de Datos'
author: 'Paola-Elizabeth-Sebastian'
etl_date: '27-10-2020'
```
De las anteriores claves, `model_comment` es la única obligatoria, y será esencial para la lectura de los resultados de los experimentos.
### 2.2 Configuración temporal, Timechop (_temporal_config_)
En este tipo de ejercicios utilizamos data longitudinal de entidades y cada cierta periodicidad hacermos predicciones sobre las etiquetas de las mismas. Para el momento en que nuestros modelos hacen predicción tienen disponible solo cierta cantidad de información, y esto va a determinar totalmente la forma en que hacemos predicción, pues debemos usar ''validación cruzada temporal".
Con base en lo anterior debemos "cortar" (_chop_) los datos para construir nuestras matrices de entrenamiento y validación temporales. Podemos evidenciar lo anterior gráficamente:

> Tomado de la [documentación](https://dssg.github.io/triage/dirtyduck/triage_intro/) de `triage`.
En `triage` esta sección se inicia con la clave `temporal_config`, y nos va a permitir definir el **_timechop_** que determina la forma en que hacemos predicción. En particular, determinar la forma como se define la matriz de entrenamiento, y las etiquetas a utilizar. A continuación podemos ver un ejemplo sencillo de la estructura de esta sección:
```yaml
temporal_config:
feature_start_time: '1990-08-01'
feature_end_time: '2020-08-01'
label_start_time: '2003-08-15'
label_end_time: '2020-08-15'
label_timespans: ['1y']
model_update_frequency: '1y'
training_as_of_date_frequencies: '1y'
max_training_histories: '10y'
test_durations: '1y'
test_as_of_date_frequencies: '1y'
```
- `feature_start_time` y `feature_end_time`: Los datos de _features_ son agregados desde y antes de dichas fechas, respectivamente.
- `label_start_time` y label_end_time: Los datos de _labels_ son agregados desde y antes de dichas fechas, respectivamente.
- `label_timespans`: Cuanto tiempo hacia adelante se predice el _label_.
- `max_training_histories`: La máxima cantidad de información que se utiliza para una entidad. Depende de que tanto se espera que tanto hayan cambiado los patrones de comportamiento.
- `test_durations y `model_update_frequency`: `cuanto tiempo debe ser usado un modelo para hacer predicciones, y cada cuanto se actualiza, respectivamente.
- `test_as_of_date_frequencies`: Espaciamiento entre columnas apra una misma entidad.
Gŕaficamente, el _timechop_ para este experimento se ve así:

Más adelante mostraremos como obtener esta imagen a partir de la configuración del experimento.
### 2.3 Cohorte
Esta sección se inicia con la clave `label_config`, y en ella debemos definir el **_query_ parametrizado** que nos permite seleccionar la cohorte en cada periodo de interés. En el siguiente ejemplo presentamos el query que extrae los estudiantes sin materias reprobadas al inicio del semestre.
```yaml
cohort_config:
query: |
with semestres as(
select
semestre_codigo
from semantic.semestres
where nivel = 'licenciatura'
and tipo in ('otoño', 'primavera')
and fecha_inicio >= (timestamp '{as_of_date}'- interval '3 weeks')::date
and fecha_fin <= (timestamp '{as_of_date}'+ interval '6 months')::date
),
reprobados as(
select
alumno
from semantic.eventos_alumnos as current_semester_students
where fecha < '{as_of_date}'
and tipo = 'reprobar curso'
)
-- alumnos al inicio del semestre
select distinct
alumno::int as entity_id
from semantic.eventos_alumnos as current_semester_students
inner join semestres
on current_semester_students.atributos->>'semestre_codigo' =
semestres.semestre_codigo
where current_semester_students.tipo = 'dar de alta curso'
and alumno not in (select alumno from reprobados)
name: 'alumnos_sin_reprobadas'
```
### 2.4 Configuración del la etiqueta (_label_config_)
Esta sesión empiza con la clave `label_config`, seguida por el **_query_ parametrizado** para determinar la etiqueta de los alumnos sobre los que se realiza predicción.
```yaml
label_config:
query: |
select
alumno::int as entity_id,
'{as_of_date}'::date as as_of_date,
bool_or(tipo = 'reprobar curso')::int as outcome
from semantic.eventos_alumnos
where fecha >= ('{as_of_date}'::timestamp + interval '12 months')::date and
fecha < ('{as_of_date}'::timestamp + interval '{label_timespan}')::date
group by alumno
name: 'reprueban'
```
### 2.5 Features
Esta sección se inicia con la clave `feature_aggregations`.
```yaml
## Features
feature_aggregations:
### Proceso de admisión en el ITAM
-
prefix: 'examen_admision'
from_obj: |
(select alumno::int as entity_id, fecha, (atributos->>'calificacion')::int as calificacion from semantic.eventos_alumnos where tipo = 'presentar examen administrativo' and atributos->>'examen'= 'paat') as examen
knowledge_date_column: 'fecha'
aggregates_imputation:
count:
type: 'zero_noflag'
max:
type: 'constant'
value: 1375
aggregates:
-
quantity:
total: "calificacion"
metrics:
- 'max'
intervals: ['all']
groups:
- 'entity_id'
### sociodemográficas
# género
-
prefix: 'genero'
from_obj: |
(with primer_semestre as( select alumno, fecha from semantic.eventos_alumnos where tipo = 'terminar semestre' and atributos->>'semestre_nominal' = '1' ) select alumno::int as entity_id, genero, fecha from semantic.alumnos left join primer_semestre using(alumno) ) as generos
knowledge_date_column: 'fecha'
categoricals_imputation:
all:
type: 'mean'
categoricals:
- # gender
column: genero
choice_query: 'select distinct genero from semantic.alumnos'
metrics:
- 'max'
intervals: ['all']
groups:
- 'entity_id'
```
Aunque definimos dos grupos de _features anteriormente_, podemos ver los efectos que tienen las diferentes combinaciones de las mismas dentro de nuestros modelos. Para lo anterior, utilizamos la clave `feature_group_definition`. Dentro de sus valores, podemos especificar una o varias de las siguientes opciones:
- `all`
- `leave-one-out`
- ` leave-one-in`
- `all-combinations`
La opción _default_ es `all`.
```yaml
feature_group_definition:
prefix:
- 'examen_admision'
- 'genero'
feature_group_strategies: ['all-combinations']
```
En este ejemplo no explotamos mucho la naturaleza de las opciones que evaluan distintas combinaciones. Sin embargo, si consideramos un ejemplo con tres _features_: `examen_admision`, `genero`, y `promedio`, entonces la opción `all-combinations`, evaluaría los siguientes grupos de _features_:
- `examen_admision`
- `genero`
- `promedio`
- `examen_admision` y `genero`
- `examen_admision`, y `promedio`
- `genero`, y `promedio`
- `examen_admision`, `genero`, y `promedio`
### 2.6 Métricas a optimizar (_scoring_)
Esta sección se inicia con la clave `scoring`, y posteriormente se deben incluir las métricas a evaluar, y estas van a depender del objetivo y población de interés de nuestro proyecto.
Por ejemplo, si sabemos que la institución tiene recursos para intervenir asistivamente a 50 estudiantes, probablemente qusieramos optimizar la métrica de `cobertura@50`:
```yaml
scoring:
testing_metric_groups:
-
metrics: [recall@]
thresholds:
top_n: [50]
```
Supongamos que se intervendrá de manera asistiva sobre el 5% de la población, en ese caso:
```yaml
scoring:
testing_metric_groups:
-
metrics: [recall@]
thresholds:
percentiles: [5.0]
```
Sin embargo, supongamos que en una primera fase sabemos que se intervendrá sobre 50 o 100 estudiantes, y no sabemos si la intervención será punitiva o asistiva. En ese caso, podríamos optimizar nuestro modelo de la siguiente forma:
```yaml
scoring:
testing_metric_groups:
-
metrics: [recall@, precision@]
thresholds:
percentiles: [50, 100]
```
### 2.7 Modelos (_grid_config_)
En esta sección podemos definir los experimentos que queremos utilizar. En particular, `triage` incorpora todos los modelos de la librería [`scikit-learn`](https://scikit-learn.org/stable/) y podemos utilizar la documentación de la misma para especificar los hiperparámetros deseados.
La sección es una lista de diccionarios contenida en el diccionario `grid_config`. Posteriormente se especifica cada modelo como clase, y dentro del mismo, los hiperparámetros asociados. En este ejemplo consideraremos incluir dos tipos de modelos [`DecisionTreeClassifier`](https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html) y [`RandomForestClassifier`](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html).
```yaml
grid_config:
'sklearn.tree.DecisionTreeClassifier':
criterion: ['entropy']
max_depth: [1, null]
min_samples_split: [25]
min_samples_leaf: [0.025, 0.10]
'sklearn.ensemble.RandomForestClassifier':
n_estimators: [10, 50]
max_depth: [null, 1, 2, 10]
max_features: ['log2']
min_samples_split: [10]
criterion: ['gini']
random_state: [2193]
n_jobs: [-1]
```
#### 2.5.1 _Grids_ pre-definidos
No es trivial definir qué y cuántos modelos queremos probar en nuestro experimento, ni acotar el especio de búsqueda de los hiperparámetros. Muchas veces esta decisión también depende de nuestra capacidad de computo. En esta dirección `triage` incluye algunos _grids_ recomendados, disponibles en [esta](https://github.com/dssg/triage/blob/master/src/triage/experiments/model_grid_presets.yaml) liga:
- quickstart
- small
- medium
- large
Para correr nuestro experimento con alguno de ellos podemos en lugar de la sección `grid_config`, incluir por ejemplo:
```yaml
model_grid_presets: ['quickstart']
```
## 3. Correr el experimento
Para correr experimentos en triage podemos añadir algunos _flags_ utiles al comando `triage experiment config.yaml` dependiendo de nuestros objetivos.
### 3.1 Validar el experimento
Para verificar que nuestro archivo de configuración es correcto sin correr el experimento, debemos ejecutar:
```bash
triage experiment reprobados.yaml --validate-only
```
### 3.2 Correr el experimento
Una vez se ha verificado que el archivo de configuración es correcto, es posible ejecutar el experimento con diferentes opciones.
### 3.2.1 Base de datos
Para que `triage` tenga acceso a la base de datos y pueda escribir los resultados de los experimentos en la misma, debemos proveerle las credenciales correspondientes en un archivo `.yaml`. A continuación se presenta un ejemplo con la estructura que debe tener dicho archivo:
```yaml
# database.yaml
host: database-amai.cfdemdotqpti.us-east-1.rds.amazonaws.com
user: unusuario
db: itam
pass: unusuario_pass
port: 5432
```
Al correr el experimento, debemos añadir el _flag_ `-d `:
```bash
triage experiment reprobados.yaml -d database.yaml
```
### 3.2.2 Role
Queremos que la escritura de los resultados en la base de datos brinde permisos de lectura a los demas usuarios del CCD, por lo tanto asignamos el _role_ a `itam_readwrite`. Para lo anterior corremos los experimentos utilizando el _script_ `itam_config.py`, presentado a continuación:
```python
# itam_config.py
from sqlalchemy.event import listens_for
from sqlalchemy.pool import Pool
@listens_for(Pool, "connect")
def assume_role(dbapi_con, connection_record):
print("Triage is assuming the role!")
dbapi_con.cursor().execute('set role itam_readwrite;')
print("Everything is OK!")
```
Al correr el experimento, debemos añadir el _flag_ `-s`:
```{bash}
triage experiment reprobados.yaml -s PATH/itam_cong.py
```
### 3.2.3 Guardar modelos entrenados y matrices de entrenamiento
Para guardar los archivos `pickle` de los modelos y las matrices de entrenamiento podemos elegir distintos métodos, dado que estamos utilizando datos sensibles, no los debemos las matrices en nuestro PC personal. A continuación explicaremos como almacenar esta información en un bucket de S3.
**Bucket de S3**
Si queremos guardar nuestros resultados en un _bucket_ de S3, por ejemplo, `s3://ccd-itam` , primero, debemos tener nuestras credenciales de acceso guardadas en `~/.AWS/credentials`.
Si se cumple la anterior condición, podemos ejecutar el experimento indicando la ruta del _bucket_ donde queremos que se guarden los experimentos con el _flag_ `--project-path`:
```bash
triage experiment reprobados.yaml --project-path s3://ccd-itam/ejemplos_experimentos
```
### 3.2.4 Paralelización
Si el experimento contiene modelos para los cuales se puede paralelizar el entrenamiento podemos hacer uso de los núcleos que tenga nuestra CPU. Supongamos que son 4.
Podemos utilizar el _flag_ `-n-processes`:
```bash
triage experiment reprobados.yaml --n-processes 4
```
### 3.2.5 Reemplazar experimentos
Si se desean sobreescribir experimentos con un `model_comment` existente, podemos ejecutar el _flag_ `--replace`:
```bash
triage experiment reprobados.yaml --replace
```
## 4 Ejemplo conjunto
Para correr el experimento de ejemplo en paralelo, guardar los resultados en base de datos, y los modelos en `S3`:
```bash
triage -s PATH/itam_config.py \
-d PATH/database.yaml \
experiment reprobados.yaml \
--n-processes 4 \
--project-path s3://BUCKETPATH
```
> Ejemplo en _spot_:
>
> Primero activamos el environment:
>
> ```bash
> pyenv activate triage_itam
> ```
>
> Luego ejecutamos el comando:
>
> ```bash
> triage -s itam_config.py -d database.yaml experiment src/experiments/reprobados.yaml --project-path 's3://ccd-itam/capacitacion' --n-processes 4 --validate-only
> ```
>
> Una vez validamos el experimento, podemos extraer el _timechop_ del mismo
>
> ```bash
> triage -d database.yaml experiment src/experiments/reprobados.yaml --project-path 's3://ccd-itam/capacitacion' --show-timechop
> ```
>
> La imagen del _timechop_ se genera en el la ruta especificada en `project-path` dentro de una carpeta que se crear, con el nombre `images`. En este caso, en `s3://ccd-itam/capacitacion/images` se creó la imagen `reprobados.png` . Podemos extraerla desde Bastión.
>
> ```bash
> ## En bastión
> aws s3 ls s3://ccd-itam/capacitacion/images/ # revisar que se haya creado la imagen
> aws s3 sync s3://ccd-itam/capacitacion/images/ ./capacitacion # copiarla
>
> ## En local (transferirla para verla)
> scp -r -i ~/.ssh/id_scs_ccd scadavid@bastion.ccdatos.itam.mx:/home/scadavid/capacitacion /home/sebastian/Documents
>
> ```
## 5 Exploración de resultados
Cada combinación de matriz de entrenamiento, clasificador e hiperparámetros se considerará un **modelo**.
A cada uno de los modelos que formen parte del experimento se le asignará un _hash_. Una vez entrenado, cada modelo se almacenará en un archivo pickle nombrado con el _hash_ que le corresponda, usando `joblib`.
- Un `model_group` comprende todos los modelos que comparten parámetros e hiperparámetros, pero difieren en sus ventanas de entrenamiento.
- `model` es un modelo entrenado para una ventana de tiempo específica.
Al correr un experimento, se generan tres nuevos esquemas en la base de datos. Se trata de `triage_metadata`, `train_results`, y `test_results`.
A continuación presentamos las principales tablas de los esquemas anteriores que utilizaremos para evaluar los resultados de nuestro experimento. El detalle de todas las tablas creadas puede consultarse en este [enlace](https://dssg.github.io/triage/experiments/running/#evaluating-results-of-an-experiment).
### 5.1 Esquema ```triage_metadata```
En este esquema encontraremos distintas tablas con metadata de los modelos y experimentos ejecutados. Las que usaremos más son:
* `triage_metadata.models` - Contiene metadata sobre los modelos entrenados; contendrá un registro por cada modelo entrenado que se haya guardado, indicando el tipo de clasificador, sus hiper parámetros, el hash que se le asignó y el grupo de modelos a los que pertenece.
```bash=
itam=> select * from triage_metadata.models m limit 3;
model_id | model_group_id | model_hash | run_time | batch_run_time | model_type | hyperparameters | model_comment | batch_comment | config | built_by_experiment | train_end_time | test | train_matrix_uuid | training_label_timespan | model_size | random_seed | built_in_experiment_run
----------+----------------+----------------------------------+----------------------------+----------------------------+--------------------------------------------------------------------------+----------------------------------------------------------------------------------------------+---------------+---------------+--------+----------------------------------+---------------------+------+----------------------------------+-------------------------+------------+-------------+-------------------------
1 | 1 | 2e09ec7a1c919998537ebeb68720dd8b | 2020-10-28 06:30:36.564199 | 2020-10-28 06:30:30.664236 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": 1, "min_samples_leaf": 0.025, "min_samples_split": 25} | baseline_2c | | | f1da842d6f5d7e2a0a6c5631859dc8f5 | 2005-08-15 00:00:00 | f | 1f7adb221409ea2c308d3273d02c7e50 | 2 years | 0.0625 | 868976053 | 5
2 | 2 | c4c0ef5479793352c7c1eb9985d9600e | 2020-10-28 06:30:36.865583 | 2020-10-28 06:30:30.664236 | triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression | {"C": 1.0, "penalty": "l2"} | baseline_2c | | | f1da842d6f5d7e2a0a6c5631859dc8f5 | 2005-08-15 00:00:00 | f | 1f7adb221409ea2c308d3273d02c7e50 | 2 years | 0.0625 | 1396809932 | 5
3 | 3 | 5efd69ff084e43c35e2422bc24dfae11 | 2020-10-28 06:30:37.13721 | 2020-10-28 06:30:30.664236 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": 1, "min_samples_leaf": 0.1, "min_samples_split": 25} | baseline_2c | | | f1da842d6f5d7e2a0a6c5631859dc8f5 | 2005-08-15 00:00:00 | f | 1f7adb221409ea2c308d3273d02c7e50 | 2 years | 0.0625 | 131653638 |
```
* `triage_metadata.model_groups` - Contiene los parámetros propios del grupo de modelos, así como las variables o _features_ utilizadas.
```bash=
itam=> select * from triage_metadata.model_groups limit 3;
model_group_id | model_type | hyperparameters | feature_list | model_config
----------------+--------------------------------------------------------------------------+----------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": 1, "min_samples_leaf": 0.025, "min_samples_split": 25} | {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} | {"state": "active", "label_name": "faltar_al_reglamento_2c", "cohort_name": "alumnos_inscritos_2c", "feature_groups": ["prefix: prepa", "prefix: examen_admision", "prefix: reprobar_examen_redaccion", "prefix: reprobar_examen_intro_mate"], "label_timespan": "2y", "as_of_date_frequency": "1y", "max_training_history": "10y"}
2 | triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression | {"C": 1.0, "penalty": "l2"} | {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} | {"state": "active", "label_name": "faltar_al_reglamento_2c", "cohort_name": "alumnos_inscritos_2c", "feature_groups": ["prefix: prepa", "prefix: examen_admision", "prefix: reprobar_examen_redaccion", "prefix: reprobar_examen_intro_mate"], "label_timespan": "2y", "as_of_date_frequency": "1y", "max_training_history": "10y"}
3 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": 1, "min_samples_leaf": 0.1, "min_samples_split": 25} | {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} | {"state": "active", "label_name": "faltar_al_reglamento_2c", "cohort_name": "alumnos_inscritos_2c", "feature_groups": ["prefix: prepa", "prefix: examen_admision", "prefix: reprobar_examen_redaccion", "prefix: reprobar_examen_intro_mate"], "label_timespan": "2y", "as_of_date_frequency": "1y", "max_training_history": "10y"}
```
* `triage_metadata.experiment_runs` - Contiene información sobre la ejecución del experimento: hora de inicio, usuario, versión de triage, tipo de instancia EC2, status de la ejecución, y más.
```bash=
itam=>select * from triage_metadata.experiment_runs er limit 3;
id | start_time | start_method | git_hash | triage_version | experiment_hash|platform| os_user|working_directory| ec2_instance_type | log_location |experiment_class_path| experiment_kwargs|installed_libraries| matrix_building_started | matrices_made | matrices_skipped | matrices_errored | model_building_started | models_made | models_skipped | models_errored | last_updated_time | current_status | stacktrace | python_version | random_seed
----+----------------------------+--------------+------------------------------------------+----------------+----------------------------------+----------------------------------------------------+-----------+-------------------------------------------+-------------------+--------------+--------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+---------------+------------------+------------------+------------------------+-------------+----------------+----------------+----------------------------+----------------+------------+----------------------------------------------------+-------------
1 | 2020-10-28 06:16:54.8044 | | 40ebc53c53f658a80d9a8d155cf64daa23fd075a | 4.1.1 | 64b30706529c110bb3c8301a9be9aed5 | Linux-5.3.0-1034-aws-x86_64-with-debian-buster-sid | sebastian | /home/sebastian/issue-196/src/experiments | t2.large | | triage.experiments.multicore.MultiCoreExperiment | {"cleanup": false, "profile": false, "replace": true, "partial_run": false, "project_path": "s3://ccd-itam/validated_experiments/2_conds", "cleanup_timeout": null, "skip_validation": false, "save_predictions": true, "matrix_storage_class": "triage.component.catwalk.storage.CSVMatrixStore", "features_ignore_cohort": false, "materialize_subquery_fromobjs": true} | {adjustText==0.7.3,aequitas==0.38.0,alembic==1.4.2,argcmdr==0.6.0,argcomplete==1.9.4,boto3==1.14.45,botocore==1.17.56,certifi==2020.6.20,chardet==3.0.4,click==7.1.2,coloredlogs==14.0,cycler==0.10.0,Dickens==1.0.1,docutils==0.15.2,dominate==2.5.2,Flask==0.12.2,Flask-Bootstrap==3.3.7.1,fsspec==0.8.0,graphviz==0.14,html5lib==1.1,httplib2==0.18.1,humanfriendly==8.2,idna==2.10,inflection==0.5.0,itsdangerous==1.1.0,Jinja2==2.11.2,jmespath==0.10.0,joblib==0.16.0,kiwisolver==1.2.0,Mako==1.1.3,markdown2==2.3.5,MarkupSafe==1.1.1,matplotlib==3.2.2,numpy==1.19.0,ohio==0.5.0,pandas==1.0.5,Pebble==4.5.3,Pillow==7.2.0,pip==20.1.1,plumbum==1.6.4,psycopg2==2.8.5,psycopg2-binary==2.8.5,pyparsing==2.4.7,PyPDF2==1.26.0,python-dateutil==2.8.1,python-editor==1.0.4,pytz==2020.1,PyYAML==5.3.1,reportlab==3.5.49,requests==2.24.0,retrying==1.3.3,s3fs==0.4.2,s3transfer==0.3.3,scikit-learn==0.23.1,scipy==1.5.0,seaborn==0.10.1,setuptools==47.1.0,signalled-timeout==1.0.0,six==1.15.0,SQLAlchemy==1.3.18,sqlalchemy-postgres-copy==0.5.0,sqlparse==0.3.1,tabulate==0.8.2,threadpoolctl==2.1.0,triage==4.1.1,urllib3==1.25.10,verboselogs==1.7,visitor==0.1.3,webencodings==0.5.1,Werkzeug==1.0.1,wrapt==1.12.1,xhtml2pdf==0.2.2} | | 0 | 0 | 0 | | 0 | 0 | 0 | 2020-10-28 06:16:54.811704 | started | | 3.7.9 (default, Sep 5 2020, 01:20:01) [GCC 7.5.0] | 4296593
58 | 2020-11-04 06:17:59.678631 | | dea943528749125c242c3aaf6fe15aeaad112ef0 | 4.1.1 | 3dafba558e59bf455bd0d8817213510c | Linux-5.3.0-1034-aws-x86_64-with-debian-buster-sid | sebastian | /home/sebastian/EWS_ITAM/src/experiments | t2.2xlarge | | triage.experiments.multicore.MultiCoreExperiment | {"cleanup": false, "profile": false, "replace": false, "partial_run": false, "project_path": "s3://ccd-itam/validated_experiments/3_conds", "cleanup_timeout": null, "skip_validation": false, "save_predictions": true, "matrix_storage_class": "triage.component.catwalk.storage.CSVMatrixStore", "features_ignore_cohort": false, "materialize_subquery_fromobjs": true} | {adjustText==0.7.3,aequitas==0.38.0,alembic==1.4.2,argcmdr==0.6.0,argcomplete==1.9.4,boto3==1.14.45,botocore==1.17.56,certifi==2020.6.20,chardet==3.0.4,click==7.1.2,coloredlogs==14.0,cycler==0.10.0,Dickens==1.0.1,docutils==0.15.2,dominate==2.5.2,Flask==0.12.2,Flask-Bootstrap==3.3.7.1,fsspec==0.8.0,graphviz==0.14,html5lib==1.1,httplib2==0.18.1,humanfriendly==8.2,idna==2.10,inflection==0.5.0,itsdangerous==1.1.0,Jinja2==2.11.2,jmespath==0.10.0,joblib==0.16.0,kiwisolver==1.2.0,Mako==1.1.3,markdown2==2.3.5,MarkupSafe==1.1.1,matplotlib==3.2.2,numpy==1.19.0,ohio==0.5.0,pandas==1.0.5,Pebble==4.5.3,Pillow==7.2.0,pip==20.1.1,plumbum==1.6.4,psycopg2==2.8.5,psycopg2-binary==2.8.5,pyparsing==2.4.7,PyPDF2==1.26.0,python-dateutil==2.8.1,python-editor==1.0.4,pytz==2020.1,PyYAML==5.3.1,reportlab==3.5.49,requests==2.24.0,retrying==1.3.3,s3fs==0.4.2,s3transfer==0.3.3,scikit-learn==0.23.1,scipy==1.5.0,seaborn==0.10.1,setuptools==47.1.0,signalled-timeout==1.0.0,six==1.15.0,SQLAlchemy==1.3.18,sqlalchemy-postgres-copy==0.5.0,sqlparse==0.3.1,tabulate==0.8.2,threadpoolctl==2.1.0,triage==4.1.1,urllib3==1.25.10,verboselogs==1.7,visitor==0.1.3,webencodings==0.5.1,Werkzeug==1.0.1,wrapt==1.12.1,xgboost==1.2.1,xhtml2pdf==0.2.2} | | 0 | 0 | 0 | | 0 | 0 | 0 | 2020-11-04 06:17:59.685965 | started | | 3.7.9 (default, Sep 5 2020, 01:20:01) [GCC 7.5.0] | 4296593
4 | 2020-10-28 06:29:27.870642 | | 40ebc53c53f658a80d9a8d155cf64daa23fd075a | 4.1.1 | f1da842d6f5d7e2a0a6c5631859dc8f5 | Linux-5.3.0-1034-aws-x86_64-with-debian-buster-sid | sebastian | /home/sebastian/issue-196/src/experiments | t2.medium | | triage.experiments.multicore.MultiCoreExperiment | {"cleanup": false, "profile": false, "replace": false, "partial_run": false, "project_path": "s3://ccd-itam/validated_experiments/2_conds", "cleanup_timeout": null, "skip_validation": false, "save_predictions": true, "matrix_storage_class": "triage.component.catwalk.storage.CSVMatrixStore", "features_ignore_cohort": false, "materialize_subquery_fromobjs": true} | {adjustText==0.7.3,aequitas==0.38.0,alembic==1.4.2,argcmdr==0.6.0,argcomplete==1.9.4,boto3==1.14.45,botocore==1.17.56,certifi==2020.6.20,chardet==3.0.4,click==7.1.2,coloredlogs==14.0,cycler==0.10.0,Dickens==1.0.1,docutils==0.15.2,dominate==2.5.2,Flask==0.12.2,Flask-Bootstrap==3.3.7.1,fsspec==0.8.0,graphviz==0.14,html5lib==1.1,httplib2==0.18.1,humanfriendly==8.2,idna==2.10,inflection==0.5.0,itsdangerous==1.1.0,Jinja2==2.11.2,jmespath==0.10.0,joblib==0.16.0,kiwisolver==1.2.0,Mako==1.1.3,markdown2==2.3.5,MarkupSafe==1.1.1,matplotlib==3.2.2,numpy==1.19.0,ohio==0.5.0,pandas==1.0.5,Pebble==4.5.3,Pillow==7.2.0,pip==20.1.1,plumbum==1.6.4,psycopg2==2.8.5,psycopg2-binary==2.8.5,pyparsing==2.4.7,PyPDF2==1.26.0,python-dateutil==2.8.1,python-editor==1.0.4,pytz==2020.1,PyYAML==5.3.1,reportlab==3.5.49,requests==2.24.0,retrying==1.3.3,s3fs==0.4.2,s3transfer==0.3.3,scikit-learn==0.23.1,scipy==1.5.0,seaborn==0.10.1,setuptools==47.1.0,signalled-timeout==1.0.0,six==1.15.0,SQLAlchemy==1.3.18,sqlalchemy-postgres-copy==0.5.0,sqlparse==0.3.1,tabulate==0.8.2,threadpoolctl==2.1.0,triage==4.1.1,urllib3==1.25.10,verboselogs==1.7,visitor==0.1.3,webencodings==0.5.1,Werkzeug==1.0.1,wrapt==1.12.1,xhtml2pdf==0.2.2} | | 0 | 0 | 0 | | 0 | 0 | 0 | 2020-10-28 06:29:27.880262 | started | | 3.7.9 (default, Sep 5 2020, 01:20:01) [GCC 7.5.0] | 4296593
```
### 5.2 Esquema ```train_results```
* `train_results.feature_importance` - Contiene los valores de _feature_importance_ dados por ```sklearn``` para cada modelo entrenado.
>Depende de si el modelo tiene la viabilidad de calcular _FI_, como por ejemplo, árboles de decisión.
```bash=
itam=> select * from train_results.feature_importances limit 10;
model_id | feature | feature_importance | rank_abs | rank_pct
----------+------------------------------------------------------------+--------------------+----------+----------
1 | prepa_entity_id_all_pase_8_0_max | 0.0 | 2 | 1
1 | prepa_entity_id_all_pase_8_1_max | 0.0 | 2 | 1
1 | prepa_entity_id_all_pase_8__NULL_max | 0.0 | 2 | 1
1 | examen_admision_entity_id_all_total_max | 1.0 | 1 | 0.5
1 | examen_admision_entity_id_all_total_imp | 0.0 | 2 | 1
1 | reprobar_examen_redaccion_entity_id_all_reprobar_0_max | 0.0 | 2 | 1
1 | reprobar_examen_redaccion_entity_id_all_reprobar_1_max | 0.0 | 2 | 1
1 | reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max | 0.0 | 2 | 1
1 | reprobar_examen_intro_mate_entity_id_all_reprobar_0_max | 0.0 | 2 | 1
1 | reprobar_examen_intro_mate_entity_id_all_reprobar_1_max | 0.0 | 2 | 1
```
* `train_results.predictions`
```bash=
itam=> select * from train_results.predictions limit 5;
model_id | entity_id | as_of_date | score | label_value | rank_abs_no_ties | rank_abs_with_ties | rank_pct_no_ties | rank_pct_with_ties | matrix_uuid | test_label_timespan
----------+-----------+---------------------+---------+-------------+------------------+--------------------+------------------+--------------------+----------------------------------+---------------------
1 | 3394 | 2003-08-15 00:00:00 | 0.13203 | 0 | 1 | 1 | 0.00023 | 0.50000 | 1f7adb221409ea2c308d3273d02c7e50 | 2 years
1 | 26418 | 2003-08-15 00:00:00 | 0.13203 | 0 | 2 | 1 | 0.00045 | 0.50000 | 1f7adb221409ea2c308d3273d02c7e50 | 2 years
1 | 26507 | 2003-08-15 00:00:00 | 0.13203 | 0 | 3 | 1 | 0.00068 | 0.50000 | 1f7adb221409ea2c308d3273d02c7e50 | 2 years
1 | 28960 | 2003-08-15 00:00:00 | 0.13203 | 0 | 4 | 1 | 0.00091 | 0.50000 | 1f7adb221409ea2c308d3273d02c7e50 | 2 years
1 | 31245 | 2003-08-15 00:00:00 | 0.13203 | 0 | 5 | 1 | 0.00114 | 0.50000 | 1f7adb221409ea2c308d3273d02c7e50 | 2 years
```
### 5.3 Esquema ```test_results```
* ```test_results.evaluations``` - Scores asignados por los modelos entrenados, para las métricas definidas.
```bash=
itam=> select * from test_results.evaluations limit 5;
model_id | evaluation_start_time | evaluation_end_time | as_of_date_frequency | metric | parameter | num_labeled_examples | num_labeled_above_threshold | num_positive_labels | sort_seed | matrix_uuid | subset_hash | best_value | worst_value | stochastic_value | num_sort_trials | standard_deviation
----------+-----------------------+---------------------+----------------------+---------+-----------+----------------------+-----------------------------+---------------------+-----------+----------------------------------+-------------+---------------------+---------------------+----------------------+-----------------+----------------------
1 | 2005-08-15 00:00:00 | 2005-08-15 00:00:00 | 1 year | recall@ | 100_abs | 4268 | 100 | 474 | | 9a1cb4acc106f07cd7812d2242a4cd48 | | 0.2109704641350211 | 0.0 | 0.0339662447257384 | 30 | 0.008922381390340517
2 | 2005-08-15 00:00:00 | 2005-08-15 00:00:00 | 1 year | recall@ | 100_abs | 4268 | 100 | 474 | | 9a1cb4acc106f07cd7812d2242a4cd48 | | 0.04219409282700422 | 0.04219409282700422 | 0.04219409282700422 | 0 | 0
4 | 2005-08-15 00:00:00 | 2005-08-15 00:00:00 | 1 year | recall@ | 100_abs | 4268 | 100 | 474 | | 9a1cb4acc106f07cd7812d2242a4cd48 | | 0.04008438818565401 | 0.04008438818565401 | 0.04008438818565401 | 0 | 0
3 | 2005-08-15 00:00:00 | 2005-08-15 00:00:00 | 1 year | recall@ | 100_abs | 4268 | 100 | 474 | | 9a1cb4acc106f07cd7812d2242a4cd48 | | 0.2109704641350211 | 0.0 | 0.029957805907172997 | 30 | 0.006375021440950903
5 | 2005-08-15 00:00:00 | 2005-08-15 00:00:00 | 1 year | recall@ | 100_abs | 4268 | 100 | 474 | | 9a1cb4acc106f07cd7812d2242a4cd48 | | 0.06751054852320675 | 0.0189873417721519 | 0.030520393811533052 | 30 | 0.003867681616001847
```
### 5.4 Ejemplo de consulta para selección de modelos
Si nos interesara elegir los grupos de modelos que, en promedio, tuvieron el mejor `recall@100` para todos los periodos, podríamos ejecutar la siguiente consulta:
```sql=
select
model_group_id,
mg.model_type,
mg.hyperparameters,
--count (*) as num_registros,
avg(stochastic_value) as avg_recall
from
triage_metadata.models as mo
inner join triage_metadata.model_groups as mg
using(model_group_id)
inner join test_results.evaluations as ev
using(model_id)
where
mo.model_comment = 'baseline_2c'
and ev.metric || ev.parameter = 'recall@100_abs'
group by
model_group_id,
mg.model_type,
mg.hyperparameters
order by
avg_recall desc
limit 4
```
La consulta anterior nos van a devolver un listado con 4 modelos de la siguiente forma:
```bash=
model_group_id | model_type | hyperparameters | avg_recall
----------------+--------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------+------------------------
2 | triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression | {"C": 1.0, "penalty": "l2"} | 0.03826952739091737177
4 | triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression | {"C": 0.01, "penalty": "l2"} | 0.03686464358122507315
6 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": null, "min_samples_leaf": 0.1, "min_samples_split": 25} | 0.03685125825374580485
5 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": null, "min_samples_leaf": 0.025, "min_samples_split": 25} | 0.03465288001405915238
```
Ahora supongamos una búsqueda que amplía la anterior. Supongamos que queremos saber los modelos con mejor rendimiento promedio para `recall@100` para un periodo seleccionado. En este caso, podriamos hacer el siguiente _query_:
```sql=
select model_group_id, mg.model_type,mg.hyperparameters, avg(stochastic_value) as avg_recall
from
triage_metadata.models as mo
inner join
triage_metadata.model_groups as mg using(model_group_id)
inner join
test_results.evaluations as ev using(model_id)
where
mo.model_comment = 'baseline_2c'
and ev.metric||ev.parameter = 'recall@100_abs'
and evaluation_start_time >'2010-01-01'
and evaluation_start_time <'2020-01-01'
group by model_group_id,mg.model_type,mg.hyperparameters
order by avg_recall desc limit 4
```
La consulta anterior nos van a devolver un listado con 4 modelos de la siguiente forma:
```bash=
model_group_id | model_type | hyperparameters | avg_recall
----------------+--------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------+------------------------
2 | triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression | {"C": 1.0, "penalty": "l2"} | 0.03854168380621023688
6 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": null, "min_samples_leaf": 0.1, "min_samples_split": 25} | 0.03813129850933780550
5 | sklearn.tree.DecisionTreeClassifier | {"criterion": "entropy", "max_depth": null, "min_samples_leaf": 0.025, "min_samples_split": 25} | 0.03705653565779174175
4 | triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression | {"C": 0.01, "penalty": "l2"} | 0.03682861827958306638
```
Como podemos observar, aunque el _Top 4_ sigue teniendo los mismos `model_group_id` si variamos los periodos de estudio de la métrica promedio, varía el orden de desempeño.
### 5.5 Revisión de estabilidad en el tiempo
Como vimos en los dos ejemplos anteriores, podemos realizar _queries_ que nos permitan relacionar las tablas con los resultados y los metadatos de los experimentos de acuerdo a nuestro interés.
En las consultas que se vieron solo obteníamos un listado de los modelos con mejor rendimiento en el tiempo. Si quisieramos evaluar a detalle cuál fue su rendimiento de forma dinámica, podríamos parametrizar las consultas.
La factibilidad de poder realizar consultas a la base de los resultados nos permite entender y visualizar nuestros resultados de distintas maneras, y en distintos lenguajes de programación.
El ejemplo que presentamos a continuacion lo implementamos en lenguaje `R` de forma secuencial y buscaba evaluar la estabilidad de los resultados de un 4 grupos de modelos en el tiempo para el experimento 'baseline_2c'.
0. Conexión a la base de datos
```R=
# Conexión
con <- dbConnect(RPostgres::Postgres(),
dbname = credentials$dbname,
host = credentials$host,
port = credentials$port,
user = credentials$user,
password = credentials$password
)
# Ejecución de queries
query_db <- function(query){
rs <- dbSendQuery(con, statement=query)
dbFetch(rs, n= -1)
}
```
1. Identificar los modelos con mejor rendimiento promedio.
Esta función parametriza el _query_ que vimos anteriormente.
```R=
table_top10_at_k_between <- function(mat, metric, k, model_comment, date_1, date_2, top){
"
Función para crear tabla con los componentes de los top mejores modelos entre date_1 y date_2
* Inputs
- mat(chr) : \"train\" or \"test\"
- metric: métrica para evaluar modelos
- k(int): Top k
- model_comment (chr): model comment de triage
- date_1(chr): Fecha de inicio de la evaluación. p.ej '01-01-2000'
- date_2(chr): Fecha de fin de la evaluación. p.ej '01-01-2020'
- top (int): Top de modelos a evaluar
"
# se define query a la base de datos
query_models <- sprintf("
select model_group_id,
mg.model_type,mg.hyperparameters,
avg(stochastic_value) as avg_recall
from triage_metadata.models as mo
inner join
triage_metadata.model_groups as mg using(model_group_id)
inner join
%s_results.evaluations as ev using(model_id)
where
mo.model_comment = '%s'
and ev.metric||ev.parameter = '%s@%d_abs'
and evaluation_start_time > '%s'
and evaluation_start_time < '%s'
group by model_group_id, mg.model_type, mg.hyperparameters
order by avg_recall desc limit %d
;", mat, model_comment, metric, k, date_1, date_2, top)
tbl_models <- query_db(query_models)
# ajusta información
tbl_models <- tbl_models %>% select(model_group_id)
}
```
Ejemplo:
```R=
> top4avg <- table_top10_at_k_between(mat='test', metric='recall', k=100, model_comment='baseline_2c', date_1='01-8-2010', date_2='01-8-2019', top=4)
> top4avg
model_group_id model_type hyperparameters avg_recall
2 triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression {"C": 1.0, "penalty": "l2"} 0.03854168
6 sklearn.tree.DecisionTreeClassifier {"criterion": "entropy", "max_depth": null, "min_samples_leaf": 0.1, "min_samples_split": 25} 0.03813130
5 sklearn.tree.DecisionTreeClassifier {"criterion": "entropy", "max_depth": null, "min_samples_leaf": 0.025, "min_samples_split": 25} 0.03705654
4 triage.component.catwalk.estimators.classifiers.ScaledLogisticRegression {"C": 0.01, "penalty": "l2"} 0.03682862
```
2. Identificar las métricas de evaluación para cada punto del tiempo, y además la lista de features utilizados.
```R=
table_top10_at_k <- function(mat, metric, k, model_comment, date_1, date_2, top){
"
Función para crear tabla con los componentes de los 10 mejores modelos.
* Inputs
- mat(chr) : \"train\" or \"test\"
- metric: métrica para evaluar modelos
- k(int): Top k
- model_comment (chr): model comment de triage
- date_1(chr): Fecha de inicio de la evaluación. p.ej '01-01-2000'
- date_2(chr): Fecha de fin de la evaluación. p.ej '01-01-2020'
- top (int): Top de modelos a evaluar
* Outputs:
- model_group_id
- train_end_time
- best_value
- worst_valie
- stochatic value
- model_type
- hyperparameters
- feature_list
"
# se define query a la base de datos
query_models <- sprintf("with best as(
select model_comment,
model_group_id,
train_end_time,
best_value,
worst_value,
stochastic_value,
model_type,
hyperparameters
from %s_results.evaluations
left join triage_metadata.models
using (model_id)
where metric = '%s@'
and parameter = '%d_abs'
and model_comment = '%s'
and model_group_id in (%s)
and evaluation_start_time > '%s'
and evaluation_start_time < '%s')
select * from best
left join triage_metadata.model_groups
using (model_group_id)
order by model_group_id, stochastic_value desc
;", mat, metric, k, model_comment, top, date_1, date_2)
tbl_models <- query_db(query_models)
return(tbl_models)
}
```
Ejemplo:
```R=
> metricsTop4 <- table_top10_at_k(mat='test', metric='recall', k=100, model_comment='baseline_2c', date_1='01-8-2010', date_2='01-8-2019', top=4)
model_group_id train_end_time best_value worst_value stochastic_value hyperparameters feature_list model
2 2011-08-15 0.04662005 0.04662005 0.04662005 {"C": 1.0, "penalty": "l2"} {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} Scaled Logistic Regression
2 2017-08-15 0.04578755 0.04395604 0.04499389 {"C": 1.0, "penalty": "l2"} {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} Scaled Logistic Regression
2 2016-08-15 0.04268293 0.04065041 0.04126016 {"C": 1.0, "penalty": "l2"} {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} Scaled Logistic Regression
2 2010-08-15 0.03902439 0.03902439 0.03902439 {"C": 1.0, "penalty": "l2"} {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} Scaled Logistic Regression
2 2012-08-15 0.03846154 0.03846154 0.03846154 {"C": 1.0, "penalty": "l2"} {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} Scaled Logistic Regression
2 2014-08-15 0.03846154 0.03846154 0.03846154 {"C": 1.0, "penalty": "l2"} {examen_admision_entity_id_all_total_imp,examen_admision_entity_id_all_total_max,prepa_entity_id_all_pase_8_0_max,prepa_entity_id_all_pase_8_1_max,prepa_entity_id_all_pase_8__NULL_max,reprobar_examen_intro_mate_entity_id_all_reprobar_0_max,reprobar_examen_intro_mate_entity_id_all_reprobar_1_max,reprobar_examen_intro_mate_entity_id_all_reprobar__NULL_max,reprobar_examen_redaccion_entity_id_all_reprobar_0_max,reprobar_examen_redaccion_entity_id_all_reprobar_1_max,reprobar_examen_redaccion_entity_id_all_reprobar__NULL_max} Scaled Logistic Regression
```
3. Graficar el rendimiento en el tiempo
Utilizando la tabla anterior podemos graficar el rendimiento en el tiempo de los modelos con mejor ḿetricas promedio en los periodos evaluados:

Además del promedio también se pueden añadir otro tipo de comparaciones, dependiendo del problema.

## 6. _Postmodeling_ y _Crosstabs_
### 6.1 Análisis _Postmodeling_
#### 6.1.1 Preliminares
Una vez hechos los ejercicios de modelado podemos comparar modelos de distintas formas, bien sea modelos del mismo grupo, o modelos de diferentes grupos, dependiendo de nuestro propósito.
`triage` incorpora una serie de rutinas útiles para este propósito, en este ejemplo solo introduciremos algunas, y nos basaremos en el [ejemplo](https://github.com/dssg/triage/blob/master/src/triage/component/postmodeling/contrast/postmodeling_tutorial.ipynb) oficial de la librería.
- `postmodeling_config.yaml`
Primero, debemos crear un archivo con la condifuración de nuestro análisis con la extensión `.yaml`.
Este archivo, debe tener los siguienes campos:
- `project_path`: ruta a las matrices del experimento
- `model_group_id`: lista de modelos a comparar
- `metric`: Selected metric string (i.e. precision@,recall@)
- `thresholds`: Selected threshold list (i.e. rank_abs[50, 100])
- `baseline_query`:A SQL query that returns evaluation metrics for the baseline models
- `n_features_plots`: Number of features to plot importances (int)
Para este proyecto, tenemos el siguiente ejemplo:
```yaml=
## postmodeling_config.yaml
# Postmodeling Configuration File
project_path: 's3://ccd-itam/experiments/'
model_group_id: # List of model_id's [optional if a audition_output_path is given]
- 390
- 433
- 409
- 386
- 438
metric: recall@
thresholds:
rank_abs: [100]
baseline_query: | # SQL query for defining a baseline for comparison in plots. It needs a metric and parameter
select g.model_group_id,
m.model_id,
extract('year' from m.evaluation_end_time) as as_of_date_year,
m.metric,
m.parameter,
m.stochastic_value,
m.num_labeled_examples,
m.num_labeled_above_threshold,
m.num_positive_labels
from test_results.evaluations m
left join triage_metadata.models g
using(model_id)
where g.model_group_id = 56
and metric = 'recall@'
and parameter = '100_abs'
n_features_plots: 10 # Number of features for importances
figsize: [12, 8] # Default size for plots
fontsize: 14 # Default fontsize for plots
```
Vamos a utilizar las siguientes clases y funciones para ete análisis:
```python=
from triage.component.postmodeling.contrast.utils.aux_funcs import create_pgconn, get_models_ids
from triage.component.postmodeling.contrast.parameters import PostmodelParameters
from triage.component.postmodeling.contrast.model_evaluator import ModelEvaluator
from triage.component.postmodeling.contrast.model_group_evaluator import ModelGroupEvaluator
```
Una vez realizado lo anterior, en `Python` podemos crear la clase con los paŕametros del análisis de la siguiente forma:
```python=
> params = PostmodelParameters('postmodeling_config.yaml')
> params.__dict__
{'project_path': 's3://ccd-itam/experiments/',
'model_group_id': [390, 433, 409, 386, 438],
'thresholds': {'rank_abs': [100]},
'baseline_query': "select g.model_group_id,\n m.model_id,\n extract('year' from m.evaluation_end_time) as as_of_date_year,\n m.metric,\n m.parameter,\n m.stochastic_value,\n m.num_labeled_examples,\n m.num_labeled_above_threshold,\n m.num_positive_labels\nfrom test_results.evaluations m\nleft join triage_metadata.models g\nusing(model_id)\nwhere g.model_group_id = 56\n and metric = 'recall@'\n and parameter = '100_abs'\n",
'max_depth_error_tree': 5,
'n_features_plots': 10,
'figsize': (12, 8),
'fontsize': 14}
```
- Credenciales de la base de datos
Igual que en ejemplos anteriores, dado que vamos a consutlar los resultados de los experimentos que hemos corrido y est́an en la base de datos, debemos crear la conexión en `Python`:
Podemos utilizar el archivo `database.yaml` que habíamos creado anteriormente para incorporar la infromación de las credenciales:
```yaml=
# database.yaml
db:
db_credentials:
host: database-amai.cfdemdotqpti.us-east-1.rds.amazonaws.com
database: itam
user: usuario
password: unusuario_pass
port: 5432
```
Y ahora creamos la conexión en `Python`:
```
engine = create_pgconn('db_credentials.yaml')
engine
```
#### 6.1.2 Evaluación individual de modelos
Podemos crear una lista de todos los modelos que queremos comparar para evaluar su desempeño en los diferentes puntos del tiempo utilizando la clase `ModelEvaluator`.
```python=
# List individual models
list_tuple_models = get_models_ids(params.model_group_id, engine)
l_t = [ModelEvaluator(i.model_group_id, i.model_id, engine) for i in list_tuple_models]
```
En el siguiente ejemplo evaluamos el desempeño de dos modelos, utilizando el ḿetodo:
```python=
lt[i].plot_precision_recall_n()
```
> Nota: realizamos algunas modificaciones al ḿetodo para graficar la linea vertical en el `k` de inteŕes.


Aunque en este ejercicio no deseamos ver el rendimiento totalizado de los modelos (pues solo podremos intervenir sobre un número reducido de personas), podemos también graficar las curva ROC, o la curva RecallvsFPR con los siguientes métodos, respectivamente:
```python=
lt[i].plot_ROC()
lt[i].plot_recall_fpr_n()
```
- Feature importance
En modelos como ́arboles de decisión o bosques aleatorios nos podría interesar qué variables son las ḿas relevantes para realizar clasificacíon.
Para este tipo de modelos existe el ḿetodo:
```python=
lt[i].plot_feature_importances()
```

> Adicionales: También se puede analizar esta característica con el método:
> `lt[i].plot_feature_importances_std_err()`
- Distribución de los _features_
Es posible analizar como es la distribución de los features utilizados por cada modelo por etiqueta o conjuntamente,por medio del método:
```python=
lt[i].plot_feature_distribution()
```
- Distribución de los _scores_ asignados por el modelo por etiqueta o conjuntamente:
```python=
>lt[i].plot_score_distribution()
>lt[i].plot_score_label_distributions()
```
#### 6.1.3 Evaluación conjunta de modelos
Como vimos en la sección anterior, resulta de mucha utilidad comparar conjuntamente los modelos que entrenamos. La clase `ModelGroupEvaluator` nos permite hacer este tipo de análisis.
```python=
# Model group object (useful to compare across model_groups and models in time)
audited_models_class = ModelGroupEvaluator(tuple(params.model_group_id), engine)
```
>Nota: Los métodos que se mencionan a continuación podemos utilizar el argumento `model_subset=[...]` si queremos restringir el análisis a un subconjunto de modelos específico.
- Analisis de estabilidad

- Similitud de predicciones
Una característica deseable es que exista alto _overlap_ en los individuos que identifican nuestros mejores modelos. Podemos analizar esto de distintas formas.
Jaccard
Consideremos la similitud de Jaccard:
$$
\mathcal{J}(A,B)=\frac{\mid A\cap B\mid}{\mid A\cup B\mid}
$$
Si lo consideramos para el grupo de modelos analizados, en agosto de 2017, tenemos:

En `triage` podemos generar esta matriz para todos los periodos donde se evaluan los modelos seleccionados por medio del ḿetodo:
```python=
audited_models_class.plot_jaccard(temporal_comparison=True)
```
Comparación avanzada
También podemos evaluar la similitud de las predicciones y su ordenamiento por percentiles utilizando el método `plot_preds_comparison`.
```python=
audited_models_class.plot_preds_comparison(temporal_comparison=True)
```
### 6.2 Crosstabs
El módulo de Postmodeling de Triage también nos permite comparar los promedios poblacionales de las entidades consideradas en riesgo con los de las consideradas como de bajo riesgo.
Estos datos se almacenarán en la tabla de _crosstabs_ una vez se ejecuten. Para ello, se requiere un archivo de configuración y `postmodeling_crosstabs.yaml` que contiene:
* `output`: Define el esquema y tabla donde se almacenarán los resultados de _crosstabs_
* `thresholds`: umbral que define las predicciones positivas
* `entity_id_list`: (opcional) listado de _entity_ids_ a considerar en el análisis de _crosstabs_
* `models_list_query`: query SQL para obtener los `model_ids`
* `as_of_dates_query`: query SQL para obtener `as_of_dates`
* `models_dates_join_query`: no cambiar el query a menos que sea estrictamente necesario. Valida los pares de (`model_id`, `as_of_date`) en la tabla de predicciones
* `features_query`: debe hacer join entre `models_dates_join_query` con una o más tablas de features, usando `as_of_date`
* `predictions_query`: debe devolver `model_id`, `as_of_date`, `entity_id`, `score` , `label_value`, `rank_abs` and `rank_pct`. Debe hacer join con `models_dates_join_query` usando `model_id` y `as_of_date`.
Un par de ejemplos de dicho archivo se puede encontrar [aquí](https://github.com/dssg/triage/blob/master/example/config/postmodeling_config.yaml) o en el apéndice de este documento.
Una vez listo el archivo de configuración, podremos ejecutarlo mediante la siguiente instrucción
```bash=
triage crosstabs postmodeling_crosstabs.yaml
```
Nótese que también se requiere el archivo `database.yaml` para tener acceso a la base de datos.
La tabla de crosstabs se verá así:
```bash=
itam=> select * from test_results.crosstabs_best_models limit 6;
model_id | as_of_date | metric | feature_column | value | threshold_unit | threshold_value
----------+---------------------+--------------------------+-----------------------------------------+------------------+----------------+-----------------
139 | 2005-08-15 00:00:00 | count_predicted_positive | examen_admision_entity_id_all_total_max | 100 | abs | 100
139 | 2005-08-15 00:00:00 | count_predicted_negative | examen_admision_entity_id_all_total_max | 4472 | abs | 100
139 | 2005-08-15 00:00:00 | mean_predicted_positive | examen_admision_entity_id_all_total_max | 1151.83 | abs | 100
139 | 2005-08-15 00:00:00 | mean_predicted_negative | examen_admision_entity_id_all_total_max | 1363.37947227191 | abs | 100
139 | 2005-08-15 00:00:00 | std_predicted_positive | examen_admision_entity_id_all_total_max | 58.0513626633407 | abs | 100
139 | 2005-08-15 00:00:00 | std_predicted_negative | examen_admision_entity_id_all_total_max | 68.3202541830632 | abs | 100
```
Y podemos ver las distintas métricas incluidas:
```bash=
itam=> select distinct metric from test_results.crosstabs_best_models;
metric
--------------------------------------------------
count_predicted_positive
ratio_predicted_positive_over_predicted_negative
mean_predicted_positive
std_predicted_negative
count_predicted_negative
ttest_p
std_predicted_positive
ttest_T
mean_predicted_negative
```
podemos centrarnos en el análisis de las variables significativas apoyándonos en queries como el siguiente:
```bash=
with significant_features as (
select
feature_column,
as_of_date,
threshold_unit
from
test_results.crosstabs_best_models
where
metric = 'ttest_p'
and
value < 0.05 and as_of_date = '2017-08-15 00:00:00.000'
)
select
distinct model_id,
as_of_date::date as as_of_date,
format('%s %s',
threshold_value,
t1.threshold_unit) as threshold,
feature_column,
value as "ratio PP / PN"
from
test_results.crosstabs_best_models as t1
inner join significant_features as t2
using(feature_column,
as_of_date)
where
metric = 'ratio_predicted_positive_over_predicted_negative'
and t1.threshold_unit = 'abs'
and model_id = '4506'
order by
value desc
```
El resultado se verá de la siguiente forma:
```bash=
model_id | as_of_date | threshold | feature_column | ratio PP / PN
----------+------------+-----------+--------------------------------------------+------------------
4506 | 2017-08-15 | 100 abs | total_reprobadas_entity_id_1y_total_count | 6.04233236151603
4506 | 2017-08-15 | 100 abs | total_reprobadas_entity_id_2y_total_count | 5.55221908022027
4506 | 2017-08-15 | 100 abs | total_reprobadas_entity_id_all_total_count | 4.51673005153723
```
Esto nos dice, por ejemplo, que para el modelo con _id_ 4506, la media de materias reprobadas en los predichos positivos es 6 veces mayor que en los predichos negativos.
En particular, queremos comparar de que forma est́an clasificando los distintos modelos que estamos evaluando. Por ejemplo, a continuación comparamos a cuatro modelos de grupos distintos para agosto de 2017.

### 6.3 Aequitas
Para terminar, queremos analizar los modelos seleccionados respecto a _bias_ y _fairness_. para ello, podemos utilizar la [herramienta de línea de comandos](https://dssg.github.io/aequitas/CLI.html) de [Aequitas](http://www.datasciencepublicpolicy.org/projects/aequitas/).
Una vez más, usaremos un archivo de configuración `config_aequitas.yaml` como el que se muestra [aquí](https://github.com/dssg/aequitas/blob/master/src/aequitas_cli/configs_database_example.yaml) o en el apéndice de este documento. En él se definen, entre otras cosas, el esquema y la tabla que almacenarán los resultados, los grupos de referencia y los atributos a evaluar. Cabe mencionar que estos últimos no necesitan ser alguna de las variables utilizadas en el modelo.
Posteriormente ejecutaremos:
```bash=
aequitas-report --config config_aequitas.yaml --create-tables
```
Esto creará una tabla con las siguientes columnas:
* model_id
* attribute_name
* score_threshold
* k
* attribute_value
* tpr
* tnr
* for
* fdr
* fpr
* fnr
* npv
* precision
* pp
* pn
* ppr
* pprev
* fp
* fn
* tn
* tp
* group_label_pos
* group_label_neg
* group_size
* total_entities
* prev
* ppr_disparity
* pprev_disparity
* precision_disparity
* fdr_disparity
* for_disparity
* fpr_disparity
* fnr_disparity
* tpr_disparity
* tnr_disparity
* npv_disparity
* ppr_ref_group_value
* pprev_ref_group_value
* precision_ref_group_value
* fdr_ref_group_value
* for_ref_group_value
* fpr_ref_group_value
* fnr_ref_group_value
* tpr_ref_group_value
* tnr_ref_group_value
* npv_ref_group_value
* FOR Parity
* FNR Parity
* TypeII Parity
* Supervised Fairness
* group_size_pct
Una vez definidas las medidas de _bias_ que nos interesan, observaremos las medidas de disparidad correspondientes, teniendo en cuenta que diremos que hay paridad dentro de un grupo si
$(1-\tau)\leq MedidaDisparidad_{grupo} \leq \frac{1}{(1-\tau)}$
En nuestro caso, nos interesaron _False Omission Rate (FOR)_ y _False Negative Rate (FNR)_, e hicimos el análisis respecto a género y ser foráneo:
```bash=
itam=> select '4506' as model_id,attribute_name ,attribute_value, for_disparity, "FOR Parity", fnr_disparity, "FNR Parity"
itam-> from test_results.aequitas_model_4506_predefined amp;
model_id | attribute_name | attribute_value | for_disparity | FOR Parity | fnr_disparity | FNR Parity
----------+----------------+-----------------+-------------------+------------+-------------------+------------
4506 | genero | f | 0.912681981004962 | t | 1.02721088435374 | t
4506 | genero | m | 1 | t | 1 | t
4506 | foraneo_string | n | 1 | t | 1 | t
4506 | foraneo_string | y | 1.28685839848994 | f | 0.988883358221337 | t
```
# Apéndice
## Experimento (Config file completo)
```yaml
## Información del experimento
config_version: 'v7'
random_seed: 6174
model_comment: 'primera_prueba'
user_metadata:
label_definition: 'perderan_materias'
experiment_type: 'baseline'
file_name: 'reprobados.yaml'
description: |
Primer experimento con triage - modelos básicos
purpose: 'baseline'
org: 'ITAM'
team: 'Centro de Ciencia de Datos'
author: 'Paola-Elizabeth-Sebastian'
etl_date: '27-10-2020'
## Timechop
temporal_config:
feature_start_time: '1990-08-01'
feature_end_time: '2020-08-01'
label_start_time: '2003-08-15'
label_end_time: '2020-08-15'
label_timespans: ['1y']
model_update_frequency: '1y'
training_as_of_date_frequencies: '1y'
max_training_histories: '10y'
test_durations: '1y'
test_as_of_date_frequencies: '1y'
## Cohorte
cohort_config:
query: |
with semestres as(
select
semestre_codigo
from semantic.semestres
where nivel = 'licenciatura'
and tipo in ('otoño', 'primavera')
and fecha_inicio >= (timestamp '{as_of_date}'- interval '3 weeks')::date
and fecha_fin <= (timestamp '{as_of_date}'+ interval '6 months')::date
),
reprobados as(
select
alumno
from semantic.eventos_alumnos as current_semester_students
where fecha < '{as_of_date}'
and tipo = 'reprobar curso'
)
-- alumnos al inicio del semestre
select distinct
alumno::int as entity_id
from semantic.eventos_alumnos as current_semester_students
inner join semestres
on current_semester_students.atributos->>'semestre_codigo' =
semestres.semestre_codigo
where current_semester_students.tipo = 'dar de alta curso'
and alumno not in (select alumno from reprobados)
name: 'alumnos_sin_reprobadas'
## Etiquetas
label_config:
query: |
select
alumno::int as entity_id,
'{as_of_date}'::date as as_of_date,
bool_or(tipo = 'reprobar curso')::int as outcome
from semantic.eventos_alumnos
where fecha >= ('{as_of_date}'::timestamp + interval '12 months')::date and
fecha < ('{as_of_date}'::timestamp + interval '{label_timespan}')::date
group by alumno
name: 'reprueban'
## Features
feature_aggregations:
### Proceso de admisión en el ITAM
-
prefix: 'examen_admision'
from_obj: |
(select alumno::int as entity_id, fecha, (atributos->>'calificacion')::int as calificacion from semantic.eventos_alumnos where tipo = 'presentar examen administrativo' and atributos->>'examen'= 'paat') as examen
knowledge_date_column: 'fecha'
aggregates_imputation:
count:
type: 'zero_noflag'
max:
type: 'constant'
value: 1375
aggregates:
-
quantity:
total: "calificacion"
metrics:
- 'max'
intervals: ['all']
groups:
- 'entity_id'
### sociodemográficas
# género
-
prefix: 'genero'
from_obj: |
(with primer_semestre as( select alumno, fecha from semantic.eventos_alumnos where tipo = 'terminar semestre' and atributos->>'semestre_nominal' = '1' ) select alumno::int as entity_id, genero, fecha from semantic.alumnos left join primer_semestre using(alumno) ) as generos
knowledge_date_column: 'fecha'
categoricals_imputation:
all:
type: 'mean'
categoricals:
- # gender
column: genero
choice_query: 'select distinct genero from semantic.alumnos'
metrics:
- 'max'
intervals: ['all']
groups:
- 'entity_id'
# Agrupación de features a probar
feature_group_definition:
prefix:
- 'examen_admision'
- 'genero'
feature_group_strategies: ['all']
## Métricas a optimizar
scoring:
testing_metric_groups:
-
metrics: [recall@, precision@]
thresholds:
percentiles: [50, 100]
## Grid de modelos
grid_config:
'sklearn.tree.DecisionTreeClassifier':
criterion: ['entropy']
max_depth: [1, null]
min_samples_split: [25]
min_samples_leaf: [0.025, 0.10]
'sklearn.ensemble.RandomForestClassifier':
n_estimators: [10, 50]
max_depth: [null, 1, 2, 10]
max_features: ['log2']
min_samples_split: [10]
criterion: ['gini']
random_state: [2193]
n_jobs: [-1]
```
## Crosstabs config file
```yaml=
output: # Define the schema and table for crosstabs
schema: 'test_results'
table: 'crosstabs_best_models'
thresholds: # Thresholds for defining positive predictions
rank_abs: [100]
rank_pct: []
#(optional): a list of entity_ids to subset on the crosstabs analysis
entity_id_list: []
#SQL query for getting model_ids
models_list_query: "select model_id :: int as model_id, model_group_id
from triage_metadata.models where
model_group_id in(390,433,409,386,438,56)"
as_of_dates_query: "select distinct as_of_date :: date as as_of_date from cohort_alumnos_inscritos_3c_b743bb3631d8df996676f945221497fc" #SQL query for getting as_of_dates
#don't change this query unless strictly necessary. It is just validating pairs of (model_id,as_of_date)
#it is just a join with distinct (model_id, as_of_date) in a predictions table
models_dates_join_query: "
select model_id,
as_of_date
from models_list_query m
cross join as_of_dates_query a join (select distinct model_id, as_of_date from test_results.predictions) p
using (model_id, as_of_date)"
#features_query must join models_dates_join_query with 1 or more features table using as_of_date
features_query: |
select m.model_id, f1.*, f2.prepa_entity_id_all_pase_8_1_max, f3.reprobar_examen_redaccion_entity_id_all_reprobar_1_max,
f4.reprobar_examen_intro_mate_entity_id_all_reprobar_1_max, f5.edad_primer_semestre_entity_id_all_total_max,
f5.edad_primer_semestre_entity_id_all_total_imp, f6.total_aprobadas_entity_id_all_total_count,
f7.total_reprobadas_entity_id_1y_total_count, f7.total_reprobadas_entity_id_2y_total_count,
f7.total_reprobadas_entity_id_all_total_count, f8.total_bajas_entity_id_all_total_count, f9.porcentaje_aprobadas_entity_id_all_total_avg,f9.porcentaje_aprobadas_entity_id_all_total_max,
f9.porcentaje_aprobadas_entity_id_all_total_min, f10.cambio_carrera_entity_id_all_total_count,
f11.prioridad_entity_id_1y_total_avg,f11.prioridad_entity_id_1y_total_imp,
f11.prioridad_entity_id_1y_total_max,f11.prioridad_entity_id_1y_total_min,
f11.prioridad_entity_id_2y_total_avg,f11.prioridad_entity_id_2y_total_imp,
f11.prioridad_entity_id_2y_total_max,f11.prioridad_entity_id_2y_total_min,
f11.prioridad_entity_id_all_total_avg,f11.prioridad_entity_id_all_total_imp,
f11.prioridad_entity_id_all_total_max,f11.prioridad_entity_id_all_total_min,
f12."genero_entity_id_all_genero__NULL_max" , f12.genero_entity_id_all_genero_f_max,
f12.genero_entity_id_all_genero_m_max
from features.examen_admision_aggregation_imputed f1
inner join features.prepa_aggregation_imputed f2 using (entity_id, as_of_date)
inner join features.reprobar_examen_redaccion_aggregation_imputed f3 using (entity_id, as_of_date)
inner join features.reprobar_examen_intro_mate_aggregation_imputed f4 using (entity_id, as_of_date)
inner join features.edad_primer_semestre_aggregation_imputed f5 using (entity_id, as_of_date)
inner join features.total_aprobadas_aggregation_imputed f6 using (entity_id, as_of_date)
inner join features.total_reprobadas_aggregation_imputed f7 using (entity_id, as_of_date)
inner join features.total_bajas_aggregation_imputed f8 using (entity_id, as_of_date)
inner join features.porcentaje_aprobadas_aggregation_imputed f9 using (entity_id, as_of_date)
inner join features.cambio_carrera_aggregation_imputed f10 using (entity_id, as_of_date)
inner join features.prioridad_aggregation_imputed f11 using (entity_id, as_of_date)
inner join features.genero_aggregation_imputed f12 using (entity_id, as_of_date)
inner join models_dates_join_query m using (as_of_date)
#the predictions query must return model_id, as_of_date, entity_id, score, label_value, rank_abs and rank_pct
#it must join models_dates_join_query using both model_id and as_of_date
predictions_query: "
select model_id,
as_of_date,
entity_id,
score,
label_value,
coalesce(rank_abs_no_ties, row_number() over (partition by (model_id, as_of_date) order by score desc)) as rank_abs,
coalesce(rank_pct_no_ties*100, ntile(100) over (partition by (model_id, as_of_date) order by score desc)) as rank_pct
from test_results.predictions
JOIN models_dates_join_query USING(model_id, as_of_date)
where model_id IN (select model_id from models_list_query)
AND as_of_date in (select as_of_date from as_of_dates_query)"
```
## Aequitas config file
```yaml=
# Thresholds for positive class (absolutes top k and top k %)
score_thresholds:
rank_abs: [100]
#rank_pct: [0.01, 0.02, 0.05, 0.10] #needs to be a float between 0 and 1
#available options: "predefined", "majority" and "min_metric"
ref_groups_method: "predefined"
# if "predefined" is selected, you need to provide the column:value pairs to be used as reference
ref_groups:
"genero": "m"
"foraneo_string": "n"
# fairness threshold to be used for assessing fairness
fairness_threshold: 0.8
# available fairness_measures "Impact Parity", "Statistical Parity", "FPR Parity", "FDR Parity", "FNR Parity", "FOR Parity"
fairness_measures: ["FOR Parity", "FNR Parity"]
# to connect to a database instead of using "--input <filename>", use the db key, credentials, and input_query
db:
db_credentials:
host: database-amai.cfdemdotqpti.us-east-1.rds.amazonaws.com
database: itam
user: user
password: your_pass
port: 5432
# the input query should return a table with score, label_value columns and then each attribute you want to use for bias analysis
input_query: |
select
entity_id as id,
score,
label_value,
genero,
case
when foraneo = 0 then 'n'
else 'y'
end foraneo_string
from
test_results.predictions
left join features.genero_from_obj
using (entity_id)
left join features.foraneo_from_obj
using (entity_id)
where
model_id in (4506)
-- 4511,4528,4552,4558
# the output schema is optional, default=public
output_schema: test_results
output_table: aequitas_model_4506_predefined
```