Como prever passagem aérea?

Olá a todos!

Este é o terceiro artigo sobre como presto um serviço pequeno e confortável, o que em teoria deve ajudar no planejamento de viagens. Neste artigo, falarei sobre como prever tarifas aéreas com os dados de Clickhouse, Catboost e 1 TB * em mãos.

imagem

Para que serve?


Uma das principais características do Cheappster.travel é a combinação flexível de rotas complexas (mais no artigo anterior ). Para combinar "tudo com tudo", é usado um cache agregador, no qual nem sempre os tickets são raramente pesquisados ​​e falta muito para a criação de rotas complexas. Essa. bilhetes quentes (baratos) nos quais basear uma rota complexa para , mas não o suficiente 1-2 segmentos dos ingressos "normais" (pelo preço normal, não na direção mais popular). Foi esse problema que me levou à necessidade de construir um modelo que pudesse prever a passagem aérea.

Formalização de tarefas


  • Você precisa prever bilhetes para voos diretos (apenas ida e volta)
  • Você precisa prever e armazenar isso regularmente no banco de dados (cenário simples)
  • Precisa ser capaz de prever "on the fly" (cenário complexo)
  • Isso tudo acontece em um hardware muito limitado - portanto, um mínimo de manipulação com grandes quantidades de dados

Como fazer isso?


Para começar, treinaremos o modelo: prepare o conjunto de dados, destacando o número máximo de recursos nas colunas, faça o upload para tsv, carregue-o no DataFrame / Pool, analise, selecione os parâmetros ... Pare, temos muitos dados e eles não cabem na memória , - pegue os seguintes erros:

MemoryError: Unable to allocate array with shape (38, 288224989) and data type float64
OSError: [Errno 12] Cannot allocate memory

Para contornar essa limitação, era necessário aprender iterativamente em pedaços pequenos, assim:

model = CatBoostRegressor(cat_features=cat_features,
          iterations=100,
          learning_rate=.5,
          depth=10,
          l2_leaf_reg=9,
          one_hot_max_size=5000)

for df in tqdm(pd.read_csv('history.tsv', sep='\t', 
                           na_values=['\\N'], 
                           chunksize=2_000_000)):
    ...
     model.fit(X=df[df.columns[:-1]][:train_size].values,
                  y=df['price'][:train_size].values,
                  eval_set=eval_pool,
                  verbose=False,
                  plot=False,
                  init_model=model) # <--        

O resultado foi um modelo com RMSE ~ 100 - em geral, eu ficaria feliz com esse resultado, mas depois de uma pequena análise e "normalização" das previsões (negativos e valores que diferem muito dos valores mín / máx na história são levados para os limites correspondentes dos preços históricos) . Depois disso, a métrica de destino é ~ 80, levando em consideração o fato de que, na minha experiência, quase não há lógica e bom senso no preço de passagens aéreas.

Recursos que afetam o preço mais:

imagem

Estatísticas dos recursos “Distância entre cidades”:

imagem

Ótimo, temos um modelo - agora é hora de usá-lo. Primeiro de tudo, adicione o modelo KX, isso é feito com uma configuração simples:

Config
<models>
    <model>
        <!-- Model type. Now catboost only. -->
        <type>catboost</type>
        <!-- Model name. -->
        <name>price</name>
        <!-- Path to trained model. -->
        <path>/opt/models/price_iter_model_2.bin</path>
        <!-- Update interval. -->
        <lifetime>0</lifetime>
    </model>
</models>


Fazemos um processo de previsão regular - é fácil o suficiente usando o Apache Airflow.

O DAG resultante se parece com isso
image
DAGa ( Airflow):

SimpleHttpOperator
insert_ow_in_tmp = SimpleHttpOperator(
    task_id='insert_ow_in_tmp',
    http_conn_id='clickhouse_http',
    endpoint=dll_endpoint,
    method='POST',
    data=sql_templates.INSERT_OW_PREDICTIONS_IN_TMP,
    pool='clickhouse_select',
    dag=dag
)



Para previsão "on the fly" usando sql comum:

select origin, destination, date,
         modelEvaluate('price', *)  predicted_price -- ,   
from log.history

+--------+-------------+------------+-----------------+
| origin | destination | date       | predicted_price |
+--------+-------------+------------+-----------------+
| VKO    | DEB         | 2020-03-20 | 3234.43244      |
+--------+-------------+------------+-----------------+
--* ,   

Quero substituir o fato de que essa abordagem foi escolhida, não apenas porque é mais fácil de implementar - ainda há vantagens:

  • Não é necessário fazer upload de dados para o exterior do KH (isso significa mais rápido e menos dispendioso com a carga no ferro)
  • Não há necessidade de executar processos etl (mais fácil = mais confiável)

Corrigimos um pouco a API e o front-end e obtivemos as previsões há muito esperadas.

Essas previsões também se encaixam bem na seção Histórico de preços de passagens aéreas : a

imagem

funcionalidade está disponível em cheappster.travel/history (será aberta de maneira torta no celular, apenas em telas grandes).

Isso é tudo, um dia produtivo!

Artigos anteriores


Uma tentativa de resolver o problema de escolher passagens aéreas antes das férias
Uma tentativa de resolver o problema de escolher passagens aéreas antes das férias # 2

Outra característica interessante


Combinador de rotas difíceis
Bilhetes complexos (triângulos)

PS
Importante! Não tome essas previsões como algo que o ajude a escolher uma data de compra - o modelo pode não prever corretamente, além disso, sua adequação não foi verificada por mim ou por qualquer outra pessoa (tudo por seu próprio risco e risco, sem garantias).

1 TB * - isto é, se você fizer o upload para tsv, no KX é preciso uma ordem de magnitude menor.

UPD:

Principais problemas não óbvios ao usar pacotes Catboost - Clickhouse


  1. Os recursos categóricos no KH alteram a ordem e se tornam no final (e não na ordem que estava durante o treinamento);
  2. modelEvaluate retorna null - você precisa verificar se possui valores nulos nos recursos, se precisar substituí-los por nan
  3. Nas novas versões, há um momento não óbvio com o formato de configuração para o KX, descrito aqui

All Articles