Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шолле Ф. - Глубокое обучение на Python (Библиотека программиста) - 2023.pdf
Скачиваний:
4
Добавлен:
07.04.2024
Размер:
11.34 Mб
Скачать

116    Глава 3. Введение в Keras и TensorFlow

Важно.отметить,.что.каждая.из.предыдущих.операций.выполняется.немедленно:. в.любой.момент.вы.можете.вывести.текущий.результат,.как.в.NumPy..Мы.называем.это.жадным выполнением (eager execution).

3.5.3. Второй взгляд на GradientTape

Пока.что.TensorFlow.кажется.очень.похожим.на.NumPy..Но.вот.кое-что,.чего. NumPy.не.умеет.делать:.вычисление.градиента.любого.дифференцируемого. выражения.по.отношению.к.любому.из.его.входов..Просто.откройте.контекст. GradientTape,.примените.некоторые.вычисления.к.одному.или.нескольким. входным.тензорам.—.и.получите.градиент.результата.относительно.входов.

Листинг 3.10. Пример использования GradientTape

input_var = tf.Variable(initial_value=3.) with tf.GradientTape() as tape:

result = tf.square(input_var)

gradient = tape.gradient(result, input_var)

Этот.прием.чаще.всего.используется.для.получения.градиентов.потерь.модели. относительно.ее.весов:.gradient.=.tape.gradient(loss,.weights)..Вы.уже.видели,. как.это.делается,.в.главе.2.

До .сих .пор .мы .рассматривали .только .случай, .когда .входными .тензорами. в.tape.gradient() .были.переменные.TensorFlow..Входные.данные.могут.быть. представлены.любым.тензором,.но.по.умолчанию.отслеживаются.только.обучаемые переменные..Чтобы.задействовать.тензор-константу,.придется.вручную. отметить.его.как.отслеживаемый.вызовом.tape.watch().

Листинг 3.11. Пример использования GradientTape с входным тензором-константой

input_const = tf.constant(3.) with tf.GradientTape() as tape:

tape.watch(input_const)

result = tf.square(input_const)

gradient = tape.gradient(result, input_const)

Почему.так?.Потому.что.было.бы.слишком.дорого.хранить.информацию,.не- обходимую.для.вычисления.градиента.чего-либо.по.отношению.к.чему-либо.. Чтобы.не.тратить.ресурсы.впустую,.объект.GradientTape .должен.знать,.за.чем. наблюдать..Обычно.GradientTape.используется.для.вычисления.градиента.потерь. относительно.списка.обучаемых.переменных,.так.что.по.умолчанию.наблюдение. ведется.именно.за.обучаемыми.переменными.

3.5. Первые шаги с TensorFlow    117

GradientTape.—.мощный.объект,.способный.даже.вычислять.градиенты второго порядка,.то.есть.градиенты.градиентов..Например,.градиент.положения.объекта. относительно.времени.—.это.скорость.объекта,.а.градиент.второго.порядка.—.его. ускорение.

Если.измерить.положение.падающего.яблока.вдоль.вертикальной.оси.с.течением. времени.и.обнаружить,.что.результаты.соответствуют.формуле.position(time) . =.4.9.*.time.**.2,.как.отсюда.получить.ускорение?.Давайте.воспользуемся.двумя. вложенными.контекстами.GradientTape .и.выясним.это.

Листинг 3.12. Использование вложенных контекстов GradientTape для вычисления градиента второго порядка

time = tf.Variable(0.)

Мы использовали внешний объект

 

 

with tf.GradientTape() as outer_tape:

GradientTape для вычисления градиента

 

with tf.GradientTape() as inner_tape:

из градиента внутреннего объекта

 

position = 4.9 * time ** 2

GradientTape. Естественно, результат

 

получился равным 4.9 * 2 = 9.8

 

speed = inner_tape.gradient(position, time)

 

 

 

acceleration = outer_tape.gradient(speed, time)

 

 

 

 

3.5.4. Полный пример: линейный классификатор на TensorFlow

Вы.познакомились.с.тензорами,.переменными.и.тензорными.операциями.и.узнали,.как.вычислять.градиенты..Оказывается,.этого.достаточно,.чтобы.построить. любую.модель.машинного.обучения.на.основе.градиентного.спуска..А.ведь.вы. только.начали.читать.главу.3!

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

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

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

118    Глава 3. Введение в Keras и TensorFlow

