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