Краткое изложение обучения линейной регрессии и его вводный случай

искусственный интеллект

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

Наименьших квадратов

использоватьtorch.lstsq()Решение задач линейной регрессии

Два важных следствия

  1. Умножение с весами

X[i,:]W=x[i,0]w[0]+x[i,1]w[1]++x[i,m1]w[m1]+x[i,m]w[m]X[i, :] W = x[i, 0]w[0] + x[i, 1]w[1] + ... + x[i, m-1]w[m-1] + x [я, м]ш[м]2. Метод расчета двух нормYXW22=i=0n1(y[i]X[i,:]W)|| Y - X · W ||_2^2 = \sum_{i=0}^{n-1}(y[i] - X[i, : ] · W)3. Выражение ошибкиζ(W;X,Y)=1nYXW22\zeta(W; X, Y) = {1\over n} || Y - X · W ||_2^2

import torch
x = torch.tensor([[1., 1., 1.], [2., 3., 1.], [3., 5., 1.], [4., 2., 1.], [5., 4., 1.]])
y = torch.tensor([-10., 12., 14., 16., 18.])
wr, _ = torch.lstsq(y, x)
w = wr[:3]
print(wr)
print(w)
tensor([[  4.6667],
        [  2.6667],
        [-12.0000],
        [ 10.0885],
        [  2.2110]])
tensor([[  4.6667],
        [  2.6667],
        [-12.0000]])

Несколько функций потерь

Функция потерь MSE

Вычисляется сумма квадратов разницы между целевым значением и прогнозируемым значением.формула

MSE=1ni=1n(yiyip)2 MSE = {1 \over n}\sum_{i=1}^n(y_i - y_i ^p)^2

  • Достоинства: все точки непрерывны и гладки, что удобно для вывода, имеет относительно устойчивое решение
  • Недостатки: недостаточно надежная, когда входное значение функции далеко от центрального значения, градиент очень большой при использовании метода градиентного спуска для решения, что может привести к взрыву градиента
  • Класс, соответствующий pytorchtorch.nn.MSELoss

Функция потерь MAE

Вычисляется сумма абсолютного значения разницы между целевым значением и прогнозируемым значением.формула

MAE=1ni=1nyiyip MAE = {1 \over n}\sum_{i=1}^n|y_i - y_i ^p|

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

Функция потерь L1

Функция потери нормы L1, также известная как наименьшее абсолютное отклонение (LAD), наименьшая абсолютная ошибка (LAE). В общем, он минимизирует сумму (S) абсолютных разностей между целевым значением (Yi) и оценочным значением (f(xi)):формула

L1=i1nYif(xi)L1 = \sum_{i-1}^n|Y_i - f(x_i)|

  • Соответствующий класс в pytorchtorch.nn.L1Loss

Функция потерь L2

Функция потери нормы L2, также известная как ошибка наименьших квадратов (LSE). В общем, он минимизирует сумму квадратов (S) разницы между целевым значением (Yi) и расчетным значением (f(xi)):формула

L2=i1n(Yif(xi))2L2 = \sum_{i-1}^n(Y_i - f(x_i))^2

Преимущества и недостатки функции потерь L1 и функции потерь L2 взаимосвязаны с преимуществами и недостатками предыдущей функции потерь MSE и функции потерь MAS.

гладкая функция потерь L1

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

0.5x^2 & |x| < 1 \\ |x| - 0.5 & |x| \geq 1 \end{cases}
  • Преимущества: эта функция на самом деле является кусочной функцией.На самом деле это потеря L2 между [-1,1], которая решает негладкую проблему L1.Вне интервала [-1,1] это фактически потеря L1, которая решает проблему взрывных градиентов для выбросов. А Smooth L1 Loss сочетает в себе преимущества L1 и L2: на ранней стадии используется L1, градиент стабильный, сходимость быстрая, а L2 используется на более поздней стадии, которая постепенно сходится к оптимальному решению.
  • Соответствующий класс в pytorchtorch.nn.SmoothL1Loss
# 一个调用MSE损失函数的例子

#实例化该类
criterion = torch.nn.MSELoss()
pred = torch.arange(5, dtype=torch.float32,requires_grad=True)
y = torch.ones(5)
loss = criterion(pred, y)
print(loss)
loss.backward()
# print(loss.grad)

вывод:

tensor(3., grad_fn=<MseLossBackward0>)

Решение линейной регрессии с помощью оптимизатора

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

import torch
import torch.nn
import torch.optim

