В этой статье мы создадим очень простую модель с помощью TensorFlow, которая учится распознавать рукописные цифры. Пример предназначен для использования в качестве основы для будущих статей.

Статья написана с использованием Jupyter Lab, и вы можете скачать соответствующий код по адресу https://github.com/alan-cooney/blog/blob/main/posts/mnist-simple/mnist-simple.ipynb.

Настраивать

Сначала мы импортируем ключевые зависимости:

import typing
from os import getcwd, path
import tensorflow as tf
import tensorflow_datasets as tfds
from keras import models
from matplotlib import pyplot
from tensorflow.python.data.ops import dataset_ops
from tensorflow_datasets.core import dataset_info

Далее мы загрузим набор данных MNIST, используя tensorflow_datasets:

# Add types for the dataset
ds_train: dataset_ops.Dataset
ds_test: dataset_ops.Dataset
ds_info = dataset_info.DatasetInfo
# Download
(ds_train, ds_test), ds_info = tfds.load(
    'mnist',
    split=['train', 'test'], # Split into training and testing data
    data_dir=path.join(getcwd(), ".data"), # Store in .data/
    as_supervised=True, # Get tuples (features, label)
    with_info=True, # Include extra info about the dataset
)

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

def normalize_img(image: tf.Tensor, label: tf.Tensor) -> typing.Tuple[tf.Tensor, tf.Tensor]:
  """Normalize image from `uint8` to `float32`
  The image pixels are given as integers from 0 (black)  to 255 (white).
  Args:
      image (tf.uint8): Image tensor of type unit8
      label (tf.int64): Label
  Returns:
      typing.Tuple[tf.float32, tf.int64]: Tuple with the normalised image, and label
  """
  normalized_image = tf.cast(image, tf.float32) / 255. 
  return normalized_image, label
ds_train = ds_train.map(normalize_img)
ds_test = ds_test.map(normalize_img)

Затем, наконец, мы поместим изображения в пакеты.

ds_train = ds_train.batch(64)
ds_test = ds_test.batch(64)

Модель

Далее мы создадим очень простую модель. Сначала нам нужно сгладить каждое изображение (чтобы оно стало вектором 1x784), а затем самый простой подход — просто использовать один слой (что дает точность около 90%). Однако, чтобы получить точность 95%+, мы будем использовать два слоя со слоем активации ReLU между ними.

model = models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(10)
])

Далее мы скомпилируем модель, настроим оптимизатор и функцию потерь.

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

Для потери мы будем использовать кросс-энтропию.

model.compile(
    optimizer=tf.keras.optimizers.SGD(0.01),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(
      from_logits=True
    ), # Standard cross-entropy loss
    metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
)

И с этим мы можем обучить модель:

history = model.fit(
    ds_train,
    epochs=30,
    validation_data=ds_test,
)

Наконец, вот график точности:

pyplot.plot(history.history['val_sparse_categorical_accuracy'])
pyplot.title('Model accuracy (on test data)')
pyplot.ylabel('Accuracy')
pyplot.ylim([0.85, 1])
pyplot.xlabel('Epoch')
pyplot.show()