LabelPropagation - Как избежать деления на ноль?

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

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

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

РЕДАКТИРОВАТЬ:

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

        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(), который вызывает функцию ядра). ХОРОШО. Но до сих пор. Что-то не так

OLD POST (перед редактированием)

Я напоминаю вам, как вычисляются веса графа для распространения: 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

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

Общий способ предотвращения 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_

РЕДАКТИРОВАТЬ:

В соответствии с рекомендациями @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