Projeto Data Science, da pesquisa à implementação, usando o Talking Hat como exemplo


Há um mês, a Lenta lançou um concurso no qual o mesmo chapéu falante de Harry Potter identifica os participantes que têm acesso à rede social a uma das quatro faculdades. A competição não é ruim, os nomes que soam diferentemente são determinados por diferentes faculdades, e nomes e sobrenomes semelhantes em inglês e russo são distribuídos de maneira semelhante. Não sei se a distribuição depende apenas dos nomes e sobrenomes, e se o número de amigos ou outros fatores são levados em consideração, mas essa competição sugeriu a ideia deste artigo: tente treinar um classificador do zero, o que permitirá que os usuários sejam distribuídos para diferentes faculdades.


Neste artigo, criaremos um modelo ML simples que distribui as pessoas para os departamentos de Harry Potter, dependendo do nome e sobrenome, depois de passar por um pequeno processo de pesquisa seguindo a metodologia CRISP . Ou seja, nós:


  • Nós formulamos o problema;
  • Investigamos possíveis abordagens para sua solução e formulamos requisitos de dados (métodos e dados da solução);
  • Coletaremos os dados necessários (métodos e dados da solução);
  • Estudaremos o conjunto de dados coletados (Pesquisa Exploratória);
  • Extrair recursos de dados brutos (Feature Engineering);
  • Vamos ensinar um modelo de aprendizado de máquina (avaliação de modelo);
  • Compare os resultados obtidos, avalie a qualidade das soluções e, se necessário, repita os parágrafos 2-6;
  • Empacotamos a solução em um serviço que pode ser usado (Produção).


Essa tarefa pode parecer trivial; portanto, imporemos uma restrição adicional a todo o processo (para que leve menos de 2 horas) e a este artigo (para que o tempo de leitura seja menor que 15 minutos).


Se você já está imerso no mundo maravilhoso e maravilhoso da Data Science e não vê Kagglite constantemente ou (se Deus não permitir) gosta de medir a duração do seu Hadup durante reuniões com colegas, provavelmente o artigo parecerá simples e desinteressante para você. Além disso: a qualidade dos modelos finais não é o principal valor deste artigo. Nós avisamos você. Vamos lá


Um repositório do github com o código usado no artigo também está disponível para leitores curiosos. Em caso de erros, abra o PR.


Nós formulamos o problema


É possível resolver um problema que não possui critérios de decisão claros por um tempo infinitamente longo, por isso decidimos imediatamente que queremos obter uma solução que nos permita obter a resposta "Grifinória", "Corvinal", "Lufa-Lufa" ou "Sonserina" em resposta à linha inserida.


De fato, queremos obter uma caixa preta:


" " => [?] => Griffindor 

O chapéu preto original distribuía jovens magos aos departamentos, dependendo de sua natureza e qualidades pessoais. Como os dados sobre o caráter e a personalidade de acordo com as condições do problema não estão disponíveis, usaremos o nome e o sobrenome do participante, lembrando que, neste caso, devemos distribuir os caracteres do livro para os departamentos que correspondem aos departamentos nativos do livro. E os potteromanos definitivamente ficariam chateados se nossa decisão distribuísse Harry para Lufa-Lufa ou Corvinal (mas deveria enviá-lo à Grifinória e à Sonserina com a mesma probabilidade de transmitir o espírito do livro).


Como estamos falando de probabilidades, formalizamos o problema em termos matemáticos mais rigorosos. Do ponto de vista da ciência de dados, resolvemos o problema de classificação, designando a um objeto (linha, na forma de nome e sobrenome) uma determinada classe (na verdade, é apenas um rótulo ou rótulo, que pode ser um número ou 4 variáveis ​​com valor sim / não ) Entendemos que, pelo menos no caso de Harry, será correto dar duas respostas: Grifinória e Sonserina, portanto, seria melhor não prever a faculdade específica que o chapéu define, mas a probabilidade de que uma pessoa seja alocada para essa faculdade, para que nossa decisão seja tomada. tipo de alguma função


f(<Nome><Sobrenome>)=(Pgriffindor;Pravenclaw;Phufflpuff;Pslitherin)


Métricas e avaliação da qualidade


A tarefa e o objetivo são formulados, Agora vamos pensar em como resolvê-lo mas isso não é tudo. Para iniciar o estudo, você precisa inserir métricas de qualidade. Em outras palavras, para determinar como vamos comparar duas soluções diferentes entre si.


Tudo na vida é bom e simples - entendemos intuitivamente que um detector de spam deve passar o mínimo de spam para as mensagens recebidas, além de passar o máximo de letras necessárias e certamente não deve enviar as letras necessárias para o spam.


Na realidade, tudo é mais complicado e a confirmação disso é um grande número de artigos que explicam como e quais métricas são usadas. A prática ajuda a entender melhor isso, mas esse é um tópico tão volumoso que prometemos escrever um post separado sobre ele e criar uma mesa aberta para que todos possam brincar e entender na prática como isso difere.


O agregado familiar “mas vamos escolher o melhor” para nós será o ROC AUC . É exatamente isso que queremos da métrica nesse caso: quanto menos falsos positivos e mais precisa a previsão real, maior a AUC do ROC.