x = torch.tensor([[1., 1., 1.], [2., 3., 1.], [3., 5., 1.], [4., 2., 1.], [5., 4., 1.]], device='cuda')
y = torch.tensor([-10., 12., 14., 16., 18.], device='cuda')
w = torch.zeros(3, requires_grad=True, device='cuda')

criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam([w, ], )

for step in range(30001):
    if step:
        optimizer.zero_grad() # 清零
        loss.backward()       # 求梯度
        optimizer.step()      # 根据梯度更新自变量
    
    pred  = torch.mv(x, w) # 矩阵乘法
    loss = criterion(pred, y)
    if step % 5000 == 0:
        print('step = {} loss = {:g} W = {}'.format(step, loss, w.tolist()))

вывод:

step = 0 loss = 204 W = [0.0, 0.0, 0.0]
step = 5000 loss = 40.8731 W = [2.3051974773406982, 1.712536334991455, -0.6180324554443359]
step = 10000 loss = 27.9001 W = [3.6783804893493652, 1.7130744457244873, -5.2205023765563965]
step = 15000 loss = 22.31 W = [4.292291641235352, 2.293663263320923, -9.385353088378906]
step = 20000 loss = 21.3341 W = [4.655962944030762, 2.6559813022613525, -11.925154685974121]
step = 25000 loss = 21.3333 W = [4.666664123535156, 2.666663885116577, -12.0]
step = 30000 loss = 21.3333 W = [4.666667938232422, 2.666668176651001, -11.999998092651367]

использоватьtorch.nn.Linear()выполнить

import torch
import torch.nn
import torch.optim

x = torch.tensor([[1., 1., 1.], [2., 3., 1.], [3., 5., 1.], [4., 2., 1.], [5., 4., 1.]])
y = torch.tensor([-10., 12., 14., 16., 18.])

fc = torch.nn.Linear(3, 1)
criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam(fc.parameters())

weights, bias = fc.parameters()
fc(x)
for step in range(30001):
    if step:
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    pred = fc(x)
    loss = criterion(pred, y)
    if step % 5000 == 0:
        print('step = {} loss = {:g} W = {}, bias = {}'.format(step, loss, weights[0, :].tolist(), bias.item()))

вывод:

<generator object Module.parameters at 0x000001ED118B8270>
step = 5000 loss = 106.462 W = [0.4140699803829193, 0.7813165187835693, 2.938326358795166], bias = 2.9747958183288574
step = 10000 loss = 104 W = [0.007105899043381214, 0.007294247858226299, 4.956961631774902], bias = 4.993431568145752
step = 15000 loss = 104 W = [2.2107651602709666e-06, 2.068739377136808e-06, 4.981757640838623], bias = 5.018227577209473
step = 20000 loss = 104 W = [2.710844455577899e-07, 2.585106244623603e-07, 4.981764793395996], bias = 5.018234729766846
step = 25000 loss = 104 W = [-4.070022259838879e-05, -4.075446486240253e-05, 4.981725215911865], bias = 5.018195152282715
step = 30000 loss = 104 W = [1.3781600500806235e-06, 1.4800637018197449e-06, 4.981767177581787], bias = 5.018237113952637

нормализация данных

Зачем нормализовать?

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

Как нормализовать?

Нормализация функции A mean(A) — это среднее значение A, а дисперсия std(A)Anorm=Amean(A)std(A)A_{norm} = {A-mean(A) \over std(A)}

Каковы характеристики данных после нормализации?

После нормализации среднее значение данных равно 0, а дисперсия равна 1.

Пример кода:

  • Ненормализованный код:
import torch.nn
import torch.optim

x = torch.tensor([[1000000, 0.0001], [2000000, 0.0003], [3000000, 0.0005], [4000000, 0.0002], [5000000, 0.0004]], device="cuda")
y = torch.tensor([-1000., 1200., 1400., 1600., 1800.], device='cuda').reshape(-1, 1)

fc = torch.nn.Linear(2, 1)
fc = fc.cuda()
# 得出当前权值所计算出来的结果
pred = fc(x)
print(pred)
criterion = torch.nn.MSELoss()
criterion = criterion.cuda()
optimizer = torch.optim.Adam(fc.parameters())

for step in range(100001):
    if step:
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    pred = fc(x)
    loss = criterion(pred, y)
    if step % 10000 == 0:
        print('step = {}, loss = {:g}'.format(step, loss))

вывод

tensor([[ 580872.8750],
        [1161746.1250],
        [1742619.3750],
        [2323492.5000],
        [2904365.7500]], device='cuda:0', grad_fn=<AddmmBackward0>)
