Эффективно вычесть вектор из Матрицы (Scipy)
у меня есть большая матрица, хранящаяся как scipy.редкий.csc_matrix и хотите вычесть вектор столбца из каждого из столбцов в большой матрице. Это довольно распространенная задача, когда вы делаете такие вещи, как нормализация/стандартизация, но я не могу найти правильный способ сделать это эффективно.
вот пример, чтобы продемонстрировать:
# mat is a 3x3 matrix
mat = scipy.sparse.csc_matrix([[1, 2, 3],
[2, 3, 4],
[3, 4, 5]])
#vec is a 3x1 matrix (or a column vector)
vec = scipy.sparse.csc_matrix([1,2,3]).T
"""
I want to subtract `vec` from each of the columns in `mat` yielding...
[[0, 1, 2],
[0, 1, 2],
[0, 1, 2]]
"""
один из способов выполнить то, что я хочу, это hstack vec
к себе 3 раза, давая матрицу 3x3, где каждый колонка vec
а затем вычесть это из mat
. Но опять же, я ищу способ сделать это эффективно, и hstacked матрица занимает много времени, чтобы создать. Я уверен, что есть какой-то волшебный способ сделать это с нарезкой и трансляцией, но он ускользает от меня.
спасибо!
EDIT: удалено ограничение "на месте", потому что структура разреженности будет постоянно меняться в сценарии назначения на месте.
3 ответов
для начала, что мы будем делать с плотными массивами?
mat-vec.A # taking advantage of broadcasting
mat-vec.A[:,[0]*3] # explicit broadcasting
mat-vec[:,[0,0,0]] # that also works with csr matrix
In https://codereview.stackexchange.com/questions/32664/numpy-scipy-optimization/33566
мы обнаружили, что с помощью as_strided
на mat.indptr
вектор является наиболее эффективным способом перехода через строки разреженной матрицы. (The x.rows
, x.cols
на lil_matrix
почти так же хорошо. getrow
медленно). Эта функция реализует такие функции, как итерация.
def sum(X,v):
rows, cols = X.shape
row_start_stop = as_strided(X.indptr, shape=(rows, 2),
strides=2*X.indptr.strides)
for row, (start, stop) in enumerate(row_start_stop):
data = X.data[start:stop]
data -= v[row]
sum(mat, vec.A)
print mat.A
я использую vec.A
для простота. Если мы сохраним vec
sparse нам придется добавить тест на ненулевое значение в row
. Также Этот тип итерации изменяет только ненулевые элементы mat
. 0's
не изменился.
я подозреваю, что преимущества времени будут во многом зависеть от разреженности матрицы и вектора. Если vec
имеет много нулей, тогда имеет смысл перебирать, изменяя только те строки mat
здесь vec
нулю. Но!--9--> почти плотный, как этот пример, это может быть трудно победить mat-vec.A
.
резюме
короче говоря, если вы используете CSR вместо CSC, это однострочный:
mat.data -= numpy.repeat(vec.toarray()[0], numpy.diff(mat.indptr))
объяснение
если вы это поняли, это лучше сделать по порядку, так как мы будем вычитать одно и то же число из каждой строки. В вашем примере: вычтите 1 из первой строки, 2 из второй строки, 3 из третьей строки.
Я действительно столкнулся с этим в приложении реальной жизни, где я хочу классифицировать документы, каждый из которых представлен как строка в матрица, в то время как столбцы представляют слова. Каждый документ имеет оценку, которая должна быть умножена на оценку каждого слова в этом документе. Используя строковое представление разреженной Матрицы, я сделал что-то подобное этому (я изменил свой код, чтобы ответить на ваш вопрос):
mat = scipy.sparse.csc_matrix([[1, 2, 3],
[2, 3, 4],
[3, 4, 5]])
#vec is a 3x1 matrix (or a column vector)
vec = scipy.sparse.csc_matrix([1,2,3]).T
# Use the row version
mat_row = mat.tocsr()
vec_row = vec.T
# mat_row.data contains the values in a 1d array, one-by-one from top left to bottom right in row-wise traversal.
# mat_row.indptr (an n+1 element array) contains the pointer to each first row in the data, and also to the end of the mat_row.data array
# By taking the difference, we basically repeat each element in the row vector to match the number of non-zero elements in each row
mat_row.data -= numpy.repeat(vec_row.toarray()[0],numpy.diff(mat_row.indptr))
print mat_row.todense()
что приводит к:
[[0 1 2] [0 1 2] [0 1 2]]
визуализация-это что-то вроде этого:
>>> mat_row.data
[1 2 3 2 3 4 3 4 5]
>>> mat_row.indptr
[0 3 6 9]
>>> numpy.diff(mat_row.indptr)
[3 3 3]
>>> numpy.repeat(vec_row.toarray()[0],numpy.diff(mat_row.indptr))
[1 1 1 2 2 2 3 3 3]
>>> mat_row.data -= numpy.repeat(vec_row.toarray()[0],numpy.diff(mat_row.indptr))
[0 1 2 0 1 2 0 1 2]
>>> mat_row.todense()
[[0 1 2]
[0 1 2]
[0 1 2]]
вы можете ввести поддельные размеры, изменив strides
ваш вектор. Вы можете без дополнительного выделения "преобразовать" свой вектор в матрицу 3 x 3, используя np.lib.stride_tricks.as_strided
. Это страница имеет пример и немного обсуждения об этом вместе с некоторым обсуждением связанных тем (например, представлений). Поиск по странице " пример: поддельные размеры с шагами."
есть также довольно много примеров на SO об этом... но мои навыки поиска не меня сейчас.