python 实现的 linear regression

机器学习于Python

  前情提要:线性回归

  前篇文章里面主要讲的是一些理论知识以及导数的推到之类的东西 其实真的写的时候根本不用管导数是怎么求的[合十],这篇我们就来看看具体是怎么用python代码实现的。

Requirement

  1. python 3.9
  2. mxnet 1.9.1
  3. d2l 0.17.6

1. 手动实现 Linear Regression

首先是一些基本的数据定义

  这里我们实现一个很简单的线性回归模型

  首先是import一些东西:

import random
from mxnet import np, npx, autograd
from d2l import mxnet as d2l
npx.set_np()

  我们这里还没有数据,所以我们整一个人为设定的 t r u e _ w true\_w true_w t r u e _ b true\_b true_b 来人为生成训练数据和测试数据。

true_w = np.array([2, -3.4])                                     # 真实的 w 和 b
true_b = 4.2

def gen_date(w, b, n):
    x = np.random.normal(0, 1, (n, len(w)))                      # 正态分布 0~1 的 n*2 的矩阵
    y = np.dot(x, w) + true_b                                    # y = x * w + b
    y += np.random.normal(0, 0.01, y.shape)                      # y += random_bias
    return x, y

  然后我们调用这个函数之后就生成好了我们的数据集:

feats, labs = gen_date(true_w, true_b, 1000)

  我们考虑每次训练的时候从数据集中取一些数据出来训练:

bat_size = 10
def date_iter(bat_size, feats, labs):
    n = len(feats)
    ind = list(range(n))                                          # 生成一个这样的数组:[0, 1, 2, ..., n-1]
    random.shuffle(ind)                                           # 打乱
    for i in range(0, n, bat_size):
        bat_ind = np.array(ind[i : min(i + bat_size, n)])
        # print("bat_ind :\n", bat_ind)
        yield feats[bat_ind], labs[bat_ind]

  然后我们再定义一些我们需要训练的值和我们的网络 n e t ( ) net() net() 函数和损失 l o s s ( ) loss() loss() 函数

w = np.random.normal(0, 0.01, (2, 1))
b = np.zeros(1)

def h(w, b, x):
    return np.dot(x, w) + b                             # h(x) = x * w + b

