Как я должен выборочно суммировать несколько осей массива? - PullRequest
0 голосов
/ 28 октября 2018

Каков предпочтительный подход в J для выборочного суммирования нескольких осей массива?

Например, предположим, что a - это следующий массив ранга 3:

   ]a =: i. 2 3 4
 0  1  2  3
 4  5  6  7
 8  9 10 11

12 13 14 15
16 17 18 19
20 21 22 23

Myцель состоит в том, чтобы определить диаду "sumAxes" для суммирования по нескольким осям по моему выбору:

   0 1 sumAxes a      NB. 0+4+8+12+16+20 ...
60 66 72 78

   0 2 sumAxes a      NB. 0+1+2+3+12+13+14+15 ...
60 92 124

   1 2 sumAxes a      NB. 0+1+2+3+4+5+6+7+8+9+10+11 ...
66 210

Способ, которым я сейчас пытаюсь реализовать этот глагол, состоит в том, чтобы использовать диаду |: для первой перестановкиосей a, а затем разбейте элементы необходимого ранга, используя ,"n (где n - число осей, по которым я хочу подвести итог) перед суммированием результирующих элементов:

   sumAxes =: dyad : '(+/ @ ,"(#x)) x |: y'

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

Для контекста большая часть моего предыдущего опыта программирования массивов связана с библиотекой Python NumPy.

NumPy не имеет концепции ранга J и вместо этого ожидает, что пользователь явно пометит оси массива для уменьшения:

>>> import numpy
>>> a = numpy.arange(2*3*4).reshape(2, 3, 4) # a =: i. 2 3 4
>>> a.sum(axis=(0, 2))                       # sum over specified axes
array([ 60,  92, 124])

В качестве сноски моя текущая реализация sumAxes имеет недостаток, заключающийся в том, что он работает "неправильно" по сравнению с NumPy, когда указана только одна ось (поскольку ранг не взаимозаменяем с "осью").

1 Ответ

0 голосов
/ 28 октября 2018

Мотивация

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

Большая ось (фактически, ведущие оси в целом) неявно привилегированные .Эта концепция лежит в основе, например, #, представляющего собой количество элементов (т. Е. Размерность первой оси), заниженную элегантность и универсальность +/ без дальнейшей модификации и множество другихпрекрасные части языка.

Но это также то, что объясняет препятствия, с которыми вы сталкиваетесь, пытаясь решить эту проблему.

Стандартный подход

Так что общий подход кРешение проблемы происходит так же, как у вас: переставьте или иным образом переставьте данные , чтобы интересующие вас оси стали ведущими осями .Ваш подход классический и безупречный.Вы можете использовать его с чистой совестью.

Альтернативные подходы

Но, как и вы, меня немного раздражает, что мы вынуждены перепрыгивать через такие обручи в подобных обстоятельствах.Один из признаков того, что мы работаем против структуры языка, - это динамический аргумент соединения "(#x);обычно аргументы для конъюнкций фиксированы, и их вычисление во время выполнения часто вынуждает нас использовать либо явный код (как в вашем примере), либо значительно более сложный код .Когда язык делает что-то трудное, обычно это знак того, что вы режете зерно.

Другой - это ворон (,).Мы не просто хотим транспонировать некоторые оси;это то, что мы хотим сфокусироваться на одной конкретной оси, а затем запустить все элементы, ведущие ее в плоский вектор.Хотя на самом деле я думаю, что это отражает скорее ограничение, налагаемое тем, как мы формулируем проблему, а не то, что в нотации.Более подробно в последнем разделе этого поста.

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

Вводные примеры

Например, диадический |. ( rotate ) имеет ранг 1 _, то есть занимает вектор слева.

Это иногда удивляет людей, которые имеютиспользовал это в течение многих лет, никогда не проходя больше чем скаляр слева.Это, наряду с несвязанным правым рангом, является еще одним тонким следствием смещения ведущей оси J: мы рассматриваем правый аргумент как вектор элементов , а левый аргумент - как простое скалярное значение вращения этого вектора .

Таким образом:

   3 |. 1 2 3 4 5 6
4 5 6 1 2 3

и

   1 |. 1 2 , 3 4 ,: 5 6
3 4
5 6
1 2

Но в этом последнем случае, что если бы мы не хотелиобрабатывать таблицу как вектор строк , но как вектор столбцов ?

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

   1 |."1 ] 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5

Теперь, это совершенно идиоматично, стандартно и вездесущев коде J: J побуждает нас думать с точки зрения ранга.Никто не будет моргать глазом при чтении этого кода.

Но, как описано в начале, в другом смысле это может показаться отговоркой или ручной регулировкой.Особенно, когда мы хотим динамически выбирать ранг во время выполнения.В настоящее время мы больше не обращаемся к массиву в целом, а обращаемся к каждой строке .

И вот тут появляется левый ранг |.: это один из немногихпримитивы, которые могут адресовать не ведущие оси напрямую .

   0 1 |. 1 2 , 3 4 ,: 5 6
2 1
4 3
6 5

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

Прямое суммирование не ведущих осей

Итак, теперь мы знаем, что J позволяет нам обращаться к не ведущим осям в определенныхслучаев, у нас просто есть , чтобы рассмотреть эти случаи и определить случай, который кажется подходящим для нашей цели.