Para um modelo ROC ideal, AUC é 1, para um modelo aleatório ideal que define classes absolutamente aleatoriamente - 0,5.


ROC AUC em[0,5;1]


Algoritmos


Nossa caixa preta deve levar em conta a distribuição dos heróis dos livros, usar um nome e sobrenome diferentes como entrada e fornecer o resultado. Para resolver o problema de classificação, você pode usar diferentes algoritmos de aprendizado de máquina:


redes neurais, máquinas de fatoração, regressão linear ou, por exemplo, SVM.


Ao contrário da crença popular, a Ciência de Dados não se limita apenas às redes neurais e, para popularizar essa idéia, neste artigo as redes neurais são deixadas como um exercício para um leitor curioso . Aqueles que não fizeram um único curso de análise de dados (especialmente o subjetivamente melhor do ODS), ou simplesmente leram n notícias sobre aprendizado de máquina ou IA, que agora são publicadas mesmo nas revistas de pescadores amadores, devem ter encontrado os nomes de grupos gerais de algoritmos : ensacamento, reforço, método de vetor de suporte (SVM), regressão linear. São eles que usaremos para resolver nosso problema.


E para ser mais preciso, comparamos:


  • Regressão linear
  • Reforço (XGboost, LightGBM)
  • Decidir árvores (estritamente falando, esse é o mesmo impulso, mas o removeremos separadamente: Árvores Extra)
  • Ensacamento (floresta aleatória)
  • SVM

Podemos resolver o problema de distribuir cada aluno de Hogwarts a uma das faculdades definindo a faculdade correspondente a ele, mas, estritamente falando, essa tarefa se resume a resolver o problema de determinar se cada classe pertence individualmente. Portanto, no quadro deste artigo, estabelecemos o objetivo de obter 4 modelos, um para cada faculdade.


Dados


Encontrar o conjunto de dados correto para treinamento e, mais importante, legal para usá-lo para a finalidade correta, é uma das tarefas mais complexas e demoradas da Data Science. Para nossa tarefa, levaremos os dados da wikia ao redor do mundo de Harry Potter. Por exemplo, neste link você encontra todos os personagens que estudaram na faculdade da Grifinória. É importante que neste caso usemos os dados para fins não comerciais, portanto, não violamos a licença deste site.



Para aqueles que pensam que os cientistas de dados são pessoas tão legais, eu vou até os cientistas de dados e deixo-me ensinar, lembramos que existe uma etapa como limpar e preparar dados. Os dados baixados devem ser moderados manualmente para excluir, por exemplo, “O Sétimo Prefeito da Grifinória” e excluir a “Garota Desconhecida da Grifinória” semi-automaticamente. No trabalho real, uma parte proporcionalmente grande da tarefa está sempre associada à preparação, limpeza e restauração de valores ausentes no conjunto de dados.


Um pouco de ctrl + c & ctrl + ve na saída temos 4 arquivos de texto, que contêm os nomes dos caracteres em 2 idiomas: inglês e russo.


Estudamos os dados coletados (EDA, Análise Exploratória de Dados)


Para esta etapa, temos 4 arquivos contendo os nomes dos alunos das faculdades, veremos com mais detalhes:


 $ ls ../input griffindor.txt hufflpuff.txt ravenclaw.txt slitherin.txt 

