Эффективно вычесть вектор из Матрицы (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 об этом... но мои навыки поиска не меня сейчас.