[Data science] Глубокое обучение VS линейная регрессия

В настоящее время нейронные сети намного быстрее, чем раньше, благодаря аппаратным улучшениям, и их также легче разрабатывать. Но действительно ли они так нужны?‎
Далее рассмотрим использование алгоритма глубокого обучения (многослойный перцептрон), который в последствии сравним с простейшим и популярным методом классического машинного обучения  - линейной регрессией.

Итак, в данном примере будем применять питон и его известные библиотеки (numpy, pandas, sklearn и т.д.). Импортируем их:

import matplotlib.pyplot as plt 
import numpy as np
import pandas as pd
import seaborn as sns
from numpy.linalg import inv
import time
import plotly.graph_objects as go
import plotly.express as px
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split

plotly здесь испольузется для 3D визуализации и необязателен для импортирования.

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

x_1 = np.linspace(-5,5,1000)
x_2 = np.linspace(-5,5,1000)
x_2 = np.random.choice(x_2,len(x_2))
X = np.array([x_1,x_2]).T
data = pd.DataFrame(X,columns=['x1','x2'])
c0 = 2.8
c1 = 3.1
c2 = 1.5
c3 = 0.8
t = c0+c1*x_1+c2*x_2+c3*x_1*x_2
data['t'] = t
data.head()


Как можно видеть целью является следующая функция:

t = c_0 +c_1*x+c_2*y+c_3*x*y

Конечно, эта модель не совсем линейна. Но, если подумать, x и y в основном являются одной и той же переменной, поэтому ее можно рассматривать примерно так:

t = c_0 +(c_1+c_2)*x+c_3*x*x

Нарисуем поверхность:

fig = go.Figure(data=[go.Scatter3d(
    x=data.x1,
    y=data.x2,
    z=data.t,
    mode='markers',
    marker=dict(
        size=4,
        color=data.t,          # set color to an array/list of desired values
        colorscale='plasma',   # choose a colorscale
        opacity=0.8
    )
)])
fig.update_layout(margin=dict(l=0, r=0, b=0, t=0))
fig.show()



Линейная регрессия

Теперь определим прогнозирующую функцию:

def y(W,data):
    """x needs to be a bidimensional element to make it work"""
    y=[]
    for x in data:
        y_x=0
        X=[1,x[0],x[1],x[0]*x[1]]
        for i in range(len(X)):
            y_x=y_x+W[i]*X[i]
        y.append(y_x)
    return y

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

C = [c0,c1,c2,c3]
exact_pred = y(C,X)
exact_result = t
exact_data = pd.DataFrame([exact_pred,exact_result]).T
exact_data.columns=['Predicted','Target']
exact_data.head()


plt.figure(figsize=(10,8))
sns.lineplot(x='Predicted',y='Target',data=exact_data)
plt.xlabel('Predicted',fontsize=30)
plt.ylabel('Target',fontsize=30)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
>>
(array([-30., -20., -10.,   0.,  10.,  20.,  30.,  40.,  50.]),
 [Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, ''),
  Text(0, 0, '')])


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

def L(W,X,t):
    return np.round(0.5*((y(W,X)-t)**2).sum(),10)

И далее определяем градиент функции потерь, который должен стремиться к нулю:

def gradient_L_W(W,X,t):
    grad_0 = (y(W,X)-t).sum()
    grad_1 = ((y(W,X)-t)*X[:,0]).sum()
    grad_2 = ((y(W,X)-t)*X[:,1]).sum()
    grad_3 = ((y(W,X)-t)*X[:,1]*X[:,0]).sum()
    return np.round([grad_0,grad_1,grad_2,grad_3],10)

В данном "идеальном примере" функция потерь и градиент предсказуемо равны 0:

print('Loss function for the right parameters is %.2f'%(L(C,X,t)))
>>> Loss function for the right parameters is 0.00

print('The gradient of this function for the right parameters is',gradient_L_W(C,X,t))
>>> The gradient of this function for the right parameters is [-0.  0. -0. -0.]

Оптимальное решение задачи линейной регрессии следующее:

Если мы рассмотрим следующую функцию потерь:


Получаем, установив градиент на 0:


А реализация следующая:

def phi(X):
    phi_0 = np.ones(len(X))
    phi_1 = X[:,0]
    phi_2 = X[:,1]
    phi_12 = X[:,0]*X[:,1]
    phi = np.array([phi_0,phi_1,phi_2,phi_12]).T
    return phi

def W_opt(X,t):
    phi_X = phi(X)
    A = inv(phi_X.T@phi_X)@phi_X.T
    w_opt =A@t
    return w_opt

print('The coefficients that we found are the following ones:',W_opt(X,t))
print('The real coefficients are the following ones:',C)

>>> 
The coefficients that we found are the following ones: [2.8 3.1 1.5 0.8]
The real coefficients are the following ones: [2.8, 3.1, 1.5, 0.8]

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

Хоть и был использован весь набор данных, но на самом деле в этом случае действительно нужно только 4 точки данных, и получим тот же точный результат.

Deep Learning (глубокое обучение)

В алгоритме глубокого обучения всё делается по другому.
Есть данные, есть несколько скрытых слоев, которые обрабатывают входные данные, и выходной узел, который дает прогноз. Определяем функцию оценки, которая находится в диапазоне от 0 до 1, и хотим ее максимизировать. В данном случае используем метрику R2:


Пробуем применить этот метод к заданному набору данных:

