[Перевод] TensorFlow Tutorial #01 Простая линейная модель

искусственный интеллект TensorFlow Python NumPy

исходный адрес

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

Вы должны быть знакомы с основами линейной алгебры, Python и редактором Jupyter Notebook. Также полезно, если у вас есть базовые знания о машинном обучении и классификации.

#Imports

%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from sklearn.metrics import confusion_matrix

#Load Data

Набор данных MNIST имеет размер около 12 МБ и будет загружен автоматически, если его нет по указанному пути.

from mnist import MNIST
data = MNIST(data_dir="data/MNIST/")

Набор данных MNIST теперь загружен и состоит из 70 000 изображений и номеров классов изображений. Набор данных был разделен на 3 взаимоисключающих подмножества. В этом руководстве мы будем использовать только наборы для обучения и тестирования.

print("Size of:")
print("- Training-set:\t\t{}".format(data.num_train))
print("- Validation-set:\t{}".format(data.num_val))
print("- Test-set:\t\t{}".format(data.num_test))

Size of:
- Training-set:		55000
- Validation-set:	5000
- Test-set:		10000

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

# The images are stored in one-dimensional arrays of this length.
img_size_flat = data.img_size_flat

# Tuple with height and width of images used to reshape arrays.
img_shape = data.img_shape

# Number of classes, one class for each of 10 digits.
num_classes = data.num_classes

#One-Hot Encoding

Выходные данные загружаются в виде целочисленных чисел и так называемых массивов с кодировкой One-Hot. Это означает, что номер класса был преобразован из одного целого числа в вектор длины, равной количеству возможных классов. Все элементы вектора равны нулю, кромеi' элемент равен 1 и означает, что классi. Например, метки с кодировкой One-Hot для первых 5 изображений в тестовом наборе:

data.y_test[0:5, :]

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

Нам также необходимо выполнять различные сравнения и измерения производительности с классами как целыми числами. Эти данные можно найти в закодированном массиве One-Hot с помощью функции np.argmax() для получения индекса самого высокого элемента. Но мы сделали это при загрузке набора данных, поэтому мы можем видеть номера классов для первых пяти изображений в тестовом наборе. Сравните их с массивом, закодированным One-Hot выше.

data.y_test_cls[0:5]

array([7, 2, 1, 0, 4])

Далее делаем небольшой пример: Нарисуйте 9 изображений в сетке 3x3 и запишите функции для истинного и предсказанного классов под каждым изображением.

def plot_images(images, cls_true, cls_pred=None):
    assert len(images) == len(cls_true) == 9
    
    # Create figure with 3x3 sub-plots.
    fig, axes = plt.subplots(3, 3)
    fig.subplots_adjust(hspace=0.3, wspace=0.3)

    for i, ax in enumerate(axes.flat):
        # Plot image.
        ax.imshow(images[i].reshape(img_shape), cmap='binary')

        # Show true and predicted classes.
        if cls_pred is None:
            xlabel = "True: {0}".format(cls_true[i])
        else:
            xlabel = "True: {0}, Pred: {1}".format(cls_true[i], cls_pred[i])

        ax.set_xlabel(xlabel)
        
        # Remove ticks from the plot.
        ax.set_xticks([])
        ax.set_yticks([])
        
    # Ensure the plot is shown correctly with multiple plots
    # in a single Notebook cell.
    plt.show()

посмотрите на выходное изображение

# Get the first images from the test-set.
images = data.x_test[0:9]

# Get the true classes for those images.
cls_true = data.y_test_cls[0:9]

# Plot the images and labels using our helper-function above.
plot_images(images=images, cls_true=cls_true)


#TensorFlow Graph

Весь смысл TensorFlow в том, чтобы иметь так называемый граф вычислений, который может быть выполнен гораздо эффективнее, чем выполнение тех же вычислений непосредственно в Python. TensorFlow может быть более эффективным, чем NumPy, потому что TensorFlow знает весь граф вычислений, которые необходимо выполнить, тогда как NumPy знает только вычисление одной математической операции за раз. TensorFlow также может автоматически вычислять градиенты, необходимые для оптимизации переменных графика, чтобы модель работала лучше. Это связано с тем, что график представляет собой комбинацию простых математических выражений, поэтому градиент всего графика можно рассчитать с помощью производного цепного правила. TensorFlow также может использовать преимущества многоядерных процессоров и графических процессоров — и Google даже создала специализированные чипы для TensorFlow, называемые TPU (Tensor Processing Units), которые даже быстрее, чем графические процессоры.

