При обучении нейронной сети (НС) выполняется минимизация функции потерь, которая при использовании библиотеки Keras указывается в качестве параметра метода compile класса Model [1], например:
from keras.models import Model
from keras.layers import Input, Dense
a = Input(shape=(32,))
b = Dense(32)(a)
model = Model(inputs = a, outputs = b)
model.compile(loss = keras.losses.mean_squared_error, optimizer = 'adam', metrics = ['accuracy'])
или
model.compile(loss = 'mean_squared_error', optimizer = 'adam', metrics = ['accuracy'])
или
model.compile('adam', 'mse', ['accuracy'])
model.summary()
В случае НС с несколькими выходами для каждого выхода можно указать "свою" функцию потерь, передав методу compile либо словарь, либо список задействованных функций потерь, например:
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers.recurrent import LSTM
visible = Input(shape = (32,1))
extract = LSTM(10, return_sequences = True)(visible)
output1 = Dense(1, activation='sigmoid')(extract)
output2 = Dense(1, activation='sigmoid')(extract)
model = Model(inputs = visible, outputs = [output1, output2])
model.summary()
model.compile('adam', loss = ['mse', 'hinge'], metrics = ['accuracy'])
Для первого выхода использована функция потерь "Средняя квадратическая ошибка", для второго – "Верхняя граница". В этом случае при обучении минимизируется сумма заданных потерь.
При необходимости может быть задана пользовательская функция, например:
from keras.models import Model
from keras.layers import Input, Dense
from keras import backend as K
# Пользовательская функция потерь "Средняя квадратическая ошибка"
def myMse(y_true, y_pred):
err = K.mean(K.square(y_pred - y_true))
return err # Вернет тензор с shape=(?,)
a = Input(shape=(32,))
b = Dense(32)(a)
model = Model(inputs = a, outputs = b)
model.summary()
model.compile(loss = myMse, optimizer = 'adam', metrics = ['accuracy'])
Пользовательская функция потерь принимает параметры тензоры y_true, y_pred – соответственно истинное и предсказанные значения на выходе НС.
Далее рассматриваются функции потерь, определенные в библиотеке Keras.
Перечень функция потерь библиотеки Keras перечислены в табл. 1.
Таблица 1. Функции потерь библиотеки Keras.
В таблице:
yi – прогнозируемое значение;
xi – истинное значение;
n – размер вектора xi (yi).
Функция | Обозначение | Формула |
---|---|---|
Средняя квадратическая ошибка / mean squared error | MSE | |
Средняя абсолютная ошибка / mean absolute error | MAE | |
Средняя абсолютная процентная ошибка / mean absolute percentage error | MAPE | |
Средняя квадратическая логарифмическая ошибка / mean squared logarithmic error | MSLE | |
Квадрат верхней границы / squared hinge | SH | |
Верхняя граница / hinge | H | |
Категориальная верхняя граница / categorical hinge | CH | max(0, neg – pos + 1), где |
Логарифм гиперболического косинуса / logcosh | LC | |
Категориальная перекрестная энтропия / categorical crossentropy | CCE | |
Разреженная категориальная перекрестная энтропия / sparse categorical crossentropy | SCCE | (σ(yi) – softmax-функция, или номализованная экспонента) |
Бинарная перекрестная энтропия / binary crossentropy | BCE | |
Расстояние Кульбака-Лейблера / kullback leibler divergence | KLD | |
Пуассон / Poisson | PSS | |
Косинусная близость / cosine proximity | CP |
При указании функции потерь можно употреблять следующие псевдонимы:
mse = MSE = mean_squared_error
mae = MAE = mean_absolute_error
mape = MAPE = mean_absolute_percentage_error
msle = MSLE = mean_squared_logarithmic_error
kld = KLD = kullback_leibler_divergence
cosine = cosine_proximity
Приводимые ниже программы (функции) взяты из [2, 3].
Все функции потерь принимают 2D-тензоры с истинным (y_true) и предсказанным сетью значениями (y_pred) и возвращают 1D-тензор, содержащий текущее значение функции потерь.
Все функции потерь, кроме sparse_categorical_crossentropy, работают с категориальным представлением входных данных. Поэтому в программах обучения НС метки классов представляются в виде векторов, длина которых равна числу классов. Например, при обучении НС классификации рукописных цифр метка 5 (соответствует цифре 5 в обучающих и тестовых выборках) представляется в виде следующего вектора:
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.].
Это обеспечивает следующий код:
import keras
num_classes = 10 # Число классов
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
В приведенном выше коде y_train и y_test – соответственно массивы с метками классов для обучающей и тестовых выборок.
Программы, вычисляющие функции потерь, используют следующие функции: abs, clip, epsilon, l2_normalize, log, max, maximum, mean, softplus, square и sum. Эти функции иллюстрируются следующими примерами.
import tensorflow as tf
from keras import backend as K
print(K.epsilon()) # 1e-07
sess = tf.Session()
print(sess.run(K.abs([-3.0, -4.0]))) # [3., 4.]
# Натуральный логарифм
print(sess.run(K.log([100.0, 1000.0]))) # [4.6051702 6.9077554]
# Нормализация
print(sess.run(K.l2_normalize([3.0, 4.0]))) # [0.6 0.8] = [3.0/5.0, 4.0/5.0]
#
t = K.square([1.2, 2.0])
print(sess.run(t)) # [1.44 4.] = [1.2**2, 2.0**2]
print(sess.run(K.mean(t))) # 2.72 = (1.44 + 4) / 2
print(sess.run(K.clip(t, 1.0, 5.0))) # [1.44 4.] = [max(1.44, 1.0), min(4.0, 5.0)]
print(sess.run(K.clip(t, 3.5, 5.0))) # [3.5 4.] = [max(1.44, 3.5), min(4.0, 5.0)]
print(sess.run(K.clip(t, 3.5, 3.75))) # [3.5 3.75] = [max(1.44, 3.5), min(4.0, 3.75)]
print(sess.run(K.sum(t))) # 5.44 = 1.44 + 4.0
print(sess.run(K.max(t))) # 4.0 = max(1.44, 4.0)
print(sess.run(K.maximum(t, 3.0))) # [3. 4.] = [max(1.44, 3.0), max(4.0, 3.0)]
print(sess.run(K.softplus(t))) # [1.6526307 4.01815] = [log(1 + exp(1.44)), log(1 + exp(4.0))]
#
t2 = K.exp([1.0, 2.0])
print(sess.run(t2)) # [2.7182817 7.389056]
print(sess.run(K.log(t2))) # [0.99999994 2.]
Функции потерь вычисляются следующими процедурами:
from keras import backend as K
#
# Средняя квадратическая ошибка
def mean_squared_error(y_true, y_pred):
return K.mean(K.square(y_pred - y_true), axis = -1)
#
# Средняя абсолютная ошибка
def mean_absolute_error(y_true, y_pred):
return K.mean(K.abs(y_pred - y_true), axis = -1)
#
# Средняя абсолютная процентная ошибка
def mean_absolute_percentage_error(y_true, y_pred):
diff = K.abs((y_true - y_pred) / K.clip(K.abs(y_true), K.epsilon(), None))
return 100. * K.mean(diff, axis = -1)
#
# Средняя квадратическая логарифмическая ошибка
def mean_squared_logarithmic_error(y_true, y_pred):
first_log = K.log(K.clip(y_pred, K.epsilon(), None) + 1.)
second_log = K.log(K.clip(y_true, K.epsilon(), None) + 1.)
return K.mean(K.square(first_log - second_log), axis = -1)
#
# Квадрат верхней границы
def squared_hinge(y_true, y_pred):
return K.mean(K.square(K.maximum(1. - y_true * y_pred, 0.)), axis = -1)
#
# Верхняя граница
def hinge(y_true, y_pred):
return K.mean(K.maximum(1. - y_true * y_pred, 0.), axis = -1)
#
# Категориальная верхняя граница
def categorical_hinge(y_true, y_pred):
pos = K.sum(y_true * y_pred, axis=-1)
neg = K.max((1. - y_true) * y_pred, axis = -1)
return K.maximum(0., neg - pos + 1.)
#
# Логарифм гиперболического косинуса
def logcosh(y_true, y_pred):
# Логарифм гиперболического косинуса
# log(cosh(x)) приблизительно равен (x ** 2) / 2 для небольших значений x
# и приблизительно равен abs(x) - log(2) для больших значений x
# Поэтому logcosh работает преимущественно как средняя квадратическая ошибка,
# но не будет в то же время сильно подвержен случайным большим ошибкам предсказания
# Возвращает тензор со скалярным значением потери
def _logcosh(x):
return x + K.softplus(-2. * x) - K.log(2.)
#
return K.mean(_logcosh(y_pred - y_true), axis = -1)
# Категориальная перекрестная энтропия
def categorical_crossentropy(y_true, y_pred):
return K.categorical_crossentropy(y_true, y_pred)
#
# Разреженная категориальная перекрестная энтропия
def sparse_categorical_crossentropy(y_true, y_pred):
return K.sparse_categorical_crossentropy(y_true, y_pred)
#
# Бинарная перекрестная энтропия
def binary_crossentropy(y_true, y_pred):
return K.mean(K.binary_crossentropy(y_true, y_pred), axis = -1)
#
# Расстояние Кульбака-Лейблера
def kullback_leibler_divergence(y_true, y_pred):
y_true = K.clip(y_true, K.epsilon(), 1)
y_pred = K.clip(y_pred, K.epsilon(), 1)
return K.sum(y_true * K.log(y_true / y_pred), axis = -1)
#
# Пуассон
def poisson(y_true, y_pred):
return K.mean(y_pred - y_true * K.log(y_pred + K.epsilon()), axis = -1)
#
# Косинусная близость
def cosine_proximity(y_true, y_pred):
y_true = K.l2_normalize(y_true, axis = -1)
y_pred = K.l2_normalize(y_pred, axis = -1)
return -K.sum(y_true * y_pred, axis = -1)
Детализация вычисления категориальной перекрестной энтропии:
def categorical_crossentropy(y_true, y_pred, from_logits=False, axis=-1):
# Масштабируем y_pred, чтобы сумма компонентов y_pred была равна 1
y_pred = y_pred / math_ops.reduce_sum(y_pred, axis, True)
epsilon_ = _to_tensor(epsilon(), output.dtype.base_dtype)
y_pred = clip_ops.clip_by_value(y_pred, epsilon_, 1. - epsilon_)
return -math_ops.reduce_sum(y_true * math_ops.log(y_pred), axis)
Детализация вычисления разреженной перекрестной энтропии:
def sparse_categorical_crossentropy(y_true, y_pred, from_logits=False, axis=-1):
# Категориальная перекрестная энтропия с целочисленным y_true
rank = len(y_pred.shape)
axis = axis % rank
if axis != rank - 1:
permutation = list(range(axis)) + list(range(axis + 1, rank)) + [axis]
y_pred = array_ops.transpose(y_pred, perm=permutation)
epsilon_ = _to_tensor(epsilon(), y_pred.dtype.base_dtype)
y_pred = clip_ops.clip_by_value(y_pred, epsilon_, 1 - epsilon_)
y_pred = math_ops.log(y_pred)
y_pred_shape = y_pred.shape
targets = cast(flatten(y_true), 'int64')
logits = array_ops.reshape(y_pred, [-1, int(y_pred_shape[-1])])
res = nn.sparse_softmax_cross_entropy_with_logits(labels = targets, logits = logits)
if len(y_pred_shape) >= 3:
# Если y_pred содержит timesteps или пространственные измерения, то необходимо выполнить reshape
return array_ops.reshape(res, array_ops.shape(y_pred)[:-1])
else:
return res
Детализация вычисления бинарной перекрестной энтропии:
def binary_crossentropy(y_true, y_pred, from_logits=False):
# Бинарная перекрестная энтропия между y_pred и y_true
# Обратное преобразование в логиты: logit(p) = log(p / (1 – p))
epsilon_ = _to_tensor(epsilon(), y_pred.dtype.base_dtype)
y_pred = clip_ops.clip_by_value(y_pred, epsilon_, 1 - epsilon_)
y_pred = math_ops.log(y_pred / (1 - y_pred))
return nn.sigmoid_cross_entropy_with_logits(labels=y_true, logits=y_pred)
Приведенные функции не всегда удовлетворяют пользователей, поэтому имеется много дополнительно разработанных функций потерь [4]. К подобным разработкам, однако, следует приступать, только убедившись, что среди встроенных функций потерь не найдется ни одной подходящей для решаемой задачи.
Результат обучения оценивается по тестовой выборке X_test, y_test. X_test[i] содержит тестовый образец, а y_test[i] – метку, указывающую на класс X_test[i]. Например, при классификации рукописных цифр, заданных в оттенках серого цвета на площадке 28*28 пикселей, образец содержит 28*28 значений в диапазоне [0, 255], метка – это число из диапазона [0, 9], равное числу, которое представляет образец.
Результат обучения характеризует, насколько правильно были приняты решения при выборе архитектуры НС, параметров обучения (размер обучающей порции, скорость обучения, ...), метода оптимизации и его параметров, а также функции потерь.
Результат обучения можно проверить двумя способами:
score = model.evaluate(X_test, y_test, verbose = 0)
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
и
y_pred = model.predict(X_test)В приведенном выше коде
y_test_0 = y_test
до преобразования y_test в категориальное представление.
Очевидно, что точность оценки score[1] и точность прогнозирования acc должны совпадать.