一,数据操作
数据操作三件事:获取数据、存储数据、处理数据。
基本概念
张量(tensor):通俗的说就是n维数组。0D张量对应标量(例如一个单独数值),1D张量对应向量(vector,例如一组数字),2D张量对应矩阵(matrix,例如一个表格中的数据)
张量的维度:对应数学上的维度,表示上:(1,2)表示二维1行两列数组,(2,3,4)表示一个三维数组
这里的维度你也可以类比现实生活中的东西,零维表示点,一维表示线,二维表示面,三维表示体
元素(分量):张量的每个值
张量形状:张量每一维的大小
入门
引入包,更改张量形状,初始化值
import torch # 引入pytorch
x = torch.arange(12) # 创建一个从0开始的12个整数tensor数组,默认创建整数
x.shape # 张量(每个轴的长度)形状
x.numel() # 张量中元素总数
X = x.reshape(3,4) # 改变张量的形状,比如一行12个元素转化为二维三行四列的矩阵,它的元素和之前相同
X = x.reshape(-1,4) # 通过设置-1来让torch自动计算,比如这里的第一个维度会自动计算出3
torch.zeros((2,3,4)) # 自动初始值为0
torch.ones((2,3,4)) # 自动初始值为1
torch.randn(3,4) # 自动初始值为随机值,这个随机值从均值为0、标准差为1的标准高斯分布随机取样
torch.tensor([1,2,3,4]) # 直接为张量赋值
运算
按元素运算:形状相同的张量进行基本运算,相当于对应位置元素的运算
线性代数运算:向量点积和矩阵乘法、张量连结(沿着某个轴堆叠起来)
x = torch.tensor([1,2,3])
y = torch.tensor([3,4,5])
x + y, x/y,x**y # **是求幂运算
torch.exp(x) # e的x次方
X = torch.arange(12).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1) #分别是将x和y沿行和列堆叠
X == Y #构建一个新的二元张量,每个位置的元素是x和y中对应元素是否相等的布尔值
X.sum() # 对张量所有元素求和
广播机制
当我们在形状不相同的两个张量上进行元素操作时会使用的机制。
- 通过适当复制元素来使两个张量形状相同。
- 对生成张量进行元素运算。
a = torch.arange(3).reshape((3, 1)) # 3*1矩阵
b = torch.arange(2).reshape((1, 2)) # 1*2矩阵
a + b #广播机制会将两个矩阵广播为3*2矩阵,之后进行运算
索引和切片
张量中元素也可以进行索引访问。
X[-1], X[1:3] # 访问X的最后一个元素、X的第二个和第三个元素
X[0:2, :] = 12 # 对第1行和第2行的所有元素进行赋值
节省内存:
一些操作会为新结果分配内存,比如Y = X + Y,程序会为结果的Y分配新的内存,意思是左边的Y和右边的Y指向的不是同一块内存。
before = id(Y) # id函数可以获取对象的内存确切地址
Y = Y + X
id(Y) == before #false
这种情况可能会产生问题:
- 如果变量参数过多,或者更新过于频繁,会分配很多不必要的内存。
- 这样的后果是某些代码可能会偶然间获取到旧的变量参数
解决方法:其实就是将新结果赋值给旧对象就行
Z = torch.zeros_like(Y)
Z[:] = X + Y
转换为其他python对象
深度学习框架转换Numpy张量:
A = X.numpy() # 将X转化为numpy张量
B = torch.tensor() # 将numpy张量转换为torch张量
张量转换为Python标量:
a = torch.tensor([3.5])
a.item(), float(a), int(a) #item函数和python内置函数都可以
二,数据预处理
数据预处理指的是将我们获取到的原始数据处理转换成张量格式的步骤。
数据预处理我们一般使用pandas包。
1,读取数据集
import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
#这里的exist_ok是当已经存在此文件夹时不会触发异常,默认这个值是false,就是说默认如果存在这个文件夹是会报错的
data_file = os.path.join('..', 'data', 'house_tiny.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') #NA和NaN表示缺失值
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
import pandas as pd
data = pd.read_csv(data_file) # 读取数据
print(data)
2,处理缺失值
两种方法:插值法和删除法。前者用一个值替代缺失值,后者直接忽略缺失值。
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
#iloc是索引函数,index location
inputs = inputs.fillna(inputs.mean()) #用同一列的均值代替NaN
#但是对于像类别值或者离散值(一些不能均值的数值),我们可以将NaN视为一个类别,然后将这一列的数值转换为列名的形式,有Pave时Alley_Pave为1,Alley_nan为0,反之则反过来
inputs = pd.get_dummies(inputs, dummy_na=True) # dummy_na就是设置列的参数
3,转换为张量
import torch
X, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
X, y
三,线性代数
线性代数是数学基础,这里我学过一些,所以写的会比较简略(虽然本来也很简略)。
这里要说一个要点:向量的默认方向是列向量。简单地说就是我们规定x = torch.arange(4),这里的数组为数学上的一维列向量。
向量的维度:向量的长度,通常就是说向量的特定维度的元素数量。我们在这里规定一下张量的维度表示为张量具有的维数,张量和向量不是一个东西哦。
A = B.T #通过.T函数获取张量的转置张量
矩阵的按元素乘法称为Hadamard积,数学符号是一个圆圈里面一个点。
降维
先看求和函数:
x = torch.arange(4, dtype=torch.float32)
x.sum() # tensor(6.)
这里求的和不是6,而是tensor(6.),为什么是这样呢?相信结合题目你可能有所猜测了。
默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。意思就是说将所有轴的值都加在一起,成为一个零轴的标量值。
我们还可以指定我们想要求和的轴来降维,比如我们想要求和所有行的元素来降维(可以理解为将每一列里的所有行值求和并压缩成一行),可以调用时传入axis = 0(传入哪个轴,哪个轴被降)。
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
'''
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]]),
'''
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
# (tensor([40., 45., 50., 55.]), torch.Size([4]))
A.mean() # 求平均值,等价于A.sum()/A.numel()
如果我们不想降维:
sum_A = A.sum(axis=1, keepdims=True)
计算某个轴的累计总和,可以调用cumsum函数,这个函数不会降维。
A.cumsum(axios=0)
点积
向量的点积就是相同元素的元素乘积的和。
torch.dot(x, y)
矩阵-向量积
一个矩阵和一个向量相乘,可以理解为矩阵的某种转换。
A.shape, x.shape, torch.mv(A, x)
矩阵-矩阵乘法
也称为矩阵的点积。我们机器学习中的点积通常指的是矩阵乘法。
矩阵的点积(也称为内积或点乘)是两个矩阵的对应元素相乘和再相加的结果。点积只有当第一个矩阵的列数等于第二个矩阵的行数时才有定义,点积的结果是一个新的矩阵,其行数等于第一个矩阵的行数,列数等于第二个矩阵的列数。
torch.mm(A,B)
范数
L2范数表示向量元素平方和的平方根,L1范数表示向量元素的绝对值之和。
u = torch.tensor([3.0, -4.0])
np.linalg.norm(u) # l2范数
四,微积分
暂时略。
五,自动微分
求导是深度学习中既重要又关键的一件事,手工求导非常痛苦。
深度学习框架提供自动微分的方式加快求导。
注意:一个标量函数关于向量X的梯度是向量,并且与X具有相同的形状。
import torch
x = torch.arange(4.0)
x.requires_grad(True) #此时我们会产生一个地方来存储梯度
x.grad # 梯度存储位置,默认为none
y = 2*torch.dot(x,x) # 函数y
# tensor(28., grad_fn=<MulBackward0>)
y.backward() # 调用反向传播函数自动计算y关于x每个分量的梯度
x.grad
x.grad.zero_() # 默认情况下,pyTorch会积累梯度,我们需要清除之前的值
y = x.sum()
y.backward()
x.grad
当y不是标量时,y关于向量x的导数是一个矩阵。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
分离计算
x.grad.zero_()
y = x * x
u = y.detach() #分离y到u,u与y有相同的值,但是不会被计算图跟踪
z = u * x
z.sum().backward()
x.grad == u
python控制流中的梯度计算
使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
a.grad == d / a
# tensor(True)