LabelPropagation - Как избежать деления на ноль? - PullRequest
0 голосов
/ 28 августа 2018

При использовании LabelPropagation , я часто сталкиваюсь с этим предупреждением (imho, это должно быть ошибкой, потому что это полностью не дает распространения):

/ usr / local / lib / python3.5 / dist-packages / sklearn / semi_supervised / label_propagation.py: 279: RuntimeWarning: недопустимое значение, встречающееся в true_divide self.label_distributions_ / = normalizer

Итак, после нескольких попыток с ядром RBF я обнаружил, что параметр gamma оказывает влияние.

EDIT:

Проблема возникает из этих строк :

        if self._variant == 'propagation':
            normalizer = np.sum(
                self.label_distributions_, axis=1)[:, np.newaxis]
            self.label_distributions_ /= normalizer

Я не понимаю, как label_distributions_ может быть всеми нулями, особенно когда его определение:

self.label_distributions_ = safe_sparse_dot(
graph_matrix, self.label_distributions_)

Гамма влияет на graph_matrix (потому что graph_matrix является результатом _build_graph (), который вызывает функцию ядра). ХОРОШО. Но до сих пор. Что-то не так

СТАРЫЙ ПОСТ (перед редактированием)

Я напоминаю вам, как вычисляются веса графиков для распространения: W = exp (-gamma * D), D матрица попарных расстояний между всеми точками набора данных.

Проблема в том, что np.exp(x) возвращает 0.0, если x очень маленький .
Давайте представим, что у нас есть две точки i и j, такие что dist(i, j) = 10.

>>> np.exp(np.asarray(-10*40, dtype=float)) # gamma = 40 => OKAY
1.9151695967140057e-174
>>> np.exp(np.asarray(-10*120, dtype=float)) # gamma = 120 => NOT OKAY
0.0

На практике я не устанавливаю гамму вручную, но я использую метод, описанный в этой статье (раздел 2.4).

Итак, как можно избежать этого деления на ноль, чтобы получить правильное распространение?

Единственный способ, о котором я могу думать, - это нормализовать набор данных в каждом измерении , но мы теряем некоторые геометрические / топологические свойства набора данных (например, прямоугольник 2x10 становится квадратом 1x1)


Воспроизводимый пример:

В этом примере это хуже всего: даже при гамме-20 это дает сбой.

In [11]: from sklearn.semi_supervised.label_propagation import LabelPropagation

In [12]: import numpy as np

In [13]: X = np.array([[0, 0], [0, 10]])

In [14]: Y = [0, -1]

In [15]: LabelPropagation(kernel='rbf', tol=0.01, gamma=20).fit(X, Y)
/usr/local/lib/python3.5/dist-packages/sklearn/semi_supervised/label_propagation.py:279: RuntimeWarning: invalid value encountered in true_divide
  self.label_distributions_ /= normalizer
/usr/local/lib/python3.5/dist-packages/sklearn/semi_supervised/label_propagation.py:290: ConvergenceWarning: max_iter=1000 was reached without convergence.
  category=ConvergenceWarning
Out[15]: 
LabelPropagation(alpha=None, gamma=20, kernel='rbf', max_iter=1000, n_jobs=1,
         n_neighbors=7, tol=0.01)

In [16]: LabelPropagation(kernel='rbf', tol=0.01, gamma=2).fit(X, Y)
Out[16]: 
LabelPropagation(alpha=None, gamma=2, kernel='rbf', max_iter=1000, n_jobs=1,
         n_neighbors=7, tol=0.01)

In [17]: 

1 Ответ

0 голосов
/ 03 сентября 2018

В основном вы выполняете функцию softmax, верно?

Общий способ предотвращения чрезмерного / недостаточного расхода softmax (от здесь )

# Instead of this . . . 
def softmax(x, axis = 0):
    return np.exp(x) / np.sum(np.exp(x), axis = axis, keepdims = True)

# Do this
def softmax(x, axis = 0):
    e_x = np.exp(x - np.max(x, axis = axis, keepdims = True))
    return e_x / e_x.sum(axis, keepdims = True)

Это ограничивает e_x между 0 и 1 и гарантирует, что одно значение e_x всегда будет 1 (а именно элемент np.argmax(x)). Это предотвращает переполнение и потерю (когда np.exp(x.max()) больше или меньше, чем может обработать float64).

В этом случае, так как вы не можете изменить алгоритм, я бы взял ввод D и сделал бы D_ = D - D.min(), поскольку это должно быть численно эквивалентно приведенному выше, так как W.max() должно быть -gamma * D.min() (как ты просто переворачиваешь знак). Сделайте ваш алгоритм в отношении D_

EDIT:

В соответствии с рекомендациями @PaulBrodersen ниже, вы можете собрать «безопасное» ядро ​​rbf на основе sklearn реализации здесь :

def rbf_kernel_safe(X, Y=None, gamma=None): 

      X, Y = sklearn.metrics.pairwise.check_pairwise_arrays(X, Y) 
      if gamma is None: 
          gamma = 1.0 / X.shape[1] 

      K = sklearn.metrics.pairwise.euclidean_distances(X, Y, squared=True) 
      K *= -gamma 
      K -= K.max()
      np.exp(K, K)    # exponentiate K in-place 
      return K 

А затем используйте его в своем распространении

LabelPropagation(kernel = rbf_kernel_safe, tol = 0.01, gamma = 20).fit(X, Y)

К сожалению, у меня есть только v0.18, который не принимает пользовательские функции ядра для LabelPropagation, поэтому я не могу его протестировать.

EDIT2:

Проверка вашего источника, почему у вас такие большие значения gamma, заставляет задуматься, используете ли вы gamma = D.min()/3, что было бы неправильно. Определение sigma = D.min()/3, в то время как определение sigma в w равно

w = exp(-d**2/sigma**2)  # Equation (1)

, что даст правильное gamma значение 1/sigma**2 или 9/D.min()**2

...