Подробное объяснение математического уравнения для создания практических математических основ для вашего пути к машинному обучению или глубокому обучению.
Всем привет! Это пошаговая инструкция «Уравнение-код», часть 3. На этот раз
В части 1 мы говорили о том, как использовать линейную регрессию для решения линейной разделяемой задачи. Мы изучили векторное представление, стандартизацию, добавление смещения, сигмовидную функцию, функцию логарифма правдоподобия и обновление параметров.
На этот раз мы займемся нелинейной разделимой проблемой. Если вы не видели первую часть, ничего страшного. Часть 2 является самодостаточной. Но если вы хотите лучше понять часть 2, лучше сначала прочитать часть 1.
Пошаговое руководство по проекту машинного обучения« Уравнение в код на Python - Часть 1, разделимая линейно…
Подробное объяснение математического уравнения для построения практических математических основ для машинного обучения или… в сторонуdatascience.com »
Контент структурирован следующим образом. *
означает, что вы можете пропустить этот шаг, если вы уже часть 1.
- Посмотрите на данные
- Нелинейная разделимая задача
- Стандартизация*
- Добавьте смещение и полиномиальный член
- Сигмовидная функция *
- Функция правдоподобия *
- Обновить параметр θ *
- Постройте линию
- Точность
- Резюме
1 Посмотрите на данные
Вот данные non_linear_data.csv
x1,x2,y 0.54508775,2.34541183,0 0.32769134,13.43066561,0 4.42748117,14.74150395,0 2.98189041,-1.81818172,1 4.02286274,8.90695686,1 2.26722613,-6.61287392,1 -2.66447221,5.05453871,1 -1.03482441,-1.95643469,1 4.06331548,1.70892541,1 2.89053966,6.07174283,0 2.26929206,10.59789814,0 4.68096051,13.01153161,1 1.27884366,-9.83826738,1 -0.1485496,12.99605136,0 -0.65113893,10.59417745,0 3.69145079,3.25209182,1 -0.63429623,11.6135625,0 0.17589959,5.84139826,0 0.98204409,-9.41271559,1 -0.11094911,6.27900499,0
Во-первых, нам нужно нанести эти данные на график, чтобы увидеть, как они выглядят. Мы создаем файл Python и называем его non_logistic_regression.py.
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("non_linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # plot data points plt.plot(train_x[train_y == 1, 0], train_x[train_y == 1, 1], 'o') plt.plot(train_x[train_y == 0, 0], train_x[train_y == 0, 1], 'x') plt.show()
После запуска приведенного выше сценария вы должны увидеть рисунок ниже.
Кажется, мы не можем использовать одну прямую линию для разделения X и O. Мы называем такую проблему проблемой нелинейной разделимости, когда данные не разделимы линейно.
2 Нелинейная разделимая задача
В части 1 мы используем линейную функцию для решения линейной разделяемой задачи.
Но для нелинейной разделяемой задачи линейная функция слишком проста в обращении. Итак, мы вводим полиномиальную логистическую регрессию, которая добавляет полиномиальный член в логистическую регрессию.
Мы используем θ для представления параметра. Знак θ в левой части означает, что функция f (x) имеет параметр theta. θ в правой части означает, что есть два параметра. Последний член - это полиномиальный член, который делает модель обобщенной для нелинейных разделяемых данных.
Обратите внимание, что у нас есть две функции x1 и x2 в non_linear_data.csv. Мы выбираем x1 как полиномиальный член. Таким образом, функция должна стать ниже формы.
Инициализируем 4 параметра
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # initialize parameter theta = np.random.randn(4)
3 Стандартизация
Чтобы обучение происходило быстро, мы используем стандартизацию, также называемую z - оценкой. Мы делаем это по столбцам.
- 𝜇 - среднее значение в каждом столбце
- 𝜎 - стандартное отклонение в каждом столбце
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # initialize parameter theta = np.random.randn(4) # standardization mu = train_x.mean(axis=0) sigma = train_x.std(axis=0) def standardizer(x): return (x - mu) / sigma std_x = standardizer(train_x)
4 добавить смещение и полиномиальный член
Нам нужно добавить смещение и полиномиальный член, чтобы построить матрицу данных. Мы добавляем константу x0 = 1, чтобы выровнять векторное представление.
Более подробную информацию о векторном представлении вы можете найти в части 1: 3 Векторное представление.
Чтобы упростить расчет, мы преобразуем x в матрицу.
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # initialize parameter theta = np.random.randn(4) # standardization mu = train_x.mean(axis=0) sigma = train_x.std(axis=0) def standardizer(x): return (x - mu) / sigma std_x = standardizer(train_x) # add x0 and x1^2 to get matrix def to_matrix(x): x0 = np.ones([x.shape[0], 1]) x3 = x[:, 0, np.newaxis] ** 2 return np.hstack([x0, x, x3]) mat_x = to_matrix(std_x) # dot product def f(x): return np.dot(x, theta)
Мы используем x3 для представления x1*x1
.
Размер std_x
равен (20, 2)
. После to_matrix(std_x)
размер mat_x
равен (20, 4)
. Что касается скалярного произведения, размер результата равен (4,)
. Таким образом, результат производства точек должен быть (20, 4) x (4,) -> (20,)
, который представляет собой одномерный массив, содержащий прогнозы для 20 выборок.
5 сигмовидная функция
Ниже представлено векторное представление
Затем мы построим на его основе более мощную функцию прогнозирования - сигмовидную функцию.
Мы используем z для представления линейной функции и передаем ее сигмоидной функции. Сигмоидальная функция даст вероятность для каждой выборки данных. У нас есть два класса в наших данных, один - 1
, а другой - 0
.
Мы можем видеть, что модель предсказывает выборку на основе линейной функциональной части.
Мы можем написать код ниже
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # initialize parameter theta = np.random.randn(4) # standardization mu = train_x.mean(axis=0) sigma = train_x.std(axis=0) def standardizer(x): return (x - mu) / sigma std_x = standardizer(train_x) # add x0 and x1^2 to get matrix def to_matrix(x): x0 = np.ones([x.shape[0], 1]) x3 = x[:, 0, np.newaxis] ** 2 return np.hstack([x0, x, x3]) mat_x = to_matrix(std_x) # change dot production to sigmoid function def f(x): return 1 / (1 + np.exp(-np.dot(x, theta)))
6 Функция правдоподобия
Вы можете пропустить этот шаг, если вас не интересует объяснение уравнения или если вы уже читали это в части 1.
Хорошо, мы подготовили наши данные, модель (сигмовидную), а что еще нам нужно? Да, целевая функция. Целевая функция может помочь нам в правильном обновлении параметра. Что касается сигмовидной (логистической регрессии), мы обычно используем логарифмическую вероятность в качестве целевой функции.
Подожди, подожди ... какого черта в этих штуках!
Не паникуйте. Успокойся.
Давайте разбираться.
- 1- ›2 (как перенести строку 1 в строку 2):
log(ab) = log a + log b
- 2->3:
log(a)^b = b * log a
- 3- ›4: Поскольку у нас есть только два класса, y = 0 и y = 1, мы можем использовать следующее уравнение:
- 4- ›5: мы используем приведенное ниже преобразование, чтобы сделать уравнение более читаемым.
Итак, мы получили финальную часть.
Не забывай, почему мы это начали. Целевая функция может помочь нам в правильном обновлении параметра.
Нам нужно использовать это для расчета потерь для обновления параметра. В частности, нам нужно вычислить производную функции логарифмического правдоподобия. Здесь я приведу окончательное уравнение обновления. (Если вам интересно, как получить это уравнение, вам может пригодиться это видео)
На шаге 6 самое важное уравнение - это. Если вы не можете понять, как это получить, ничего страшного. Все, что нам нужно сделать, это написать его как настоящий код.
7 Обновить параметр θ
Вы можете пропустить этот шаг, если уже читали его в части 1.
Этот шаг очень важен. Не паникуйте. Мы его взломаем.
θj - j-й параметр.
- η - скорость обучения, мы установили ее равной 0,001 (1e-3).
- n - количество выборок данных, в нашем случае их 20.
- i - i-я выборка данных
Поскольку у нас есть три параметра, мы можем записать его в виде трех уравнений. Мы используем x3 для представления x1*x1
.
Обозначение :=
такое же, как =
. Вы можете найти объяснение здесь.
Самая сложная часть - это Σ (символ суммирования), поэтому я расширяю Σ для лучшего понимания.
Смотри внимательно.
Я раскрасил три части уравнения, потому что мы можем представить их в виде матриц. Посмотрите на красную и синюю часть в первом ряду, где мы обновляем theta 0.
Запишем красную часть и синюю часть как векторы-столбцы.
Поскольку у нас есть 20 выборок данных, размер f
равен (20,1)
. Размер x0
равен (20,1)
. Мы можем написать матричное умножение с транспонированием.
Таким образом, размер должен быть (1, 20) x (20, 1) -> (1,)
. Получаем одну шкалу для обновления тета 0.
x1
и x2
также являются вектор-столбцом. И мы можем записать в них матрицу X.
А тета - это вектор-строка
Вернемся к уравнению.
Мы можем написать это как
Запишите это как одно уравнение.
Версия, подобная массиву Numpy, может быть простой для понимания.
Давайте сделаем небольшой расчет, чтобы убедиться, что размер правильный.
θ: (1, 4) f^T: (1, 20) x: (20, 4) dot production: (1, 20) x (20, 4) -> (1, 4)
Все кажется таким правильным. Напишем код. Собственно, всего две строчки.
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # initialize parameter theta = np.random.randn(4) # standardization mu = train_x.mean(axis=0) sigma = train_x.std(axis=0) def standardizer(x): return (x - mu) / sigma std_x = standardizer(train_x) # add x0 and x1^2 to get matrix def to_matrix(x): x0 = np.ones([x.shape[0], 1]) x3 = x[:, 0, np.newaxis] ** 2 return np.hstack([x0, x, x3]) mat_x = to_matrix(std_x) # sigmoid function def f(x): return 1 / (1 + np.exp(-np.dot(x, theta))) # update times epoch = 2000 # learning rate ETA = 1e-3 # update parameter for _ in range(epoch): """ f(mat_x) - train_y: (20,) mat_x: (20, 4) theta: (4,) dot production: (20,) x (20, 4) -> (4,) """ theta = theta - ETA * np.dot(f(mat_x) - train_y, mat_x)
Что-то странное? Помните, что мы пишем перед кодом?
dot production: (1, 20) x (20, 4) -> (1, 4) The dimension changes make sense here.
Но почему, когда мы пишем код, мы используем (20,) x (20, 4) -> (4,)
?
На самом деле это не настоящая математическая нотация, это нотация Numpy. И если вы используете TensorFlow или PyTroch, вы должны быть с ними знакомы.
(20,)
означает, что это одномерный массив с 20 числами. Это может быть вектор-строка или вектор-столбец, потому что он имеет только одно измерение. Если мы установим это как двумерный массив, например (20, 1)
или (1, 20)
, мы можем легко определить, что(20, 1)
- вектор-столбец, а (1, 20)
- вектор-строка.
Но почему бы не задать размер явно, чтобы исключить двусмысленность?
Хорошо. Поверьте, когда я впервые это вижу, у меня возникает непростой вопрос. Но после некоторой практики программирования я думаю, что знаю причину.
Потому что это может сэкономить наше время!
Возьмем для примера (20,) x (20, 4) -> (4,)
. Если мы хотим получить (1, 20) x (20, 4) -> (1, 4)
, что нам нужно делать с (20,) x (20, 4) -> (4,)
?
- Преобразовать (20,) в (1, 20)
- Вычислить (1, 20) x (20, 4) - ›(1, 4)
- Поскольку (1, 4) - это двумерный вектор-столбец, нам нужно преобразовать его в одномерный массив. (1,4) - ›(4,)
Честно говоря, это расстраивает. Почему мы не можем выполнить их за один шаг?
Да, поэтому мы можем написать(20,) x (20, 4) -> (4,)
.
Хорошо, давайте посмотрим, как написано в документе numpy.dot ().
Numpy.dot (): если a является массивом ND, а b - одномерным массивом, это произведение суммы по последней оси а и б.
Хм, вообще-то я не понимаю сути. Но np.matmul () описывает аналогичные вычисления с преобразованием в (20,1) или (1,20) для выполнения стандартного двумерного матричного произведения. Может, нам удастся получить вдохновение.
Np.matmul (): Если первый аргумент - 1-D, он перемещается в матрицу, добавляя 1 к его размерам. После матричного умножения добавленная 1 удаляется.
Ха, это недостающая часть! В нашем случае (20,)
становится (1, 20)
, потому что первое измерение (20,4)
равно 20. И (1, 20) * (20, 4) -> (1, 4)
. Затем добавленная 1 удаляется, поэтому мы получаем (4,)
. Один шаг для всех.
8 Постройте линию
После обновления параметра 2000 раз мы должны построить результат, чтобы увидеть производительность нашей модели.
Мы сделаем некоторые точки данных как x1 и вычислим x2 на основе изученных нами параметров.
# plot line x1 = np.linspace(-2, 2, 100) x2 = - (theta[0] + x1 * theta[1] + theta[3] * x1**2) / theta[2] plt.plot(std_x[train_y == 1, 0], std_x[train_y == 1, 1], 'o') # train data of class 1 plt.plot(std_x[train_y == 0, 0], std_x[train_y == 0, 1], 'x') # train data of class 0 plt.plot(x1, x2, linestyle='dashed') # plot the line we learned plt.show()
9 Точность
В части 2 мы используем точность, чтобы оценить производительность нашей модели.
import numpy as np import matplotlib.pyplot as plt # read data data = np.loadtxt("linear_data.csv", delimiter=',', skiprows=1) train_x = data[:, 0:2] train_y = data[:, 2] # initialize parameter theta = np.random.randn(4) # standardization mu = train_x.mean(axis=0) sigma = train_x.std(axis=0) def standardizer(x): return (x - mu) / sigma std_x = standardizer(train_x) # add x0 and x1^2 to get matrix def to_matrix(x): x0 = np.ones([x.shape[0], 1]) x3 = x[:, 0, np.newaxis] ** 2 return np.hstack([x0, x, x3]) mat_x = to_matrix(std_x) # sigmoid function def f(x): return 1 / (1 + np.exp(-np.dot(x, theta))) # classify sample to 0 or 1 def classify(x): return (f(x) >= 0.5).astype(np.int) # update times epoch = 2000 # learning rate ETA = 1e-3 # accuracy log accuracies = [] # update parameter for _ in range(epoch): theta = theta - ETA * np.dot(f(mat_x) - train_y, mat_x) result = classify(mat_x) == train_y accuracy = sum(result) / len(result) accuracies.append(accuracy) # plot accuracy line x = np.arange(len(accuracies)) plt.plot(x, accuracies) plt.show()
classify(x)
: если вероятность больше 0,5, мы классифицируем ее как Trueresult
: содержит прогноз в виде списка [Ture, False,…]accuracy = sum(result) / len(result)
: вычислить, сколько правильных выборок в прогнозе в текущую эпоху.
Наконец, мы строим линию точности.
Мы можем видеть, что линия становится устойчивой после 1000 эпох.
10 Резюме
Если вы уже видели часть 1, вы должны обнаружить, что часть 2 довольно проста для понимания. Вы можете найти весь код ниже. Оставляйте комментарии, чтобы я знал, легко ли понять мою статью. Следите за моей следующей статьей о стохастическом градиентном спуске.
Просмотрите другие мои сообщения на Medium с категориальным представлением!
GitHub: BrambleXu
LinkedIn: Xu Liang
Блог: BrambleXu