Busque incidentes y reclamos similares. Métricas y Optimización

En un artículo anterior, hablé sobre nuestro motor de búsqueda para aplicaciones similares . Después de su lanzamiento, comenzamos a recibir las primeras críticas. A los analistas les gustaron y recomendaron algunas recomendaciones, otras no.


Para seguir adelante y encontrar mejores modelos, fue necesario evaluar primero el rendimiento del modelo actual. También fue necesario seleccionar criterios por los cuales los dos modelos pudieran compararse entre sí.


Debajo del corte, hablaré sobre:


  • Recopilación de comentarios sobre las recomendaciones
  • Desarrollo de métricas para evaluar la calidad de las recomendaciones.
  • construyendo un ciclo de optimización del modelo
  • recibió ideas y un nuevo modelo

Recolección de comentarios


Sería ideal recopilar comentarios explícitos de los analistas: qué tan relevante es la recomendación de cada uno de los incidentes propuestos. Esto nos permitirá comprender la situación actual y continuar mejorando el sistema basado en indicadores cuantitativos.


Se decidió recopilar reseñas en un formato extremadamente simple:


  • número de incidentes que estamos analizando
  • número de incidente recomendado
  • revisión de recomendaciones: bueno / malo

El "voto" (un pequeño proyecto que aceptaba solicitudes GET con parámetros y colocaba la información en un archivo) se colocó directamente en el bloque de recomendaciones para que los analistas pudieran dejar sus comentarios de inmediato simplemente haciendo clic en uno de los enlaces: "bueno" o "malo".


Además, para una revisión retrospectiva de la recomendación, se hizo una solución muy simple:


  • para una gran porción de datos históricos, se lanzó un modelo;
  • Las recomendaciones recopiladas se presentaron en forma de varios archivos HTML independientes, en los que se utilizó la misma "votación";
  • Se entregaron archivos preparados a los analistas para ver los resultados de 50-100 incidentes.

Por lo tanto, fue posible recopilar datos sobre aproximadamente 4000+ pares de recomendación de incidentes.


Análisis de revisión inicial


Las métricas iniciales fueron "más o menos": la proporción de recomendaciones "buenas", según los colegas, fue de solo alrededor del 25%.


Los principales problemas del primer modelo:


  1. los incidentes sobre "nuevos" problemas recibieron recomendaciones irrelevantes del sistema; Resultó que en ausencia de coincidencias en el contenido de la apelación, el sistema seleccionó incidentes cerca del departamento del empleado en contacto.
  2. Las recomendaciones para un incidente en un sistema afectaron incidentes de otros sistemas. Las palabras utilizadas en la apelación fueron similares, pero describieron los problemas de otros sistemas y fueron diferentes.

Se seleccionaron posibles formas de mejorar la calidad de las recomendaciones:


  • ajuste de la composición y el peso de los atributos de tratamiento que se incluyen en el vector final
  • selección de configuraciones de vectorización TfidfVectorizer
  • selección de las recomendaciones de distancia de "corte"

Desarrollo de criterios de calidad y métodos de evaluación.


Para buscar una versión mejorada del modelo, es necesario determinar el principio de evaluar la calidad de los resultados del modelo. Esto le permitirá comparar cuantitativamente los dos modelos y elegir el mejor.


Lo que se puede obtener de las revisiones recopiladas


Tenemos muchas m tuplas de la forma: "Incidente", "Incidente recomendado", "Evaluación de recomendación".


  • "Clasificación de recomendación" ( v ) - se establece en binario: "Bueno" | Pobre (1 / -1);
  • "Incidente" e "Incidente recomendado" son simplemente números de incidentes. En ellos puedes encontrar el incidente en la base de datos.