def cost(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

然后是关于 autograd

  然后我们就要考虑怎么求导了。

  在这里就要介绍一下我们 m x n e t mxnet mxnet 中的自动求导工具 a u t o g r a d autograd autograd 了。我们先来看这样一段代码:

from mxnet import autograd, np, npx

npx.set_np()

x = np.arange(4)
print("x : ", x)
x.attach_grad()                                   # 通过调用attach_grad来为一个张量的梯度分配内存
print("x.grad : ", x.grad, '\n')                  # 在计算关于x的梯度后,将能够通过'grad'属性访问它,它的值被初始化为0

def f1(x):                                        # f1(x) = 2 * x^2
    return 2 * np.dot(x, x)

with autograd.record():                           # 把代码放到autograd.record内,以建立计算图
    y = f1(x)

print("y : ", y, '\n')

y.backward()                                      # 反向传播函数来自动计算y关于x每个分量的梯度
print("x.grad : ", x.grad)
print("4x : ", 4 * x)
print("is equal : ", x.grad == 4 * x, '\n')       # 看看算对没有

  首先我们从 m x n e t mxnet mxnet 中import了我们的 a u t o g r a d autograd autograd,然后我们定义了一个 x : [ 0 , 1 , 2 , 3 ] x : [0, 1, 2, 3] x:[0,1,2,3]。然后我们用 x . a t t a c h _ g r a d ( ) x.attach\_grad() x.attach_grad() x x x 分配了一个梯度的内存。

  我们的 f 1 ( x ) f1(x) f1(x) 是一个关于 x x x 的函数,既 f 1 ( x ) = 2 x 2 f1(x) = 2x^2 f1(x)=2x2,然后我们在 w i t h a u t o g r a d . r e c o r d ( ) with autograd.record() withautograd.record() 下面写了一个 y = f 1 ( x ) y = f1(x) y=f1(x) 表示我们记录我们需要求导的函数的计算过程,既后面我们把 y y y x x x 求导。

  然后 y . b a c k w a r d ( ) y.backward() y.backward() 就是 y y y x x x 求导了,最后我们打印 x . g r a d x.grad x.grad 打印出来的就是梯度了,而我们手动求导一下知道 f 1 ′ ( x ) = 4 x f1'(x) = 4x f1(x)=4x,最后一步我们打印了 x . g r a d = = 4 ∗ x x.grad == 4 * x x.grad==4x 来判断是否求导正确。

  这个就是一个 a u t o g r a d autograd autograd 最简单的例子了。

  其他复杂的东西其实是一样的,我们只需要在 w i t h a u t o g r a d . r e c o r d ( ) with autograd.record() withautograd.record() 下面放上我们要计算的函数,我们就能对这个函数进行自动求导了。

最后是用autograd实现SGD

  有了这个 a u t o g r a d autograd autograd 我们就能用它来进行我们最难受的一步求导操作了,我们首先 a t t a c h attach attach 一下:

w.attach_grad()
b.attach_grad()

  然后就能写 S G D SGD SGD 了:

lr = 0.03
def SGD(paras, lr, bat_size):
    for para in paras:
        para[:] = para - lr * para.grad / bat_size                         # 向着'下山'方向走一步 步长为lr

  然后是训练函数:

num_epochs = 10
for epoch in range(num_epochs):
    for x, y in date_iter(bat_size, feats, labs):
        with autograd.record():                                            # 记录我们把h(x)对x求导
            J = cost(h(w, b, x), y)
        J.backward()                                                       # 求导
        SGD([w, b], lr, bat_size)                                          # 向着'下山'方向走一步
    trainedJ = cost(h(w, b, feats), labs)
    print(f'epoch {epoch + 1}, loss {float(trainedJ.mean()):f}')           # 输出一下每次train后的loss

  最后我们把train之后的 w w w b b b 打出来和 t r u e _ w , t r u e _ b true\_w, true\_b true_w,true_b 比较一下:

print("true w : ", true_w)
print("true b : ", true_b)
print("w : ", w.reshape(1, 2))
print("b : ", b)

  最后打出来是这样的:

epoch 1, loss 0.024880
epoch 2, loss 0.000091
epoch 3, loss 0.000051
epoch 4, loss 0.000051
epoch 5, loss 0.000051
epoch 6, loss 0.000051
epoch 7, loss 0.000051
epoch 8, loss 0.000051
epoch 9, loss 0.000051
epoch 10, loss 0.000051
true w :  [ 2.  -3.4]
true b :  4.2
w :  [[ 1.9995474 -3.400332 ]]
b :  [4.199814]

  看样子已经和真实值十分接近,效果很好了。下面是完整的代码:

import random
from mxnet import np, npx, autograd
from d2l import mxnet as d2l

npx.set_np()

true_w = np.array([2, -3.4])                                     # 真实的 w 和 b
true_b = 4.2
bat_size = 10

def gen_date(w, b, n):
    x = np.random.normal(0, 1, (n, len(w)))
    # print("w :\n", w)
    # print("x :\n", x)
    y = np.dot(x, w) + true_b
    # print("y :\n", y)
    y += np.random.normal(0, 0.01, y.shape)
    # print("y :\n", y)
    return x, y

def date_iter(bat_size, feats, labs):
    n = len(feats)
    ind = list(range(n))                                          # 生成一个这样的数组:[0, 1, 2, ..., n-1]
    random.shuffle(ind)                                           # 打乱
    for i in range(0, n, bat_size):
        bat_ind = np.array(ind[i : min(i + bat_size, n)])
        # print("bat_ind :\n", bat_ind)
        yield feats[bat_ind], labs[bat_ind]

feats, labs = gen_date(true_w, true_b, 1000)

# for x, y in date_iter(bat_size, feats, labs):
#     print("x :\n", x)
#     print("y :\n", y)
#     break

# 初始化
w = np.random.normal(0, 0.01, (2, 1))
b = np.zeros(1)
w.attach_grad()
b.attach_grad()

def h(w, b, x):
    return np.dot(x, w) + b

def cost(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

def SGD(paras, lr, bat_size):
    for para in paras:
        para[:] = para - lr * para.grad / bat_size

lr = 0.03
num_epochs = 10

for epoch in range(num_epochs):
    for x, y in date_iter(bat_size, feats, labs):
        with autograd.record():
            J = cost(h(w, b, x), y)
        J.backward()
        SGD([w, b], lr, bat_size)
    trainedJ = cost(h(w, b, feats), labs)
    print(f'epoch {epoch + 1}, loss {float(trainedJ.mean()):f}')

print("true w : ", true_w)
print("true b : ", true_b)
print("w : ", w.reshape(1, 2))
print("b : ", b)

2. gluon 的 Linear Regression

  上面是手写的 L i n e a r R e g r e s s i o n Linear Regression LinearRegression 虽然已经很简单了,但是还可以更简单qwq。

  首先还是老样子,先import和定义数据:

from mxnet import autograd, gluon, np, npx, init
from d2l import mxnet as d2l
from mxnet.gluon import nn              # nn 是 neural network 的缩写

npx.set_np()

times = 5                                                    # 训练次数
batch_size = 10
training_size = 1000
true_w = np.array([3.4, -5.2])
true_b = 4
xs, ys = d2l.synthetic_data(true_w, true_b, training_size)   # 根据 w, b 生成训练数据

def load_array(data_arrays, batch_size, is_train = True):
    # 构造一个Gluon数据迭代器
    dataset = gluon.data.ArrayDataset(*data_arrays)
    return gluon.data.DataLoader(dataset, batch_size, shuffle = is_train)
data_iter = load_array((xs, ys), batch_size)                 # data iterator

  然后就是 n e t net net 的定义和训练:

net = nn.Sequential()
net.add(nn.Dense(1))                                     # 一层全连接层/线性层 dense layer
net.initialize(init.Normal(sigma = 0.01))                # 初始化要训练的 w, b
loss = gluon.loss.L2Loss()                               # 平方平均的损失函数
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.03})

