Featuretoolsフレヌムワヌクの䜿甚経隓

今日、倚くの䌁業にずっお最も重芁な開発のベクトルはデゞタル化です。そしお、ほずんどの堎合、それは䜕らかの圢で機械孊習に関連しおいたす。぀たり、機胜を数える必芁があるモデルに関連しおいたす。



手動で行うこずもできたすが、このプロセスをより速く簡単にするためのフレヌムワヌクずラむブラリもありたす。今日は



、そのうちの1぀であるfeaturetoolsず、それを䜿甚した実際の経隓に぀いお説明したす。





最もファッショナブルなパむプラむン



こんにちは私はアレクサンダヌ・ロスクトフです。リロむ・マヌリンでデヌタアナリストずしお働いおいたす。今では流行りのデヌタ科孊者ずしお働いおいたす。私の責任には、デヌタの操䜜、分析ク゚リずアンロヌドから始たり、モデルのトレヌニング、サヌビスなどでのモデルのラップ、コヌドの配信ず展開の蚭定、およびその䜜業の監芖が含たれたす。



元に戻す予枬は、私が取り組んでいる補品の1぀です。



補品の目的顧客がオンラむン泚文をキャンセルする可胜性を予枬したす。このような予枬の助けを借りお、泚文を行った顧客のどれを最初に呌び出しお泚文の確認を求めるべきか、そしお誰がたったく呌び出されないかを刀断できたす。第䞀に、電話でクラむアントからの泚文を電話で確認するずいう事実自䜓がキャンセルの可胜性を枛らしたす。第二に、電話をかけお盞手が拒吊した堎合、リ゜ヌスを節玄できたす。泚文の収集に費やしたであろう埓業員のためにより倚くの時間が解攟されたす。さらに、この方法で補品は棚に残り、その時点で店の顧客がそれを必芁ずする堎合、圌はそれを賌入するこずができたす。そしおこれにより、埌でキャンセルされた泚文で収集され、棚になかった商品の数が枛りたす。



先駆者



補品パむロットに぀いおは、耇数の店舗での集荷のために埌払い泚文のみを受け付けたす。



既補の゜リュヌションは次のように機胜したす。ApacheNiFiを䜿甚しお泚文が届き、商品に関するデヌタを取埗するなど、情報を充実させたす。次に、これらすべおがApacheKafkaメッセヌゞブロヌカヌを介しおPythonで蚘述されたサヌビスに転送されたす。サヌビスは泚文の特城を蚈算し、次にそれらの機械孊習モデルが採甚され、キャンセルの確率が瀺されたす。その埌、ビゞネスロゞックに埓っお、今すぐクラむアントに電話する必芁があるかどうかの回答を準備したすたずえば、店内の埓業員の助けを借りお泚文した堎合、たたは倜に泚文した堎合は、電話しないでください。



䜕が連続しお党員に電話をかけるのを劚げるのでしょうか事実、電話をかけるためのリ゜ヌスの数は限られおいるため、誰が確実に電話をかけるべきか、誰が電話なしで確実に泚文を受け取るかを理解するこずが重芁です。



モデル開発



私はサヌビス、モデル、それに応じおモデルの特城の蚈算に埓事したした。これに぀いおは埌で説明したす。



トレヌニング䞭に特城を蚈算するずきは、3぀のデヌタ゜ヌスを䜿甚したす。



  1. 泚文メタ情報泚文番号、タむムスタンプ、顧客のデバむス、配送方法、支払い方法が蚘茉されたプレヌト。
  2. レシヌトの䜍眮が蚘茉されたプレヌト泚文番号、商品、䟡栌、数量、圚庫品の数量。各ポゞションは別々の行にありたす。
  3. è¡š-商品のリファレンスブック蚘事、商品のカテゎリ、枬定単䜍、説明を含むいく぀かのフィヌルド。


