PaddlePaddleのクリック率に基づくディープラーニング手法の試み

PaddlePaddleのクリック率に基づくディープラーニング手法の試み

序文

チーム内でクリック率に関する記事をいくつか共有した際に、広告のクリック率の推定値を計算する一般的な方法のまとめを出力しました。最も古典的なロジスティック回帰から因数分解、FFM、FNN、PNN、そして今年のDeepFM、記事では触れられていないgbdt + lrまで、広告のクリック率に関するいくつかの記事を読みました。私はずっと練習する時間を見つけたいと思っていました。今回たまたまパドルを学んでいたときに、モデルディレクトリにDeepFMの実装を見ました。DeepFMについては以前に詳しく説明したことがあるため、ここで簡単にレビューします。

DeepFM のさらに興味深い点は、WDL と FM を組み合わせていることです。これは実際には PNN と WDL の組み合わせです。PNN は、Wide の補足としてニューラル ネットワーク方式で FM を構築します。元の Wide と Deep では、Wide 部分は単なる LR で、線形関係を構築し、Deep 部分はより高次の関係をモデル化します。そのため、Wide と Deep では、Cross Column 作業など、いくつかの機能を行う必要があります。ご存知のように、FM は 2 次関係をモデル化して Cross column の効果を実現できます。DeepFM は FM と NN を組み合わせており、Cross Column 作業などの機能を実行する必要はありません。これが私にとって最も魅力的な部分です。実際、FM 部分は PNN の説明のように感じます。ここでは構造図のみを説明します。PNN 部分は FM 部分の前に説明しました。

深部:

FNNやPNNと比較すると、DeepFMはDeep部分を使用して高次情報(2次以上)をモデル化することができ、WideやDeepと比較すると、特徴エンジニアリングの作業の一部を軽減できます。ワイド部分は、1次と2次の特徴の関係をモデル化する点でFMに似ており、NNとFMのより完璧な組み合わせです。もう1つの違いは、次の図に示すように、DeepFMのワイド部分とディープ部分は埋め込みベクトル空間を共有し、ワイド部分とディープ部分の両方が埋め込み部分を更新できることです。ワイド部分は純粋にPNNの仕事ですが、それでも非常に興味深いものです。

この記事の関連コード部分はすべて paddlepaddle/model からのものです。ここではプロセスを見て学習します。アルゴリズムの原理を理解したい場合は、上記の記事を注意深く読んでください。今日は paddlepaddle で実験を行い、コード レベルから DeepFM がどのように実装されているかを学びます。

データセットの説明

Criteo ディスプレイ広告チャレンジでは、データは主に Criteolab の 1 週間のビジネス データから取得され、ユーザーがページにアクセスしたときに広告をクリックするかどうかを予測するために使用されます。

wget --no-check-certificate https://s3-eu-west-1.amazonaws.com/criteo-labs/dac.tar.gz
tar zxf dac.tar.gz
rm -f dac.tar.gz