Примитив, который я нашел наиболее полезным для работы без ведущей оси: ;. с в штучной упаковке левым аргументом.Так что мой инстинкт - сначала достичь этого.

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

    ]a =: i. 2 3 4
    sumAxes =: dyad : '(< @ ,"(#x)) x |: y'

     0 1 sumAxes a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+ 
     0 2 sumAxes a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
    1 2 sumAxes a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+

Соответствующая часть определенияиз для диад, полученных из ;.1 и друзей:

Лады в диадических случаях 1, _1, 2 и _2 определяются1 в логическом векторе x; пустой вектор x и ненулевой #y указывают на все y . Если x является атомом 0 или 1, он рассматривается как (#y)#x.В целом, логический вектор >j{x указывает, как должна быть вырезана ось j, с атомом, который рассматривается как (j{$y)#>j{x.

Что это означает: если мы простопытаясь разрезать массив по его измерениям без внутренней сегментации, мы можем просто использовать диаду с левым аргументом, состоящим только из 1 s и a: s .Число 1 s в векторе (т. Е. Сумма) определяет ранг полученного массива.

Таким образом, чтобы воспроизвести приведенные выше примеры:

     ('';'';1) <@:,;.1 a
+--------------+--------------+---------------+---------------+
|0 4 8 12 16 20|1 5 9 13 17 21|2 6 10 14 18 22|3 7 11 15 19 23|
+--------------+--------------+---------------+---------------+
     ('';1;'') <@:,;.1 a
+-------------------+-------------------+---------------------+
|0 1 2 3 12 13 14 15|4 5 6 7 16 17 18 19|8 9 10 11 20 21 22 23|
+-------------------+-------------------+---------------------+
     (1;'';'') <@:,;.1 a
+-------------------------+-----------------------------------+
|0 1 2 3 4 5 6 7 8 9 10 11|12 13 14 15 16 17 18 19 20 21 22 23|
+-------------------------+-----------------------------------+

Et voila.Кроме того, обратите внимание на шаблон в аргументе левой руки?Два туза точно по индексам ваших исходных вызовов sumAxe.Понимаете, что я подразумеваю под тем фактом, что предоставление значения для каждого измерения, которое пахнет как хорошая вещь, в духе J?

Итак, чтобы использовать этот подход для предоставления аналога sumAxe с тем же интерфейсом:

   sax =: dyad : 'y +/@:,;.1~ (1;a:#~r-1) |.~ - {. x -.~ i. r=.#$y'     NB. Explicit
   sax =: ]  +/@:,;.1~  ( (] (-@{.@] |. 1 ; a: #~ <:@[) (-.~ i.) ) #@$) NB. Tacit

Результаты приведены для краткости, но они идентичны вашим sumAxe.

Заключительные соображения

Есть еще одна вещь, на которую я хотел бы обратить внимание,Интерфейс для вашего sumAxe вызова, вызванный из Python, называет две оси, которые вы хотели бы «запустить вместе».Это, безусловно, один из способов взглянуть на это.

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

Это изменение в перспективе, когда мы говорим о том, что вас интересует, имеет то преимущество, что это всегда одна вещь , и эта особенность допускает некоторые упрощения в нашем коде (опять же,особенно в J, где мы обычно говорим о [новой, т. е. пост-транспонированной] ведущей оси) ¹.

Давайте еще раз посмотрим на наши векторные аргументы с единичными знаками ;., чтобы проиллюстрировать, что яозначает:

     ('';'';1) <@:,;.1 a
     ('';1;'') <@:,;.1 a
     (1;'';'') <@:,;.1 a

Теперь рассмотрим три заключенных в скобки аргумента как одну матрицу из трех строк.Что выделяется для вас?Для меня это те, которые вдоль анти-диагонали.Они менее многочисленны и имеют ценности;напротив, тузы образуют «фон» матрицы (нули).Они являются истинным контентом.

В отличие от того, как теперь работает наш интерфейс sumAxe: он просит нас указать тузы (нули).Как же вместо этого мы указываем 1, то есть ось, которая на самом деле нас интересует ?

Если мы сделаем это, мы можем переписать наши функции следующим образом:

  xas  =: dyad : 'y +/@:,;.1~ (-x) |. 1 ; a: #~ _1 + #$y'  NB. Explicit
  xas  =: ]  +/@:,;.1~  -@[ |. 1 ; a: #~ <:@#@$@]          NB. Tacit

Ивместо вызова 0 1 sax a вы бы назвали 2 xas a, вместо 0 2 sax a, вы бы назвали 1 xas a и т. д.

Относительная простота этих двух глаголов предполагает, что J согласен с этой инверсиейфокуса.


¹ В этом коде я предполагаю, что вы всегда хотите свернуть все оси, кроме 1. Это предположение закодировано в подходе, который я использую для генерации вектора единичных различий, используя |..

Тем не менее, ваша сноска sumAxes имеет недостаток, заключающийся в том, что она работает «неправильно» по сравнению с NumPy, когда указана только одна ось предполагает, что иногда требуется свернуть только одну ось.

Это вполне возможно, и подход ;. может принимать произвольные (ортотопические) срезы;нам нужно всего лишь изменить метод, которым мы его инструктируем (сгенерировать вектор 1s-and-aces).Если вы предоставите пару примеров обобщения, которые вы хотели бы, я обновлю пост здесь.Вероятно, просто вопрос использования (<1) x} a: #~ #$y или ((1;'') {~ (e.~ i.@#@$)) вместо (-x) |. 1 ; a:#~<:#$y.

...