Граф TensorFlow состоит из следующих частей, которые подробно описаны ниже:

占位符变量用于将输入提供给图表。
模型变量将进行优化,以使模型更好地运行。
该模型本质上只是一个数学函数,它根据占位符变量和模型变量中的输入计算一些输出。
可用于指导变量优化的成本度量。
一种更新模型变量的优化方法。

Кроме того, графики TensorFlow также могут содержать различные операторы отладки, такие как: для регистрации данных, отображаемых с помощью TensorBoard, которые не рассматриваются в этом руководстве.


#Placeholder variables

Переменные-заполнители используются в качестве входных данных для графика, и мы можем изменять эти входные данные каждый раз, когда выполняется график. Мы называем это переменной-заполнителем и описываем ее ниже.

Во-первых, мы определяем переменные-заполнители для входного изображения. Это позволяет нам изменить вход изображения в график TensorFlow. Это так называемый тензор, что означает, что это многомерный вектор или матрица. Для типа данных установлено значение float32, а для формы установлено значение [None, img_size_flat], где None означает, что тензор может содержать любое количество изображений, каждое изображение представляет собой вектор длины img_size_flat.

x = tf.placeholder(tf.float32, [None, img_size_flat])

Затем у нас есть переменная-заполнитель для метки истинности, связанной с входным изображением в переменной-заполнителе x. Эта переменная-заполнитель имеет форму [None, num_classes], что означает, что она может содержать любое количество меток, каждая метка представляет собой вектор длины num_classes, в данном случае 10.

y_true = tf.placeholder(tf.float32, [None, num_classes])

Наконец, мы предоставляем переменные-заполнители для истинного класса каждого изображения в переменной-заполнителе x. Это целые числа, и для размерности этой переменной-заполнителя задано значение [Нет], что означает, что переменная-заполнитель представляет собой одномерный вектор произвольной длины.

y_true_cls = tf.placeholder(tf.int64, [None])

#Variables to be optimized

В дополнение к переменным-заполнителям, определенным выше и используемым для подачи входных данных в модель, есть некоторые переменные модели, которые TensorFlow должен изменить, чтобы модель лучше работала с обучающими данными.

Первая переменная, которую необходимо оптимизировать, называется весами, здесь они определены как переменные TensorFlow, должны быть инициализированы нулями и иметь форму [img_size_flat, num_classes], поэтому это двумерный тензор (или матрица) со строками img_size_flat и столбцами num_classes.

weights = tf.Variable(tf.zeros([img_size_flat, num_classes]))

Вторая переменная, которую необходимо оптимизировать, называется смещением и определяется как одномерный тензор (или вектор) длины num_classes.

biases = tf.Variable(tf.zeros([num_classes]))

#Model

Эта простая математическая модель умножает изображения в переменной-заполнителе x на веса, а затем добавляет смещение.

Результатом является матрица формы [num_images, num_classes], поскольку x имеет форму [num_images, img_size_flat], а веса имеют форму [img_size_flat, num_classes], поэтому умножение этих двух матриц представляет собой матрицу формы [num_images, num_classes], тогда вектор смещения добавляется к каждой строке этой матрицы.

Обратите внимание, что имя logits является типичным термином TensorFlow, но другие могут относиться к переменной как к чему-то другому.

logits = tf.matmul(x, weights) + biases

Теперь логиты — это матрица со строками num_images и столбцами num_classes, гдеiлиния иj' Поэлементная оценка столбцаi' насколько входное изображение может бытьj'.

Однако эти оценки немного приблизительны и их трудно интерпретировать, так как числа могут быть очень маленькими или большими, поэтому мы хотим нормализовать их так, чтобы сумма каждой строки логит-матрицы равнялась 1, а каждый элемент был ограничен между 0 и 1. Это вычисляется с помощью так называемой функции softmax, а результат сохраняется в y_pred.

y_pred = tf.nn.softmax(logits)

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

y_pred_cls = tf.argmax(y_pred, axis=1)

#Cost-function to be optimized

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

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