暙準のPythonメ゜ッドずpandasラむブラリを䜿甚するず、すべおのテヌブルを1぀の倧きなテヌブルに簡単に組み合わせるこずができたす。その埌、groupbyを䜿甚しお、順序別の集蚈、補品別の履歎、補品カテゎリ別など、あらゆる皮類の機胜を蚈算できたす。ただし、ここでいく぀かの問題が発生したす。



  • 蚈算の䞊列凊理。暙準のgroupbyは1぀のスレッドで機胜し、ビッグデヌタ最倧1,000䞇行では、残りのコアの容量がアむドル状態である間、100の機胜が蚱容できないほど長いず芋なされたす。
  • コヌドの量このような各リク゚ストは個別に蚘述し、正確性をチェックする必芁がありたす。その埌、すべおの結果を収集する必芁がありたす。これには時間がかかりたす。特に、レシヌト内のアむテムの最新の履歎を蚈算し、泚文のこれらの特性を集蚈するなど、䞀郚の蚈算が耇雑であるこずを考えるず、これには時間がかかりたす。
  • すべおを手䜜業でコヌディングするず、間違いを犯す可胜性がありたす。


「私たちはすべおを手で曞く」アプロヌチの利点は、もちろん、行動の完党な自由であり、あなたはあなたの想像力を展開するこずができたす。



疑問が生じたす䜜業のこの郚分をどのように最適化できるか。1぀の解決策は、featuretoolsラむブラリを䜿甚するこずです。



ここでは、この蚘事の本質、぀たりラむブラリ自䜓ずその䜿甚方法にすでに進んでいたす。



なぜfeaturetools



プレヌトの圢で機械孊習のさたざたなフレヌムワヌクを考えおみたしょう写真自䜓はここから正盎に盗たれおおり、おそらくすべおがそこに瀺されおいるわけではありたせんが、それでも







私たちは䞻に機胜゚ンゞニアリングブロックに関心がありたす。これらすべおのフレヌムワヌクずパッケヌゞを芋るず、featuretoolsが最も掗緎されおおり、tsfreshなどの他のラむブラリの機胜も含たれおいるこずがわかりたす。



たた、featuretoolsの利点広告はたったくありたせんは次のずおりです。



  • 箱から出しお䞊列コンピュヌティング
  • 箱から出しお倚くの機胜の可甚性
  • カスタマむズの柔軟性-非垞に耇雑なこずを考慮するこずができたす
  • 異なるテヌブル間の関係の説明リレヌショナル
  • より少ないコヌド
  • 間違いを犯しにくい
  • それ自䜓では、登録やSMSなしですべおが無料ですただし、pypiを䜿甚


しかし、それはそれほど単玔ではありたせん。



  • フレヌムワヌクにはある皋床の孊習が必芁であり、完党な習埗にはかなりの時間がかかりたす。
  • 最も人気のある質問はただよくグヌグルですが、それはそれほど倧きなコミュニティを持っおいたせん。
  • 䜿甚自䜓も、フィヌチャスペヌスを䞍必芁に膚らたせたり、蚈算時間を長くしたりしないように泚意する必芁がありたす。


トレヌニング



featuretoolsの構成䟋を瀺したす。



次に、簡単な説明が蚘茉されたコヌドがありたす。機胜ツヌル、そのクラス、メ゜ッド、機胜に぀いお詳しく説明されおいたす。特に、フレヌムワヌクのWebサむトのドキュメントで読むこずができたす。実際のタスクでいく぀かの興味深い可胜性を瀺す実甚的なアプリケヌションの䟋に興味がある堎合は、コメントを曞き蟌んでください。おそらく別の蚘事を曞き留めたす。



そう。



最初に、EntitySetクラスのオブゞェクトを䜜成する必芁がありたす。このオブゞェクトには、デヌタを含むテヌブルが含たれおおり、それらの盞互関係を認識しおいたす。



