Python Не отрицательная матричная факторизация, которая обрабатывает как нули, так и отсутствующие данные?

Я ищу реализацию NMF, которая имеет интерфейс python, и обрабатывает как отсутствующие данные, так и нули.

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

Кажется, что ни scikit-learn, ни nimfa, ни graphlab, ни mahout не предлагают такой вариант.

Спасибо!

Ответ 1

Используя этот Matlab для листа преобразования кода python Я смог переписать NMF из Библиотека инструментов Matlab.
Мне пришлось разложить матрицу 40k X 1k с разрешающей способностью 0,7%. Используя 500 скрытых функций, моя машина заняла 20 минут на 100 итераций.

Вот способ:

import numpy as np
from scipy import linalg
from numpy import dot

def nmf(X, latent_features, max_iter=100, error_limit=1e-6, fit_error_limit=1e-6):
    """
    Decompose X to A*Y
    """
    eps = 1e-5
    print 'Starting NMF decomposition with {} latent features and {} iterations.'.format(latent_features, max_iter)
    X = X.toarray()  # I am passing in a scipy sparse matrix

    # mask
    mask = np.sign(X)

    # initial matrices. A is random [0,1] and Y is A\X.
    rows, columns = X.shape
    A = np.random.rand(rows, latent_features)
    A = np.maximum(A, eps)

    Y = linalg.lstsq(A, X)[0]
    Y = np.maximum(Y, eps)

    masked_X = mask * X
    X_est_prev = dot(A, Y)
    for i in range(1, max_iter + 1):
        # ===== updates =====
        # Matlab: A=A.*(((W.*X)*Y')./((W.*(A*Y))*Y'));
        top = dot(masked_X, Y.T)
        bottom = (dot((mask * dot(A, Y)), Y.T)) + eps
        A *= top / bottom

        A = np.maximum(A, eps)
        # print 'A',  np.round(A, 2)

        # Matlab: Y=Y.*((A'*(W.*X))./(A'*(W.*(A*Y))));
        top = dot(A.T, masked_X)
        bottom = dot(A.T, mask * dot(A, Y)) + eps
        Y *= top / bottom
        Y = np.maximum(Y, eps)
        # print 'Y', np.round(Y, 2)


        # ==== evaluation ====
        if i % 5 == 0 or i == 1 or i == max_iter:
            print 'Iteration {}:'.format(i),
            X_est = dot(A, Y)
            err = mask * (X_est_prev - X_est)
            fit_residual = np.sqrt(np.sum(err ** 2))
            X_est_prev = X_est

            curRes = linalg.norm(mask * (X - X_est), ord='fro')
            print 'fit residual', np.round(fit_residual, 4),
            print 'total residual', np.round(curRes, 4)
            if curRes < error_limit or fit_residual < fit_error_limit:
                break

return A, Y

Здесь я использовал Scipy разреженную матрицу, так как входные и отсутствующие значения были преобразованы в 0 с использованием метода toarray(). Поэтому маска была создана с помощью функции numpy.sign(). Однако, если у вас есть значения nan, вы можете получить одинаковые результаты с помощью функции numpy.isnan().

Ответ 2

У Scipy есть метод для решения неотрицательной проблемы наименьших квадратов (NNLS). В этом ответе я воспроизвожу blogpost об использовании scipy NNLS для неотрицательной матричной факторизации. Вы также можете быть заинтересованы в других моих блогах, которые используют autograd, Tensorflow и CVXPY для NNMF.

Цель: нашей цели дана матрица A, разложите ее на два неотрицательных фактора следующим образом:

A (M×N) ≈ W (M×K) × H (K×N), such that  W (M×K) ≥ 0 and  H (K×N) ≥ 0 

Цель

Обзор

Наше решение состоит из двух этапов. Во-первых, мы фиксируем W и изучаем H, учитывая A. Затем мы фиксируем H и изучаем W, учитывая A. Мы повторяем эту процедуру итеративно. Фиксирование одной переменной и изучение другой (в этой настройке) широко известно как чередующиеся наименьшие квадраты, поскольку проблема сводится к задаче наименьших квадратов. Однако важно отметить, что, поскольку мы хотим ограничить W и H неотрицательными, мы используем NNLS вместо наименьших квадратов.

Шаг 1: Изучение H, заданное A и W

введите описание изображения здесь

Используя приведенную выше иллюстрацию, мы можем узнать каждый столбец H, используя соответствующий столбец из A и матрицы W.

H[:,j]=NNLS(W,A[:,j])

Обработка отсутствующих записей в A

В проблеме совместной фильтрации A обычно является матрицей пользовательского элемента, и в ней много отсутствующих записей. Эти пропущенные записи соответствуют пользователю, у которого нет позиций. Мы можем изменить нашу формулировку, чтобы учесть эти недостающие записи. Рассмотрим, что M '≤ M записей в наблюдали данные, теперь мы изменим приведенное выше уравнение как:

H[:,j]=NNLS(W[mask],A[:,j][mask])

где, маска найдена, рассматривая только записи M '.

Шаг 2: Изучение W, учитывая A и H

введите описание изображения здесь

Пример кода

Импорт

import numpy as np
import pandas as pd

Создание матрицы для факторизации

M, N = 20, 10

np.random.seed(0)
A_orig = np.abs(np.random.uniform(low=0.0, high=1.0, size=(M,N)))
print pd.DataFrame(A_orig).head()


