2016-02-01 15 views
6

Tình hìnhClojure: Nhóm-by quá chậm (nộp 13 triệu-lines)

Tôi có 13 triệu dòng CSV mà tôi muốn thực hiện hồi quy logistic (incanter) cho mỗi nhóm. tập tin của tôi là như thế (giá trị chỉ là mẫu)

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

Vì vậy, đầu tiên tôi đọc nó với một csv-reader, everithing là tốt.

Tôi có sau đó một cái gì đó như thế:

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

Tôi muốn nhóm theo các giá trị của Id, Nếu tôi nhớ không lầm thì có khoảng 1,2 triệu Id. (Tôi đã làm nó bằng Python với gấu trúc và nó là siêu nhanh)

Đây là chức năng của tôi để đọc và định dạng tập tin (nó hoạt động tốt trên các tập dữ liệu nhỏ hơn):

(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 {})))) 

Tôi muốn cuối cùng để có một cái gì đó như vậy để thực hiện hồi quy logistic (tôi linh hoạt về điều đó, không cần vectơ cho: x và: y, seqs là ok)

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

vấn đề

Tôi gặp sự cố với thao tác theo nhóm. Tôi đã thử nó một cách riêng biệt trên đầu ra từ CSV và điều này được lấy mãi mãi khi nó không chết vì bộ nhớ Java Heap Space. Tôi nghĩ rằng vấn đề là điều mapv của tôi nhưng đây là nhóm-by.

Tôi đã nghĩ về việc sử dụng giảm hoặc giảm kv nhưng tôi không biết cách sử dụng các chức năng này cho loại mục đích này.

Tôi không quan tâm đến thứ tự ": x" và ": y" (ngay khi chúng giống nhau giữa chúng, tôi có nghĩa là x và y có cùng chỉ mục ... không phải là vấn đề vì chúng trên cùng một dòng) và của các Id trên kết quả cuối cùng và tôi đọc nhóm đó bằng cách giữ nguyên thứ tự. Có thể đó là điều tốn kém cho hoạt động?

tôi cung cấp cho bạn dữ liệu mẫu nếu có người đã gặp rằng:

(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})) 

lựa chọn thay thế khác

tôi có những ý tưởng khác, nhưng tôi không chắc chắn họ là "Clojure" -friendly.

  • Trong Python, vì bản chất của hàm và bởi vì các tập tin đã được ra lệnh, thay vì sử dụng nhóm-by, tôi đã viết trong một dataframe bắt đầu và chỉ số cuối cho mỗi nhóm vì vậy mà tôi chỉ phải chọn trực tiếp tiểu datatab.

  • Tôi cũng có thể tải danh sách các id thay vì tính toán nó từ Clojure. Giống như

    (id id '("1" "2" v.v ...

Vì vậy, có lẽ chúng ta có thể bắt đầu với:

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

từ seq trước và sau đó kết hợp các tập tin lớn trên mỗi ID.

Tôi không biết liệu thực tế có hiệu quả hơn hay không.

Tôi có tất cả các chức năng khác để hồi quy logistic, tôi chỉ thiếu phần này! Cảm ơn!

EDIT

Cảm ơn câu trả lời, cuối cùng tôi có giải pháp này.

Trong file project.clj tôi

:jvm-opts ["-Xmx13g"]) 

Code:

(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))) 

Tôi bọc tất cả các mã của tôi và nó hoạt động. Việc phân chia mất khoảng 5 phút nhưng tôi không cần tốc độ lớn. Sử dụng bộ nhớ có thể tăng lên đến tất cả bộ nhớ để đọc tập tin sau đó ít hơn cho sigmoid.

+1

là cardinality của id cao hay thấp? Các id trong csv có được sắp xếp không? Nếu vậy, bạn có thể thực hiện việc nhóm trong khi bạn đang đọc qua CSV trong một lần truyền. –

+0

Xin chào, cảm ơn bạn đã trả lời. Tôi có khoảng 1,2-1,3 triệu id (thấp hơn 10 lần so với dữ liệu thực tế). Tệp được đặt hàng như ví dụ của tôi, tức là: cấp đầu tiên = ID, cấp hai = Max (Xác suất và Tối đa được sắp xếp giống nhau vì chúng được liên kết bằng đường cong đang phát triển). Vì vậy, có lẽ ý tưởng của bạn là tốt, tôi vẫn không biết làm thế nào để làm điều đó. Vòng lặp có phải là một ý tưởng hay không? Nó không mang lại lợi ích của đa xử lý tôi nghĩ. Tôi sẽ thử một cái gì đó với hợp nhất với firt định dạng lại dữ liệu. –

Trả lời

6

nếu tệp của bạn được sắp xếp theo id, bạn có thể sử dụng partition-by thay vì group-by.

sau đó mã của bạn sẽ trông như thế này:

(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 {})))) 

rằng cần đẩy nó lên. Sau đó, bạn có lẽ có thể làm cho nó nhanh hơn sử dụng đầu dò

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

chúng ta hãy làm một số xét nghiệm:

đầu tiên tạo ra một dữ liệu khổng lồ như của bạn:

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

chúng tôi có mười triệu mục dữ liệu, với triệu của {:Id 0 :Max 1 :Probability 10}, triệu của {:Id 1 :Max 1 :Probability 10} và cứ tiếp tục như vậy.

tại chức năng để được kiểm tra:

(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)) 

và bây giờ kiểm tra thời gian:

(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" 

Thông báo, rằng partition-by sản xuất chuỗi lười biếng, trong khi nhóm-by nên nhận ra toàn bộ bộ sưu tập. Vì vậy, nếu bạn cần nhóm dữ liệu theo nhóm, không phải toàn bộ bản đồ, bạn có thể xóa (into {}) và truy cập mỗi một nhanh hơn:

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

kiểm tra:

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 
+0

Xin chào, cảm ơn câu trả lời. Tôi đã thử giải pháp của bạn với dữ liệu mẫu của bạn và nó nhanh hơn nhiều. Với CSV của tôi, nó rất chậm. Vì vậy, có lẽ nguyên nhân là tập tin đọc với slurp. Tôi không biết làm thế nào để giải quyết nó nhưng có vẻ như nhóm-by không phải là vấn đề thực sự (ngay cả khi tôi đã học được một giải pháp tốt hơn với bài viết của bạn). Nhưng vấn đề là tôi có vấn đề Java Heap Space khi sử dụng def, beacuase lạ tôi có 16 Go ram. –

+0

Xin chào! Làm thế nào để bạn tải và phân tích cú pháp tệp css của bạn? bạn có thể cập nhật câu hỏi của mình không? – leetwinski

+0

Vấn đề với không gian java heap có thể được giải quyết bằng cách điều chỉnh jvm, bằng cách đặt giá trị 'Xmx'. http://stackoverflow.com/questions/14763079/what-are-the-xms-and-xmx-parameters-when-starting-jvms. Nhưng vấn đề thực sự, có thể được kết nối với thực tế là bạn giữ lại tất cả các dữ liệu được tải (thậm chí không cần thiết). – leetwinski