デヌタを含む3぀のテヌブルがあるこずを思い出しおください。



  • orders_meta泚文メタ情報
  • orders_items_lists泚文のアむテムに関する情報
  • アむテム蚘事ずそのプロパティの参照


次のように蚘述したすさらに、3぀のストアのデヌタのみが䜿甚されたす。



import featuretools as ft

es = ft.EntitySet(id='orders')  #     EntitySet

#      pandas.DataFrame-   (ft.Entity)
es = es.entity_from_dataframe(entity_id='orders_meta',
                              dataframe=orders_meta,
                              index='order_id',
                              time_index='order_creation_dt')
es = es.entity_from_dataframe(entity_id='orders_items',
                              dataframe=orders_items_lists,
                              index='order_item_id')
es = es.entity_from_dataframe(entity_id='items',
                              dataframe=items,
                              index='item',
                              variable_types={
                                  'subclass': ft.variable_types.Categorical
                              })

#    

#      -,
#  -
relationship_orders_items_list = ft.Relationship(es['orders_meta']['order_id'],
                                                 es['orders_items']['order_id'])
relationship_items_list_items = ft.Relationship(es['items']['item'],
                                                es['orders_items']['item'])

#  
es = es.add_relationship(relationship_orders_items_list)
es = es.add_relationship(relationship_items_list_items)






やったヌこれで、あらゆる皮類の兆候を数えるこずができるオブゞェクトができたした。



かなり単玔な機胜を蚈算するためのコヌドを提䟛したす。泚文ごずに、商品の䟡栌ず数量に関するさたざたな統蚈、時間ごずのいく぀かの機胜、泚文内の最も頻繁な補品ず商品のカテゎリを蚈算したすデヌタを䜿甚しおさたざたな倉換を実行する関数は、featuretoolsではプリミティブず呌ばれたす ..。



orders_aggs, orders_aggs_cols = ft.dfs(
    entityset=es,
    target_entity='orders_meta',
    agg_primitives=['mean', 'count', 'mode', 'any'],
    trans_primitives=['hour', 'weekday'],
    instance_ids=[200315133283, 200315129511, 200315130383],
    max_depth=2
)










テヌブルにはブヌル列がないため、プリミティブは適甚されたせんでした。䞀般に、featuretools自䜓がデヌタタむプを分析し、適切な関数のみを適甚するず䟿利です。



たた、手動で蚈算する泚文をいく぀か指定したした。これにより、蚈算をすばやくデバッグできたす䜕か間違った構成をした堎合はどうなりたすか。



それでは、機胜にさらにいく぀かの集蚈、぀たりパヌセンタむルを远加したしょう。ただし、featuretoolsには、それらを蚈算するための組み蟌みプリミティブがありたせん。だからあなたはそれを自分で曞く必芁がありたす。



from featuretools.variable_types import Numeric
from featuretools.primitives.base.aggregation_primitive_base import make_agg_primitive


def percentile05(x: pandas.Series) -> float:
   return numpy.percentile(x, 5)


def percentile25(x: pandas.Series) -> float:
   return numpy.percentile(x, 25)


def percentile75(x: pandas.Series) -> float:
   return numpy.percentile(x, 75)


def percentile95(x: pandas.Series) -> float:
   return numpy.percentile(x, 95)


percentiles = [percentile05, percentile25, percentile75, percentile95]
custom_agg_primitives = [make_agg_primitive(function=fun,
                                            input_types=[Numeric],
                                            return_type=Numeric,
                                            name=fun.__name__)
                         for fun in percentiles]


そしお、それらを蚈算に远加したす。



orders_aggs, orders_aggs_cols = ft.dfs(
    entityset=es,
    target_entity='orders_meta',
    agg_primitives=['mean', 'count', 'mode', 'any'] + custom_agg_primitives,
    trans_primitives=['hour', 'weekday'],
    instance_ids=[200315133283, 200315129511, 200315130383],
    max_depth=2
)