Teniendo tales datos, puede calcular:


  • n_inc_total - El número total de incidentes para los cuales hay recomendaciones
  • n_inc_good - El número de incidentes para los cuales hay recomendaciones "buenas"
  • avg_inc_good - El número promedio de recomendaciones "buenas" para incidentes
  • n_rec_total - Número total de recomendaciones
  • n_rec_good - El número total de recomendaciones "buenas"
  • pct_inc_good : porcentaje de incidentes para los que hay recomendaciones "buenas"
    pct_inc_good = n_inc_good / n_inc_total
  • pct_rec_good : porcentaje total de recomendaciones "buenas"
    pct_rec_good = n_rec_good / n_rec_total

Estos indicadores, calculados sobre la base de estimaciones de los usuarios, pueden considerarse como "indicadores básicos" del modelo original. Con él compararemos indicadores similares de nuevas versiones del modelo.


Tome todos los "incidentes" únicos de m y guíelos a través del nuevo modelo.


Como resultado, obtenemos muchas m * tuplas: "Incidente", "Incidente recomendado", "Distancia".
Aquí, "distancia" es la métrica definida en NearestNeighbor. En nuestro modelo, esta es la distancia del coseno. El valor "0" corresponde a la coincidencia completa de los vectores.


Selección de "distancia de corte"


Complementando el conjunto de recomendaciones m * con información sobre la estimación verdadera de v del conjunto inicial de estimaciones de m , obtenemos la correspondencia entre la distancia d y la estimación verdadera de v para este modelo.


Con el conjunto ( d , v ), es posible elegir el nivel de corte óptimo t , que para d <= t la recomendación será "buena" y para d> t - "mala". La selección de t se puede lograr optimizando el clasificador binario más simple v = -1 if d>t else 1 respecto al hiperparámetro t, y utilizando, por ejemplo, AUC ROC como métrica.


 #     class BinarizerClassifier(Binarizer): def transform(self, x): return np.array([-1 if _x > self.threshold else 1 for _x in np.array(x, dtype=float)]).reshape(-1, 1) def predict_proba(self, x): z = self.transform(x) return np.array([[0 if _x > 0 else 1, 1 if _x > 0 else 0] for _x in z.ravel()]) def predict(self, x): return self.transform(x) # #   : # -  , # -    m* # -   (d,v)  z_data_for_t # #   t b = BinarizerClassifier() z_x = z_data_for_t[['distance']] z_y = z_data_for_t['TYPE'] cv = GridSearchCV(b, param_grid={'threshold': np.arange(0.1, 0.7, 0.01)}, scoring='roc_auc', cv=5, iid=False, n_jobs=-1) cv.fit(z_x, z_y) score = cv.best_score_ t = cv.best_params_['threshold'] best_b = cv.best_estimator_ 

El valor t obtenido se puede usar para filtrar recomendaciones.


Por supuesto, este enfoque puede omitir las recomendaciones "malas" y cortar las "buenas". Por lo tanto, en esta etapa siempre mostramos las recomendaciones "Top 5", pero marcamos especialmente aquellas que se consideran "buenas", teniendo en cuenta la t encontrada.
Alternativa: si se encuentra al menos una recomendación "buena", muestre solo "buena". De lo contrario, muestre todos los disponibles (también - "Top N").


Suposición para comparar modelos


Para los modelos de entrenamiento, se usa el mismo caso de incidente.
Suponga que si se encontró previamente una "buena" recomendación, entonces el nuevo modelo también debería encontrar una "buena" recomendación para el mismo incidente. En particular, el nuevo modelo puede encontrar las mismas recomendaciones "buenas" que el anterior. Sin embargo, con el nuevo modelo, esperamos que el número de recomendaciones "malas" sea menor.


Luego, considerando los mismos indicadores para las recomendaciones m * del nuevo modelo, se pueden comparar con los indicadores correspondientes para m . Según la comparación, puede elegir el mejor modelo.