Делаем разделение на тренировочную и тестовую выборки:

train_number = int(len(data)*0.8)
data_shuffle = data.sample(frac=1).reset_index().drop('index',axis=1)
train_data = data_shuffle.drop('t',axis=1)

X_train = train_data.loc[0:train_number-1]
X_test = train_data.loc[train_number::]
print('The training set dimension is:',X_train.shape)
print('The test set dimension is:', X_test.shape)
y_train = data_shuffle['t'].loc[0:train_number-1]
y_test = data_shuffle['t'].loc[train_number::]

>>> The training set dimension is: (800, 2)
>>> The test set dimension is: (200, 2)

Смотрим оценку по метрике:

regr = MLPRegressor(random_state=1, max_iter=3000,
    hidden_layer_sizes=(5,3)).fit(X_train, y_train)
print('The R2 score of our prediction is %.4f' %(regr.score(X_test, y_test)))
>>> The R2 score of our prediction is 0.9915

Визуализируем результат:

test_data = data_shuffle.loc[train_number::]
pred_data = test_data.append(test_data).reset_index().drop('index',axis=1)
pred_data['Z'] = test_data.t.tolist()+regr.predict(X_test).tolist()
pred_data['Target/Prediction'] = ['Target']*len(test_data)+['Prediction']*len(test_data)
fig = px.scatter_3d(pred_data, x='x1', y='x2', z='Z',
              color='Target/Prediction')
fig.update_traces(marker_size = 4)
fig.show()


Метрика составляет 99,2%. Это хороший результат, но меньший по сравнению с точным результатом, полученным от линейной регрессии.

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

Второй пример

Усложним предыдущую ситуацию, добавив функцию sin со случайной амплитудой:

x_1 = np.linspace(-5,5,1000)
x_2 = np.linspace(-5,5,1000)
x_2 = np.random.choice(x_2,len(x_2))
X = np.array([x_1,x_2]).T
data = pd.DataFrame(X,columns=['x1','x2'])
t = c0+c1*x_1+c2*x_2+c3*x_1*x_2+
        np.random.choice(np.linspace(-5,5,10),len(x_1))*np.sin(x_1)
data['t']=t
fig = go.Figure(data=[go.Scatter3d(
    x=data.x1,
    y=data.x2,
    z=data.t,
    mode='markers',
    marker=dict(
        size=4,
        color=data.t,          # set color to an array/list of desired values
        colorscale='plasma',   # choose a colorscale
        opacity=0.8
    )
)])
fig.update_layout(margin=dict(l=0, r=0, b=0, t=0))
fig.show()



Имеем:
t = c_0 +c_1*x+c_2*y+c_3*x*y + Rsin(x)
где R - это случайная амплитуда от -5 до 5.

Если использовать модель линейной регрессии, получим тот же результат, что и раньше. Это означает, что линейная модель делает все возможное, но видим сильные потери.

pred_w = W_opt(X,t)
print('The coefficients that we found are the following ones:',pred_w)
print('The real coefficients are the following ones:',C)
print('Our loss function value is %.2f'%(L(pred_w,X,t)))

>>> 
The coefficients that we found are the
 following ones: [2.95883299 3.09447238 1.53884188 0.79436024]
The real coefficients are the following ones: [2.8, 3.1, 1.5, 0.8]
Our loss function value is 2664.41

Если использовать глубокое обучение, результат будет очень низким:

train_number = int(len(data)*0.8)
data_shuffle = data.sample(frac=1).reset_index().drop('index',axis=1)
train_data = data_shuffle.drop('t',axis=1)

X_train = train_data.loc[0:train_number-1]
X_test = train_data.loc[train_number::]
print('The training set dimension is:',X_train.shape)
print('The test set dimension is:', X_test.shape)
y_train = data_shuffle['t'].loc[0:train_number-1]
y_test = data_shuffle['t'].loc[train_number::]

>>> The training set dimension is: (800, 2)
>>> The test set dimension is: (200, 2)

С другой стороны, нейронная сеть выдает очень низкую ошибку (R2 почти 1).

regr = MLPRegressor(random_state=1, max_iter=3000,
    hidden_layer_sizes=(5,3)).fit(X_train, y_train)
print('The R2 score of our prediction is %.4f' %(regr.score(X_test, y_test)))
>>> The R2 score of our prediction is 0.9920

Опять же, R2=99,20%, что отлично по сравнению с огромными потерями предыдущего метода.

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

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

dimensions = np.arange(1,2000,1)
comp_time = []
for d in dimensions:
    print('Dimensionality = %i'%(d))
    start_time = time.time()
    X = np.random.rand(d,d)
    np.linalg.inv(X)
    comp_time.append(time.time() - start_time)
plt.figure(figsize=(20,20))
plt.plot(dimensions,comp_time)
plt.xlabel('Dimensionality of the matrix',fontsize=20,weight='bold')
plt.ylabel('Computational Time',fontsize=20,weight='bold')
plt.yticks(fontsize=30)
plt.xticks(fontsize=30)


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

Вывод всегда следующий: сначала посмотрим на данные.
Если замечено некоторое «линейное» или «полиномиальное» поведение, то может не стоит использовать глубокое обучение, а просто применить модель линейной регрессии и в итоге получить очень низкую долю ошибки.
Если же поведение странное и его сложно назвать линейным или полиномиальным, возможно, имеет смысл использовать Deep Learning, если имеется достаточно данных.

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

Комментариев нет :

Отправить комментарий