0   1   2   3   4   5   6   7   8   9
0   0.548814    0.715189    0.602763    0.544883    0.423655    0.645894    0.437587    0.891773    0.963663    0.383442
1   0.791725    0.528895    0.568045    0.925597    0.071036    0.087129    0.020218    0.832620    0.778157    0.870012
2   0.978618    0.799159    0.461479    0.780529    0.118274    0.639921    0.143353    0.944669    0.521848    0.414662
3   0.264556    0.774234    0.456150    0.568434    0.018790    0.617635    0.612096    0.616934    0.943748    0.681820
4   0.359508    0.437032    0.697631    0.060225    0.666767    0.670638    0.210383    0.128926    0.315428    0.363711

Маскирование нескольких записей

A = A_orig.copy()
A[0, 0] = np.NAN
A[3, 1] = np.NAN
A[6, 3] = np.NAN

A_df = pd.DataFrame(A)
print A_df.head()


0   1   2   3   4   5   6   7   8   9
0   NaN 0.715189    0.602763    0.544883    0.423655    0.645894    0.437587    0.891773    0.963663    0.383442
1   0.791725    0.528895    0.568045    0.925597    0.071036    0.087129    0.020218    0.832620    0.778157    0.870012
2   0.978618    0.799159    0.461479    0.780529    0.118274    0.639921    0.143353    0.944669    0.521848    0.414662
3   0.264556    NaN 0.456150    0.568434    0.018790    0.617635    0.612096    0.616934    0.943748    0.681820
4   0.359508    0.437032    0.697631    0.060225    0.666767    0.670638    0.210383    0.128926    0.315428    0.363711

Определение матриц W и H

K = 4
W = np.abs(np.random.uniform(low=0, high=1, size=(M, K)))
H = np.abs(np.random.uniform(low=0, high=1, size=(K, N)))
W = np.divide(W, K*W.max())
H = np.divide(H, K*H.max())

pd.DataFrame(W).head()

0   1   2   3
0   0.078709    0.175784    0.095359    0.045339
1   0.006230    0.016976    0.171505    0.114531
2   0.135453    0.226355    0.250000    0.054753
3   0.167387    0.066473    0.005213    0.191444
4   0.080785    0.096801    0.148514    0.209789

pd.DataFrame(H).head()

0   1   2   3   4   5   6   7   8   9
0   0.074611    0.216164    0.157328    0.003370    0.088415    0.037721    0.250000    0.121806    0.126649    0.162827
1   0.093851    0.034858    0.209333    0.048340    0.130195    0.057117    0.024914    0.219537    0.247731    0.244654
2   0.230833    0.197093    0.084828    0.020651    0.103694    0.059133    0.033735    0.013604    0.184756    0.002910
3   0.196210    0.037417    0.020248    0.022815    0.171121    0.062477    0.107081    0.141921    0.219119    0.185125

Определение стоимости, которую мы хотим свести к минимуму

def cost(A, W, H):
    from numpy import linalg
    WH = np.dot(W, H)
    A_WH = A-WH
    return linalg.norm(A_WH, 'fro')

Однако, поскольку в отсутствуют записи, мы должны определить стоимость в терминах записей, присутствующих в A

def cost(A, W, H):
    from numpy import linalg
    mask = pd.DataFrame(A).notnull().values
    WH = np.dot(W, H)
    WH_mask = WH[mask]
    A_mask = A[mask]
    A_WH_mask = A_mask-WH_mask
    # Since now A_WH_mask is a vector, we use L2 instead of Frobenius norm for matrix
    return linalg.norm(A_WH_mask, 2)

Давайте просто попробуем оценить стоимость начального набора значений W и H, которые мы случайно присвоили.

cost(A, W, H)

7.3719938519859509

Альтернативная процедура NNLS

num_iter = 1000
num_display_cost = max(int(num_iter/10), 1)
from scipy.optimize import nnls

for i in range(num_iter):
    if i%2 ==0:
        # Learn H, given A and W
        for j in range(N):
            mask_rows = pd.Series(A[:,j]).notnull()
            H[:,j] = nnls(W[mask_rows], A[:,j][mask_rows])[0]
    else:
        for j in range(M):
            mask_rows = pd.Series(A[j,:]).notnull()
            W[j,:] = nnls(H.transpose()[mask_rows], A[j,:][mask_rows])[0]
    WH = np.dot(W, H)
    c = cost(A, W, H)
    if i%num_display_cost==0:
        print i, c

0 4.03939072472
100 2.38059096458
200 2.35814781954
300 2.35717011529
400 2.35711130357
500 2.3571079918
600 2.35710729854
700 2.35710713129
800 2.35710709085
900 2.35710708109



A_pred = pd.DataFrame(np.dot(W, H))
A_pred.head()

0   1   2   3   4   5   6   7   8   9
0   0.564235    0.677712    0.558999    0.631337    0.536069    0.621925    0.629133    0.656010    0.839802    0.545072
1   0.788734    0.539729    0.517534    1.041272    0.119894    0.448402    0.172808    0.658696    0.493093    0.825311
2   0.749886    0.575154    0.558981    0.931156    0.270149    0.502035    0.287008    0.656178    0.588916    0.741519
3   0.377419    0.743081    0.370408    0.637094    0.071684    0.529433    0.767696    0.628507    0.832910    0.605742
4   0.458661    0.327143    0.610012    0.233134    0.685559    0.377750    0.281483    0.269960    0.468756    0.114950

Посмотрите значения замаскированных записей.

A_pred.values[~pd.DataFrame(A).notnull().values]

array([ 0.56423481,  0.74308143,  0.10283106])

Original values were:

A_orig[~pd.DataFrame(A).notnull().values]

array([ 0.5488135 ,  0.77423369,  0.13818295])