step = 0, loss = 3.70667e+12
step = 10000, loss = 436096
step = 20000, loss = 435005
step = 30000, loss = 432516
step = 40000, loss = 430062
step = 50000, loss = 427641
step = 60000, loss = 425254
step = 70000, loss = 432383
step = 80000, loss = 420584
step = 90000, loss = 418410
step = 100000, loss = 416046

Можно обнаружить, что эта скорость слишком низкая, а значение потерь 10 000 итераций все еще очень велико, поэтому данные нормализованы ниже.

import torch
import torch.nn
import torch.optim

x = torch.tensor([[1000000, 0.0001], [2000000, 0.0003], [3000000, 0.0005], [4000000, 0.0002], [5000000, 0.0004]])
y = torch.tensor([-1000., 1200., 1400., 1600., 1800.]).reshape(-1, 1)

x_mean, x_std = torch.mean(x, dim=0), torch.std(x, dim=0)
x_norm = (x - x_mean) / x_std

y_mean, y_std = torch.mean(y, dim=0), torch.std(y, dim=0)
y_norm = (y - y_mean) / y_std

fc = torch.nn.Linear(2, 1)
# 得出当前权值所计算出来的结果
pred = fc(x)
print(pred)
criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam(fc.parameters())

for step in range(10001):
    if step:
        optimizer.zero_grad()
        loss_norm.backward()
        optimizer.step()
    pred_norm = fc(x_norm)
    loss_norm = criterion(pred_norm, y_norm)
    # 数据还原
    pred = pred_norm * y_std + y_mean
    loss = criterion(pred, y)
    if step % 1000 == 0:
        print('step = {}, loss = {:g}'.format(step, loss))

вывод:

tensor([[ -599029.2500],
        [-1198058.6250],
        [-1797088.0000],
        [-2396117.5000],
        [-2995146.7500]], grad_fn=<AddmmBackward0>)
steop = 0, loss = 4.38259e+06
steop = 1000, loss = 654194
steop = 2000, loss = 224888
steop = 3000, loss = 213705
steop = 4000, loss = 213341
steop = 5000, loss = 213333
steop = 6000, loss = 213333
steop = 7000, loss = 213333
steop = 8000, loss = 213333
steop = 9000, loss = 213333
steop = 10000, loss = 213333

настоящий бой

Линейная регрессия населения мира с использованием метода наименьших квадратов

import os
os.environ["KMP_DUPLICATE_LIB_OK"]  =  "TRUE"
# 上面两行忽略,不然可能会报警告
import torch
import pandas as pd
url = "https://zh.wikipedia.org/wiki/%E4%B8%96%E7%95%8C%E4%BA%BA%E5%8F%A3"
# 从维基百科获取数据
df = pd.read_html(url, header=0, attrs={"class": "wikitable"}, encoding="utf8")[0]
# print(df)
world_populations = df.copy().iloc[18:31, [0, 1]]

# 要是访问不了维基百科,数据点击 https://oss.xuziao.cn/blogdata/%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE.csv 下载
# world_populations.to_csv('测试数据.csv')

# 把年对应的列转换为张量
years = torch.tensor(world_populations.iloc[:, 0].values.astype(float), dtype=torch.float32)
# 把人口对应的列转换为张量
populations = torch.tensor(world_populations.iloc[:, 1].values.astype(float), dtype=torch.float32)

# 变成[[年份,1], [年份,1], .....]的形式,矩阵相乘时就会时w1 * 年份 + w2 * 1的样式
x = torch.stack([years, torch.ones_like(years)], 1)

y = populations

# 使用最小二乘法
wr, _ = torch.lstsq(y, x)
# print(wr)
# 获取前两位(即w1, w2)
slope, intercept = wr[:2, 0]
result = 'population = {:.2e}*year {:.2e}'.format(slope, intercept)
print('回归结果:'+result)

# 绘图
import matplotlib.pyplot as plt
plt.scatter(years, populations, s = 7, c='blue', marker='o')
estimates = [slope * yr + intercept for yr in years]
plt.plot(years, estimates, c='red')
plt.xlabel('Year')
plt.ylabel('Population')
plt.show()

вывод:

回归结果:population = 7.43e+03*year -1.43e+07

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

Линейная регрессия с оптимизатором Адама

import os
os.environ["KMP_DUPLICATE_LIB_OK"]  =  "TRUE"
# 上面两行忽略,不然可能会报警告
import pandas as pd
import torch
url = "https://zh.wikipedia.org/wiki/%E4%B8%96%E7%95%8C%E4%BA%BA%E5%8F%A3"
# 从维基百科获取数据
df = pd.read_html(url, header=0, attrs={"class": "wikitable"}, encoding="utf8")[0]
# print(df)
world_populations = df.copy().iloc[18:31, [0, 1]]