for i in range(times):                                   # 训练
    for x, y in data_iter:
        with autograd.record():
            J = loss(net(x), y)
        J.backward()
        trainer.step(batch_size)
    trainedJ = loss(net(xs), ys)
    print("第 ", i + 1, " 次训练 : cost = ", trainedJ.mean())

  最后就是打印训练结果了:

w = net[0].weight.data()
b = net[0].bias.data()
print("true w : ", true_w)
print("true b : ", true_b)
print("w : ", w.reshape(1, 2))
print("b : ", b)

  然后就训练完了qwq:

第  1  次训练 : cost =  0.04249185
第  2  次训练 : cost =  0.00011438833
第  3  次训练 : cost =  5.1260362e-05
第  4  次训练 : cost =  5.1200972e-05
第  5  次训练 : cost =  5.1297753e-05
true w :  [ 3.4 -5.2]
true b :  4
w :  [[ 3.3993742 -5.2005877]]
b :  [4.0002184]

  完整代码如下:

from mxnet import autograd, gluon, np, npx, init
from d2l import mxnet as d2l
from mxnet.gluon import nn

npx.set_np()

times = 5
batch_size = 10
training_size = 1000
true_w = np.array([3.4, -5.2])
true_b = 4
xs, ys = d2l.synthetic_data(true_w, true_b, training_size)

def load_array(data_arrays, batch_size, is_train = True): 
    # 构造一个Gluon数据迭代器
    dataset = gluon.data.ArrayDataset(*data_arrays)
    return gluon.data.DataLoader(dataset, batch_size, shuffle = is_train)
data_iter = load_array((xs, ys), batch_size)

net = nn.Sequential()
net.add(nn.Dense(1))
net.initialize(init.Normal(sigma = 0.01))
loss = gluon.loss.L2Loss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.03})

for i in range(times):
    for x, y in data_iter:
        with autograd.record():
            J = loss(net(x), y)
        J.backward()
        trainer.step(batch_size)
    trainedJ = loss(net(xs), ys)
    print("第 ", i + 1, " 次训练 : cost = ", trainedJ.mean())

w = net[0].weight.data()
b = net[0].bias.data()
print("true w : ", true_w)
print("true b : ", true_b)
print("w : ", w.reshape(1, 2))
print("b : ", b)

  我们会发现这里很多东西封装在了 d 2 l d2l d2l 的包里,我们根本不用自己写,比如 l o s s loss loss 函数,再比如 S G D SGD SGD

  当然,在这个简答的模型里,我们可能看不出 g l u o n gluon gluon 有什么优势,但是在之后写更复杂的模型时, g l u o n gluon gluon 的优势就很明显了,它只需要你往 n e t net net 里加不同的 l a y e r layer layer 就行了,不用你去操心每个 l a y e r layer layer 的输入大小和如何计算等问题。所以后面我们大多会用 g l u o n gluon gluon 来实现算法而非手写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值