Преобразование вложенного списка без копирования или потери точности - PullRequest
6 голосов
/ 07 января 2012

Я использую Mathematica 7 для обработки большого набора данных.Набор данных представляет собой трехмерный массив целых чисел со знаком.Три уровня можно рассматривать как соответствующие X баллов за снимок , Y снимков за сканирование и Z сканирований за набор .

У меня также есть выстрел «обнуления» (содержащий X точек, которые являются дробными знаками целых чисел ), который я хотел бы вычесть из каждого снимка в наборе данных.После этого мне больше никогда не понадобится исходный набор данных.

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

Следующее автономноекод захватывает все аспекты того, что я пытаюсь сделать:

(* Create some offsetted data, and a zero data set. *)
myData = Table[Table[Table[RandomInteger[{1, 100}], {k, 500}], {j, 400}], {i, 200}];
myZero = Table[RandomInteger[{1, 9}]/RandomInteger[{1, 9}] + 50, {i, 500}];

(* Method 1 *)
myData = Table[
   f1 = myData[[i]];
   Table[
     f2 = f1[[j]];
     f2 - myZero, {j, 400}], {i, 200}];

(* Method 2 *)
Do[
 Do[
  myData[[i]][[j]] = myData[[i]][[j]] - myZero, {j, 400}], {i, 200}]

(* Method 3 *)
Attributes[Zeroing] = {HoldFirst};
Zeroing[x_] := Module[{}, 
   Do[
     Do[
       x[[i]][[j]] = x[[i]][[j]] - myZero, {j, Length[x[[1]]]}
       ], {i, Length[x]}
     ]
 ];

(Примечание: шляпа подсказка для Аарона Хонеккера для метода № 3.)

На моеммашина (процессор Intel Core2 Duo 3,17 ГГц, 4 ГБ ОЗУ, 32-разрядная ОС Windows 7), все три метода используют примерно 1,25 ГБ памяти, с обтекателем № 2 и № 3 чуть лучше.

Если я неНе забывайте терять точность, оборачивая N[ ] во внутренности myData и myZero, когда они создаются, изначально увеличивает их размер в памяти на 150 МБ, но уменьшает объем памяти, необходимый для обнуления (методами # 1-№ 3 выше) с 1,25 ГБ до всего лишь 300 МБ!Это мое рабочее решение, но было бы здорово узнать, как лучше всего решить эту проблему.

Ответы [ 2 ]

6 голосов
/ 07 января 2012

К сожалению, у меня сейчас мало времени, поэтому я должен быть кратким ...

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

Пожалуйста, оцените ?Developer`*Packed*, чтобы увидеть, какие функции доступны для прямого преобразования в / из них, если этоне происходит автоматически.

Итак, краткое объяснение того, почему мое решение быстрое и эффективно использует память, состоит в том, что оно использует упакованные массивы.Я проверял Developer`PackedArrayQ, что мои массивы никогда не распаковывались, и я использовал машинные реалы (я применил N[] ко всему)

In[1]:= myData = N@RandomInteger[{1, 100}, {200, 400, 500}];

In[2]:= myZero = 
  Developer`ToPackedArray@
   N@Table[RandomInteger[{1, 9}]/RandomInteger[{1, 9}] + 50, {i, 500}];

In[3]:= myData = Map[# - myZero &, myData, {2}]; // Timing

Out[3]= {1.516, Null}

Кроме того, операция, которую вы запрашивали ("Я бы хотелсканирование каждого элемента и изменение его в этом месте в памяти ") называется отображением (см. Map[] или /@).

3 голосов
/ 08 января 2012

Позвольте мне начать с того, что этот ответ должен рассматриваться как дополнительный к ответу @Szabolcs, причем последний, по моему мнению, является лучшим вариантом.Хотя решение @Szabolcs, вероятно, является самым быстрым и лучшим в целом, оно не соответствует первоначальной спецификации в том, что Map возвращает (измененную) копию исходного списка, а не «сканирует каждый элемент и изменяет его в этом местев памяти".Такое поведение, AFAIK, обеспечивается только командой Part.Я буду использовать его идеи (преобразование всего в упакованные массивы), чтобы показать код, который выполняет изменения в памяти, в исходный список:

In[5]:= 
Do[myData[[All,All,i]]=myData[[All,All,i]]- myZero[[i]],
     {i,Last@Dimensions@myData}];//Timing

Out[5]= {4.734,Null}

Это концептуально эквивалентно методу 3, указанному в вопросе, ноработает намного быстрее, потому что это частично векторизованное решение, и требуется только один цикл.Это, однако, все еще по крайней мере на порядок медленнее, чем решение @Szabolcs.

Теоретически кажется классическим компромиссом между скоростью и памятью: если вам нужна скорость и немного свободной памяти, решение @ Szabolcs - это путь.Если у вас жесткие требования к памяти, в теории этот более медленный метод позволит сэкономить промежуточное потребление памяти (в методе @Szabolcs исходный список собирается после «1014 *).присваивается результат Map, поэтому окончательное использование памяти остается тем же, но во время вычисления, один дополнительный массив размером myData поддерживается Map).

На практике , однако, потребление памяти, по-видимому, не меньше, так как дополнительная копия списка по какой-то причине поддерживается в переменной Out в обоих случаях во время (или сразу после) вычисления, даже когда вывод подавлен (может также быть, что этот эффект проявляется не во всех случаях).Я пока не совсем понимаю, но мой текущий вывод заключается в том, что метод @Szabolcs так же хорош с точки зрения промежуточного потребления памяти, как и нынешний, основанный на модификациях списка на месте.Поэтому его метод кажется подходящим во всех случаях, но я все же решил опубликовать этот ответ в качестве дополнительного.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...