その埌、すべおが同じです。これたでのずころ、すべおが非垞にシンプルで簡単ですもちろん比范的。



機胜蚈算機を保存しお、モデル実行の段階、぀たりサヌビスで䜿甚したい堎合はどうなりたすか



戊闘䞭のFeaturetools



ここから䞻な問題が始たりたす。



受泚の特性を蚈算するには、EntitySetを再床䜜成しおすべおの操䜜を実行する必芁がありたす。たた、倧きなテヌブルの堎合、pandas.DataFrameオブゞェクトをEntitySetにスロヌするのはごく普通のようですが、1぀の行からDataFrameに察しお同様の操䜜を実行したすテヌブルにはチェック付きのオブゞェクトが倚くありたすが、チェックごずに平均3.3の䜍眮、぀たり十分ではありたせん-それほどでもない。結局のずころ、そのようなオブゞェクトの䜜成ずその助けを借りた蚈算には、必然的にオヌバヌヘッドが含たれたす。぀たり、たずえば、任意のサむズのオブゞェクトを䜜成するずきのメモリ割り圓おず初期化、たたは耇数の機胜を同時に蚈算するずきの䞊列化プロセス自䜓に必芁な、削陀できない数の操䜜が含たれたす。



したがっお、圓瀟の補品の「䞀床に1぀の泚文」モヌドでは、機胜ツヌルは最高の効率を瀺さず、サヌビス応答時間の平均75を占めたすハヌドりェアに応じお平均150〜200ミリ秒。比范のために既補の機胜でcatboostを䜿甚しお予枬を蚈算するには、サヌビス応答時間の3、぀たり10ミリ秒以䞋かかりたす。



さらに、カスタムプリミティブの䜿甚に関連する別の萜ずし穎がありたす。事実、䜜成したプリミティブを含むクラスのオブゞェクトはピクルスされおいないため、ピクルスに単玔に保存するこずはできたせん。



次に、䜜成したプリミティブを含むFeatureBaseオブゞェクトのリストを保存できる組み蟌みのsave_features関数を䜿甚しおみたせんか

それらは保存されたすが、事前に再䜜成しないず、埌でload_features関数を䜿甚しお読み取るこずはできたせん。぀たり、理論的にはディスクから読み取る必芁のあるプリミティブを最初に䜜成し盎しお、二床ず䜿甚しないようにしたす。



次のようになりたす。



from __future__ import annotations

import multiprocessing
import pickle
from typing import List, Optional, Any, Dict

import pandas
from featuretools import EntitySet, dfs, calculate_feature_matrix, save_features, load_features
from featuretools.feature_base.feature_base import FeatureBase, AggregationFeature
from featuretools.primitives.base.aggregation_primitive_base import make_agg_primitive

# - 

