Введение в нейронные сети для глубокого обучения в Python | Вызов в августе

глубокое обучение Python

Deep Learning with Python

Эта статья — одна из серии заметок, которые я написал, изучая Deep Learning with Python (второе издание, Франсуа Шолле). Содержимое статьи конвертируется из блокнотов Jupyter в Markdown, и когда я закончу все статьи, я опубликую все написанные мной блокноты Jupyter на GitHub.

Вы можете прочитать оригинальный текст (на английском языке) этой книги онлайн на этом сайте:live book.manning.com/book/deep-come…

Автор этой книги также дает набор блокнотов Jupyter:GitHub.com/ Very OL T/…


Эта статьяГлава 3 Начало работы с нейронными сетями(Глава 3. Начало работы с нейронными сетями) отмечает интеграцию.

Категоризация обзоров фильмов: проблема бинарной классификации

Оригинальная ссылка

набор данных IMDB

В базе данных IMDB 50 000 обзоров фильмов. Половина — это тренировочный набор, а половина — тестовый набор. 50% данных — положительные отзывы, а 50% — отрицательные.

Keras имеет встроенный предварительно обработанный набор данных IMDB, который преобразует последовательности слов в целочисленные последовательности (число соответствует слову в словаре):

from tensorflow.keras.datasets import imdb

# 数据集
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words=10000)

num_words=10000заключается в том, чтобы оставить только первые 10 000 слов по частоте.

Давайте сначала посмотрим на комментарий, это хороший комментарий:

# 字典
index_word = {v: k for k, v in imdb.get_word_index().items()}

# 还原一条评论看看
text = ' '.join([index_word[i] for i in train_data[0]])

print(f"{train_labels[0]}:", text)

вывод:

1: the as you with out themselves powerful lets loves their becomes reaching had journalist of lot from anyone to have after out atmosphere never more room and it so heart shows to years of every never going and help moments or of every chest visual movie except her was several of enough more with is now current film as you of mine potentially unfortunately of you than him that with out themselves her get for was camp of you movie sometimes movie that with scary but and to story wonderful that in seeing in character to of 70s musicians with heart had shadows they of here that with her serious to have does when from why what have critics they is you that isn't one will very to as itself with other and in of seen over landed for anyone of and br show's to whether from than out themselves history he name half some br of and odd was two most of mean for 1 any an boat she he should is thought frog but of script you not while history he heart to real at barrel but when from one bit then have two of script their with her nobody most that with wasn't to with armed acting watch an for with heartfelt film want an

подготовка данных

Давайте посмотрим на текущую форму train_data:

train_data.shape

вывод:

(25000,)

мы собираемся превратить его в(samples, word_indices)Это выглядит так:

[[0, 0, ..., 1, ..., 0, ..., 1],
 [0, 1, ..., 0, ..., 1, ..., 0],
 ...
]

Наличие слова равно 1, а не 0.

import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.
    return results

x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)

x_train

вывод:

array([[0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.],
       ...,
       [0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.]])

Надписи также можно сделать небрежно:

train_labels

вывод:

array([1, 0, 0, ..., 0, 1, 0])

Проработай это:

y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
y_train

вывод:

array([1., 0., 0., ..., 0., 1., 0.], dtype=float32)

Теперь эти данные безопасны для нейронной сети, которую мы сейчас создадим.

построить сеть

Для такого рода задач, когда вход представляет собой вектор, а метка — скаляр (или даже 0 или 1): Сеть с повторно активированным Dense (полностью подключенная):

Dense(16, activation='relu')

Роль этого слоя заключается вoutput = relu(dot(W, input) + b).

16 — количество скрытых юнитов в каждом слое. Скрытая единица — это измерение в пространстве представления этого слоя. Форма W также(input_dimension, 16), точка получается как 16-мерный вектор, который также проецирует данные в 16-мерное пространство представления.

Это измерение (количество скрытых единиц) можно рассматривать как контроль над степенями свободы обучения сети. Чем выше размерность, тем более сложные вещи можно изучить, но тем дороже это требует вычислений, и могут быть изучены некоторые неважные вещи, которые могут привести к переобучению.

Здесь мы будем использовать два слоя этих 16 скрытых блоков, Наконец, есть активированный сигмовидный слой для вывода результата (в[0,1][0, 1]значение внутри), Этот результат указывает, насколько вероятно предсказание, что эти данные имеют метку 1, что является положительным отзывом.

relu — отфильтровывать отрицательные значения (выводить отрицательное значение ввода как 0), а sigmoid — приводить значение к[0, 1]:

relu and sigmoid

Реализуйте эту сеть в Keras:

from tensorflow.keras import models
from tensorflow.keras import layers

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

Роль функции активации

Ранее мы использовали функцию активации relu в MNIST, поэтомуфункция активацииДля чего это?

Плотный слой без функции активации — это просто линейное преобразование:

output = dot(W, input) + b

Если каждый слой является своего рода линейным преобразованием, а несколько слоев объединены в слои, пространство предположений не станет больше, поэтому то, что можно узнать, очень ограничено.