Листинг 3.13. Создание набора случайных точек двух классов на двумерной плоскости

num_samples_per_class = 1000

negative_samples = np.random.multivariate_normal( mean=[0, 3],

cov=[[1, 0.5],[0.5, 1]], size=num_samples_per_class)

positive_samples = np.random.multivariate_normal( mean=[3, 0],

cov=[[1, 0.5],[0.5, 1]], size=num_samples_per_class)

Сгенерировать 1000 случайных точек первого класса. cov=[[1, 0.5],[0.5, 1]] соответствует облаку точек овальной формы, вытянутому в направлении от левого нижнего к правому верхнему углу

Сгенерировать точки второго класса с той же ковариационной матрицей, но другим средним значением

Здесь.negative_samples .и.positive_samples .—.это.массивы.с.формой.(1000, .2).. Объединим.их.в.один.массив.с.формой.(2000, .2).

Листинг 3.14. Объединение точек двух классов в один массив с формой (2000, 2)

inputs = np.vstack((negative_samples, positive_samples)).astype(np.float32)

Теперь.сгенерируем.соответствующие.целевые.метки,.массив.нулей.и.единиц. с.формой.(2000, .1),.где.элементы.targets[i, .0] .равны.0,.если.input[i] .принадлежит.классу.0.(и.наоборот).

Листинг 3.15. Создание целевых меток (0 или 1)

targets = np.vstack((np.zeros((num_samples_per_class, 1), dtype="float32"), np.ones((num_samples_per_class, 1), dtype="float32")))

Теперь.нарисуем.точки.с.помощью.Matplotlib.

Листинг 3.16. Вывод классов точек на плоскости (рис. 3.6)

import matplotlib.pyplot as plt

plt.scatter(inputs[:, 0], inputs[:, 1], c=targets[:, 0]) plt.show()

Теперь.создадим.линейный.классификатор,.который.научится.разделять.эти. два.облака..Линейный.классификатор.—.это.аффинное.преобразование.(predic­ tion .= .W .•.input .+ .b),.обученное.минимизировать.квадрат.разницы.между.предсказаниями.и.целями.

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

3.5. Первые шаги с TensorFlow    119

Рис. 3.6. Наши искусственные данные: два класса случайных точек на двумерной плоскости

Давайте.создадим.переменные.W .и.b,.инициализированные.случайными.значениями.и.нулями.соответственно.

Листинг 3.17. Создание переменных для линейного классификатора

 

 

На вход подаются

Прогноз на выходе — единственная оценка

 

 

для каждого образца (близкая к 0, если предполагается,

 

 

двумерные точки

input_dim = 2

 

что образец относится к классу 0, или к 1, если

 

 

output_dim = 1

 

предполагается, что образец относится к классу 1)

 

 

W = tf.Variable(initial_value=tf.random.uniform(shape=(input_dim, output_dim))) b = tf.Variable(initial_value=tf.zeros(shape=(output_dim,)))

Вот.функция.прямого.прохода.

Листинг 3.18. Функция прямого прохода

def model(inputs):

return tf.matmul(inputs, W) + b

Наш.линейный.классификатор.будет.работать.с.двумерными.входными.данными,.поэтому.W .на.самом.деле.представляет.два.скалярных.коэффициента,. w1 .и.w2:.W .= .[[w1], .[w2]],.а.b .—.единственный.скалярный.коэффициент..То.есть. прогноз.для.каждой.данной.входной.точки.[x,.y].вычисляется.так:.prediction.= .

= [w1], .[w2]] ..[x, .y] .+ .b .= .w1 .* .x .+ .w2 .* .y .+ .b.

Обновление
весов
Получение градиента потерь относительно весов
Прямой проход внутри контекста GradientTape

120    Глава 3. Введение в Keras и TensorFlow

В.следующем.листинге.показана.наша.функция.потерь.

Листинг 3.19. Функция потерь, вычисляющая средний квадрат ошибок

Тензор per_sample_losses имеет ту же форму, что и тензоры targets и predictions, и содержит оценки потерь для каждого образца

def square_loss(targets, predictions): per_sample_losses = tf.square(targets - predictions)

return tf.reduce_mean(per_sample_losses)

Нам нужно усреднить оценки потерь по образцам в одно скалярное значение потерь: именно это делает reduce_mean

Далее.следует.этап.обучения,.который.принимает.некоторые.обучающие.данные. и.обновляет.веса.W .и.b,.стремясь.минимизировать.потери.на.данных.

