Статьи

Clojure: чтение и запись файла разумного размера

В посте пару дней назад я описал некоторый код, который я написал на R, чтобы выяснить все функции с нулевой дисперсией в наборе данных Kaggle Digit Recognizer, и вчера я начал работать над некоторым кодом для удаления этих функций.

Джен и я ранее написали некоторый код для анализа обучающих данных в Clojure, поэтому я подумал, что я попытаюсь адаптировать его для записи нового файла без нежелательных пикселей.

В первой версии мы инкапсулировали чтение файла и его разбор в более полезную структуру данных, например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
(defn get-pixels [pix] (map #( Integer/parseInt %) pix))
 
(defn create-tuple [[ head & rem]] {:pixels (get-pixels rem) :label head})
 
(defn tuples [rows] (map create-tuple rows))
 
(defn parse-row [row] (map #(clojure.string/split % #",") row))
 
(defn read-raw [path n]
  (with-open [reader (clojure.java.io/reader path)] (vec (take n (rest  (line-seq reader))))))
 
(def read-train-set-raw  (partial read-raw "data/train.csv"))
 
(def parsed-rows (tuples (parse-row (read-train-set-raw 42000))))

Таким образом, def parsed- row дает представление в памяти строки, где мы разделили метку и пиксели на разные ключевые записи на карте. Мы хотели удалить все пиксели, которые имели дисперсию 0 в наборе данных, что в данном случае означает, что они всегда имеют значение 0:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
(def dead-to-us-pixels
  [0 1 2 3 4 5 6 7 8 9 10 11 16 17 18 19 20  21  22  23  24  25  26  27 28 29  30 31 52 53 54 55 56 57 82 83 84 85 111 112 139 140 141 168 196 392 420 421 448 476 532 560 644 645 671 672 673 699 700 701 727 728 729 730 731 754 755 756 757 758 759 760 780 781 782 783])
 
(defn in?
  "true if seq contains elm"
  [seq elm] 
  (some #(= elm %) seq))
 
(defn dead-to-us? [pixel-with-index]
  (in? dead-to-us-pixels (first pixel-with-index)))
 
(defn remove-unwanted-pixels [row]
  (let [new-pixels
        (->> row :pixels (map-indexed vector) (remove dead-to-us?) (map second))]
    {:pixels new-pixels :label (:label row)}))
 
(defn -main []
  (with-open [wrt (clojure.java.io/writer "/tmp/attempt-1.txt")]
    (doseq [line parsed-rows]
      (let [line-without-pixels (to-file-format (remove-unwanted-pixels line))]
        (.write wrt (str line-without-pixels "\n"))))))

Затем мы запустили метод main, используя leon run, который выписал новый файл. Экран печати использования кучи во время работы этой функции выглядит следующим образом:

Encapsulated read tiff

Когда я писал эту версию функции, я где-то допустил ошибку и в итоге передал неправильную структуру данных одной из функций, что привело к тому, что все промежуточные этапы, которые проходит структура данных, сохранялись в памяти и вызывали исключение OutOfMemory.

Дамп кучи показал следующее:

Gone wrong tiff

Когда я уменьшил размер ошибочной коллекции с помощью ‘take 10’, я получил исключение, указывающее, что функция не может обработать структуру данных, что позволило мне разобраться с ней.

Сначала я думал, что проблема заключается в загрузке файла в память вообще, но так как вышеприведенное, кажется, работает, я не думаю, что это так. Когда я работал над этой теорией, Джен предположила, что может иметь больше смысла делать чтение и запись файлов внутри «with-open», что соответствует предложению, с которым я столкнулся в посте StackOverflow .

Я получил следующий код:

01
02
03
04
05
06
07
08
09
10
(defn split-on-comma [line]
  (string/split line #","))
 
(defn clean-train-file []
  (with-open [rdr (clojure.java.io/reader "data/train.csv")
              wrt (clojure.java.io/writer "/tmp/attempt-2.csv")]
    (doseq [line (drop 1 (line-seq rdr))]
      (let [line-with-removed-pixels
             ((comp to-file-format remove-unwanted-pixels create-tuple split-on-comma) line)]
        (.write wrt (str line-with-removed-pixels "\n"))))))

Который вызывается в основном методе так:

1
(defn -main [] (clean-train-file))

Эта версия имела следующее использование кучи:

All in with open tiff

Его пики немного ниже, чем у первого, и похоже, что он буферизует несколько строк, записывает их в файл (и, следовательно, не хватает памяти) и повторяет.

Ссылка: Clojure: чтение и запись файла разумного размера от нашего партнера по JCG Марка Нидхэма в блоге Марка Нидхэма .