# 要是访问不了维基百科,数据点击 https://oss.xuziao.cn/blogdata/%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE.csv 下载
# world_populations.to_csv('测试数据.csv')

# 把年对应的列转换为张量
years = torch.tensor(world_populations.iloc[:, 0].values.astype(float), dtype=torch.float32)
# 把人口对应的列转换为张量
populations = torch.tensor(world_populations.iloc[:, 1].values.astype(float), dtype=torch.float32)

# 以上代码是复制的上一节,就没有什么好看的了


import torch.nn
import torch.optim

x = years.reshape(-1, 1)
# print(x)
y = populations
# 下面进行数据的归一化,可以看出这数据量级差别较大,进行数据归一化可以快速下降

x_mean, x_std = torch.mean(x, dim=0), torch.std(x, dim=0)
x_norm = (x - x_mean) / x_std

y_mean, y_std = torch.mean(y, dim=0), torch.std(y, dim=0)
y_norm = (y - y_mean) / y_std

# 一个输入一个输出 会随机生成一个1*1的矩阵
fc = torch.nn.Linear(1, 1)
# MSE损失函数
criterion = torch.nn.MSELoss()
# 创建优化器
optimizer = torch.optim.Adam(fc.parameters())
# 浅拷贝?
weights_norm, bias_norm = fc.parameters()

for step in range(6001):
    if step:
        # 权值清零
        fc.zero_grad()
        # 计算梯度
        loss_norm.backward()
        # 更新权值(fc里面的一些属性)
        optimizer.step()
    # 矩阵乘法,即获取输出(归一化之后的输出,此例中但凡有个_norm后缀的都是归一化之后的值)
    output_norm = fc(x_norm)
    # 去掉所有维度为一的维度
    pred_norm = output_norm.squeeze()
    # 通过MSE损失函数计算损失值
    loss_norm = criterion(pred_norm, y_norm)
    # 通过归一化之后的权重计算原数据权重,这个公式跟下面那个公式皆由高等数学推出
    weights = y_std / x_std * weights_norm
    # 通过归一化之后的偏移量得到原数据的偏移量
    bias = (weights_norm * (0 - x_mean) / x_std + bias_norm) * y_std + y_mean
    if step % 1000 == 0:
        print('第{}步:weight = {}, bias = {}'.format(step, weights.item(), bias.item()))

# 绘图
import matplotlib.pyplot as plt
plt.scatter(years, populations, s = 7, c='blue', marker='o')
estimates = [weights * yr + bias for yr in years]
plt.plot(years, estimates, c='red')
plt.xlabel('Year')
plt.ylabel('Population')
plt.show()

вывод:

第0步:weight = -4349.91064453125, bias = 9026279.0
第1000步:weight = 1948.0953369140625, bias = -3404077.75
第2000步:weight = 5750.35400390625, bias = -10932547.0
第3000步:weight = 7200.87255859375, bias = -13804574.0
第4000步:weight = 7425.09765625, bias = -14248540.0
第5000步:weight = 7432.94873046875, bias = -14264084.0
第6000步:weight = 7432.95751953125, bias = -14264102.0

Прикрепите метод построения тензора:

Имя функции содержимое элемента в тензоре
torch.tensor() Контент - это входящие данные
torch.zeros(), torch.zeros_like() Все элементы равны 0
факел.ones(), факел.ones_like() Все элементы равны 1
факел.full(), факел.full_like() Все элементы имеют все указанные значения
факел.пусто(), факел.empty_like() значение неуказанного элемента
torch.eye() Главная диагональ 1, остальные 0
torch.arange(), torch.range(), torch.linspace() Эквивариантность каждого элемента
torch.logspace() Равное соотношение каждого элемента
факел.rand(), факел.rand_like() Каждый элемент независимо подчиняется стандартному равномерному распределению
факел.randn(), факел.randn_like(), факел.нормальный() Каждый элемент независимо подчиняется стандартному нормальному распределению
факел.randint(), факел.randint_like() Каждый элемент независимо подчиняется дискретному равномерному распределению
torch.bernoulli() Двухточечное распределение на {0, 1}
torch.multinomial() Многоточечное равномерное распределение по {0, 1, ..., n-1}
torch.randperm() Каждый элемент представляет собой случайную перестановку (0, 1, ..., n-1)