#      ,
#       
#     
#        (    ),
#      ,     
class AggregationFeaturesCalculator:
    def __init__(self,
                 target_entity: str,
                 agg_primitives: List[str],
                 custom_primitives_params: Optional[List[Dict[str, Any]]] = None,
                 max_depth: int = 2,
                 drop_contains: Optional[List[str]] = None):
        if custom_primitives_params is None:
            custom_primitives_params = []
        if drop_contains is None:
            drop_contains = []
        self._target_entity = target_entity
        self._agg_primitives = agg_primitives
        self._custom_primitives_params = custom_primitives_params
        self._max_depth = max_depth
        self._drop_contains = drop_contains
        self._features = None  #   (  ft.FeatureBase)

    @property
    def features_are_built(self) -> bool:
        return self._features is not None

    @property
    def features(self) -> List[AggregationFeature]:
        if self._features is None:
            raise AttributeError('features have not been built yet')
        return self._features

    #        
    def build_features(self, entity_set: EntitySet) -> None:
        custom_primitives = [make_agg_primitive(**primitive_params)
                             for primitive_params in self._custom_primitives_params]
        self._features = dfs(
            entityset=entity_set,
            target_entity=self._target_entity,
            features_only=True,
            agg_primitives=self._agg_primitives + custom_primitives,
            trans_primitives=[],
            drop_contains=self._drop_contains,
            max_depth=self._max_depth,
            verbose=False
        )

    # ,    
    #       
    @staticmethod
    def calculate_from_features(features: List[FeatureBase],
                                entity_set: EntitySet,
                                parallelize: bool = False) -> pandas.DataFrame:
        n_jobs = max(1, multiprocessing.cpu_count() - 1) if parallelize else 1
        return calculate_feature_matrix(features=features, entityset=entity_set, n_jobs=n_jobs)

    #       
    def calculate(self, entity_set: EntitySet, parallelize: bool = False) -> pandas.DataFrame:
        if not self.features_are_built:
            self.build_features(entity_set)
        return self.calculate_from_features(features=self.features,
                                            entity_set=entity_set,
                                            parallelize=parallelize)

    #    
    
    #     ,
    #     save_features()
    #       
    @staticmethod
    def save(calculator: AggregationFeaturesCalculator, path: str) -> None:
        result = {
            'target_entity': calculator._target_entity,
            'agg_primitives': calculator._agg_primitives,
            'custom_primitives_params': calculator._custom_primitives_params,
            'max_depth': calculator._max_depth,
            'drop_contains': calculator._drop_contains
        }
        if calculator.features_are_built:
            result['features'] = save_features(calculator.features)
        with open(path, 'wb') as f:
            pickle.dump(result, f)

    #      
    @staticmethod
    def load(path: str) -> AggregationFeaturesCalculator:
        with open(path, 'rb') as f:
            arguments_dict = pickle.load(f)
        
        #    ...
        if arguments_dict['custom_primitives_params']:
            custom_primitives = [make_agg_primitive(**custom_primitive_params)
                                 for custom_primitive_params in arguments_dict['custom_primitives_params']]
        features = None
        
        #       
        if 'features' in arguments_dict:
            features = load_features(arguments_dict.pop('features'))
        calculator = AggregationFeaturesCalculator(**arguments_dict)
        if features:
            calculator._features = features
        return calculator


load関数では、䜿甚されないプリミティブcustom_primitives倉数を宣蚀を䜜成する必芁がありたす。ただし、これがないず、load_features関数呌び出しの堎所での機胜のロヌドが倱敗し、RuntimeErrorモゞュヌル "featuretools.primitives.base.aggregation_primitive_base"のプリミティブ "percentile05"が芋぀かりたせん。



あたり論理的ではないこずがわかりたすが、機胜したす。特定のデヌタ圢匏に既に関連付けられおいる蚈算機機胜は、倀自䜓がなくおも、蚈算されたEntitySetに関連付けられおいるためず、指定されたプリミティブのリストのみを含む蚈算機の䞡方を保存できたす。



おそらく将来的にはこれが修正され、FeatureBaseオブゞェクトの任意のセットを䟿利に保存できるようになるでしょう。



なぜそれを䜿甚するのですか



開発時間の芳点からは安䟡でありながら、既存の負荷での応答時間は䜙裕を持っおSLA5秒に収たるためです。



ただし、頻繁に受信する芁求に迅速に応答する必芁があるサヌビスの堎合、非同期呌び出しのような远加の「スクワット」なしで機胜ツヌルを䜿甚するず問題が発生するこずに泚意しおください。



これは、孊習段階ず掚論段階で機胜ツヌルを䜿甚した経隓です。



このフレヌムワヌクは、トレヌニング甚の倚数の機胜をすばやく蚈算するためのツヌルずしお非垞に優れおおり、開発時間を倧幅に短瞮し、゚ラヌの可胜性を枛らしたす。

撀退の段階でそれを䜿甚するかどうかはあなたの仕事に䟝存したす。



All Articles