И функция активацииdot(W, input) + bФункция внешнего покрытия, такая как активация relu,output = relu(dot(W, input) + b). С помощью этой функции активации пространство представления может быть расширено, что позволяет сети обучаться более сложным «знаниям».

Скомпилируйте модель

При составлении модели нам также необходимо выбрать функцию потерь, оптимизатор и метрику.

Для такой задачи двоичной классификации, где конечный результат равен 0 или 1, можно использовать функцию потерь.binary_crossentropy(Как видно из названия, очень подходит.)

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

Что касается оптимизатора, такого как MNIST, мы используемrmsprop(в книге не написано почему), показатель все равно точность:

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

Поскольку эти оптимизаторы, потери и метрики широко используются, Keras встроен и может напрямую передавать строки. Но вы также можете передать экземпляр класса для настройки некоторых параметров:

from tensorflow.keras import optimizers
from tensorflow.keras import losses
from tensorflow.keras import metrics

model.compile(optimizer=optimizers.RMSprop(lr=0.001),
              loss=losses.binary_crossentropy,
              metrics=[metrics.binary_accuracy])

Обучите модель

Чтобы проверить, насколько точна модель на данных, которые она не видела во время обучения, мы разделим 10 000 выборок из исходных данных обучения:

x_val = x_train[:10000]
partial_x_train = x_train[10000:]

y_val = y_train[:10000]
partial_y_train = y_train[10000:]

Используйте пакет из 512 образцов мини-пакетов, запустите 20 раундов (все данные в x_train запускаются один раз, чтобы считаться за один раунд), И используйте только что разделенные 10 000 образцов для проверки точности:

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))
Train on 15000 samples, validate on 10000 samples
Epoch 1/20
15000/15000 [==============================] - 3s 205us/sample - loss: 0.5340 - acc: 0.7867 - val_loss: 0.4386 - val_acc: 0.8340
.......
Epoch 20/20
15000/15000 [==============================] - 1s 74us/sample - loss: 0.0053 - acc: 0.9995 - val_loss: 0.7030 - val_acc: 0.8675

fitШирокая для возврата к истории, которая сохраняет черную историю каждой Эпохи в процессе обучения:

history_dict = history.history
history_dict.keys()

вывод:

dict_keys(['loss', 'acc', 'val_loss', 'val_acc'])

Мы можем нарисовать эти вещи, чтобы увидеть:

# 画训练和验证的损失

import matplotlib.pyplot as plt

history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']

epochs = range(1, len(loss_values) + 1)

plt.plot(epochs, loss_values, 'ro-', label='Training loss')
plt.plot(epochs, val_loss_values, 'bs-', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

png

# 画训练和验证的准确度

plt.clf()

acc = history_dict['acc']
val_acc = history_dict['val_acc']

plt.plot(epochs, acc, 'ro-', label='Training acc')
plt.plot(epochs, val_acc, 'bs-', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

png

Мы видим, что точность на тренировочном наборе увеличивается (уменьшаются потери), Однако на проверочном наборе потери увеличиваются позже и достигают наилучшего пика примерно в четвертом раунде.

Это переобучение, которое фактически началось со второго тура. Итак, мы на самом деле провели 3 или 4 раунда, и все было в порядке. Если мы продолжим работать, наша модель будет «владеть» только обучающим набором и не будет знать других данных, которых она не видела раньше.

Итак, давайте переобучим модель (перепишем ее из построения сети, иначе подгонка будет следовать тому, что было сделано только что), а затем протестируем ее на тестовом наборе:

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test, verbose=2)    # verbose=2 to avoid a looooong progress bar that fills the screen with '='. https://github.com/tensorflow/tensorflow/issues/32286
Train on 25000 samples
Epoch 1/4
25000/25000 [==============================] - 2s 69us/sample - loss: 0.4829 - accuracy: 0.8179
Epoch 2/4
25000/25000 [==============================] - 1s 42us/sample - loss: 0.2827 - accuracy: 0.9054
Epoch 3/4
25000/25000 [==============================] - 1s 42us/sample - loss: 0.2109 - accuracy: 0.9253
Epoch 4/4
25000/25000 [==============================] - 1s 43us/sample - loss: 0.1750 - accuracy: 0.9380
25000/1 - 3s - loss: 0.2819 - accuracy: 0.8836

Выведите результат, чтобы увидеть:

print(result)
[0.2923990402317047, 0.8836]

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

model.predict(x_test)

Output:

array([[0.17157233],
       [0.99989915],
       [0.79564804],
       ...,
       [0.11750051],
       [0.05890778],
       [0.5040823 ]], dtype=float32)

Дальнейшие эксперименты

  1. попробуй только в один слой
model = models.Sequential()
# model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
# model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid', input_shape=(10000, )))

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test, verbose=2)    # verbose=2 to avoid a looooong progress bar that fills the screen with '='. https://github.com/tensorflow/tensorflow/issues/32286
print(result)
Train on 25000 samples
Epoch 1/4
25000/25000 [==============================] - 3s 116us/sample - loss: 0.5865 - accuracy: 0.7814
Epoch 2/4
25000/25000 [==============================] - 1s 31us/sample - loss: 0.4669 - accuracy: 0.8608
Epoch 3/4
25000/25000 [==============================] - 1s 32us/sample - loss: 0.3991 - accuracy: 0.8790
Epoch 4/4
25000/25000 [==============================] - 1s 33us/sample - loss: 0.3538 - accuracy: 0.8920
25000/1 - 3s - loss: 0.3794 - accuracy: 0.8732
[0.3726908649635315, 0.8732]

