Clojure:分组速度太慢(1300万行文件)

Clojure:分组速度太慢(1300万行文件),clojure,group-by,incanter,Clojure,Group By,Incanter,形势 我有一个1300万行的CSV,我想对每个组执行逻辑回归(白炽灯)。 我的文件是这样的(值只是示例) 所以我首先用csv阅读器阅读,一切都很好 我有这样的想法: ( {"Id" "1", "Max" 1, "Probability" 0.5} {"Id" "1", "Max" 5, "Probability" 0.6} etc. 我想按Id对这些值进行分组,如果我没记错的话,大约有120万个Id。(我是用Python和熊猫一起做的,速度非常快) 这是我的函数,用于读取和格式化文件(它在较小

形势

我有一个1300万行的CSV,我想对每个组执行逻辑回归(白炽灯)。 我的文件是这样的(值只是示例)

所以我首先用csv阅读器阅读,一切都很好

我有这样的想法:

( {"Id" "1", "Max" 1, "Probability" 0.5} {"Id" "1", "Max" 5, "Probability" 0.6} etc.
我想按Id对这些值进行分组,如果我没记错的话,大约有120万个Id。(我是用Python和熊猫一起做的,速度非常快)

这是我的函数,用于读取和格式化文件(它在较小的数据集上运行良好):

最后,我希望有类似的东西来执行逻辑回归(我在这方面很灵活,不需要:x和:y的向量,seqs可以)

问题

我在操作上与小组有困难。我在CSV的输出上分别尝试了它,但由于Java堆空间内存的原因,它不会消失,这需要花费很长时间。 我以为问题出在我的mapv上,但这是小组讨论的结果

我曾考虑过使用reduce或reduce-kv,但我不知道如何将这些函数用于此类目的

我不关心“:x”和“:y”的顺序(只要它们之间相同,我的意思是x和y具有相同的索引…这不是问题,因为它们在同一行上)和最终结果上ID的顺序,我通过保持顺序来读取该组。 也许这就是手术的成本

如果任何人遇到以下情况,我将向您提供示例数据:

(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中,由于函数的性质以及文件已经排序,我没有使用GROUPBY,而是为每个组编写了一个数据帧开始和结束索引,因此我只需直接选择子datatab

  • 我还可以加载一个ID列表,而不是从Clojure计算它。 像

    (定义ID)(“1”“2”等)

因此,也许可以从以下几点开始:

{"1" {:x [] :y []} "2" {:x [] :y []} etc.
然后在每个ID上匹配大文件

我不知道它实际上是否更有效

我有逻辑回归的所有其他函数,我只是缺少这一部分! 谢谢

编辑

谢谢你的回答,我终于有了这个解决方案

在my 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分钟,但我不需要超高速。内存使用率可以上升到所有内存,用于文件读取,而不是用于sigmoid。

如果您的文件是按id排序的,您可以使用
按分区
而不是
按分组

那么您的代码将如下所示:

(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))))
我们有一千万个项目数据集,其中有百万个
{:Id 0:Max 1:Probability 10}
,百万个
{: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"
请注意,
partition by
会产生延迟序列,而group 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

ID的基数是高还是低?csv中的ID是否有序?如果是这样,您可以在一次通过csv时进行分组。您好,感谢您的回复。我有大约120-130万个ID(比实际数据少10倍)。文件的顺序与我的示例相同,即:第一级=ID,第二级=Max(概率和最大值的顺序相同,因为它们由增长曲线连接)。因此,也许你的想法是好的,但我仍然不知道如何去做。循环是个好主意吗?我认为它不会从多重处理中受益。我将尝试使用merge和by firt重新格式化数据。您好,感谢您的回答。我用您的示例数据尝试了您的解决方案,速度要快得多。使用我的CSV,速度非常慢。因此,可能是c因为是文件读取时发出的咕噜声。我不知道如何解决它,但似乎分组不是真正的问题(即使我在你的帖子中学到了更好的解决方案).但问题是,我在使用def时遇到了Java堆空间问题,奇怪的是我有16个Go ram。嗨!你如何加载和解析css文件?你能更新你的问题吗?Java堆空间问题可能可以通过jvm调优解决,通过设置
Xmx
值..但真正的问题可能与事实有关保留所有加载的数据(甚至是不需要的)。是的,我会调查。我真的看不出哪里不懒惰,因为单独应用时,操作似乎很快(即使csv读取器也会返回懒惰的seq)但是当谈到包装它们时,有些地方出现了问题。奇怪的是,我处理的是来自Google Cloud的非常大的数据,我没有遇到任何问题。它最终成功了,谢谢。我根据我的使用情况调整了一些代码,并增加了java堆空间!编辑了我的帖子。
(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"
(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