Использование numpy для векторизации вычитания массива со скаляром (через другой массив) без использования двойного цикла for - PullRequest
0 голосов
/ 25 октября 2018

Предположим, кто-то хотел использовать numpy для векторизации вычитаний массива.В качестве примера рассмотрим следующую настройку (код ниже): Я вычисляю евклидово расстояние между некоторыми (x, y) точками с данным центроидом.Причина этого вопроса в том, что приведенный ниже пример кода работает точно для 2-х измерений (x и y), но я бы хотел обобщить и адаптировать эту операцию к N-измерениям в целях адаптации моего Алгоритм k-средних .Приведенный ниже код предназначен только для вычисления ошибки с учетом указанного центроида.

import numpy as np

np.random.seed(10) ## for reproducibility
x = np.random.normal(40, 10, 10)
y = np.random.normal(50, 10, 10)
data = np.array([x, y])

centroids = np.array([[25, 75], [45, 55], [20, 80], [40, 60]])
k = len(centroids)
print("\nDATA:\n{}\n\n{} CENTROIDS:\n{}\n".format(data, k, centroids))
partials = np.array([[(data[i] - centroid[i])**2 for i in range(len(data))] for centroid in centroids])
res = np.sqrt(np.sum(partials))
print("\nPARTIAL DISTANCES:\n{}\n\nTOTAL DISTANCE:\n{}\n".format(partials, res))

Запуск приведенного выше кода приводит к следующему выводу:

DATA:
[[53.31586504 47.15278974 24.54599708 39.9161615  46.21335974 32.79914439
  42.65511586 41.08548526 40.04291431 38.25399789]
 [54.3302619  62.03037374 40.34934329 60.28274078 52.2863013  54.45137613
  38.63397788 51.35136878 64.84537002 39.20195114]]

4 CENTROIDS:
[[25 75]
 [45 55]
 [20 80]
 [40 60]]


PARTIAL DISTANCES:
[[[8.01788213e+02 4.90746093e+02 2.06118652e-01 2.22491874e+02
   4.50006631e+02 6.08266533e+01 3.11703116e+02 2.58742836e+02
   2.26289271e+02 1.75668460e+02]
  [4.27238073e+02 1.68211205e+02 1.20066801e+03 2.16597719e+02
   5.15912109e+02 4.22245943e+02 1.32248756e+03 5.59257758e+02
   1.03116510e+02 1.28150030e+03]]

 [[6.91536114e+01 4.63450368e+00 4.18366235e+02 2.58454139e+01
   1.47224186e+00 1.48860878e+02 5.49848164e+00 1.53234257e+01
   2.45726985e+01 4.55085444e+01]
  [4.48549123e-01 4.94261549e+01 2.14641742e+02 2.79073501e+01
   7.36416063e+00 3.00988153e-01 2.67846680e+02 1.33125097e+01
   9.69313108e+01 2.49578348e+02]]

 [[1.10994686e+03 7.37273991e+02 2.06660894e+01 3.96653489e+02
   6.87140229e+02 1.63818097e+02 5.13254274e+02 4.44597689e+02
   4.01718414e+02 3.33208439e+02]
  [6.58935454e+02 3.22907468e+02 1.57217458e+03 3.88770311e+02
   7.68049096e+02 6.52732182e+02 1.71114779e+03 8.20744071e+02
   2.29662810e+02 1.66448079e+03]]

 [[1.77312262e+02 5.11624011e+01 2.38826206e+02 7.02889396e-03
   3.86058392e+01 5.18523215e+01 7.04964021e+00 1.17827824e+00
   1.84163795e-03 3.04852335e+00]
  [3.21459301e+01 4.12241752e+00 3.86148309e+02 7.99423486e-02
   5.95011476e+01 3.07872269e+01 4.56506901e+02 7.47988219e+01
   2.34776106e+01 4.32558836e+02]]]

TOTAL DISTANCE:
163.00230640508593

Я использую вложенный двойной цикл forв этом коде.Я заметил, что numpy.subtract не имеет axis kwarg.Я думал, что смогу numpy.tile центроидам выполнить вычитание, но это кажется неэффективным для больших N, особенно если для сходимости требуется много итераций.Есть ли другой способ векторизации этой операции?

1 Ответ

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

Вы можете использовать expand_dims для создания отсутствующей оси:

partials = (data.T - np.expand_dims(centroids, axis=1))**2

Таким образом data.T имеет форму (10,2), и вы вычитаете из нее массив с формой (4,1,2), так что вычитание получаетсяшироковещательная рассылка по второй оси этого массива.

Вы также можете сделать это, добавив дополнительную ось в конце centroids и не транспонируя `data:

partials = (data - centroids[:,:,np.newaxis])**2
...