... эта проблема относительно проста, эффект слоя настолько хорош, но он намного хуже, чем предыдущий серьезный

  1. Делайте больше слоев
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test, verbose=2)    # verbose=2 to avoid a looooong progress bar that fills the screen with '='. https://github.com/tensorflow/tensorflow/issues/32286
print(result)
Train on 25000 samples
Epoch 1/4
25000/25000 [==============================] - 3s 123us/sample - loss: 0.5285 - accuracy: 0.7614
Epoch 2/4
25000/25000 [==============================] - 1s 45us/sample - loss: 0.2683 - accuracy: 0.9072s - loss:
Epoch 3/4
25000/25000 [==============================] - 1s 45us/sample - loss: 0.1949 - accuracy: 0.9297
Epoch 4/4
25000/25000 [==============================] - 1s 47us/sample - loss: 0.1625 - accuracy: 0.9422
25000/1 - 2s - loss: 0.3130 - accuracy: 0.8806
[0.30894253887176515, 0.88056]

Лучше, но не так хорошо, как серьезная версия

  1. больше единиц скрытых слоев
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(1024, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test, verbose=2)    # verbose=2 to avoid a looooong progress bar that fills the screen with '='. https://github.com/tensorflow/tensorflow/issues/32286
print(result)
Train on 25000 samples
Epoch 1/4
25000/25000 [==============================] - 15s 593us/sample - loss: 0.5297 - accuracy: 0.7964
Epoch 2/4
25000/25000 [==============================] - 12s 490us/sample - loss: 0.2233 - accuracy: 0.9109
Epoch 3/4
25000/25000 [==============================] - 12s 489us/sample - loss: 0.1148 - accuracy: 0.9593
Epoch 4/4
25000/25000 [==============================] - 12s 494us/sample - loss: 0.0578 - accuracy: 0.9835
25000/1 - 9s - loss: 0.3693 - accuracy: 0.8812
[0.4772889766550064, 0.8812]

Не намного лучше.

  1. потеря с мсэ
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
             loss='mse',
             metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test, verbose=2)    # verbose=2 to avoid a looooong progress bar that fills the screen with '='. https://github.com/tensorflow/tensorflow/issues/32286
print(result)
Train on 25000 samples
Epoch 1/4
25000/25000 [==============================] - 3s 119us/sample - loss: 0.1472 - accuracy: 0.8188
Epoch 2/4
25000/25000 [==============================] - 1s 46us/sample - loss: 0.0755 - accuracy: 0.9121
Epoch 3/4
25000/25000 [==============================] - 1s 50us/sample - loss: 0.0577 - accuracy: 0.9319
Epoch 4/4
25000/25000 [==============================] - 1s 47us/sample - loss: 0.0474 - accuracy: 0.9442
25000/1 - 3s - loss: 0.0914 - accuracy: 0.8828
[0.08648386991858482, 0.88276]
  1. активировать с тан
