Em um artigo anterior, falei sobre nosso mecanismo de pesquisa para aplicativos semelhantes . Após o seu lançamento, começamos a receber as primeiras críticas. Os analistas gostaram e recomendaram algumas recomendações, outras não.
Para seguir em frente e encontrar melhores modelos, foi necessário avaliar primeiro o desempenho do modelo atual. Também foi necessário selecionar critérios pelos quais os dois modelos pudessem ser comparados entre si.
Abaixo do corte, falarei sobre:
- coletando feedback sobre recomendações
- desenvolvimento de métricas para avaliar a qualidade das recomendações
- construindo um ciclo de otimização de modelo
- idéias recebidas e um novo modelo
Coleta de Feedback
Seria ideal coletar feedback explícito dos analistas: quão relevante é a recomendação de cada um dos incidentes propostos. Isso nos permitirá entender a situação atual e continuar a melhorar o sistema com base em indicadores quantitativos.
Decidiu-se coletar resenhas em um formato extremamente simples:
- número de incidentes que estamos analisando
- número de incidente recomendado
- revisão de recomendação: bom / ruim
O "voto" (um pequeno projeto que aceitou solicitações GET com parâmetros e colocou as informações em um arquivo) foi colocado diretamente no bloco de recomendações, para que os analistas pudessem deixar seus comentários imediatamente, basta clicar em um dos links: "bom" ou "ruim".
Além disso, para uma revisão retrospectiva da recomendação, uma solução muito simples foi feita:
- para uma grande quantidade de dados históricos, um modelo foi lançado;
- As recomendações coletadas foram apresentadas na forma de vários arquivos HTML independentes, nos quais a mesma "votação" foi usada;
- arquivos preparados foram entregues aos analistas para visualizar os resultados de 50 a 100 incidentes.
Portanto, foi possível coletar dados sobre aproximadamente 4000 + pares de recomendação de incidentes.
Análise de revisão inicial
As métricas iniciais eram "mais ou menos" - a parcela de "boas" recomendações, segundo os colegas, era de apenas 25%.
Os principais problemas do primeiro modelo:
- incidentes sobre problemas “novos” receberam recomendações irrelevantes do sistema; Verificou-se que, na ausência de coincidências no conteúdo da apelação, o sistema selecionava incidentes próximos ao departamento do funcionário em contato.
- as recomendações para um incidente em um sistema atingem incidentes de outros sistemas. As palavras usadas no apelo eram semelhantes, mas descreviam os problemas de outros sistemas e eram diferentes.
As possíveis formas de melhorar a qualidade das recomendações foram selecionadas:
- ajuste da composição e peso dos atributos de tratamento incluídos no vetor final
- seleção de configurações de vetorização
TfidfVectorizer
- seleção da distância de "corte" das recomendações
Desenvolvimento de critérios de qualidade e métodos de avaliação
Para procurar uma versão aprimorada do modelo, é necessário determinar o princípio de avaliar a qualidade dos resultados do modelo. Isso permitirá comparar quantitativamente os dois modelos e escolher o melhor.
O que pode ser obtido com as análises coletadas
Temos muitas tuplas do formulário: "Incidente", "Incidente recomendado", "Avaliação de recomendação".
- "Classificação da recomendação" ( v ) - é definido como binário: "Bom" | Fraco (1 / -1);
- "Incidente" e "Incidente recomendado" são simplesmente números de incidentes. Neles você pode encontrar o incidente no banco de dados.
Tendo esses dados, você pode calcular:
n_inc_total
- o número total de incidentes para os quais existem recomendaçõesn_inc_good
- O número de incidentes para os quais existem recomendações "boas"avg_inc_good
- O número médio de recomendações “boas” para incidentesn_rec_total
- Número total de recomendaçõesn_rec_good
- O número total de recomendações "boas"pct_inc_good
- parcela de incidentes para os quais existem recomendações "boas"
pct_inc_good = n_inc_good / n_inc_total
pct_rec_good
- compartilhamento total de recomendações "boas"
pct_rec_good = n_rec_good / n_rec_total
Esses indicadores, calculados com base em estimativas dos usuários, podem ser considerados "indicadores básicos" do modelo original. Com ele, compararemos indicadores semelhantes de novas versões do modelo.
Pegue todos os "incidentes" exclusivos de me conduza-os pelo novo modelo.
Como resultado, obtemos muitas tuplas m * : "Incidente", "Incidente recomendado", "Distância".
Aqui, "distância" é a métrica definida em NearestNeighbor. Em nosso modelo, essa é a distância do cosseno. O valor "0" corresponde à completa coincidência de vetores.
Seleção de "distância de corte"
Complementando o conjunto de recomendações m * com informações sobre a estimativa verdadeira de v do conjunto inicial de estimativas de m , obtemos a correspondência entre a distância d e a estimativa verdadeira de v para este modelo.
Tendo o conjunto ( d , v ), é possível escolher o nível de corte ideal t , que para d <= t a recomendação será "boa" e para d> t - "ruim". A seleção de t pode ser realizada otimizando o classificador binário mais simples v = -1 if d>t else 1
relação ao hiperparâmetro t e usando, por exemplo, AUC ROC como métrica.
O valor t obtido pode ser usado para filtrar recomendações.
Obviamente, essa abordagem ainda pode ignorar as recomendações "ruins" e cortar as recomendações "boas". Portanto, nesta fase, sempre mostramos as recomendações "Top 5", mas marcamos especialmente as consideradas "boas", levando em consideração o valor encontrado.
Alternativa: se pelo menos uma recomendação “boa” for encontrada, mostre apenas “boa”. Caso contrário, mostre todos os disponíveis (também - "Top N").
Suposição para comparar modelos
Para modelos de treinamento, o mesmo caso de incidente é usado.
Suponha que, se uma recomendação “boa” foi encontrada anteriormente, o novo modelo também deve encontrar uma recomendação “boa” para o mesmo incidente. Em particular, o novo modelo pode encontrar as mesmas recomendações “boas” que as antigas. No entanto, com o novo modelo, esperamos que o número de recomendações "ruins" se torne menor.
Então, considerando os mesmos indicadores para as recomendações m * do novo modelo, eles podem ser comparados com os indicadores correspondentes para m . Com base na comparação, você pode escolher o melhor modelo.
Há duas maneiras de levar em consideração as recomendações “boas” para o conjunto m * :
- com base no t encontrado: considere que todas as recomendações de m * com d < t são "boas" e leve-as em consideração para calcular métricas
- com base nas estimativas verdadeiras correspondentes do conjunto m : nas recomendações m *, selecione apenas aquelas para as quais existe uma estimativa verdadeira em me descarte o restante.
No primeiro caso, os indicadores "absolutos" ( n_inc_good
, n_rec_good
) do novo modelo devem ser maiores que no modelo base. No segundo caso, os indicadores devem se aproximar dos indicadores do modelo base.
O problema do segundo método: se o novo modelo for melhor que o original e encontrar algo anteriormente desconhecido, essa recomendação não será levada em consideração no cálculo.
Opções de comparação de modelos
Ao escolher um novo modelo, quero que os indicadores melhorem em comparação com o modelo existente:
- número médio de recomendações "boas" por incidente (
avg_inc_good
) - número de incidentes para os quais existem recomendações "boas" (
n_inc_good
).
Para comparação com o modelo original, usaremos as relações desses parâmetros do novo modelo e do original. Assim, se a razão entre o parâmetro do novo modelo e o antigo for maior que 1, o novo modelo será melhor.
benchmark_agv_inc_good = avg_inc_good* / avg_inc_good benchmark_n_inc_good = n_inc_good* / n_inc_good
Para simplificar a seleção, é melhor usar um único parâmetro. Tomamos a média harmônica de indicadores relativos individuais e a usamos como o único critério de qualidade composto para o novo modelo.
composite = 2 / ( 1/benchmark_agv_inc_good + 1/benchmark_n_inc_good)
Novo modelo e sua otimização
Para o novo modelo, no vetor final que representa o incidente, adicione os componentes responsáveis pela "área do incidente" (um dos vários sistemas atendidos por nossa equipe).
As informações sobre a unidade e a localização do funcionário que criou o incidente também são colocadas em um componente vetorial separado. Todos os componentes têm seu peso no vetor 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)
Espera-se que os hiperparâmetros do modelo afetem os destinos do modelo. Na arquitetura do modelo selecionado, consideraremos como hiperparâmetros:
- Parâmetros de vetorização TF-IDF - n-gramas usados (intervalo de ngram), tamanho do dicionário (max_features), duração mínima de condução (min_df)
- contribuição do componente para o vetor final - transformer_weights.
Os valores iniciais dos hiperparâmetros de vetorização de texto são retirados do modelo anterior. Os pesos iniciais dos componentes são selecionados com base no julgamento de especialistas.
Ciclo de seleção de parâmetros
Como comparar, selecionar o nível de falha de ignição e comparar modelos entre si já foram determinados. Agora podemos avançar para a otimização através da seleção de hiperparâmetros.

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 otimização
A tabela mostra os resultados de experimentos em que foram alcançados resultados interessantes - os 5 melhores e os piores valores para indicadores controlados.