Hay dos formas de tener en cuenta las recomendaciones "buenas" para el conjunto m * :


  1. basado en el t encontrado: considere que todas las recomendaciones de m * con d < t son "buenas" y tómelas en cuenta para calcular las métricas
  2. sobre la base de las estimaciones verdaderas correspondientes del conjunto m : de las recomendaciones m *, seleccione solo aquellas para las que haya una estimación verdadera en m , y descarte el resto.

En el primer caso, los indicadores "absolutos" ( n_inc_good , n_rec_good ) del nuevo modelo deberían ser mayores que los del modelo base. En el segundo caso, los indicadores deben acercarse a los indicadores del modelo base.
El problema del segundo método: si el nuevo modelo es mejor que el original y encuentra algo previamente desconocido, dicha recomendación no se tendrá en cuenta en el cálculo.


Seleccionar opciones de comparación de modelos


Al elegir un nuevo modelo, quiero que los indicadores mejoren en comparación con el modelo existente:


  • número promedio de recomendaciones "buenas" por incidente ( avg_inc_good )
  • cantidad de incidentes para los que hay recomendaciones "buenas" ( n_inc_good ).

Para comparar con el modelo original, utilizaremos las relaciones de estos parámetros del nuevo modelo y el original. Por lo tanto, si la relación del parámetro del nuevo modelo y el antiguo es superior a 1, el nuevo modelo es mejor.


 benchmark_agv_inc_good = avg_inc_good* / avg_inc_good benchmark_n_inc_good = n_inc_good* / n_inc_good 

Para simplificar la selección, es mejor usar un solo parámetro. Tomamos la media armónica de los indicadores relativos individuales y la usamos como el único criterio de calidad compuesto para el nuevo modelo.


 composite = 2 / ( 1/benchmark_agv_inc_good + 1/benchmark_n_inc_good) 

Nuevo modelo y su optimización.


Para el nuevo modelo, en el vector final que representa el incidente, agregue los componentes responsables del "área del incidente" (uno de varios sistemas atendidos por nuestro equipo).
La información sobre la unidad y la ubicación del empleado que creó el incidente también se coloca en un componente vectorial separado. Todos los componentes tienen su peso en el vector final.


 p = Pipeline( steps=[ ('grp', ColumnTransformer( transformers=[ ('text', Pipeline(steps=[ ('pp', CommentsTextTransformer(n_jobs=-1)), ("tfidf", TfidfVectorizer(stop_words=get_stop_words(), ngram_range=(1, 3), max_features=10000, min_df=0)) ]), ['short_description', 'comments'] ), ('area', OneHotEncoder(handle_unknown='ignore'), ['area'] ), ('dept', OneHotEncoder(handle_unknown='ignore'), ['u_impacted_department'] ), ('loc', OneHotEncoder(handle_unknown='ignore'), ['u_impacted_location'] ) ], transformer_weights={'text': 1, 'area': 0.5, 'dept': 0.1, 'loc': 0.1}, n_jobs=-1 )), ('norm', Normalizer()), ("nn", NearestNeighborsTransformer(n_neighbors=10, metric='cosine')) ], memory=None) 

Se espera que los hiperparámetros del modelo afecten a los objetivos del modelo. En la arquitectura del modelo seleccionado, consideraremos como hiperparámetros:


  • Parámetros de vectorización TF-IDF: n-gramas usados ​​(ngram_range), tamaño del diccionario (max_features), mínimo plazo de conducción (min_df)
  • contribución de componentes al vector final - transformer_weights.

Los valores iniciales de los hiperparámetros de vectorización de texto se toman del modelo anterior. Los pesos de los componentes iniciales se seleccionan en base al juicio de expertos.


Ciclo de selección de parámetros


Cómo comparar, seleccionar el nivel de falla de encendido y comparar modelos entre ellos ya han sido determinados. Ahora podemos proceder a la optimización mediante la selección de hiperparámetros.