model = models.Sequential()
model.add(layers.Dense(16, activation='tanh', input_shape=(10000, )))
model.add(layers.Dense(16, activation='tanh'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
result = model.evaluate(x_test, y_test, verbose=2)    # verbose=2 to avoid a looooong progress bar that fills the screen with '='. https://github.com/tensorflow/tensorflow/issues/32286
print(result)
Train on 25000 samples
Epoch 1/4
25000/25000 [==============================] - 4s 149us/sample - loss: 0.4237 - accuracy: 0.8241
Epoch 2/4
25000/25000 [==============================] - 1s 46us/sample - loss: 0.2310 - accuracy: 0.9163
Epoch 3/4
25000/25000 [==============================] - 1s 46us/sample - loss: 0.1779 - accuracy: 0.9329
Epoch 4/4
25000/25000 [==============================] - 1s 49us/sample - loss: 0.1499 - accuracy: 0.9458
25000/1 - 3s - loss: 0.3738 - accuracy: 0.8772
[0.3238203083658218, 0.87716]

Следовательно, эти эксперименты означают, что модель, использованная в нашей книге ранее, разумна, а вы не так хороши, как его.

Рубрика новостей: Проблема мультиклассификации

Оригинальная ссылка

Наша проблема обзора фильма только что разделила векторный ввод на две категории.В этом разделе мы разделим вещи на несколько категорий, то есть «мультиклассовую классификацию».

Мы хотим разделить новости от Reuters на 46 тематических категорий, здесь мы требуем, чтобы новость могла принадлежать только к одной категории, поэтому, в частности, мы хотим решить проблему «классификации с одной меткой и несколькими классами».

Набор данных Рейтер

Набор данных Reuters, выпущенный Reuters в 1986 году (намного старше меня), содержит 46 категорий новостей, и каждая категория в обучающем наборе содержит не менее 10 фрагментов данных.

Подобно IMDB и MNIST, этот игрушечный набор данных встроен в Keras:

from tensorflow.keras.datasets import reuters

(train_data, train_labels), (test_data, test_labels) = reuters.load_data(
    num_words=10000)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/reuters.npz
2113536/2110848 [==============================] - 6s 3us/step

Данные в этом наборе такие же, как и в предыдущем IMDB, который переводит слова в числа, а затем мы перехватываем только 10 000 слов с наибольшей частотой.

У нас есть 8K+ фрагментов данных в нашем обучающем наборе и 2K+ в тестовом наборе:

print(len(train_data), len(test_data))
8982 2246

Давайте восстановим данные и увидим текст, как мы это сделали с IMDB:

def decode_news(data):
    reverse_word_index = {v: k for k, v in reuters.get_word_index().items()}
    return ' '.join([reverse_word_index.get(i - 3, '?') for i in data])
    # i - 3 是因为 0、1、2 为保留词 “padding”(填充)、“start of sequence”(序列开始)、“unknown”(未知词)


text = decode_news(train_data[0])
print(text)
? ? ? said as a result of its december acquisition of space co it expects earnings per share in 1987 of 1 15 to 1 30 dlrs per share up from 70 cts in 1986 the company said pretax net should rise to nine to 10 mln dlrs from six mln dlrs in 1986 and rental operation revenues to 19 to 22 mln dlrs from 12 5 mln dlrs it said cash flow per share this year should be 2 50 to three dlrs reuter 3

Метки представляют собой числа от 0 до 45:

train_labels[0]

Output:

3

подготовка данных

Прежде всего, давайте векторизируем биты данных и напрямую применим код, который мы написали, когда занимались IMDB:

import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.
    return results


x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)

Тогда это эффект:

print(x_train)
array([[0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.],
       ...,
       [0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.],
       [0., 1., 1., ..., 0., 0., 0.]])

Затем вам нужно разобраться с тегами. Мы можем рассматривать метки как целочисленные тензоры или использоватьOne-hotкодирование Для задач классификации мы часто используем однократное кодирование (также называемоеКод классификации, категориальное кодирование).

Для нашей текущей задачи используйте однократное кодирование, которое представляет собой вектор со всеми нулями, кроме позиции индекса метки 1:

def to_one_hot(labels, dimension=46):
    results = np.zeros((len(labels), dimension))
    for i, label in enumerate(labels):
        results[i, label] = 1.
    return results


one_hot_train_labels = to_one_hot(train_labels)
one_hot_test_labels = to_one_hot(test_labels)

Фактически, в Keras есть функция, которая может это сделать:

from tensorflow.keras.utils import to_categorical
# 书上是 from keras.utils.np_utils import to_categorical 但,,,时代变了,而且咱这用的是 tensorflow.keras,所以稍微有点区别

one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
print(one_hot_train_labels)
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]], dtype=float32)

Создайте сеть

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

Для стека слоев Dense, который мы использовали, каждый слой получает в качестве входных данных информацию, выводимую предыдущим слоем. Таким образом, если слой теряет некоторую информацию, то эта информация больше не может быть восстановлена ​​последующими уровнями. Если отсутствующая информация бесполезна для классификации, то эта потеря хороша, и мы ожидаем, что это произойдет; Но если недостающая информация отвечает за окончательную классификацию, то эта потеря ограничивает результаты работы сети. То есть это может создать «информационное узкое место». Это узкое место может возникнуть на каждом уровне.

Предыдущая классификация обзоров фильмов имела только 2 результата в конце, поэтому мы использовали 16 единиц в слое, То есть пусть машина учится в 16-мерном пространстве, а оно достаточно большое, чтобы меньше было "узких мест информации".

Для нашей текущей задачи пространство решений 46-мерное. Напрямую скопируйте предыдущий код и дайте ему обучиться в 16-мерном пространстве, должно быть узкое место!

Решение узкого места очень простое, просто увеличьте количество единиц в слое напрямую. Здесь мы 16 -> 64:

from tensorflow.keras import models
from tensorflow.keras import layers

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

В последнем слое наш вывод 46-мерный, соответствующий 46 категориям, А функция активации этого слоя — softmax, такая же, как мы использовали при обучении MNIST.

Использование softmax позволяет сети выводить распределение вероятностей по 46 категориям, то есть 46-мерный вектор, где i-й элемент представляет вероятность того, что вход принадлежит i-й категории, и сумма этих 46 элементов равна1.

Скомпилируйте модель

После компиляции модели пришло время определить функцию потерь, оптимизатор и цели оптимизации.

  • Функция потерь, проблема классификации или использование «категориальной перекрестной энтропии» categorical_crossentropy.
  • Оптимизатор, на самом деле мы используем rmsprop для многих задач
  • Цель пока одна, точность предсказания
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

