Курсовая работа. Глубокое обучение
Скачать 4.97 Mb.
|
Линейная регрессия: еще одна визуализация и математическая модель X W ν P L Λ Y Рис. 2.3. Вычислительный граф для уравнения линейной регрессии. Жирным выделены входные данные, буква W обозначает веса Представим функцию потерь как набор вложенных функций: Линейная регрессия 67 Свободный член Благодаря диаграмме несложно понять, как добавить в нашу модель свободный член. Ведь это еще один элемент диаграммы, отвечающий за «смещение» (bias), как показано на рис. 2.4. X W γ N P L α Λ B Y Чем меньше, тем лучше Рис. 2.4. Вычислительный граф для функции линейной регрессии с добавленным членом смещения Прежде чем переходить к написанию кода, нужно понять, что поменялось в математическом представлении после добавления смещения. К уже знакомому нам скалярному произведению в каждом элементе p i вектора предсказания будет прибавляться константа b: Обратите внимание, что поскольку в линейной регрессии всего одно пересечение линии оценки, к каждому наблюдению добавляется одно и то же значение смещения. В следующем разделе мы поговорим о том, как это влияет на вычисление производных. Код Теперь мы готовы написать функцию, которая принимает данные на- ших наблюдений X batch , целевые значения y batch и дает на выходе прогноз и функцию потерь. Напомню, что в случае вложенных функций вычис- ление производных происходит в два этапа. Сначала во время «прямого прохода» входные данные последовательно пропускаются через набор операций с сохранением полученного результата. Затем следует «обрат- ный проход», во время которого сохраненные результаты применяются для вычисления соответствующих производных. 68 Глава 2. Основы глубокого обучения Результаты прямого прохода мы будем сохранять в словарь. Чтобы от- делить их от параметров (которые также потребуются во время обратного прохода), поместим параметры в отдельный словарь: def forward_linear_regression(X_batch: ndarray, y_batch: ndarray, weights: Dict[str, ndarray]) -> Tuple[float, Dict[str, ndarray]]: ''' Прямой проход для линейной регрессии. ''' # проверяем совпадение размеров X и y assert X_batch.shape[0] == y_batch.shape[0] # проверяем допустимость умножения матриц assert X_batch.shape[1] == weights['W'].shape[0] # проверяем, что B это объект ndarray размером 1x1 assert weights['B'].shape[0] == weights['B'].shape[1] == 1 # вычисления N = np.dot(X_batch, weights['W']) P = N + weights['B'] loss = np.mean(np.power(y_batch — P, 2)) # сохранение информации, полученной во время прямого прохода forward_info: Dict[str, ndarray] = {} forward_info['X'] = X_batch forward_info['N'] = N forward_info['P'] = P forward_info['y'] = y_batch return loss, forward_info Теперь все готово к обучению модели. Поговорим о том, что это такое и как реализуется. Обучение модели 69 Обучение модели Воспользуемся инструментами и методами, с которыми мы познакоми- лись в прошлой главе, и вычислим для всех элементов w i вектора W, а также . Для этого выполним обратный проход, во время которого оценим частные производные вложенных функций при заданных входных значениях, а затем перемножим полученные результаты. Диаграмма для операции вычисления градиентов Концепция того, что мы хотим получить, представлена на рис. 2.5. ν [x] B [w] N P (N, B) (x, w) α L Y Λ ∂ γ ∂ w αα ∂ N (P, Y) ∂ Λ ∂ P (N, B) ∂ α ∂ B Рис. 2.5. Обратный проход по вычислительному графу линейной регрессии Еще раз напомню, что мы просто будем вычислять частные производные каждой из вложенных функций, двигаясь изнутри наружу, а затем оце- нивать их для значений, полученных во время прямого прохода. После чего останется получить произведение результатов. Математическое представление Согласно рис. 2.5, мы хотим получить следующее произведение: Первым делом вычислим элемент ∂Λ. Так как , для всех элементов векторов Y и P получим: 70 Глава 2. Основы глубокого обучения Вот как это выражение выглядит в виде кода: dLdP = -2 * (Y — P) Следующим идет элемент, содержащий матрицы: . Но так как под обозначением α скрывается всего лишь свободный член, можно при- менить уже знакомую по предыдущей главе логику: маленькое увеличе- ние любого элемента матрицы N приведет к такому же увеличению вектора P, который равен . Соответственно производная рассматриваемого элемента будет матрицей, заполненной единицами, по форме совпадающей с матрицей N. Код для такой производной выглядит очень просто: dPdN = np.ones_like(N) Ну и наконец, компонент . В конце предыдущей главы мы установили, что при вычислении производных вложенных функций, в случае когда внутри осуществляется умножение матриц, можно про- вести следующую замену: В виде кода это выглядит так: dNdW = np.transpose(X, (1, 0)) Сделаем то же самое для свободного члена. Так как мы его просто при- бавляем, его частная производная будет равна 1: dPdB = np.ones_like(weights['B']) Осталось получить произведение всех производных, проследив за пра- вильным порядком умножения содержащих матрицы компонентов dNdW и dNdX Обучение модели 71 Код для вычисления градиента Напомню, что нужно взять все, что мы вычислили и ввели во время пря- мого прохода (на рис. 2.5 эти компоненты обозначены как X, W, N, B, P и y), и рассчитать частные производные и . Это реализует при- веденный ниже код. Входные данные W и B представлены в виде словарей, первый из которых, содержащий веса, называется weights , а второй, со- держащий остальные параметры, — forward_info : def loss_gradients(forward_info: Dict[str, ndarray], weights: Dict[str, ndarray]) -> Dict[str, ndarray]: ''' Вычисление dLdW и dLdB для модели линейной регрессии. ''' batch_size = forward_info['X'].shape[0] dLdP = -2 * (forward_info['y'] — forward_info['P']) dPdN = np.ones_like(forward_info['N']) dPdB = np.ones_like(weights['B']) dLdN = dLdP * dPdN dNdW = np.transpose(forward_info['X'], (1, 0)) # умножение матриц, в котором первым идет компонент # dNdW (см. примечание в конце предыдущей главы) dLdW = np.dot(dNdW, dLdN) # суммирование по измерению, представляющему размер набора # (объяснение ниже) dLd B = (dLdP * dPdB).sum(axis=0) loss_gradients: Dict[str, ndarray] = {} loss_gradients['W'] = dLdW loss_gradients['B'] = dLdB return loss_gradients 72 Глава 2. Основы глубокого обучения Как видите, мы просто вычисляем все производные и перемножаем их, проследив за порядком следования матриц 1 . Вскоре вы убедитесь, что это действительно работает. Впрочем, после того как в предыдущей главе мы интуитивно доказали применимость цепного правила, удивления быть не должно. Вычисленный градиент функции потерь мы сохраняем в словарь: веса — как ключи, а параметры, увеличивающие влияние весов на потери, — как значения. Словарь весов структурирован аналогичным способом. Следовательно, перебирать веса модели можно следующим образом: for key in weights.keys(): weights[key] -= learning_rate * loss_grads[key] Именно такой способ хранения указанных параметров ничем не обусловлен. Можно сохранить их и другим способом, просто в этом случае они будут просматриваться в другом порядке и ссылаться на них мы будем по-другому. Обучение модели Теперь нужно создать цикл из следующих операций: 1. Выбор набора данных. 2. Прямой проход модели. 3. Обратный проход модели с применением данных, полученных во время прямого прохода. 4. Применение вычисленных градиентов для обновления весов. Репозиторий Jupyter Notebook к этой главе ( https://oreil.ly/2TDV5q9 ) содер- жит код функции train , предназначенной для обучения нашей модели. Он реализует все вышеуказанные шаги и, кроме того, перемешивает данные, чтобы они подавались в функцию в случайном порядке. Вот ключевые строки кода, которые повторяются внутри цикла for : 1 Кроме того, мы суммируем элементы dLdB вдоль оси 0; зачем это нужно, я подробно объясню чуть позже. Оценка точности модели 73 forward_info, loss = forward_loss(X_batch, y_batch, weights) loss_grads = loss_gradients(forward_info, weights) for key in weights.keys(): # 'weights' и 'loss_grads' имеют # одинаковые ключи weights[key] -= learning_rate * loss_grads[key] После этого обучающая функция запускается определенное количество раз на всем тренировочном наборе данных: train_info = train(X_train, y_train, learning_rate = 0.001, batch_size=23, return_weights=True, seed=80718) Обучающая функция возвращает кортеж train_info , один из элементов которого — это параметры или веса, показывающие, как в процессе тре- нировки изменилась модель. В глубоком обучении термины «параметры» и «веса» используются как синонимы. В этой книге они тоже равнозначны. Оценка точности модели Но как понять, насколько корректно построенная модель раскрывает вза- имосвязи в данных? Обучающая выборка представляет собой лишь часть от общей совокупности данных. А перед нами строит задача построить модель, выявляющую взаимосвязи во всей совокупности, несмотря на ограниченность тренировочных данных. Всегда существует опасность, что модель начнет выбирать взаимосвязи, существующие в обучающей выборке, но отсутствующие в совокупности данных. Представьте, что в выборку случайно попали дома с тремя ван- ными комнатами, облицованные желтым сланцем, которые предлагаются по относительно низкой цене. Нейронная сеть обнаружит эту законо- мерность, хотя в общей совокупности данных она не наблюдается. Это 74 Глава 2. Основы глубокого обучения явление называется переобучением (overfitting). Как понять, что у модели может быть подобный недостаток? Чтобы избежать такой ситуации, данные, предназначенные для обучения модели, разбивают на обучающий набор (training set) и тестовый набор (testing set). Первый используется для обучения модели (то есть для итеративного обновления весов), после чего точность работы модели оценивается на тестовых данных. В основе этого подхода лежит простая логика. Если модель смогла обна- ружить взаимосвязи, которые работают и на остальной части обучающей выборки (то есть на всем наборе данных), велика вероятность, что они присутствуют и в общей совокупности данных. Код Давайте попробуем оценить нашу модель на тестовом наборе. Первым делом напишем функцию, генерирующую предсказания, обрезав уже знакомую нам функцию forward_loss : def predict(X: ndarray, weights: Dict[str, ndarray]): ''' Генерация предсказаний для модели линейной регрессии. ''' N = np.dot(X, weights['W']) return N + weights['B'] Теперь возьмем веса, которые возвращает обучающая функция, и на- пишем: preds = predict(X_test, weights) # weights = train_info[0] Насколько хороши эти предсказания? Пока ответа на этот вопрос нет. Ведь мы еще не знаем, работает ли выбранный подход — определение модели как набора операций и ее обучение путем итеративной коррек- тировки параметров, для которой мы вычисляем частные производные функции потерь по различным параметрам. Так что будет здорово, если окажется, что все это хоть как-то работает. Код 75 Для проверки результатов построим график зависимости предсказанных значений от фактических. В идеальном случае все точки должны ока- заться на прямой линии с наклоном в 45 градусов. Реальный результат показан на рис. 2.6. Предсказанное значение Реальное зн ач ение Рис. 2.6. Сравнение предсказанных и действительных значений для модели линейной регрессии График выглядит вполне приемлемо, поэтому количественная оценка точности работы модели имеет смысл. Это можно сделать двумя спо- собами: y Вычислить абсолютное значение среднего расстояния между предска- заниями модели и фактическими значениями. Эту метрику называют средним модулем отклонения (mean absolute error, MAE): def mae(preds: ndarray, actuals: ndarray): ''' Вычисление среднего линейного отклонения. ''' return np.mean(np.abs(preds — actuals)) 76 Глава 2. Основы глубокого обучения y Вычислить средний квадрат расстояния между предсказаниями модели и фактическими значениями. Эту метрику называют корнем из среднего квадрата отклонения (root mean squared error, RMSE): def rmse(preds: ndarray, actuals: ndarray): ''' Вычисление корня из среднего квадрата отклонения. ''' return np.sqrt(np.mean(np.power(preds — actuals, 2))) Для рассматриваемой модели были получены значения: Mean absolute error: 3.5643 Root mean squared error: 5.0508 Корень из среднего квадрата отклонения — распространенная метрика, поскольку она находится в одном масштабе с целевыми значениями. Разделив это число на среднее от целевого показателя, мы увидим, на- сколько полученный прогноз далек от фактического значения. В рас- сматриваемом случае среднее значение параметра y_test составляет 22.0776 . Соответственно прогнозы цен на недвижимость в этой модели в среднем отклоняются от фактических на 5.0508/22.0776 22.9%. Хорошая ли это точность? В репозиторий Jupyter Notebook к этой главе ( https://oreil.ly/2TDV5q9 ) добавлен результат, полученный на этом же на- боре данных для модели линейной регрессии, реализованной средствами Sci-Kit Learn — самой популярной библиотеки Python для машинного обучения. Эта модель дает средний модуль отклонения 3.5666 , а корень из среднего квадрата отклонения 5.0482 , что практически совпадает с результатами, которые дала наша модель, построенная на базе матема- тических формул. Это показывает, что такой подход вполне применим для построения моделей машинного обучения. Чуть позже мы расширим его на области нейронных сетей и глубокого обучения. Определение самого важного признака Перед началом моделирования признаки были масштабированы так, что- бы в результате среднее значение стало равным 0 и среднеквадратическое отклонение — 1. Зачем это нужно, подробно расскажу в главе 4. В случае линейной регрессии такое преобразование позволяет интерпретировать абсолютные значения коэффициентов как меру важности признаков. Код 77 Чем выше коэффициент, тем важнее признак. Вот коэффициенты в рас- сматриваемом случае: np.round(weights['W'].reshape(-1), 4) array([-1.0084, 0.7097, 0.2731, 0.7161, -2.2163, 2.3737, 0.7156, -2.6609, 2.629, -1.8113, -2.3347, 0.8541, -4.2003]) Последний коэффициент больше всего по модулю, соответственно он и является самым важным. Сравнение этого признака с целевым значе- нием показано на рис. 2.7. Самый важный признак (нормализованный) Це ль Рис. 2.7. Сравнение самого важного признака с целевым значением в модели линейной регрессии Мы видим, что по мере роста значения признака значение целевого па- раметра уменьшается, причем это нелинейная зависимость. Изменение целевого параметра при изменении значения признака с –2 до –1 и с 1 до 2 будет разным. Позднее мы еще вернемся к этому моменту. На рис. 2.8 к этому графику добавлено соотношение между самым важным признаком и предсказаниями модели. Чтобы получить это соотношение, 78 Глава 2. Основы глубокого обучения мы пропустили через обученную модель данные, обработанные следую- щим образом: y Все признакам было присвоено их среднее значение. y За 40 итераций проведена интерполяция значений самого важного признака от –1.5 до 3.5, что примерно соответствует диапазону этого признака после масштабирования. Самый важный признак (нормализованный) Це ль/Прогно з Рис. 2.8. Сравнение самого важного признака с целевым значением и предсказаниями, данными моделью линейной регрессии График наглядно показывает ограниченность модели линейной регрессии. Несмотря на нелинейное соотношение между самым важным признаком и целью, в результате обучения извлекается только линейная связь. Такое поведение обусловлено внутренней структурой модели. Получается, что для определения более сложных, нелинейных закономер- ностей требуется другая модель. Но как ее построить? Ответом на этот вопрос станут нейронные сети. Основы нейронных сетей 79 Основы нейронных сетей Я показал, как на базе теоретической информации построить и обучить модель линейной регрессии. Но как, используя аналогичную цепочку рассуждений, спроектировать модель, которая сможет обнаруживать нелинейные взаимосвязи? Давайте создадим много моделей линейной регрессии, чьи результаты работы пропустим через нелинейную функ- цию, после чего применим еще одну модель линейной регрессии, которая и даст прогноз. Как вы вскоре увидите, вычислять градиенты для этой более сложной модели можно тем же способом, что и для модели линей- ной регрессии. Шаг 1. Набор моделей линейной регрессии Как выглядит создание «набора линейных регрессий»? В модели линей- ной регрессии происходит умножение матриц с наборами параметров. Матрица данных X, имеющая форму [размер_пакета, число_признаков] , умножается на матрицу весов W формы [число_признаков, 1] , в резуль- тате мы получаем матрицу формы [размер_пакета, 1] , то есть взвешенную сумму исходных признаков для каждого наблюдения в пакете. Поэтому набор линейных регрессий эквивалентен умножению матрицы входных данных на матрицу весов, имеющую форму [размер_пакета, число_выво- дов] , что даст для каждого наблюдения число_выводов взвешенных сумм исходных признаков. Что это за взвешенные суммы? Их можно рассматривать как «извле- ченные признаки», то есть как комбинации оригинальных признаков, способствующие последующему процессу обучения и обеспечивающие точность прогноза. Сколько таких признаков нужно в рассматриваемом случае? Так как оригинальных признаков у нас 13, создадим такое же число извлеченных признаков. |