As células com indicadores na tabela estão marcadas como:
- verde escuro é o melhor indicador entre todas as experiências
- verde pálido - o valor do indicador está entre os top 5
- vermelho escuro - o pior indicador entre todas as experiências
- vermelho pálido - o indicador está no pior dos 5
O melhor indicador composto foi obtido para um modelo com 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}
Um modelo com esses parâmetros mostrou uma melhoria no indicador composto em comparação com o modelo original 24%
Algumas observações e conclusões
De acordo com os resultados da otimização:
O uso de trigramas ( ngram_range = (1,3)
) não parece ser justificado. Eles inflam o dicionário e aumentam ligeiramente a precisão em comparação com os bigrams.
Um comportamento interessante ao criar um dicionário usando apenas ngram_range = (2,2)
( ngram_range = (2,2)
): a "precisão" das recomendações aumenta e o número de recomendações encontradas diminui. Assim como um equilíbrio de precisão / recall nos classificadores. Um comportamento semelhante é observado na seleção do nível de corte t - para os bigrams um “cone” de corte mais estreito e uma melhor separação das recomendações “boas” e “ruins” são característicos.
O parâmetro diferente de zero min_df, juntamente com os bigrams, aumenta a precisão das recomendações. Eles começam a se basear em termos que ocorrem pelo menos várias vezes. À medida que o parâmetro aumenta, o dicionário começa a encolher rapidamente. Para amostras pequenas, como no nosso caso, provavelmente será mais compreensível operar com o número de documentos (valor inteiro min_df) do que a fração de documentos (valor fracionário min_df) que contém o termo.
Bons resultados são obtidos quando o atributo incidente responsável pela "região" é incluído no vetor final com um peso igual ou próximo ao componente de texto. Valores baixos levam a um aumento na proporção de recomendações "ruins" devido à localização de palavras semelhantes em documentos de outras áreas. Mas os sinais da localização do cliente não afetam tão bem os resultados das recomendações no nosso caso.
Algumas novas idéias surgiram:
- adicione um componente de "horário" para que incidentes recentes tenham precedência sobre incidentes semelhantes.
- veja como a introdução do parâmetro max_df influenciará - embora com tf-idf palavras gerais demais para o corpus não devam ter peso significativo, por definição.
- finalmente tente outras maneiras de vetorizar o conteúdo, por exemplo, com base em palavra para vetor ou com base na convolução de visualizações tf-idf usando redes.