Проверьте эффект

Нам все еще нужен проверочный набор для оценки модели во время обучения. Просто разделите 1K фрагментов данных из обучающего набора:

x_val = x_train[:1000]
partial_x_train = x_train[1000:]

y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]

Обучите модель

Что ж, подготовка завершена, и вы снова можете увидеть увлекательнейший тренировочный процесс!

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))
Train on 7982 samples, validate on 1000 samples
Epoch 1/20
7982/7982 [==============================] - 3s 372us/sample - loss: 2.6180 - accuracy: 0.5150 - val_loss: 1.7517 - val_accuracy: 0.6290
......
Epoch 20/20
7982/7982 [==============================] - 1s 91us/sample - loss: 0.1134 - accuracy: 0.9578 - val_loss: 1.0900 - val_accuracy: 0.8040

?Очень быстро, как обычно, давайте рисовать картинки, чтобы увидеть процесс обучения.

  1. потеря во время тренировки
import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'bo-', label='Training loss')
plt.plot(epochs, val_loss, 'rs-', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

png

  1. Точность во время тренировки
plt.clf()

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

plt.plot(epochs, acc, 'bo-', label='Training acc')
plt.plot(epochs, val_acc, 'rs-', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

png

Эммм, скажем, переобучение началось в 9-м раунде эпох (видно, что кривая валидации немного качается в 9-м раунде). Так что просто бегите 9 раундов.

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.fit(partial_x_train,
          partial_y_train,
          epochs=9,
          batch_size=512,
          validation_data=(x_val, y_val))
Train on 7982 samples, validate on 1000 samples
Epoch 1/9
7982/7982 [==============================] - 1s 153us/sample - loss: 2.5943 - accuracy: 0.5515 - val_loss: 1.7017 - val_accuracy: 0.6410
......
Epoch 9/9
7982/7982 [==============================] - 1s 84us/sample - loss: 0.2793 - accuracy: 0.9402 - val_loss: 0.8758 - val_accuracy: 0.8170

<tensorflow.python.keras.callbacks.History at 0x16eb5d6d0>

Затем протестируйте его с помощью тестового набора:

results = model.evaluate(x_test, one_hot_test_labels, verbose=2)
print(results)
2246/1 - 0s - loss: 1.7611 - accuracy: 0.7912
[0.983459981976082, 0.7911843]

Точность почти 80%, что на самом деле неплохо, намного лучше случайного скрайбинга.

Если вы случайным образом рисуете линию для классификации, точность задачи бинарной классификации составляет 50%, а точность классификации 46 юаней составляет менее 19%:

import copy

test_labels_copy = copy.copy(test_labels)
np.random.shuffle(test_labels_copy)
hits_array = np.array(test_labels) == np.array(test_labels_copy)
float(np.sum(hits_array)) / len(test_labels)
0.18432769367764915

Вызовите метод прогнозирования экземпляра модели, чтобы получить распределение вероятности входных данных по 46 категориям:

predictions = model.predict(x_test)
print(predictions)
array([[4.7181980e-05, 2.0765587e-05, 8.6653872e-06, ..., 3.1266565e-05,
        8.2046267e-07, 6.0611728e-06],
       [5.9005950e-04, 1.3404934e-02, 1.2290048e-03, ..., 4.2919168e-05,
        5.7422225e-05, 4.0201416e-05],
       [8.5751421e-04, 9.2367262e-01, 1.5855590e-03, ..., 4.8341672e-04,
        4.5594123e-05, 2.6183401e-05],
       ...,
       [8.5679676e-05, 2.0081598e-04, 4.1808224e-05, ..., 7.6962686e-05,
        6.5783697e-06, 2.9889508e-05],
       [1.7291466e-03, 2.5600385e-02, 1.8182390e-03, ..., 1.4499390e-03,
        4.8478998e-04, 8.5257640e-04],
       [2.5776261e-04, 8.6797208e-01, 3.9900807e-03, ..., 2.6547859e-04,
        6.5820634e-05, 6.8603881e-06]], dtype=float32)

Прогнозы представляют 46 возможных классификаций:

predictions[0].shape
(46,)

Их сумма равна 1:

np.sum(predictions[0])
0.99999994

Самый большой из них, т.е. мы думаем, что эта новость попадает в эту категорию

np.argmax(predictions[0])
3

Еще один способ борьбы с этикетками и потерями

Как упоминалось ранее, метки могут быть закодированы с использованием горячего кодирования, или они могут быть преобразованы непосредственно в целочисленные тензоры:

y_train = np.array(train_labels)
y_test = np.array(test_labels)

В этом случае функцию потерь также следует изменить на sparse_categorical_crossentropy, Это математически то же самое, что и categorical_crossentropy, но с другим интерфейсом:

 model.compile(optimizer='rmsprop',
               loss='sparse_categorical_crossentropy',
               metrics=['acc'])

Важность достаточно большого измерения в среднем слое

Мы уже говорили об «информационном узком месте», а потом сказали, что для этой 46-мерной результирующей сети размерность среднего слоя должна быть достаточно большой!

Теперь давайте попробуем, что произойдет, если он будет недостаточно большим (вызывая информационное узкое место), давайте немного утрируем и уменьшим его с 64 до 4:

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.fit(partial_x_train,
          partial_y_train,
          epochs=20,
          batch_size=128,
          validation_data=(x_val, y_val))
Train on 7982 samples, validate on 1000 samples
Epoch 1/20
7982/7982 [==============================] - 2s 288us/sample - loss: 2.8097 - accuracy: 0.4721 - val_loss: 2.0554 - val_accuracy: 0.5430
.......
Epoch 20/20
7982/7982 [==============================] - 1s 121us/sample - loss: 0.6443 - accuracy: 0.8069 - val_loss: 1.8962 - val_accuracy: 0.6800

<tensorflow.python.keras.callbacks.History at 0x16f628b50>

Посмотрите на это, это обучение ничуть не хуже предыдущего 64-мерного, разрыв вполне очевиден.

Снижение этого эффекта связано с тем, что пространственное измерение вашего обучения слишком низкое, и он отбрасывает много полезной информации для классификации.

Чем больше, тем лучше? Попробуем увеличить средний слой:

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4096, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.fit(partial_x_train,
          partial_y_train,
          epochs=20,
          batch_size=128,
          validation_data=(x_val, y_val))
Train on 7982 samples, validate on 1000 samples
Epoch 1/20
7982/7982 [==============================] - 2s 273us/sample - loss: 1.5523 - accuracy: 0.6310 - val_loss: 1.1903 - val_accuracy: 0.7060
......
Epoch 20/20
7982/7982 [==============================] - 2s 296us/sample - loss: 0.0697 - accuracy: 0.9605 - val_loss: 3.5296 - val_accuracy: 0.7850

<tensorflow.python.keras.callbacks.History at 0x1707fcf90>

Видно, что время обучения немного больше, а компьютер немного теплее, но эффект не сильно улучшился. Это связано с тем, что вход из первого слоя в средний слой имеет размерность всего 64. Независимо от того, насколько велик средний слой, он также ограничен узким местом первого слоя.

Попробуйте также увеличить первый слой!

model = models.Sequential()
model.add(layers.Dense(512, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

model.fit(partial_x_train,
          partial_y_train,
          epochs=20,
          batch_size=128,
          validation_data=(x_val, y_val))
Train on 7982 samples, validate on 1000 samples
Epoch 1/20
7982/7982 [==============================] - 5s 662us/sample - loss: 1.3423 - accuracy: 0.6913 - val_loss: 0.9565 - val_accuracy: 0.7920
......
Epoch 20/20
7982/7982 [==============================] - 5s 583us/sample - loss: 0.0648 - accuracy: 0.9597 - val_loss: 2.9887 - val_accuracy: 0.8030

<tensorflow.python.keras.callbacks.History at 0x176fbbd90>

(Чуть меньше, изначально использовал 4096, но он великоват, наша нищенская версия mbp работает очень медленно, догоняет больше 20 минут, мне лень ждать)

Это пустая трата времени, и он быстропересечь грязную рекуЭто переоснащение, и это все равно очень плохо.Нарисуй картинку и посмотри:

import matplotlib.pyplot as plt

loss = _.history['loss']
val_loss = _.history['val_loss']
epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'bo-', label='Training loss')
plt.plot(epochs, val_loss, 'rs-', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

png

Так что слишком большой тоже нехорошо. Еще есть диплом!

попробуйте использовать меньше/больше слоев

  1. меньше слоев
model = models.Sequential()
model.add(layers.Dense(46, activation='softmax', input_shape=(10000,)))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

history = model.fit(partial_x_train,
          partial_y_train,
          epochs=20,
          batch_size=128,
          validation_data=(x_val, y_val))

loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'bo-', label='Training loss')
plt.plot(epochs, val_loss, 'rs-', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
Train on 7982 samples, validate on 1000 samples
Epoch 1/20
7982/7982 [==============================] - 1s 132us/sample - loss: 2.4611 - accuracy: 0.6001 - val_loss: 1.8556 - val_accuracy: 0.6440
......
Epoch 20/20
7982/7982 [==============================] - 1s 85us/sample - loss: 0.1485 - accuracy: 0.9570 - val_loss: 1.2116 - val_accuracy: 0.7960

png

Ну давай же! Результат немного хуже.

Clown0te("防盗文爬:)虫的追踪标签,读者不必在意").by(CDFMLR)
  1. больше слоев
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

history = model.fit(partial_x_train,
          partial_y_train,
          epochs=20,
          batch_size=128,
          validation_data=(x_val, y_val))

loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'bo-', label='Training loss')
plt.plot(epochs, val_loss, 'rs-', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
Train on 7982 samples, validate on 1000 samples
Epoch 1/20
7982/7982 [==============================] - 2s 188us/sample - loss: 1.8340 - accuracy: 0.5829 - val_loss: 1.3336 - val_accuracy: 0.6910
......
Epoch 20/20
7982/7982 [==============================] - 1s 115us/sample - loss: 0.0891 - accuracy: 0.9600 - val_loss: 1.7227 - val_accuracy: 0.7900

png

Так что это не чем больше, тем лучше!

Прогнозирование цен на жилье: проблема регрессии

Оригинальная ссылка

В первых двух примерах мы решали задачу классификации (предсказывая дискретные метки). На этот раз мы рассмотрим проблему регрессии (предсказание непрерывных значений).

Набор данных Бостонской комнаты

Мы используем набор данных Boston Housing Price для прогнозирования цен на жилье в пригородах Бостона в середине 1970-х годов. Набор данных содержит некоторые данные из этого места в то время, такие как уголовные законы, налоговые ставки и так далее.

Этот набор данных довольно мал по сравнению с нашими первыми двумя наборами данных классификации: всего 506, 404 в обучающем наборе и 102 в тестовом наборе. Порядок величины входных данных для каждого признака в данных также различен.

Давайте сначала импортируем данные (этот набор данных также поставляется с Keras):

from tensorflow.keras.datasets import boston_housing

(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

print(train_data.shape, test_data.shape)
(404, 13) (102, 13)
train_data[0:3]
array([[1.23247e+00, 0.00000e+00, 8.14000e+00, 0.00000e+00, 5.38000e-01,
        6.14200e+00, 9.17000e+01, 3.97690e+00, 4.00000e+00, 3.07000e+02,
        2.10000e+01, 3.96900e+02, 1.87200e+01],
       [2.17700e-02, 8.25000e+01, 2.03000e+00, 0.00000e+00, 4.15000e-01,
        7.61000e+00, 1.57000e+01, 6.27000e+00, 2.00000e+00, 3.48000e+02,
        1.47000e+01, 3.95380e+02, 3.11000e+00],
       [4.89822e+00, 0.00000e+00, 1.81000e+01, 0.00000e+00, 6.31000e-01,
        4.97000e+00, 1.00000e+02, 1.33250e+00, 2.40000e+01, 6.66000e+02,
        2.02000e+01, 3.75520e+02, 3.26000e+00]])
train_targets[0:3]
array([15.2, 42.3, 50. ])

Единицей данных в target являетсятысячи долларов, цена еще дешевле в это время:

min(train_targets), sum(train_targets)/len(train_targets), max(train_targets)
(5.0, 22.395049504950496, 50.0)
import matplotlib.pyplot as plt

x = range(len(train_targets))
y = train_targets

plt.plot(x, y, 'o', label='data')
plt.title('House Prices')
plt.xlabel('train_targets')
plt.ylabel('prices')
plt.legend()
plt.show()

png

подготовка данных

Диапазон значений данных, которые мы подаем в нейросеть, не должен сильно отличаться, хотя нейросеть и может обрабатывать данные с большими пропусками, это не очень хорошо. Для таких данных с большими разрывами мы обычно нормализуем каждый признак (нормализация по признаку).

Это делается путем вычитания среднего значения этого столбца для каждой функции (столбца матрицы входных данных) и деления на его стандартное отклонение. После этого данные станут 0-центрированными с одним стандартным отклонением (стандартное отклонение 1).

Это легко сделать с помощью Numpy:

mean = train_data.mean(axis=0)
std = train_data.std(axis=0)

train_data -= mean
train_data /= std

test_data -= mean
test_data /= std

Обратите внимание, что при обработке тестового набора используются среднее значение и стандартное отклонение обучающего набора.

После обработки данных сеть можно построить и обучить (метки не нужно обрабатывать, что удобнее, чем классификация)

Создайте сеть

Чем меньше данных, тем легче выполнить переоснащение.Чтобы замедлить переоснащение, вы можете использовать меньшую сеть.

Например, в этой задаче мы используем сеть только с двумя скрытыми слоями, по 64 элемента в каждом:

from tensorflow.keras import models
from tensorflow.keras import layers

def build_model():
    model = models.Sequential()
    model.add(layers.Dense(64, activation="relu", input_shape=(train_data.shape[1], )))
    model.add(layers.Dense(64, activation="relu"))
    model.add(layers.Dense(1))
    
    model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"])
    
    return model

Последний слой сети имеет только одну единицу и не имеет функции активации (так что это линейный слой). Этот слой является стандартным для последнего шага в нашей задаче непрерывной регрессии с одним значением.

Если добавлена ​​функция активации, выходное значение будет иметь предел диапазона, например, сигмоид ограничит значение до[0, 1]. Без функции активации нет предела значению вывода этого линейного слоя.

Когда мы компилируем модель, используется функция потерьmse(среднеквадратичная ошибка). Эта функция возвращает квадрат разницы между прогнозом и истинным целевым значением. В задачах регрессии часто используется эта потеря.

Затем мы также использовали показатели обучения, которые раньше не использовали.mae(средняя абсолютная ошибка, средняя абсолютная ошибка), это абсолютное значение разницы между прогнозом и реальной целью.

Подтверждение соответствия - K-кратная проверка

Мы делали это раньше — чтобы оценить сеть, настроить параметры сети (например, количество эпох обучения), мы разделили данные на наборы для обучения и проверки. На этот раз нам нужно сделать то же самое. Но небольшая проблема в том, что на этот раз у нас слишком мало данных, поэтому разделяемый нами проверочный набор очень мал (может быть, всего 100 фрагментов данных). В этом случае разные данные, выбранные для проверочного набора, могут сильно повлиять на результаты проверки (то есть разные методы разбиения проверочного набора могут привести к большому расхождению результатов проверки), что повлияет на нашу проверку достоверности. модель.

В этой неловкой ситуации лучше всего использоватьK-foldПерекрестная проверка (К-кратная перекрестная проверка).

При K-кратной проверке мы делим данные на K частей (обычно k = 4 или 5), затем создаем K независимых моделей, каждая из которых обучена с K-1 копиями данных, а затем проверяется с помощью оставшейся копии, оценка проверки окончательной модели использует среднее значение K частей.

K-fold 验证示意图

Реализация кода K-кратной проверки:

Немного изменив пример в книге, добавим немного кода, использующего TensorBoard для визуализации процесса обучения. Первая загрузка tensorboard в блокноте Jupyter Lab:

# Load the TensorBoard notebook extension
# TensorBoard 可以可视化训练过程
%load_ext tensorboard
# Clear any logs from previous runs
!rm -rf ./logs/ 

вывод:

The tensorboard extension is already loaded. To reload it, use:
  %reload_ext tensorboard

Затем начните писать основной код:

import numpy as np
import datetime
import tensorflow as tf

k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []

# 准备 TensorBoard
log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

for i in range(k):
    print(f'processing fold #{i} ({i+1}/{k})')
    
    # 准备验证数据
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
    
    # 准备训练数据
    partial_train_data = np.concatenate(
        [train_data[: i * num_val_samples],
         train_data[(i+1) * num_val_samples :]],
        axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[: i * num_val_samples], 
         train_targets[(i+1) *  num_val_samples :]], 
        axis=0)
    
    #构建、训练模型
    model = build_model()
    model.fit(partial_train_data, partial_train_targets, 
              epochs=num_epochs, batch_size=1, verbose=0,
              callbacks=[tensorboard_callback])
    
    # 有验证集评估模型
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    all_scores.append(val_mae)


np.mean(all_scores)
processing fold #0 (1/4)
processing fold #1 (2/4)
processing fold #2 (3/4)
processing fold #3 (4/4)

2.4046657

Используйте следующую команду для отображения тензорной доски в блокноте Jupyter Lab:

%tensorboard --logdir logs/fit

Это выглядит так:

tensorboard的截图

Эту штуку также можно открыть прямо в браузереhttp://localhost:6006получить.

Только что это было просто для удовольствия, теперь давайте изменим его, повторим 500 раундов (MBP без независимого дисплея такой медленный) и запишем результаты обучения:

k = 4
num_val_samples = len(train_data) // k

num_epochs = 500
all_mae_histories = []

for i in range(k):
    print(f'processing fold #{i} ({i+1}/{k})')
    
    # 准备验证数据
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
    
    # 准备训练数据
    partial_train_data = np.concatenate(
        [train_data[: i * num_val_samples],
         train_data[(i+1) * num_val_samples :]],
        axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[: i * num_val_samples], 
         train_targets[(i+1) *  num_val_samples :]], 
        axis=0)
    
    #构建、训练模型
    model = build_model()
    history = model.fit(partial_train_data, partial_train_targets,
                        validation_data=(val_data, val_targets),
                        epochs=num_epochs, batch_size=1, verbose=0)

    mae_history = history.history['val_mae']
    all_mae_histories.append(mae_history)


print("Done.")
processing fold #0 (1/4)
processing fold #1 (2/4)
processing fold #2 (3/4)
processing fold #3 (4/4)
Done.

Рисунок:

average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
import matplotlib.pyplot as plt

plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

png

Эта картинка слишком плотная, чтобы ее можно было четко разглядеть, поэтому нам нужно с ней разобраться:

  • Удалите первую десятку групп данных, этот конец явно далек от других чисел;
  • Сгладьте кривую, заменив каждую точку экспоненциальной скользящей средней предыдущих точек;
def smooth_curve(points, factor=0.9):
  smoothed_points = []
  for point in points:
    if smoothed_points:
      previous = smoothed_points[-1]
      smoothed_points.append(previous * factor + point * (1 - factor))
    else:
      smoothed_points.append(point)
  return smoothed_points

smooth_mae_history = smooth_curve(average_mae_history[10:])

plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

png

Из этого графика видно, что переоснащение происходит почти через 80 эпох.

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

# 训练最终模型

model = build_model()
model.fit(train_data, train_targets, 
          epochs=80, batch_size=16, verbose=0)

# 最后评估一下
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets, verbose=0)
print(test_mse_score, test_mae_score)
17.43332971311083 2.6102107

Значение этого test_mae_score показывает, что предсказание нашей обученной модели примерно на 2 тысячи долларов хуже, чем фактическое. . . ?