Cada arquivo contém 1 nome e sobrenome (se houver) do aluno por linha:


 $ wc -l ../input/*.txt 250 ../input/griffindor.txt 167 ../input/hufflpuff.txt 180 ../input/ravenclaw.txt 254 ../input/slitherin.txt 851 total 

Os dados coletados têm o formato:


 $ cat ../input/griffindor.txt | head -3 && cat ../input/griffindor.txt | tail -3      Charlie Stainforth Melanie Stanmore Stewart 

Toda a nossa idéia baseia-se no pressuposto de que há algo semelhante nos nomes e sobrenomes que nossa caixa preta (ou chapéu preto) pode aprender a distinguir.


O algoritmo pode alimentar as linhas como estão, mas o resultado não será bom, porque os modelos básicos não serão capazes de entender independentemente como "Draco" difere de "Harry"; portanto, precisaremos extrair sinais de nossos nomes e sobrenomes.


Preparação de Dados (Engenharia de Recursos)


Sinais (ou recursos, do recurso inglês - propriedade) são as propriedades distintivas de um objeto. O número de vezes que uma pessoa mudou de emprego no ano passado, o número de dedos na mão esquerda, a capacidade do motor, se a quilometragem do carro excede 100.000 km ou não. Todos os tipos de classificações de sinais foram inventados por um número muito grande; não existe e não pode haver um sistema único nesse sentido; portanto, daremos exemplos de quais sinais podem ser:


  1. Número racional
  2. Categoria (até 12, 12-18 ou 18+)
  3. Valor binário (retornou o primeiro empréstimo ou não)
  4. Data, cor, compartilhamentos etc.

A pesquisa (ou formação) de recursos (em inglês, Feature Engineering ) muitas vezes se destaca como um estágio separado da pesquisa ou o trabalho de um especialista em análise de dados. De fato, o senso comum, a experiência e o teste de hipóteses ajudam no próprio processo. Adivinhar os sinais certos imediatamente é uma questão de uma combinação de mão cheia, conhecimento fundamental e sorte. Às vezes, existe xamanismo, mas a abordagem geral é muito simples: você precisa fazer o que vem à mente e depois verificar se era possível melhorar a solução adicionando um novo atributo. Por exemplo, como um sinal para nossa tarefa, podemos pegar o número de chiados no nome.


Na primeira versão (porque o verdadeiro estudo de Ciência de Dados - como uma obra-prima, nunca pode ser concluído) do nosso modelo, usaremos os seguintes recursos para o nome e o sobrenome:


  1. 1 e a última letra da palavra - vogal ou consoante
  2. Vogais e consoantes duplas
  3. Número de vogais, consoantes, surdas, sonoras
  4. Comprimento do nome, comprimento do sobrenome
  5. ...

Para fazer isso, tomaremos esse repositório como base e adicionaremos uma classe para que possa ser usado para letras latinas. Isso nos dará a oportunidade de determinar como cada letra soa.


 >> from Phonetic import RussianLetter, EnglishLetter >> RussianLetter('').classify() {'consonant': True, 'deaf': False, 'hard': False, 'mark': False, 'paired': False, 'shock': False, 'soft': False, 'sonorus': True, 'vowel': False} >> EnglishLetter('d').classify() {'consonant': True, 'deaf': False, 'hard': True, 'mark': False, 'paired': False, 'shock': False, 'soft': False, 'sonorus': True, 'vowel': False} 

Agora podemos definir funções simples para calcular estatísticas, por exemplo:


 def starts_with_letter(word, letter_type='vowel'): """   ,    . :param word:  :param letter_type: 'vowel'  'consonant'.   . :return: Boolean """ if len(word) == 0: return False return Letter(word[0]).classify()[letter_type] def count_letter_type(word): """       . :param word:  :param debug:    :return: :obj:`dict` of :obj:`str` => :int:count """ count = { 'consonant': 0, 'deaf': 0, 'hard': 0, 'mark': 0, 'paired': 0, 'shock': 0, 'soft': 0, 'sonorus': 0, 'vowel': 0 } for letter in word: classes = Letter(letter).classify() for key in count.keys(): if classes[key]: count[key] += 1 return count 

Usando essas funções, já podemos obter os primeiros sinais:


 from feature_engineering import * >> print("  («»): ", len(""))   («»): 5 >> print(" («»)   : ", starts_with_letter('', 'vowel'))  («»)   : False >> print(" («»)   : ", starts_with_letter('', 'consonant'))  («»)   : True >> count_Harry = count_letter_type("") >> print ("     («»): ", count_Harry['paired'])      («»): 1 

A rigor, com a ajuda dessas funções, podemos obter uma representação vetorial da string, ou seja, obtemos o mapeamento:


f(<Nome><Sobrenome>)=>(comprimentonome,comprimentosobrenomes,...,númerode vogaissobrenomes)


Agora, podemos apresentar nossos dados na forma de um conjunto de dados que pode ser inserido no algoritmo de aprendizado de máquina:


 >> from data_loaders import load_processed_data >> hogwarts_df = load_processed_data() >> hogwarts_df.head() 


Além disso, como resultado, temos os seguintes sintomas para cada aluno:


 >> hogwarts_df[hogwarts_df.columns].dtypes 

Sinais Recebidos
 name object surname object is_english bool name_starts_with_vowel bool name_starts_with_consonant bool name_ends_with_vowel bool name_ends_with_consonant bool name_length int64 name_vowels_count int64 name_double_vowels_count int64 name_consonant_count int64 name_double_consonant_count int64 name_paired_count int64 name_deaf_count int64 name_sonorus_count int64 surname_starts_with_vowel bool surname_starts_with_consonant bool surname_ends_with_vowel bool surname_ends_with_consonant bool surname_length int64 surname_vowels_count int64 surname_double_vowels_count int64 surname_consonant_count int64 surname_double_consonant_count int64 surname_paired_count int64 surname_deaf_count int64 surname_sonorus_count int64 is_griffindor int64 is_hufflpuff int64 is_ravenclaw int64 is_slitherin int64 dtype: object 

As últimas 4 colunas são direcionadas - elas contêm informações em que faculdade um aluno está matriculado.


Treinamento de algoritmo


Em suma, os algoritmos são treinados como as pessoas: eles cometem erros e aprendem com eles. Para entender o quanto eles cometeram um erro, os algoritmos usam funções de erro (funções de perda, função de perda em inglês ).


Como regra, o processo de aprendizado é muito simples e consiste em várias etapas:


  1. Faça uma previsão.
  2. Classifique o erro.
  3. Corrija os parâmetros do modelo.
  4. Repita 1-3 até o objetivo ser alcançado, o processo parar ou os dados terminarem.
  5. Classifique a qualidade do modelo resultante.


    Na prática, é claro, tudo é um pouco mais complicado. Por exemplo, existe o fenômeno da super adaptação - o algoritmo pode literalmente lembrar quais recursos correspondem à resposta e, assim, piorar o resultado para objetos que não são semelhantes aos sobre os quais ele foi treinado. Para evitar isso, existem várias técnicas e hacks.



Como mencionado acima, resolveremos 4 problemas: um para cada faculdade. Portanto, prepararemos dados para a Sonserina:


 #  ,     - : >> data_full = hogwarts_df.drop( [ 'name', 'surname', 'is_griffindor', 'is_hufflpuff', 'is_ravenclaw' ], axis=1).copy() #    ,   : >> X_data = data_full.drop('is_slitherin', axis=1) #     ,   1    >> y = data_full.is_slitherin 

Enquanto aprende, o algoritmo constantemente compara seus resultados com dados reais, pois essa parte do conjunto de dados é alocada para validação. A regra do bom tom também é considerada para avaliar o resultado do algoritmo em dados individuais que o algoritmo não viu. Portanto, agora dividimos a amostra na proporção de 70/30 e treinamos o primeiro algoritmo:


 from sklearn.cross_validation import train_test_split from sklearn.ensemble import RandomForestClassifier #      >> seed = 7 #    >> test_size = 0.3 >> X_train, X_test, y_train, y_test = train_test_split(X_data, y, test_size=test_size, random_state=seed) >> rfc = RandomForestClassifier() >> rfc_model = rfc.fit(X_train, y_train) 

Feito. Agora, se você enviar dados para a entrada desse modelo, ele produzirá um resultado. Isso é divertido, então primeiro vamos verificar se o modelo em Harry reconhece o sonserino. Para fazer isso, primeiro prepare as funções para obter a previsão do algoritmo:


Ver código
 from data_loaders import parse_line_to_hogwarts_df import pandas as pd def get_single_student_features (name): """      :param name: string     :return: pd.DataFrame     """ featurized_person_df = parse_line_to_hogwarts_df(name) person_df = pd.DataFrame(featurized_person_df, columns=[ 'name', 'surname', 'is_english', 'name_starts_with_vowel', 'name_starts_with_consonant', 'name_ends_with_vowel', 'name_ends_with_consonant', 'name_length', 'name_vowels_count', 'name_double_vowels_count', 'name_consonant_count', 'name_double_consonant_count', 'name_paired_count', 'name_deaf_count', 'name_sonorus_count', 'surname_starts_with_vowel', 'surname_starts_with_consonant', 'surname_ends_with_vowel', 'surname_ends_with_consonant', 'surname_length', 'surname_vowels_count', 'surname_double_vowels_count', 'surname_consonant_count', 'surname_double_consonant_count', 'surname_paired_count', 'surname_deaf_count', 'surname_sonorus_count', ], index=[0] ) featurized_person = person_df.drop( ['name', 'surname'], axis = 1 ) return featurized_person def get_predictions_vector (model, person): """    :param model:   :param person: string   :return: list    """ encoded_person = get_single_student_features(person) return model.predict_proba(encoded_person)[0] 

Agora vamos definir um pequeno conjunto de dados de teste para considerar os resultados do algoritmo.


 def score_testing_dataset (model): """      . :param model:   """ testing_dataset = [ " ", "Kirill Malev", " ", "Harry Potter", " ", " ","Severus Snape", " ", "Tom Riddle", " ", "Salazar Slytherin"] for name in testing_dataset: print ("{} — {}".format(name, get_predictions_vector(model, name)[1])) score_testing_dataset(rfc_model) 

   — 0.5 Kirill Malev — 0.5   — 0.0 Harry Potter — 0.0   — 0.75   — 0.9 Severus Snape — 0.5   — 0.2 Tom Riddle — 0.5   — 0.2 Salazar Slytherin — 0.3 

Os resultados foram duvidosos. Mesmo o fundador da faculdade não estaria em sua faculdade, de acordo com esse modelo. Portanto, você precisa avaliar a qualidade estrita: observe as métricas solicitadas no início:


 from sklearn.metrics import accuracy_score, roc_auc_score, classification_report predictions = rfc_model.predict(X_test) print("Classification report: ") print(classification_report(y_test, predictions)) print("Accuracy for Random Forest Model: %.2f" % (accuracy_score(y_test, predictions) * 100)) print("ROC AUC from first Random Forest Model: %.2f" % (roc_auc_score(y_test, predictions))) 

 Classification report: precision recall f1-score support 0 0.66 0.88 0.75 168 1 0.38 0.15 0.21 89 avg / total 0.56 0.62 0.56 257 Accuracy for Random Forest Model: 62.26 ROC AUC from first Random Forest Model: 0.51 

Não é de surpreender que os resultados tenham sido tão duvidosos - a AUC do ROC de cerca de 0,51 sugere que o modelo prevê um pouco melhor do que um sorteio.


Testando os resultados. Métricas de qualidade


Usando um exemplo acima, vimos como um algoritmo é treinado que suporta interfaces sklearn. O restante é treinado exatamente da mesma maneira, para que possamos treinar apenas todos os algoritmos e escolher o melhor em cada caso.



Isso não é complicado, pois para cada algoritmo treinamos 1 com configurações padrão e também treinamos um conjunto inteiro, classificando várias opções que afetam a qualidade do algoritmo. Esse estágio é chamado de Model Tuning ou Hyperparameter Optimization e sua essência é muito simples: o conjunto de configurações que fornece o melhor resultado é selecionado.


 from model_training import train_classifiers from data_loaders import load_processed_data import warnings warnings.filterwarnings('ignore') #   hogwarts_df = load_processed_data() #     data_full = hogwarts_df.drop( [ 'name', 'surname', 'is_griffindor', 'is_hufflpuff', 'is_ravenclaw' ], axis=1).copy() X_data = data_full.drop('is_slitherin', axis=1) y = data_full.is_slitherin #    slitherin_models = train_classifiers(data_full, X_data, y) score_testing_dataset(slitherin_models[5]) 

   — 0.09437856871661066 Kirill Malev — 0.20820536334902712   — 0.07550095601699099 Harry Potter — 0.07683794773639624   — 0.9414529336862744   — 0.9293671807790949 Severus Snape — 0.6576783576162999   — 0.18577792617672767 Tom Riddle — 0.8351835484058869   — 0.25930925139546795 Salazar Slytherin — 0.24008788903854789 

Os números nesta versão parecem subjetivamente melhores do que no passado, mas ainda não são bons o suficiente para um perfeccionista interno. Portanto, desceremos um nível mais profundo e retornaremos ao sentido do produto de nossa tarefa: precisamos prever a faculdade mais provável, à qual o herói será determinado pelo chapéu de distribuição. Isso significa que você precisa treinar modelos para cada uma das faculdades.



 >> from model_training import train_all_models #      >> slitherin_models, griffindor_models, ravenclaw_models, hufflpuff_models = \ train_all_models() 

Conclusão longa de resultados e resultados de regressão multinomial
 SVM Default Report Accuracy for SVM Default: 73.93 ROC AUC for SVM Default: 0.53 Tuned SVM Report Accuracy for Tuned SVM: 72.37 ROC AUC for Tuned SVM: 0.50 KNN Default Report Accuracy for KNN Default: 70.04 ROC AUC for KNN Default: 0.58 Tuned KNN Report Accuracy for Tuned KNN: 69.65 ROC AUC for Tuned KNN: 0.58 XGBoost Default Report Accuracy for XGBoost Default: 70.43 ROC AUC for XGBoost Default: 0.54 Tuned XGBoost Report Accuracy for Tuned XGBoost: 68.09 ROC AUC for Tuned XGBoost: 0.56 Random Forest Default Report Accuracy for Random Forest Default: 73.93 ROC AUC for Random Forest Default: 0.62 Tuned Random Forest Report Accuracy for Tuned Random Forest: 74.32 ROC AUC for Tuned Random Forest: 0.54 Extra Trees Default Report Accuracy for Extra Trees Default: 69.26 ROC AUC for Extra Trees Default: 0.57 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 73.54 ROC AUC for Tuned Extra Trees: 0.55 LGBM Default Report Accuracy for LGBM Default: 70.82 ROC AUC for LGBM Default: 0.62 Tuned LGBM Report Accuracy for Tuned LGBM: 74.71 ROC AUC for Tuned LGBM: 0.53 RGF Default Report Accuracy for RGF Default: 70.43 ROC AUC for RGF Default: 0.58 Tuned RGF Report Accuracy for Tuned RGF: 71.60 ROC AUC for Tuned RGF: 0.60 FRGF Default Report Accuracy for FRGF Default: 68.87 ROC AUC for FRGF Default: 0.59 Tuned FRGF Report Accuracy for Tuned FRGF: 69.26 ROC AUC for Tuned FRGF: 0.59 SVM Default Report Accuracy for SVM Default: 70.43 ROC AUC for SVM Default: 0.50 Tuned SVM Report Accuracy for Tuned SVM: 71.60 ROC AUC for Tuned SVM: 0.50 KNN Default Report Accuracy for KNN Default: 63.04 ROC AUC for KNN Default: 0.49 Tuned KNN Report Accuracy for Tuned KNN: 65.76 ROC AUC for Tuned KNN: 0.50 XGBoost Default Report Accuracy for XGBoost Default: 69.65 ROC AUC for XGBoost Default: 0.54 Tuned XGBoost Report Accuracy for Tuned XGBoost: 68.09 ROC AUC for Tuned XGBoost: 0.50 Random Forest Default Report Accuracy for Random Forest Default: 66.15 ROC AUC for Random Forest Default: 0.51 Tuned Random Forest Report Accuracy for Tuned Random Forest: 70.43 ROC AUC for Tuned Random Forest: 0.50 Extra Trees Default Report Accuracy for Extra Trees Default: 64.20 ROC AUC for Extra Trees Default: 0.49 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 70.82 ROC AUC for Tuned Extra Trees: 0.51 LGBM Default Report Accuracy for LGBM Default: 67.70 ROC AUC for LGBM Default: 0.56 Tuned LGBM Report Accuracy for Tuned LGBM: 70.82 ROC AUC for Tuned LGBM: 0.50 RGF Default Report Accuracy for RGF Default: 66.54 ROC AUC for RGF Default: 0.52 Tuned RGF Report Accuracy for Tuned RGF: 65.76 ROC AUC for Tuned RGF: 0.53 FRGF Default Report Accuracy for FRGF Default: 65.76 ROC AUC for FRGF Default: 0.53 Tuned FRGF Report Accuracy for Tuned FRGF: 69.65 ROC AUC for Tuned FRGF: 0.52 SVM Default Report Accuracy for SVM Default: 74.32 ROC AUC for SVM Default: 0.50 Tuned SVM Report Accuracy for Tuned SVM: 74.71 ROC AUC for Tuned SVM: 0.51 KNN Default Report Accuracy for KNN Default: 69.26 ROC AUC for KNN Default: 0.48 Tuned KNN Report Accuracy for Tuned KNN: 73.15 ROC AUC for Tuned KNN: 0.49 XGBoost Default Report Accuracy for XGBoost Default: 72.76 ROC AUC for XGBoost Default: 0.49 Tuned XGBoost Report Accuracy for Tuned XGBoost: 74.32 ROC AUC for Tuned XGBoost: 0.50 Random Forest Default Report Accuracy for Random Forest Default: 73.93 ROC AUC for Random Forest Default: 0.52 Tuned Random Forest Report Accuracy for Tuned Random Forest: 74.32 ROC AUC for Tuned Random Forest: 0.50 Extra Trees Default Report Accuracy for Extra Trees Default: 73.93 ROC AUC for Extra Trees Default: 0.52 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 73.93 ROC AUC for Tuned Extra Trees: 0.50 LGBM Default Report Accuracy for LGBM Default: 73.54 ROC AUC for LGBM Default: 0.52 Tuned LGBM Report Accuracy for Tuned LGBM: 74.32 ROC AUC for Tuned LGBM: 0.50 RGF Default Report Accuracy for RGF Default: 73.54 ROC AUC for RGF Default: 0.51 Tuned RGF Report Accuracy for Tuned RGF: 73.93 ROC AUC for Tuned RGF: 0.50 FRGF Default Report Accuracy for FRGF Default: 73.93 ROC AUC for FRGF Default: 0.53 Tuned FRGF Report Accuracy for Tuned FRGF: 73.93 ROC AUC for Tuned FRGF: 0.50 SVM Default Report Accuracy for SVM Default: 80.54 ROC AUC for SVM Default: 0.50 Tuned SVM Report Accuracy for Tuned SVM: 80.93 ROC AUC for Tuned SVM: 0.52 KNN Default Report Accuracy for KNN Default: 78.60 ROC AUC for KNN Default: 0.50 Tuned KNN Report Accuracy for Tuned KNN: 80.16 ROC AUC for Tuned KNN: 0.51 XGBoost Default Report Accuracy for XGBoost Default: 80.54 ROC AUC for XGBoost Default: 0.50 Tuned XGBoost Report Accuracy for Tuned XGBoost: 77.04 ROC AUC for Tuned XGBoost: 0.52 Random Forest Default Report Accuracy for Random Forest Default: 77.43 ROC AUC for Random Forest Default: 0.49 Tuned Random Forest Report Accuracy for Tuned Random Forest: 80.54 ROC AUC for Tuned Random Forest: 0.50 Extra Trees Default Report Accuracy for Extra Trees Default: 76.26 ROC AUC for Extra Trees Default: 0.48 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 78.60 ROC AUC for Tuned Extra Trees: 0.50 LGBM Default Report Accuracy for LGBM Default: 75.49 ROC AUC for LGBM Default: 0.51 Tuned LGBM Report Accuracy for Tuned LGBM: 80.54 ROC AUC for Tuned LGBM: 0.50 RGF Default Report Accuracy for RGF Default: 78.99 ROC AUC for RGF Default: 0.52 Tuned RGF Report Accuracy for Tuned RGF: 75.88 ROC AUC for Tuned RGF: 0.55 FRGF Default Report Accuracy for FRGF Default: 76.65 ROC AUC for FRGF Default: 0.50 #   ,        

 from sklearn.linear_model import LogisticRegression clf = LogisticRegression(random_state=0, solver='lbfgs', multi_class='multinomial') hogwarts_df = load_processed_data_multi() #     data_full = hogwarts_df.drop( [ 'name', 'surname', ], axis=1).copy() X_data = data_full.drop('faculty', axis=1) y = data_full.faculty clf.fit(X_data, y) score_testing_dataset(clf) 

   — [0.3602361 0.16166944 0.16771712 0.31037733] Kirill Malev — [0.47473072 0.16051924 0.13511385 0.22963619]   — [0.38697926 0.19330242 0.17451052 0.2452078 ] Harry Potter — [0.40245098 0.16410043 0.16023278 0.27321581]   — [0.13197025 0.16438855 0.17739254 0.52624866]   — [0.17170203 0.1205678 0.14341742 0.56431275] Severus Snape — [0.15558044 0.21589378 0.17370406 0.45482172]   — [0.39301231 0.07397324 0.1212741 0.41174035] Tom Riddle — [0.26623969 0.14194379 0.1728505 0.41896601]   — [0.24843037 0.21632736 0.21532696 0.3199153 ] Salazar Slytherin — [0.09359144 0.26735897 0.2742305 0.36481909] 

E confusion_matrix:


 confusion_matrix(clf.predict(X_data), y) 

 array([[144, 68, 64, 78], [ 8, 9, 8, 6], [ 22, 18, 31, 20], [ 77, 73, 78, 151]]) 

 def get_predctions_vector (models, person): predictions = [get_predictions_vector (model, person)[1] for model in models] return { 'slitherin': predictions[0], 'griffindor': predictions[1], 'ravenclaw': predictions[2], 'hufflpuff': predictions[3] } def score_testing_dataset (models): testing_dataset = [ " ", "Kirill Malev", " ", "Harry Potter", " ", " ","Severus Snape", " ", "Tom Riddle", " ", "Salazar Slytherin"] data = [] for name in testing_dataset: predictions = get_predctions_vector(models, name) predictions['name'] = name data.append(predictions) scoring_df = pd.DataFrame(data, columns=['name', 'slitherin', 'griffindor', 'hufflpuff', 'ravenclaw']) return scoring_df # Data Science —    ,       top_models = [ slitherin_models[3], griffindor_models[3], ravenclaw_models[3], hufflpuff_models[3] ] score_testing_dataset(top_models) 

  name slitherin griffindor hufflpuff ravenclaw 0   0.349084 0.266909 0.110311 0.091045 1 Kirill Malev 0.289914 0.376122 0.384986 0.103056 2   0.338258 0.400841 0.016668 0.124825 3 Harry Potter 0.245377 0.357934 0.026287 0.154592 4   0.917423 0.126997 0.176640 0.096570 5   0.969693 0.106384 0.150146 0.082195 6 Severus Snape 0.663732 0.259189 0.290252 0.074148 7   0.268466 0.579401 0.007900 0.083195 8 Tom Riddle 0.639731 0.541184 0.084395 0.156245 9   0.653595 0.147506 0.172940 0.137134 10 Salazar Slytherin 0.647399 0.169964 0.095450 0.26126 


, . ROC AUC , 0.5. , :


  • ;
  • ;
  • , ;
  • , ;
  • , , .

, , , , XGBoost CV , .


! , 70% . , 4 .


 from model_training import train_production_models from xgboost import XGBClassifier best_models = [] for i in range (0,4): best_models.append(XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1, colsample_bytree=0.7, gamma=0, learning_rate=0.05, max_delta_step=0, max_depth=6, min_child_weight=11, missing=-999, n_estimators=1000, n_jobs=1, nthread=4, objective='binary:logistic', random_state=0, reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=1337, silent=1, subsample=0.8)) slitherin_model, griffindor_model, ravenclaw_model, hufflpuff_model = \ train_production_models(best_models) top_models = slitherin_model, griffindor_model, ravenclaw_model, hufflpuff_model score_testing_dataset(top_models) 

 name slitherin griffindor hufflpuff ravenclaw 0   0.273713 0.372337 0.065923 0.279577 1 Kirill Malev 0.401603 0.761467 0.111068 0.023902 2   0.031540 0.616535 0.196342 0.217829 3 Harry Potter 0.183760 0.422733 0.119393 0.173184 4   0.945895 0.021788 0.209820 0.019449 5   0.950932 0.088979 0.084131 0.012575 6 Severus Snape 0.634035 0.088230 0.249871 0.036682 7   0.426440 0.431351 0.028444 0.083636 8 Tom Riddle 0.816804 0.136530 0.069564 0.035500 9   0.409634 0.213925 0.028631 0.252723 10 Salazar Slytherin 0.824590 0.067910 0.111147 0.085710 

, , .


, , . .


 import pickle pickle.dump(slitherin_model, open("../output/slitherin.xgbm", "wb")) pickle.dump(griffindor_model, open("../output/griffindor.xgbm", "wb")) pickle.dump(ravenclaw_model, open("../output/ravenclaw.xgbm", "wb")) pickle.dump(hufflpuff_model, open("../output/hufflpuff.xgbm", "wb")) 


, . , , , .


, , . , . , Data Scientist — -.


:


  • ;
  • json- json;
  • .

, docker-, python-. , flask.


 from __future__ import print_function # In python 2.7 import os import subprocess import json import re from flask import Flask, request, jsonify from inspect import getmembers, ismethod import numpy as npb import pandas as pd import math import os import pickle import xgboost as xgb import sys from letter import Letter from talking_hat import * from sklearn.ensemble import RandomForestClassifier import warnings def prod_predict_classes_for_name (full_name): featurized_person = parse_line_to_hogwarts_df(full_name) person_df = pd.DataFrame(featurized_person, columns=[ 'name', 'surname', 'is_english', 'name_starts_with_vowel', 'name_starts_with_consonant', 'name_ends_with_vowel', 'name_ends_with_consonant', 'name_length', 'name_vowels_count', 'name_double_vowels_count', 'name_consonant_count', 'name_double_consonant_count', 'name_paired_count', 'name_deaf_count', 'name_sonorus_count', 'surname_starts_with_vowel', 'surname_starts_with_consonant', 'surname_ends_with_vowel', 'surname_ends_with_consonant', 'surname_length', 'surname_vowels_count', 'surname_double_vowels_count', 'surname_consonant_count', 'surname_double_consonant_count', 'surname_paired_count', 'surname_deaf_count', 'surname_sonorus_count', ], index=[0] ) slitherin_model = pickle.load(open("models/slitherin.xgbm", "rb")) griffindor_model = pickle.load(open("models/griffindor.xgbm", "rb")) ravenclaw_model = pickle.load(open("models/ravenclaw.xgbm", "rb")) hufflpuff_model = pickle.load(open("models/hufflpuff.xgbm", "rb")) predictions = get_predctions_vector([ slitherin_model, griffindor_model, ravenclaw_model, hufflpuff_model ], person_df.drop(['name', 'surname'], axis=1)) return { 'slitherin': float(predictions[0][1]), 'griffindor': float(predictions[1][1]), 'ravenclaw': float(predictions[2][1]), 'hufflpuff': float(predictions[3][1]) } def predict(params): fullname = params['fullname'] print(params) return prod_predict_classes_for_name(fullname) def create_app(): app = Flask(__name__) functions_list = [predict] @app.route('/<func_name>', methods=['POST']) def api_root(func_name): for function in functions_list: if function.__name__ == func_name: try: json_req_data = request.get_json() if json_req_data: res = function(json_req_data) else: return jsonify({"error": "error in receiving the json input"}) except Exception as e: data = { "error": "error while running the function" } if hasattr(e, 'message'): data['message'] = e.message elif len(e.args) >= 1: data['message'] = e.args[0] return jsonify(data) return jsonify({"success": True, "result": res}) output_string = 'function: %s not found' % func_name return jsonify({"error": output_string}) return app if __name__ == '__main__': app = create_app() app.run(host='0.0.0.0') 

Dockerfile:


 FROM datmo/python-base:cpu-py35 #  python3-wheel,        RUN apt-get update; apt-get install -y python3-pip python3-numpy python3-scipy python3-wheel ADD requirements.txt / RUN pip3 install -r /requirements.txt RUN mkdir /code;mkdir /code/models COPY ./python_api.py ./talking_hat.py ./letter.py ./request.py /code/ COPY ./models/* /code/models/ WORKDIR /code CMD python3 /code/python_api.py 

:


 docker build -t talking_hat . && docker rm talking_hat && docker run --name talking_hat -p 5000:5000 talking_hat 


— . , Apache Benchmark . , . — .


 $ ab -p data.json -T application/json -c 50 -n 10000 http://0.0.0.0:5000/predict 

ab
 This is ApacheBench, Version 2.3 <$Revision: 1807734 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 0.0.0.0 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Werkzeug/0.14.1 Server Hostname: 0.0.0.0 Server Port: 5000 Document Path: /predict Document Length: 141 bytes Concurrency Level: 50 Time taken for tests: 238.552 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 2880000 bytes Total body sent: 1800000 HTML transferred: 1410000 bytes Requests per second: 41.92 [#/sec] (mean) Time per request: 1192.758 [ms] (mean) Time per request: 23.855 [ms] (mean, across all concurrent requests) Transfer rate: 11.79 [Kbytes/sec] received 7.37 kb/s sent 19.16 kb/s total Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 3 Processing: 199 1191 352.5 1128 3352 Waiting: 198 1190 352.5 1127 3351 Total: 202 1191 352.5 1128 3352 Percentage of the requests served within a certain time (ms) 50% 1128 66% 1277 75% 1378 80% 1451 90% 1668 95% 1860 98% 2096 99% 2260 100% 3352 (longest request) 

, :


 def prod_predict_classes_for_name (full_name): <...> predictions = get_predctions_vector([ app.slitherin_model, app.griffindor_model, app.ravenclaw_model, app.hufflpuff_model ], person_df.drop(['name', 'surname'], axis=1)) return { 'slitherin': float(predictions[0][1]), 'griffindor': float(predictions[1][1]), 'ravenclaw': float(predictions[2][1]), 'hufflpuff': float(predictions[3][1]) } def create_app(): <...> with app.app_context(): app.slitherin_model = pickle.load(open("models/slitherin.xgbm", "rb")) app.griffindor_model = pickle.load(open("models/griffindor.xgbm", "rb")) app.ravenclaw_model = pickle.load(open("models/ravenclaw.xgbm", "rb")) app.hufflpuff_model = pickle.load(open("models/hufflpuff.xgbm", "rb")) return app 

:


 $ docker build -t talking_hat . && docker rm talking_hat && docker run --name talking_hat -p 5000:5000 talking_hat $ ab -p data.json -T application/json -c 50 -n 10000 http://0.0.0.0:5000/predict 

ab
 This is ApacheBench, Version 2.3 <$Revision: 1807734 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 0.0.0.0 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Werkzeug/0.14.1 Server Hostname: 0.0.0.0 Server Port: 5000 Document Path: /predict Document Length: 141 bytes Concurrency Level: 50 Time taken for tests: 219.812 seconds Complete requests: 10000 Failed requests: 3 (Connect: 0, Receive: 0, Length: 3, Exceptions: 0) Total transferred: 2879997 bytes Total body sent: 1800000 HTML transferred: 1409997 bytes Requests per second: 45.49 [#/sec] (mean) Time per request: 1099.062 [ms] (mean) Time per request: 21.981 [ms] (mean, across all concurrent requests) Transfer rate: 12.79 [Kbytes/sec] received 8.00 kb/s sent 20.79 kb/s total Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 2 Processing: 235 1098 335.2 1035 3464 Waiting: 235 1097 335.2 1034 3462 Total: 238 1098 335.2 1035 3464 Percentage of the requests served within a certain time (ms) 50% 1035 66% 1176 75% 1278 80% 1349 90% 1541 95% 1736 98% 1967 99% 2141 100% 3464 (longest request) 

. . , .


Conclusão


, . - .


, :


  • feature engineering- ( ), , Soundex .
  • PyTorch . , , .
  • flask Quart , , .
  • - -, .

, , . , !


Este artigo não teria sido publicado sem a comunidade Open Data Science, que reúne um grande número de especialistas em língua russa no campo da análise de dados.

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


All Articles