提交 bd0950a1 编写于 作者: Y yuwei wang 提交者: Mu Li

Sync charpter1/linger-regression-scratch (#96)

上级 70d823d0
# 线性回归 --- 从0开始
虽然强大的深度学习框架可以减少很多重复性工作,但如果你过于依赖它提供的便利抽象,那么你可能不会很容易地理解到底深度学习是如何工作的。所以我们的第一个教程是如何只利用ndarray和autograd来实现一个线性回归的训练。
尽管强大的深度学习框架可以减少大量重复性工作,但若过于依赖它提供的便利,你就会很难深入理解深度学习是如何工作的。因此,我们的第一个教程是如何只利用ndarray和autograd来实现一个线性回归的训练。
## 线性回归
给定一个数据点集合`X`和对应的目标值`y`,线性模型的目标是找一根线,其由向量`w`和位移`b`组成,来最好地近似每个样本`X[i]``y[i]`。用数学符号来表示就是我们将学`w``b`来预测,
给定一个数据点集合`X`和对应的目标值`y`,线性模型的目标就是找到一条使用向量`w`和位移`b`描述的线,来尽可能地近似每个样本`X[i]``y[i]`。用数学符号来表示就是:
$$\boldsymbol{\hat{y}} = X \boldsymbol{w} + b$$
......@@ -12,16 +12,20 @@ $$\boldsymbol{\hat{y}} = X \boldsymbol{w} + b$$
$$\sum_{i=1}^n (\hat{y}_i-y_i)^2.$$
你可能会对我们把古老的线性回归作为深度学习的一个样例表示很奇怪。实际上线性模型是最简单但也可能是最有用的神经网络。一个神经网络就是一个由节点(神经元)和有向边组成的集合。我们一般把一些节点组成层,每一层使用下一层的节点作为输入,并输出给上面层使用。为了计算一个节点值,我们将输入节点值做加权和,然后再加上一个激活函数。对于线性回归而言,它是一个两层神经网络,其中第一层是(下图橙色点)输入,每个节点对应输入数据点的一个维度,第二层是单输出节点(下图绿色点),它使用身份函数($f(x)=x$)作为激活函数。
你可能会对我们把古老的线性回归作为深度学习的一个样例表示奇怪。实际上线性模型是最简单、但也是最有用的神经网络。一个神经网络就是一个由节点(神经元)和有向边组成的集合。我们一般把一些节点组成层,每一层先从下面一层的节点获取输入,然后输出给上面的层使用。要计算一个节点值,我们需要将输入节点值做加权和(权数值即 `w`),然后再加上一个**激活函数(activation function)**。对于线性回归而言,它是一个两层神经网络,其中第一层是(下图橙色点)输入,每个节点对应输入数据点的一个维度,第二层是单输出节点(下图绿色点),它使用身份函数($f(x)=x$)作为激活函数。
![](../img/simple-net-linear.png)
![](../img/onelayer.png)
## 创建数据集
这里我们使用一个人工数据集来把事情弄简单些,因为这样我们将知道真实的模型是什么样的。具体来说我们使用如下方法来生成数据
这里我们使用一个数据集来尽量简单地解释清楚,真实的模型是什么样的。具体来说,我们使用如下方法来生成数据;随机数值 `X[i]`,其相应的标注为 `y[i]`
`y[i] = 2 * X[i][0] - 3.4 * X[i][1] + 4.2 + noise`
使用数学符号表示:
$$y = X \cdot w + b + \eta, \quad \text{for } \eta \sim \mathcal{N}(0,\sigma^2)$$
这里噪音服从均值0和标准差为0.01的正态分布。
```{.python .input n=2}
......@@ -45,6 +49,14 @@ y += .01 * nd.random_normal(shape=y.shape)
print(X[0], y[0])
```
如果有兴趣,可以使用安装包中已包括的 Python 绘图包 `matplotlib`,生成第二个特征值 (`X[:, 1]`) 和目标值 `Y` 的散点图,更直观地观察两者间的关系。
```{.python .input}
import matplotlib.pyplot as plt
plt.scatter(X[:, 1].asnumpy(),y.asnumpy())
plt.show()
```
## 数据读取
当我们开始训练神经网络的时候,我们需要不断读取数据块。这里我们定义一个函数它每次返回`batch_size`个随机的样本和对应的目标。我们通过python的`yield`来构造一个迭代器。
......@@ -79,7 +91,7 @@ b = nd.zeros((1,))
params = [w, b]
```
之后训练时我们需要对这些参数求导来更新它们的值,所以我们需要创建它们的梯度。
之后训练时我们需要对这些参数求导来更新它们的值,使损失尽量减小;因此我们需要创建它们的梯度。
```{.python .input n=7}
for param in params:
......@@ -88,7 +100,7 @@ for param in params:
## 定义模型
线性模型就是将输入和模型做乘法再加上偏移
线性模型就是将输入和模型的权重(`w`)相乘,再加上偏移(`b`
```{.python .input n=8}
def net(X):
......@@ -101,13 +113,13 @@ def net(X):
```{.python .input n=9}
def square_loss(yhat, y):
# 注意这里我们把y变形成yhat的形状来避免自动广播
# 注意这里我们把y变形成yhat的形状来避免矩阵形状的自动转换
return (yhat - y.reshape(yhat.shape)) ** 2
```
## 优化
虽然线性回归有显试解,但绝大部分模型并没有。所以我们这里通过随机梯度下降来求解。每一步,我们将模型参数沿着梯度的反方向走特定距离,这个距离一般叫学习率。(我们会之后一直使用这个函数,我们将其保存在[utils.py](../utils.py)。)
虽然线性回归有显试解,但绝大部分模型并没有。所以我们这里通过随机梯度下降来求解。每一步,我们将模型参数沿着梯度的反方向走特定距离,这个距离一般叫**学习率(learning rate)** `lr`。(我们会之后一直使用这个函数,我们将其保存在[utils.py](../utils.py)。)
```{.python .input n=10}
def SGD(params, lr):
......@@ -117,25 +129,62 @@ def SGD(params, lr):
## 训练
现在我们可以开始训练了。训练通常需要迭代数据数次,一次迭代里,我们每次随机读取固定数个数据点,计算梯度并更新模型参数。
现在我们可以开始训练了。训练通常需要迭代数据数次,在这里使用`epochs`表示迭代总次数;一次迭代中,我们每次随机读取固定数个数据点,计算梯度并更新模型参数。
```{.python .input}
# 模型函数
def real_fn(X):
return 2 * X[:, 0] - 3.4 * X[:, 1] + 4.2
# 绘制损失随训练次数降低的折线图,以及预测值和真实值的散点图
def plot(losses, X, sample_size=100):
xs = list(range(len(losses)))
f, (fg1, fg2) = plt.subplots(1, 2)
fg1.set_title('Loss during training')
fg1.plot(xs, losses, '-r')
fg2.set_title('Estimated vs real function')
fg2.plot(X[:sample_size, 1].asnumpy(),
net(X[:sample_size, :]).asnumpy(), 'or', label='Estimated')
fg2.plot(X[:sample_size, 1].asnumpy(),
real_fn(X[:sample_size, :]).asnumpy(), '*g', label='Real')
fg2.legend()
plt.show()
```
```{.python .input n=11}
epochs = 5
learning_rate = .001
for e in range(epochs):
niter = 0
losses = []
moving_loss = 0
smoothing_constant = .01
# 训练
for e in range(epochs):
total_loss = 0
for data, label in data_iter():
with autograd.record():
output = net(data)
loss = square_loss(output, label)
loss.backward()
SGD(params, learning_rate)
total_loss += nd.sum(loss).asscalar()
print("Epoch %d, average loss: %f" % (e, total_loss/num_examples))
# 记录每读取一个数据点后,损失的移动平均值的变化;
niter +=1
curr_loss = nd.mean(loss).asscalar()
moving_loss = (1 - smoothing_constant) * moving_loss + (smoothing_constant) * curr_loss
# correct the bias from the moving averages
est_loss = moving_loss/(1-(1-smoothing_constant)**niter)
if (niter + 1) % 100 == 0:
losses.append(est_loss)
print("Epoch %s, batch %s. Moving avg of loss: %s. Average loss: %f" % (e, niter, est_loss, total_loss/num_examples))
plot(losses, X)
```
训练完成后我们可以比较学到的参数和真实参数
训练完成后,我们可以比较学得的参数和真实参数
```{.python .input n=12}
true_w, w
......@@ -147,7 +196,7 @@ true_b, b
## 结论
我们现在看到仅仅使用NDArray和autograd我们可以很容易地实现一个模型。
我们现在看到,仅仅是使用NDArray和autograd就可以很容易实现的一个模型。在接下来的教程里,我们会在此基础上,介绍更多现代神经网络的知识,以及怎样使用少量的MXNet代码实现各种复杂的模型。
## 练习
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册