TensorFlow имеет встроенные функции для вычисления кросс-энтропии. Обратите внимание, что он использует значение логитов, потому что он также вычисляет softmax внутри.

cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits,
                                                           labels=y_true)

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

cost = tf.reduce_mean(cross_entropy)

#Optimization method

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

Обратите внимание, что в настоящее время оптимизация не выполняется. По сути, вообще ничего не вычисляется, мы просто добавляем объект-оптимизатор в граф TensorFlow для последующего исполнения.

optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.5).minimize(cost)

#Performance measures

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

correct_prediction = tf.equal(y_pred_cls, y_true_cls)

Это вычисляет точность классификации, сначала преобразуя векторный тип логических значений в числа с плавающей запятой, так что False становится 0, а True становится 1, а затем вычисляет среднее значение этих чисел.

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

#TensorFlowRun После создания графа TensorFlow мы должны создать сеанс TensorFlow для выполнения графа.

session = tf.Session()

Прежде чем мы начнем оптимизацию, необходимо инициализировать переменные для весов и смещений.

session.run(tf.global_variables_initializer())

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

batch_size = 100

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

def optimize(num_iterations):
    for i in range(num_iterations):
        # Get a batch of training examples.
        # x_batch now holds a batch of images and
        # y_true_batch are the true labels for those images.
        x_batch, y_true_batch, _ = data.random_batch(batch_size=batch_size)
        
        # Put the batch into a dict with the proper names
        # for placeholder variables in the TensorFlow graph.
        # Note that the placeholder for y_true_cls is not set
        # because it is not used during training.
        feed_dict_train = {x: x_batch,
                           y_true: y_true_batch}

        # Run the optimizer using this batch of training data.
        # TensorFlow assigns the variables in feed_dict_train
        # to the placeholder variables and then runs the optimizer.
        session.run(optimizer, feed_dict=feed_dict_train)

#Helper-функции для демонстрации производительности Dict с данными тестового набора для использования в качестве входных данных для графика TensorFlow. Обратите внимание, что мы должны использовать правильные имена для переменных-заполнителей в графике TensorFlow.

feed_dict_test = {x: data.x_test,
                  y_true: data.y_test,
                  y_true_cls: data.y_test_cls}

Функция для печати точности классификации на тестовом наборе.

def print_accuracy():
    # Use TensorFlow to compute the accuracy.
    acc = session.run(accuracy, feed_dict=feed_dict_test)
    
    # Print the accuracy.
    print("Accuracy on test-set: {0:.1%}".format(acc))

Функции для печати и построения матриц путаницы с использованием scikit-learn.

def print_confusion_matrix():
    # Get the true classifications for the test-set.
    cls_true = data.y_test_cls
    
    # Get the predicted classifications for the test-set.
    cls_pred = session.run(y_pred_cls, feed_dict=feed_dict_test)

    # Get the confusion matrix using sklearn.
    cm = confusion_matrix(y_true=cls_true,
                          y_pred=cls_pred)

    # Print the confusion matrix as text.
    print(cm)

    # Plot the confusion matrix as an image.
    plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)

    # Make various adjustments to the plot.
    plt.tight_layout()
    plt.colorbar()
    tick_marks = np.arange(num_classes)
    plt.xticks(tick_marks, range(num_classes))
    plt.yticks(tick_marks, range(num_classes))
    plt.xlabel('Predicted')
    plt.ylabel('True')
    
    # Ensure the plot is shown correctly with multiple plots
    # in a single Notebook cell.
    plt.show()

Функция для построения неправильно классифицированных примеров изображений из тестового набора.

def plot_example_errors():
    # Use TensorFlow to get a list of boolean values
    # whether each test-image has been correctly classified,
    # and a list for the predicted class of each image.
    correct, cls_pred = session.run([correct_prediction, y_pred_cls],
                                    feed_dict=feed_dict_test)

    # Negate the boolean array.
    incorrect = (correct == False)
    
    # Get the images from the test-set that have been
    # incorrectly classified.
    images = data.x_test[incorrect]
    
    # Get the predicted classes for those images.
    cls_pred = cls_pred[incorrect]

    # Get the true classes for those images.
    cls_true = data.y_test_cls[incorrect]
    
    # Plot the first 9 images.
    plot_images(images=images[0:9],
                cls_true=cls_true[0:9],
                cls_pred=cls_pred[0:9])