mkdir 生
mv ./*.txt 生/

データは約 4.26G と少し大きいです。しばらくお待ちください。データのダウンロード後、train.csv と test.csv を解凍します。トレーニング セットには 45,840,617 個のサンプルがあり、テスト セットには 45,840,617 個のサンプルがあります。データ量はかなり大きいです。 データは主に次の 3 つの部分で構成されます。

  • ラベル: 広告がクリックされたかどうか。
  • 連続特徴: 1-13、各次元の統計情報、連続特徴。
  • 離散的特徴: 一部の鈍感なカテゴリ特徴

概要

プロジェクト全体は主にいくつかの部分で構成されています。

データ処理

ここでのデータ処理は主に 2 つの部分で構成されます。

  1. 連続値固有値処理:
  • 統計時間の95%を超えるデータを除外します。この方法で、外れ値データのほとんどを除外できます。ここでの処理方法は、私がNo.1 Storeで行った以前の作業と一致しています。この部分の作業はコードで行われ、この部分の特徴しきい値は直接与えられています。
  • 正規化処理。図には、さまざまな機能の値の範囲によってモデルの最適化がジグザグ状になり、計算量と収束時間が増加することを示す非常に明確な図があります。

  1. 離散固有値処理:
  • One-hot: 対応する特徴値は、指定された次元に 1 の値が 1 つだけあるスパース変数にマッピングされます。
  • 埋め込み: 対応する特徴値が指定された特徴次元にマッピングされます。

具体的にコードを調べてみましょう:

クラス ContinuousFeatureGenerator:
    「」
    最小最大正規化によって整数特徴を[0, 1]に正規化する
    「」

    def __init__(self, num_feature):
        自己.num_feature = num_feature
        自己最小値 = [sys.maxint] * num_feature
        自己.max = [-sys.maxint] * num_feature

    def build(self, データファイル, 連続機能):
        open(datafile, 'r') を f として実行します:
            fの行の場合:
                機能 = line.rstrip('\n').split('\t')
                i が範囲 (0, self.num_feature) 内にある場合:
                    val = 特徴[連続した特徴[i]]
                    val != '' の場合:
                        val = int(val)
                        val > continous_clip[i]の場合:
                            val = 連続クリップ[i]
                        自己.min[i] = min(自己.min[i], val)
                        自己.max[i] = max(自己.max[i], val)

    定義gen(self, idx, val):
        val == ''の場合:
            0.0を返す
        val = float(val)
        (val - self.min[idx]) / (self.max[idx] - self.min[idx]) を返します。

連続した特徴は位置 1 ~ 13 にあります。ファイルを読み込むときに、値が対応する次元の特徴値の 95% しきい値より大きい場合、特徴値はしきい値に設定され、gen 中に特徴次元の最大値と最小値が計算され、正規化されます。

クラス CategoryDictGenerator:
    「」
    各カテゴリの特徴ごとに辞書を生成する
    「」

    def __init__(self, num_feature):
        自己辞書 = []
        自己.num_feature = num_feature
        i が範囲(0, num_feature)内にある場合:
            self.dicts.append(コレクション.defaultdict(int))

    def build(self, データファイル, カテゴリ特徴, カットオフ=0):
        open(datafile, 'r') を f として実行します:
            fの行の場合:
                機能 = line.rstrip('\n').split('\t')
                i が範囲 (0, self.num_feature) 内にある場合:
                    features[categorial_features[i]] != ''の場合:
                        self.dicts[i][features[カテゴリ機能[i]]] += 1
        i が範囲 (0, self.num_feature) 内にある場合:
            self.dicts[i] = filter(lambda x: x[1] >= カットオフ,
                                self.dicts[i].items())
            self.dicts[i] = sorted(self.dicts[i], キー=lambda x: (-x[1], x[0]))
            語彙、_ = list(zip(*self.dicts[i]))
            self.dicts[i] = dict(zip(vocabs, range(1, len(vocabs) + 1)))
            self.dicts [i] [''] = 0

    def gen(self, idx, key):
        キーがself.dicts[idx]にない場合:
            res = self.dicts [idx] ['']
        それ以外:
            res = self.dicts[idx][key]
        戻り値

    dicts_sizes(self)を定義します。
        map(len, self.dicts) を返します

カテゴリ機能の処理は比較的複雑です。トラバーサルが必要で、対応するディメンションに現れるすべての値のすべてのケースを取得し、対応する ID でマークし、後続のカテゴリ機能に ID を割り当てます。この部分は非常に時間がかかるので、しばらくお待ちください。また、PaddlePaddle の友人たちが出力処理中にプロンプ​​ト情報を印刷できることを強く望んでいます。忘れてください。後で時間ができたら PR を送信できるかどうか確認します。

上記の特徴処理の後、トレーニング セットの値は次のようになります。

リーダー

paddle のリーダー ファイルは自由度が高いです。ジェネレーターを自分で作成し、バッチ API を使用してバッチサイズのデータ​​をネットワークに転送することができます。

クラスデータセット:
    def _reader_creator(self, パス, is_infer):
        デフリーダー():
            open(path, 'r') を f として:
                fの行の場合:
                    機能 = line.rstrip('\n').split('\t')
                    高密度フィーチャ = マップ(float、フィーチャ[0].split(','))
                    sparse_feature = map(int, features[1].split(','))
                    そうでない場合はis_infer:
                        ラベル = [float(機能[2])]
                        生成 [dense_feature, sparse_feature
                            ] + sparse_feature + [ラベル]
                    それ以外:
                        [dense_feature, sparse_feature] + sparse_feature を生成します。

        読者を返す

    def train(自分、パス):
        self._reader_creator(path, False) を返します。

    def test(自己、パス):
        self._reader_creator(path, False) を返します。

    def infer(自己、パス):
        self._reader_creator(path, True) を返します。

主なロジックは、ファイルを償還し、対応するネットワークデータ入力形式を生成することです。

モデル構築

モデル構築では、PaddlePaddle では専用の FM レイヤーがあるため、DeepFM は比較的簡単です。私が知る限り、TensorFlow や MXNet には専用の FM レイヤーはありません。ただし、PaddlePaddle の FM レイヤーは 2 次関係のみをモデル化しており、FM を完成させるには FC を追加する必要があることに注意してください。実装コードは次のとおりです。

fm_layer(入力、係数サイズ、fm_param_attr):
    first_order = パドル.レイヤー.fc(
        入力=input、サイズ=1、動作=paddle.activation.Linear())
    2番目の順序 = パドル.レイヤー.因数分解マシン(
        入力=入力、
        係数サイズ=係数サイズ、
        act=パドル.activation.Linear(),
        param_attr = fm_param_attr)
    アウト = パドル.レイヤー.addto(
        入力=[first_order, second_order],
        act=パドル.activation.Linear(),
        バイアス属性=False)
    戻る

次に、DeepFM を構築します。ここでは、次のコードに従って前の図を描画します。データ処理部分を除けば、DeepFM のネットワーク構造を見ることができます。

def DeepFM(factor_size, infer=False):
    高密度入力 = パドル.レイヤー.データ(
        名前="dense_input",
        タイプ = paddle.data_type.dense_vector(dense_feature_dim))
    sparse_input = パドル.レイヤー.データ(
        名前="sparse_input",
        タイプ = paddle.data_type.sparse_binary_vector(sparse_feature_dim))
    スパース入力ID = [
        パドル.レイヤー.データ(
            名前="C" + str(i)、
            タイプ = s(sparse_feature_dim))
        i が範囲(1, 27)内にある場合
    ]
    高密度fm = fm_layer(
        高密度入力、
        係数サイズ、
        fm_param_attr = paddle.attr.Param(name="DenseFeatFactors"))
    スパース_fm = fm_layer(
        スパース入力、
        係数サイズ、
        fm_param_attr = paddle.attr.Param(name="SparseFeatFactors"))
    def埋め込みレイヤー(入力):
        paddle.layer.embedding() を返す
            入力=入力、
            サイズ=係数サイズ、
            param_attr = paddle.attr.Param(name="SparseFeatFactors"))
    sparse_embed_seq = map(埋め込みレイヤー、sparse_input_ids)
    sparse_embed = paddle.layer.concat(sparse_embed_seq)

    fc1 = パドル.レイヤー.fc(
        入力=[sparse_embed,dense_input],
        サイズ=400、
        act=paddle.activation.Relu())
    fc2 = paddle.layer.fc(入力=fc1、サイズ=400、動作=paddle.activation.Relu())
    fc3 = paddle.layer.fc(入力=fc2、サイズ=400、動作=paddle.activation.Relu())

    予測 = パドル.レイヤー.fc(
        入力 = [dense_fm, sparse_fm, fc3],
        サイズ=1、
        行為 = パドル.活性化.シグモイド())

    そうでない場合は推測します:
        ラベル = パドル.レイヤー.データ(
            名前="ラベル"、タイプ=paddle.data_type.dense_vector(1))
        コスト = paddle.layer.multi_binary_label_cross_entropy_cost(
            入力=予測、ラベル=ラベル)
        パドル.評価者.分類エラー(
            name="classification_error"、入力=予測、ラベル=ラベル)
        paddle.evaluator.auc(name="auc", input=predict, label=label) 
        返品費用
    それ以外:
        予測を返す

その中で、主に 3 つの部分で構成されます。1 つは複数の fc で構成される深層部分、2 つ目は疎な fm 部分、そして図に示すように密な fm 部分です。

ここはとても簡単です。特定の API のドキュメントを確認できます。ここでは、スパース機能には 2 つの部分があることを説明したいと思います。1 つは埋め込みプロセスです。ここでは、まず対応する ID が生成され、次に ID を使用して埋め込みが行われ、それが後続の fc の出力として使用されます。次に、sparse_input は fm の出力として使用される onehot 表現であり、fm を使用して 1 次と 2 次の潜在変数の関係が計算されます。

モデルトレーニング

データ量が多すぎます。単一のマシンで実行しても問題ありません。正常に実行できます。私の社内マシンでは正常に実行できますが、問題が 2 つあります。

  1. fm で処理される特徴はスパース表現であり、paddlepaddle の FM レイヤーは CPU 上でのみこれをサポートするため、速度が非常に遅くなります。その理由は fm の速度ではなく、deepfm が複数の fc を設計しており、これがここで速度の影響を受けるはずであるためです。paddlepaddle github で問題が提起され、paddlepaddle は当面その一部を GPU 上で実行できないことがわかりました。すべてのスパースを密に変更するという解決策が示されましたが、GPU ビデオメモリがここでそれを保持できないことがわかりました。
  2. 私のマシンは調子が悪く、開発作業があるため、長い間使用できません。

まとめると、Baidu Cloud 上の k8s を介して PaddlePaddle の分散クラスターをデプロイする方法を学習する予定です。

ドキュメント https://cloud.baidu.com/doc/CCE/GettingStarted.html#.E9.85.8D.E7.BD.AEpaddlecloud

いろいろ調べた結果、最後のステップで行き詰まってしまいました。何が起こっているのかわかりませんが、ページが出てきません... 問題を報告しました: https://github.com/PaddlePaddle/cloud/issues/542、解決したら分散トレーニングの部分を更新します。

単一マシンでのトレーニングには大きな問題はありません。前述のように、FMのスパースはGPUをサポートしていないため、非常に遅いです。Baidu Cloudの16コアマシンでは約36秒/100バッチです。合計で4000w以上のサンプルがあり、1エポックに4時間かかると予想されます。MMP、待ってください、これは分散の必要性です。

さらに、paddlepaddle には問題があります:

https://github.com/PaddlePaddle/Paddle/issues/7010 によると、スパースを密に変換すると GPU で直接実行できるとのことです。スパースの次元はまだかなり高いので、これは試す価値がないようです。スパース演算のより良いソリューションを楽しみにしています。また、GPU に単一のレイヤーを配置して、複数のデバイスで同時に実行できるようになることを楽しみにしています。この点では、TensorFlow と MXNet の方がはるかに優れています。

ここで問題に遭遇しました。 paddle の docker イメージを使用すると、16 個の CPU の計算能力の大部分を安定して占有できました。 しかし、自分でクラウド ホストにインストールすると、CPU 使用率が非常に低くなりました。 これは、環境設定に問題がある可能性があります。 これは大きな問題ではありません。 その後、環境を汚染しないように、主に docker を使用して関連する開発作業を行ったので、ここでは大きな問題はありません。

CPU使用率の変動が大きい。主観的にはTensorFlowより安定していない。スパース演算の影響も否定できない。私が知る限り、TensorFlowのCPU使用率は非常に安定している。

この記事の投稿時点では、17,300 バッチを実行し、AUC 約 0.8、損失約 0.208 を達成できました。

予測する

予測コードは前回の記事の paddle のデモと同じです。ネットワークを再定義し、モデルのトレーニングで得られたパラメータをバインドし、データを渡すだけで推論が完了します。paddle には専用の推論インターフェースがあります。output_layer とトレーニングで学習したパラメータを渡すだけで、モデルの順方向推論ネットワークを簡単に作成できます。

def infer():
    引数 = parse_args()
    パドル.init(use_gpu=False、trainer_count=1)
    モデル = DeepFM(args.factor_size, 推論 = True)
    パラメータ = paddle.parameters.Parameters.from_tar(
        gzip.open(args.model_gz_path, 'r'))
    推論者 = paddle.inference.Inference(
        output_layer=モデル、parameters=パラメータ)
    データセット = reader.Dataset()
    infer_reader = paddle.batch(dataset.infer(args.data_path), batch_size=1000)
    open(args.prediction_output_path, 'w') を出力として使用します:
        ID については、enumerate(infer_reader()) でバッチ処理します。
            res = 推論器.infer(入力 = バッチ)
            予測 = [itertools.chain.from_iterable(res)] 内の x に対する x
            out.write('\n'.join(map(str, predictions)) + '\n')

要約する

いつものように、要約すると、DeemFMは2017年のクリック率予測と推奨のためのディープラーニングの新しい方法です。これは、ディープとワイドの考え方に似ています。従来のFMをNNに変換し、ニューラルネットワークの強力なモデリング機能を使用して、データ内の有効な情報をマイニングします。Paddlepaddleには、この分野で既製のdeepfmモデルがあり、単一のマシンに展開して分散するのが比較的簡単です。ここでは、Baidu Cloudのチュートリアルに従って成功していませんが、今後も注目していきます。また、最近は大規模な機械学習フレームワークに取り組んでいるため、成熟したフレームワークどころか、とにかく動作するフレームワークで十分だと感じています。TensorFlow\MXNetのような優れたフレームワークを開発するのは本当に難しいです。以前はスイッチャーをやっていたときは経験がなかったのですが、この仕事に深く関わった今、その難しさを知っています。また、さまざまな大規模な機械学習フレームワークを別の観点から見るようになりました。たとえば、TensorFlowとMXNetはディープラーニングをサポートするのに非常に優れていますが、ボトルネックもあります。大規模で大規模な機能、特にスパースな機能の場合、オペレーションのサポートに関しては、少なくとも今のところ、特に良いサポートは見られません。例えば、ここのFMは、なぜこんなに遅いのかと誰もが不満を言うかもしれません。フレームワークを作る前は私も不満を言っていましたが、触り始めてから、FMは主にスパース関連のデータオブジェクトに焦点を当てており、この部分のデータに対する高性能な計算をGPUで完了するのは難しいことを知りました。そのため、Paddleの開発者がスパース関連の計算はGPUをサポートしていないと説明したとき、私も同じように感じました。優れた大規模機械学習フレームワークは、さまざまな目標から評価する必要があります。需要が大規模な通常データである場合、安定性とスケーラビリティが重要なポイントです。より多くのアルゴリズムとモデルのサポートであれば、今がチャンスかもしれません。 TensorFlowとMXNetがベンチマークです。大規模機械学習フレームワークが多様化して、ディープラーニングと従来のアルゴリズムを強力にサポートし、データ量、トレーニング規模、並列加速を最大化できることを願っています。このような発展だけが百花繚乱と言えるでしょう。実は、見た目の異なるTensorFlowとMXNetのハンマーはそれほど多く必要ありません。時には鎌が必要なだけです。大規模機械学習フレームワークの発展がTensorFlowとMXNetのようになるのではなく、ロードマップとして大規模、大容量、並列加速の最大化に焦点を当てた新しいベンチマークが登場することを願っています。さあ。

<<:  未来の超人工知能はどれほど恐ろしいものになるのでしょうか?この記事を読んだら黙ってしまうかもしれません!

>>:  機械学習入門: HelloWorld (Tensorflow)

ブログ    
ブログ    

推薦する

AIによる朗読がオーディオブック市場に影響、声優の仕事が脅かされる

6月19日のニュース:テクノロジーの進歩に伴い、人工知能(AI)が徐々に出版業界に参入し、特にオーデ...

...

...

2021年の主なAIトレンド:AIチップスタートアップのM&Aの可能性

人工知能 (AI) と機械学習は、テクノロジーの意思決定者、業界の専門家、投資家にとって引き続き注目...

ランダム フォレスト分類アルゴリズムを使用して Iris データ分類をトレーニングするとどうなるでしょうか?

[[205745]] MLlib は、機械学習のエンジニアリング実践を簡素化し、大規模への拡張を容...

ドローンを使って「国勢調査」を実施?人だけでなく動物も!

データによれば、我が国の人口は過去 10 年間にわたり緩やかな増加傾向を維持し続けており、我が国は依...

人工知能の分野では、すでに世界中で 10 個の画期的な技術が存在します。

[[238191]]人工知能はハイテクで、多岐にわたり、多次元的で、学際的な統合装置であり、ビッグ...

生成 AI 規制: 「ディープフェイク技術」は大規模言語モデルの自由意志を実証するか?

特定のスタイルの生成 AI プロンプトを与えるということは、AI に想像力を働かせてほしいということ...

ロボット兵士はもはやSFではない

ロボット兵士はまもなく現実のものとなり、戦争作戦の遂行において人間の兵士を支援し、負傷した兵士に医療...

...

GPT-4V オープンソース代替品!清華大学と浙江大学は、LLaVAやCogAgentなどのオープンソースの視覚モデルの爆発的な普及を先導した。

現在、GPT-4 Vision は言語理解と視覚処理において並外れた能力を発揮しています。ただし、パ...

Omdia、2019年の世界IoT分野における重要な投資をまとめる

市場調査会社オムディアの最新の調査レポートによると、モノのインターネットの「誇大宣伝サイクル」のピー...

機械学習の教訓: 5 つの企業が失敗を共有

機械学習は現在注目されている技術の 1 つであり、多くのビジネスおよびテクノロジー分野の幹部は、自社...

科学者たちは人間のように「考える」ことができる人工知能を開発している

[[429745]]人間のような AI を作るということは、単に人間の行動を模倣するということだけで...

...