Листинг 3.20. Функция этапа обучения

learning_rate = 0.1

def training_step(inputs, targets): with tf.GradientTape() as tape: predictions = model(inputs)

loss = square_loss(predictions, targets)

grad_loss_wrt_W, grad_loss_wrt_b = tape.gradient(loss, [W, b]) W.assign_sub(grad_loss_wrt_W * learning_rate) b.assign_sub(grad_loss_wrt_b * learning_rate)

return loss

Для.простоты.используем.пакетное обучение.вместо.мини-пакетного:.будем. запускать.каждый.шаг.обучения.(вычисление.градиента.и.обновление.весов). сразу.для.всех.данных,.не.перебирая.их.небольшими.партиями..С.одной.стороны,.это.означает,.что.каждый.шаг.обучения.будет.занимать.гораздо.больше. времени,.поскольку.прямой.проход.и.вычисление.градиентов.будут.производиться.для.2000.образцов.одновременно..С.другой.стороны,.с.каждым.новым. обновлением.градиента.потери.на.обучающих.данных.будут.снижаться.намного. эффективнее,.ведь.в.расчетах.будут.участвовать.сразу.все.образцы,.а.не,.скажем,. 128.случайно.отобранных..В.результате.потребуется.намного.меньше.шагов. обучения.и.можно.взять.более.высокую.скорость.обучения,.чем.при.обычном. обучении.на.небольших.пакетах.(мы.используем.learning_rate.= .0.1,.как.определено.в.листинге.3.20).

Листинг 3.21. Цикл пакетного обучения

for step in range(40):

loss = training_step(inputs, targets) print(f"Loss at step {step}: {loss:.4f}")

3.5. Первые шаги с TensorFlow    121

После.40.циклов.обучения.величина.потерь.стабилизировалась.на.уровне.около.0,025..Посмотрим,.как.получившаяся.линейная.модель.классифицирует.точки. из.обучающего.набора.данных..Поскольку.целевыми.значениями.у.нас.служат. нули.и.единицы,.всякая.входная.точка.будет.классифицироваться.как.0,.если. прогнозируемое.значение.для.нее.ниже.0,5,.и.как.1,.если.больше.0,5.(рис..3.7).

Рис. 3.7. Прогноз нашей модели очень близок к исходной картине

Напомню,.что.значение.прогноза.для.данной.точки.[x, .y] .вычисляется.как. prediction .== .[[w1], .[w2]] .•.[x, .y] .+ .b .== .w1 .* .x .+ .w2 .* .y .+ .b..То.есть.считается,.что. точка.принадлежит.классу.0,.если.выполняется.условие.w1 .* .x .+ .w2 .* .y .+ .b .<= .0.5,. и.классу.1,.если.выполняется.условие.w1 .* .x .+ .w2 .* .y .+ .b .> .0.5..Обратите.внимание,.что.на.самом.деле.перед.нами.уравнение.прямой.на.двумерной.плоскости:. w1 .* .x .+ .w2 .* .y .+ .b .= .0.5..Над.линией.находятся.точки,.принадлежащие.классу.1,. а.под.линией.—.принадлежащие.классу.0..Если.вы.привыкли.видеть.линейные. уравнения.в.формате.y .= .a .* .x .+ .b,.то.уравнение.нашей.линии.можно.выразить. так:.y .= .-w1 ./ .w2 .* .x .+ .(0.5 .- .b) ./ .w2.

Построим.эту.линию.(рис..3.8):

Сгенерировать 100 чисел, равномерно

 

 

 

 

 

 

 

 

 

 

 

 

 

 

распределенных в интервале от –1 до 4, которые

Это уравнение

 

 

 

 

 

 

 

 

будут использоваться для рисования прямой

нашей прямой

 

Нарисовать линию ("-r" означает

x = np.linspace(-1, 4, 100)

 

 

 

 

 

 

 

 

 

 

красный (red) цвет)

 

 

 

 

y = - W[0] / W[1] * x + (0.5 - b) / W[1]

 

 

 

 

 

 

 

 

 

 

 

 

plt.plot(x, y, "-r")

 

 

 

 

 

 

 

 

 

 

 

 

 

 

plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] > 0.5)

 

 

 

 

 

 

 

 

 

 

Тут же нарисовать прогноз

 

 

 

 

 

 

 

нашей модели