#Вспомогательная функция для построения весов модели Функция, используемая для построения весов модели. Рисуется 10 изображений, по одному на каждую цифру, и модель обучается распознавать.

def plot_weights():
    # Get the values for the weights from the TensorFlow variable.
    w = session.run(weights)
    
    # Get the lowest and highest values for the weights.
    # This is used to correct the colour intensity across
    # the images so they can be compared with each other.
    w_min = np.min(w)
    w_max = np.max(w)

    # Create figure with 3x4 sub-plots,
    # where the last 2 sub-plots are unused.
    fig, axes = plt.subplots(3, 4)
    fig.subplots_adjust(hspace=0.3, wspace=0.3)

    for i, ax in enumerate(axes.flat):
        # Only use the weights for the first 10 sub-plots.
        if i<10:
            # Get the weights for the i'th digit and reshape it.
            # Note that w.shape == (img_size_flat, 10)
            image = w[:, i].reshape(img_shape)

            # Set the label for the sub-plot.
            ax.set_xlabel("Weights: {0}".format(i))

            # Plot the image.
            ax.imshow(image, vmin=w_min, vmax=w_max, cmap='seismic')

        # Remove ticks from each sub-plot.
        ax.set_xticks([])
        ax.set_yticks([])
        
    # Ensure the plot is shown correctly with multiple plots
    # in a single Notebook cell.
    plt.show()

#Производительность до любой оптимизации Точность на тестовом наборе составляет 9,8%. Это связано с тем, что модель только инициализирована и вообще не оптимизирована, поэтому она всегда предсказывает, что изображения показывают нулевые цифры, как показано на рисунке ниже, получается, что 9,8% изображений в тестовом наборе имеют нулевые цифры.

print_accuracy()
Accuracy on test-set: 9.8%
plot_example_errors()

Производительность после 1 итерации оптимизации

optimize(num_iterations=1)
print_accuracy()
Accuracy on test-set: 15.9%
plot_example_errors()

Веса также могут быть нарисованы, как показано ниже. Положительные веса — красные, отрицательные — синие. Эти веса можно интуитивно понять как фильтры изображения.

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

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

Обратите внимание, что веса в основном выглядят как числа, которые они должны распознавать. Это связано с тем, что выполняется только одна итерация оптимизации, поэтому веса обучаются только на 100 изображениях. После обучения на тысячах изображений вес становится труднее интерпретировать, потому что приходится распознавать множество вариаций написания чисел.

plot_weights()

Производительность после 10 итераций оптимизации

optimize(num_iterations=9)
print_accuracy()
Accuracy on test-set: 66.2%
plot_example_errors()

plot_weights()

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

optimize(num_iterations=990)
print_accuracy()
Accuracy on test-set: 91.5%
plot_example_errors()

Теперь модель обучена для 1000 итераций оптимизации, каждая из которых использует 100 изображений из обучающего набора. Из-за разнообразия изображений веса теперь трудно интерпретировать, и мы можем задаться вопросом, действительно ли модель понимает, как числа состоят из линий, или модель просто запоминает множество различных изменений пикселей.

plot_weights()

Мы также можем распечатать и построить так называемую матрицу путаницы, которая позволяет нам увидеть более подробную информацию о неправильных классификациях. Например, он показывает, что изображения, на самом деле изображающие 5, иногда ошибочно классифицируются как все другие возможные числа, но в основном 6 или 8.

print_confusion_matrix()

[[ 956 0 3 1 1 4 11 3 1 0] [ 0 1114 2 2 1 2 4 2 8 0] [ 6 8 925 23 11 3 13 12 26 5] [ 3 1 19 928 0 34 2 10 5 8] [ 1 3 4 2 918 2 11 2 6 33] [ 8 3 7 36 8 781 15 6 20 8] [ 9 3 5 1 14 12 912 1 1 0] [ 2 11 24 10 6 1 0 941 1 32] [ 8 13 11 44 11 52 13 14 797 11] [ 11 7 2 14 50 10 0 30 4 881]]

Мы закончили работу с TensorFlow, поэтому закрываем сеанс, чтобы освободить его ресурсы.

# This has been commented out in case you want to modify and experiment
# with the Notebook without having to restart it.
# session.close()