1.预备知识
1.数据操作
张量表示一个由数值组成的数组,这个数组可能有多个维度。具有一个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix);具有两个轴以上的张量没有特殊的数学名称。
import torch
x = torch.arange(12)
print(x)
print(x.shape)
# 要想改变一个张量的形状而不改变元素数量和元素值,可以调用reshape函数
x = x.reshape(3, 4)
print(x)
# 初始化一个2*2*2全为一的矩阵
x1 = torch.ones((2, 2, 2))
print (x1)
x2 = torch.rand(3, 4)
print(x2)
# 来为所需张量中的每个元素赋予确定值
x3 = torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(x3)
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
1.运算符
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # **运算符是求幂运算
eNum= torch.exp(x)
除了按元素计算外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
resutByX = torch.cat((X, Y), dim=0)
resutByY = torch.cat((X, Y), dim=1)
2.广播机制
在某些情况下,即使形状不同, 我们仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作。这种机制的工作方式如下:
- 通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
- 对生成的数组执行按元素操作。
a = torch.arange(3).reshape((3,1))
b = torch.arange(2).reshape((1,2))
c = a+b
print(c)
'''
tensor([[0, 1],
[1, 2],
[2, 3]])
'''
切片和索引
张量中的元素可以通过索引访问。与任何Python数组一样:第一个元素 的索引是0,最后一个元素索引是‐1;可以指定范围以包含第一个元素和最后一个之前的元素。
x = torch.arange(10)
print(x)
print(x[-1]) # 取最后一个元素
print(x[1:3]) # 取出第二个和第三个元素
x[1] = 9
print(x)
3.节省内存
运行一些操作可能会导致为新结果分配内存。用Python的id()函数演示了这一点,它给我们提供了内存中引用对象的确切地址。运 行Y = Y + X后,我们会发现id(Y)指向另一个位置。这是因为Python首先计算Y + X,为结果分配新的内存, 然后使Y指向内存中的这个新位置。
Y= torch.arange(12).reshape((3,4))
X = torch.arange(4).reshape((1,4))
before = id(Y)
Y = Y + X
print(id(Y) == before) # false
可以使用以下的方式来解决上述碰到的内存占用问题
Z = torch.zeros_like(Y)
print('id(Z):', id(Z)) # id(Z): 2052312090160
Z[:] = X + Y
print('id(Z):', id(Z)) # id(Z): 2052312090160
我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如Y[:] = 表达式
如果在后续计算中没有重复使用X,我们也可以使用X[:] = X + Y或X += Y来减少操作的内存开销。
Y= torch.arange(12).reshape((3,4))
X = torch.arange(4).reshape((1,4))
before = id(Y)
Y += X
print(id(Y) == before) # true
2.数据预处理
在Python中常用的数据分析工具中,我们通常使用pandas软件包。像庞大的Python生态系统中 的许多其他扩展包一样,pandas可以与张量兼容。
1.读取数据集
import os
import pandas as pd
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tinys.csv')
with open(data_file, 'w') as f:
f.write('NumRooms,Alley,Price\n') # 列名
f.write('NA,Pave,127500\n') # 每行表示一个数据样本
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
# 使用pandas读取数据
data = pd.read_csv("../data/house_tinys.csv")
print(data)
2.处理缺失值
“NaN”项代表缺失值。为了处理缺失的数据,典型的方法包括插值法和删除法,其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。
# 其中inputs为data的前两列,而outputs为data的最后一列。
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
# 如果有缺失值使用其他的数值填充
inputs = inputs.fillna(12)
outputs = outputs.fillna(13)
print(inputs)
print(outputs)
3.转换为张量格式
# 转换为张量格式
print("转换为张量格式")
x = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(outputs.to_numpy(dtype=float))
2.线性神经网络
1.线性回归
1.线性回归的基本元素
线性回归 回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领 域,回归经常用来表示输入和输出之间的关系。
常见的例子包括:预测价格(房屋、股票等)、预测住院时间(针对住院病人等)、预测需求(零售销量等)。但不是所有的预测都是回归问题。
1.线性模型
线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下面的式子: price = warea · area + wage · age + b. 其中warea和wage 称为权重(weight),权重决定了每个特征对我们预测值的影响。b称为偏置(bias)、 偏移量(offset)或截距(intercept)。偏置是指当所有特征都取值为0时,预测值应该为多少。即使现实中不 会有任何房子的面积是0或房龄正好是0年,我们仍然需要偏置项。如果没有偏置项,我们模型的表达能力将受到限制。
在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。当我们的输入 包含d个特征时,我们将预测结果yˆ (通常使用“尖角”符号表示y的估计值)表示为:
y ^ = w 1 x 1 + . . . + w d x d + b . ( 3.1.2 ) \hat{y} = w1x1 + ... + wdxd + b. (3.1.2) y^=w1x1+...+wdxd+b.(3.1.2)
将所有特征放到向量x ∈ R d中,并将所有权重放到向量w ∈ R d中,我们可以用点积形式来简洁地表达模型:
y ^ = w ⊤ x + b . ( 3.1.3 ) \hat{y}= w^⊤x + b. (3.1.3) y^=w⊤x+b.(3.1.3)
2.损失函数
在我们开始考虑如何用模型拟合(fit)数据之前,我们需要确定一个拟合程度的度量。
损失函数(loss function) 能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小, 完美预测时的损失为0。回归问题中最常用的损失函数是平方误差函数。平方误差可以定义为以下公式:
l
i
(
w
,
b
)
=
1
/
2
(
y
^
i
−
y
i
)
2
.
l ^i(w, b) = 1/2(\hat{y}^i − y^i)^2 .
li(w,b)=1/2(y^i−yi)2.
由于平方误差函数中的二次方项,估计值yˆ (i)和观测值y (i)之间较大的差异将导致更大的损失。为了度量模型 在整个数据集上的质量,我们需计算在训练集n个样本上的损失均值(也等价于求和)。
3.随机梯度下降
梯度下降(gradient descent)的方法,这种方法几乎可以优化所有深度学习模型。 它通过不断地在损失函数递减的方向上更新参数来降低误差。
梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这里也可以称为梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做小批量随机梯度下降。
在每次迭代中,我们首先随机抽样一个小批量B,它是由固定数量的训练样本组成的。然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。最后,我们将梯度乘以一个预先确定的正数η,并从 当前参数的值中减掉。 我们用下面的数学公式来表示这一更新过程(∂表示偏导数):
总结,算法的步骤如下:
- 初始化模型参数的值,如随机初始化;
- 从数据集中随机抽取小批量样 本且在负梯度的方向上更新参数,并不断迭代这一步骤。
|B|表示每个小批量中的样本数,这也称为批量大小(batch size)。η表示学习率(learning rate)。批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。这些可以调整但不在训练过程中更新 的参数称为超参数(hyperparameter)。调参是选择超参数的过程。超参数通常是我们根据训练迭代结果来调整的,而训练迭代结果是在独立的验证数据集上评估得到的。
4.用模型进行预测
2.矢量化加速
在训练我们的模型时,我们经常希望能够同时处理整个小批量的样本。为了实现这一点,需要我们对计算进行矢量化,从而利用线性代数库,而不是在Python中编写开销高昂的for循环。
n = 10000
a = torch.ones([n])
b = torch.ones([n])
c = torch.zeros(n)
timer = Timer()
for i in range(n):
c[i] = a[i] + b[i]
f'{timer.stop():.5f} sec' # '0.16749 sec'
# 方式2
timer.start()
d = a + b
f'{timer.stop():.5f} sec' # '0.00042 sec'
很明显,第二种方法比第一种方法快得多。矢量化代码通常会带来数量级的加速。