6

状況

グループごとにロジスティック回帰 (インカンター) を実行したい 1,300 万行の CSV があります。私のファイルはそのようなものです(値は単なるサンプルです)

ID Max Probability
1  1   0.5 
1  5   0.6
1  10  0.99
2  1   0.1
2  7   0.95

だから私は最初にcsvリーダーでそれを読んだ。

私はそのようなものを持っています:

( {"Id" "1", "Max" 1, "Probability" 0.5} {"Id" "1", "Max" 5, "Probability" 0.6} etc.

これらの値を Id でグループ化したいと思います。私の記憶が正しければ、約 120 万の Id があります。(私は pandas を使って Python で実行しましたが、超高速です)

これは、ファイルを読み取ってフォーマットするための私の関数です (小さなデータセットでは問題なく動作します)。

  (defn read-file
  []
    (let [path (:path-file @config)
          content-csv (take-csv path \,)]
      (->> (group-by :Id content-csv)
           (map (fn [[k v]]
                [k {:x (mapv :Max v) :y (mapv :Probability v)}]))
           (into {}))))

最終的に、ロジスティック回帰を実行するためにそのようなものが必要です(私はそれについて柔軟であり、 :x および :y のベクトルは必要ありません、seqsは問題ありません)

{"1" {:x [1 5 10] :y [0.5 0.6 0.99]} "2" {:x [1 7] :y [0.1 0.95]} etc.

問題

グループバイ操作に問題があります。CSV からの出力で個別に試してみましたが、Java ヒープ スペース メモリが原因で消えない場合、これは永遠にかかります。問題は私の mapv だと思っていましたが、これは group-by です。

reduce または reduce-kv を使用することを考えましたが、この種の目的でこれらの関数を使用する方法がわかりません。

":x" と ":y" の順序は気にしません (それらの間で同じであるとすぐに、x と y が同じインデックスを持つことを意味します...同じ上にあるので問題ではありません行) と最終結果の Ids を読み取り、そのグループバイを読み取り、順序を維持します。多分それは操作に費用がかかるものですか?

誰かがそれに遭遇した場合は、サンプルデータを提供します:

(def sample '({"Id" "1" "Max" 1 "Probability" 0.5} {"Id" "1" "Max" 5 "Probability" 0.6} {"Id" "1" "Max" 10 "Probability" 0.99} {"Id" "2" "Max" 1 "Probability" 0.1} {"Id" "2" "Max" 7 "Probability" 0.95}))

その他の代替手段

他にもアイデアはありますが、それらが「Clojure」に適しているかどうかはわかりません。

  • Python では、関数の性質上、またファイルが既に順序付けされているため、group-by を使用する代わりに、各グループの開始インデックスと終了インデックスをデータフレームに書き込んで、サブデータタブを直接選択するだけで済みました。

  • Clojure から計算する代わりに、ID のリストをロードすることもできます。お気に入り

    (定義 ID '("1" "2" など。

だから多分それはで始めることが可能です:

{"1" {:x [] :y []} "2" {:x [] :y []} etc.

前の seq から取得し、各 ID の大きなファイルと照合します。

実際にそれがより効率的かどうかはわかりません。

私はロジスティック回帰のための他のすべての機能を持っていますが、この部分が欠けているだけです! ありがとう !

編集

答えてくれてありがとう、私は最終的にこの解決策を見つけました。

私のproject.cljファイルで

 :jvm-opts ["-Xmx13g"])

コード :

(defn data-group->map [group]
  {(:Id (first group))
   {:x (map :Max group)
    :y (map :Probability group)}})


(defn prob-cumsum [data]
  (cag/fmap
    (fn [x]
      (assoc x :y (reductions + (x :y))))
  data))


(defn process-data-splitter [data]
  (->> (partition-by :Id data)
       (map data-group->map)
       (into {})
       (prob-cumsum)))

すべてのコードをラップしましたが、動作します。スプリットは約5分かかりますが、メガスピードは必要ありません。メモリ使用量は、ファイル読み取りの場合はすべてのメモリにまで達し、シグモイドの場合はそれより少なくなる可能性があります。

4

1 に答える 1

6

ファイルが ID でソートされている場合は、partition-by代わりにgroup-by.

コードは次のようになります。

(defn data-group->map [group]
  [(:Id (first group))
   {:x (mapv :Max group)
    :y (mapv :Probability group)}])

(defn read-file []
  (let [path (:path-file @config)
        content-csv (take-csv path \,)]
    (->> content-csv
         (partition-by :Id)
         (map data-group->map)
         (into {}))))

それはそれをスピードアップするはずです。その後、おそらくトランスデューサーを使用して高速化できます

(defn read-file []
  (let [path (:path-file @config)
        content-csv (take-csv path \,)]
    (into {} (comp (partition-by :Id)
                   (map data-group->map))
          content-csv)))

いくつかのテストをしましょう:

最初にあなたのような巨大なデータを生成します:

(def huge-data
  (doall (mapcat #(repeat 
                     1000000
                     {:Id % :Max 1 :Probability 10})
           (range 10))))

1000 万個のアイテム データセットがあり、100 万個{:Id 0 :Max 1 :Probability 10}、100 万個などが{:Id 1 :Max 1 :Probability 10}あります。

今テストする機能:

(defn process-data-group-by [data]
  (->> (group-by :Id data)
       (map (fn [[k v]]
              [k {:x (mapv :Max v) :y (mapv :Probability v)}]))
       (into {})))

(defn process-data-partition-by [data]
  (->> data
       (partition-by :Id)
       (map data-group->map)
       (into {})))

(defn process-data-transducer [data]
  (into {} (comp (partition-by :Id) (map data-group->map)) data))

そして今、時間テスト:

(do (time (dorun (process-data-group-by huge-data)))
    (time (dorun (process-data-partition-by huge-data)))
    (time (dorun (process-data-transducer huge-data))))

"Elapsed time: 3377.167645 msecs"
"Elapsed time: 3707.03448 msecs"
"Elapsed time: 1462.955152 msecs"

groupby はpartition-byコレクション全体を実現する必要がありますが、これは遅延シーケンスを生成することに注意してください。したがって、マップ全体ではなくグループごとにデータが必要な場合は、それぞれを削除(into {})してアクセスする方が速くなります。

(defn process-data-partition-by [data]
  (->> data
       (partition-by :Id)
       (map data-group->map)))

小切手:

user> (time (def processed-data (process-data-partition-by huge-data)))
"Elapsed time: 0.06079 msecs"
#'user/processed-data
user> (time (let [f (first processed-data)]))
"Elapsed time: 302.200571 msecs"
nil
user> (time (let [f (second processed-data)]))
"Elapsed time: 500.597153 msecs"
nil
user> (time (let [f (last processed-data)]))
"Elapsed time: 2924.588625 msecs"
nil
user.core> (time (let [f (last processed-data)]))
"Elapsed time: 0.037646 msecs"
nil
于 2016-02-01T12:40:15.650 に答える