Ciclo de optimización


 param_grid = { 'grp__text__tfidf__ngram_range': [(1, 1), (1, 2), (1, 3), (2, 2)], 'grp__text__tfidf__max_features': [5000, 10000, 20000], 'grp__text__tfidf__min_df': [0, 0.0001, 0.0005, 0.001], 'grp__transformer_weights': [{'text': 1, 'area': 0.5, 'dept': 0.1, 'loc': 0.1}, {'text': 1, 'area': 0.75, 'dept': 0.1, 'loc': 0.1}, {'text': 1, 'area': 0.5, 'dept': 0.3, 'loc': 0.3}, {'text': 1, 'area': 0.75, 'dept': 0.3, 'loc': 0.3}, {'text': 1, 'area': 1, 'dept': 0.1, 'loc': 0.1}, {'text': 1, 'area': 1, 'dept': 0.3, 'loc': 0.3}, {'text': 1, 'area': 1, 'dept': 0.5, 'loc': 0.5}], } for param in ParameterGrid(param_grid=param_grid): p.set_params(**param) p.fit(x) ... 

Resultados de optimización


La tabla muestra los resultados de experimentos en los que se lograron resultados interesantes: los 5 mejores y peores valores para los indicadores controlados.



Las celdas con indicadores en la tabla están marcadas como:


  • el verde oscuro es el mejor indicador entre todos los experimentos
  • verde pálido: el valor del indicador está entre los 5 primeros
  • rojo oscuro: el peor indicador entre todos los experimentos
  • rojo pálido: el indicador está en el peor 5

El mejor indicador compuesto se obtuvo para un modelo con parámetros:


 ngram_range = (1,2) min_df = 0.0001 max_features = 20000 transformer_weights = {'text': 1, 'area': 1, 'dept': 0.1, 'loc': 0.1} 

Un modelo con estos parámetros mostró una mejora en el indicador compuesto en comparación con el modelo original 24%


Algunas observaciones y conclusiones.


Según los resultados de optimización:


  1. El uso de trigramas ( ngram_range = (1,3) ) no parece estar justificado. Inflan el diccionario y aumentan ligeramente la precisión en comparación con bigrams.


  2. Un comportamiento interesante cuando se construye un diccionario usando solo bigrams ( ngram_range = (2,2) ): la "precisión" de las recomendaciones aumenta, y el número de recomendaciones encontradas disminuye. Al igual que un equilibrio de precisión / recuperación en clasificadores. Se observa un comportamiento similar en la selección del nivel de corte t : el "cono" de corte más estrecho y la mejor separación de las recomendaciones "buenas" y "malas" son características de las bigrams.


  3. El parámetro no cero min_df, junto con bigrams, aumenta la precisión de las recomendaciones. Comienzan a basarse en términos que ocurren al menos varias veces. A medida que aumenta el parámetro, el diccionario comienza a reducirse rápidamente. Para muestras pequeñas, como en nuestro caso, probablemente será más comprensible operar con el número de documentos (valor entero min_df) que la fracción de documentos (valor fraccional min_df) que contiene el término.


  4. Se obtienen buenos resultados cuando el atributo incidente responsable de la "región" se incluye en el vector final con un peso igual o cercano al componente de texto. Los valores bajos conducen a un aumento en la proporción de recomendaciones "malas" debido a la búsqueda de palabras similares en documentos de otras áreas. Pero los signos de la ubicación del cliente no afectan tan bien los resultados de las recomendaciones en nuestro caso.



Han surgido algunas ideas nuevas:


  • agregue un componente de "tiempo" para que los incidentes recientes tengan prioridad sobre incidentes similares.
  • vea cómo influirá la introducción del parámetro max_df, aunque con tf-idf las palabras demasiado generales para el corpus no deberían tener un peso significativo, por definición.
  • finalmente, intente otras formas de vectorizar contenido, por ejemplo, basado en palabra a vector, o en convolución de vistas tf-idf usando redes.

Source: https://habr.com/ru/post/472772/


All Articles