学习D2L深度学习课程1:数据操作、数据预处理

数据操作

N维数组是机器学习和神经网络的主要数据结构:

  • 0-d(标量):[1.0]
  • 1-d(向量):[1.0, 2.7, 3.4]
  • 2-d(矩阵):[[1.0, 2.7, 3.4], [5.0, 0.2, 4.6]]
  • 3-d、4-d、5-d等

创建数组需要的参数包括:

  • 形状:例如3 * 4矩阵
  • 每个元素的数据类型:例如32位浮点数
  • 每个元素的值:例如全部设置为0,或全部设置为随机数

访问元素的方式:

  • 单个元素访问:如[1, 2],即访问第1行第2列的元素
  • 一行访问:如[1, :],即访问第1行上的所有元素
  • 一列访问:如[:, 1],即访问第1列上的所有元素
  • 连续子区域访问:如[1:3, 1:],即访问位于第1行到第2行(冒号规定区间为左闭右开)、且位于第1列之后的所有元素
  • 非连续子区域访问:如[::3, ::2],即位于从第0行开始每三行(第0、3、6…行)上、且位于从第0列开始每两列(第0、2、4…列)上的所有元素

后续要学习的是在Python中的数据操作实现与数据预处理实现。

数据操作实现

Pytorch中的数据操作相关方法:

  • arrange()函数可以根据指定范围及步长,生成一个包含等间隔数值的一维张量
1
2
3
import torch
x = torch.arange(12)
print(x) # 输出:tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
  • 通过张量的shape属性可以访问张量的形状
1
print(x.shape) # 输出:torch.Size([12])
  • 通过numel()函数可以返回张量中元素的总数量(即"number of elements")
1
print(x.numel()) # 输出:12
  • 通过reshape()函数可以不改变元素数量和元素值,而改变一个张量的形状
1
2
3
4
5
x = x.reshape(3, 4)
print(x)
# 输出:tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]])
  • 通过zeros()ones()函数可以生成全0、全1的张量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print(torch.zeros(2, 3, 4))
# 输出:tensor([[[0., 0., 0., 0.],
# [0., 0., 0., 0.],
# [0., 0., 0., 0.]],

# [[0., 0., 0., 0.],
# [0., 0., 0., 0.],
# [0., 0., 0., 0.]]])
print(torch.ones(2, 3, 4))
# 输出:tensor([[[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]],

# [[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]]])
  • 通过tensor()方法与提供包含数值的Python列表或嵌套列表,可以为生成张量的每个元素赋予确定值
1
2
3
4
print(torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]))
# 输出:tensor([[2, 1, 4, 3],
# [1, 2, 3, 4],
# [4, 3, 2, 1]])
  • 常见的算术预运算(+、-、*、/、**(求幂))均可被升级为按元素运算
1
2
3
4
5
6
7
8
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
print(x + y, x - y, x * y, x / y, x ** y)
# 输出:tensor([ 3., 4., 6., 10.])
# tensor([-1., 0., 2., 6.])
# tensor([ 2., 4., 8., 16.])
# tensor([0.5000, 1.0000, 2.0000, 4.0000])
# tensor([ 1., 4., 16., 64.])
  • 可以使用exp()来按元素进行对e的指数运算
1
2
print(torch.exp(x)) 
# 输出:tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
  • 可以使用cat()将多个张量进行联结,并用dim参数指定联结方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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]])

print(torch.cat((X, Y), dim=0)) # dim=0相当于在行上扩展
# 输出:tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [ 2., 1., 4., 3.],
# [ 1., 2., 3., 4.],
# [ 4., 3., 2., 1.]])
print(torch.cat((X, Y), dim=1)) # dim=1相当于在列上扩展
# 输出:tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
# [ 4., 5., 6., 7., 1., 2., 3., 4.],
# [ 8., 9., 10., 11., 4., 3., 2., 1.]])
  • 可以通过逻辑运算符构建张量
1
2
3
4
print(X == Y)
# 输出:tensor([[False, True, False, True],
# [False, False, False, False],
# [False, False, False, False]])
  • 使用sum()可以对张量中的所有元素进行求和,生成一个只有一个元素的张量(标量)
1
print(X.sum()) # 输出:tensor(66.)
  • 在pytorch中,对于同维数但不同形状的张量,可以通过调用广播机制执行按元素操作。原理为将两个张量扩展至相同大小,扩展后新生成的元素使用原有元素进行复制
1
2
3
4
5
6
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
print (a + b) # 原理是将a, b张量均扩展为一个三行两列张量再按元素操作
# 输出:tensor([[0, 1],
# [1, 2],
# [2, 3]])
  • 可以使用[-1]选择最后一个元素,可以使用[1:3]选择第二、第三个元素(左闭右开,如上文)
1
2
3
4
5
print(X[-1]) 
# 输出:tensor([ 8., 9., 10., 11.])
print(X[1:3])
# 输出:tensor([[ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.]])
  • 可以通过指定张量索引将元素写入张量
1
2
3
4
5
X[1, 2] = 9
print(X)
# 输出:tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 9., 7.],
# [ 8., 9., 10., 11.]])
  • 为多个元素赋相同的值,只需要索引全部这些元素并赋值即可
1
2
3
4
5
X[0:2, :] = 9
print(X)
# 输出:tensor([[ 9., 9., 9., 9.],
# [ 9., 9., 9., 9.],
# [ 8., 9., 10., 11.]])
  • 运行一些操作会导致为新结果分配新内存位置,举例如下
1
2
3
before = id(Y) # 可将id视作Y存放位置的指针
Y = Y + X # 当Y进行了按元素相加操作后其存放位置改变
print(id(Y) == before) # 输出:False
  • 如果想要操作后不改变内存位置(执行原地操作),需要提前设置一个空张量当作内存区域
1
2
3
4
Z = torch.zeros_like(Y) # zeros_like()生成一个与输入张量形状相同但元素值全为0的新张量
print('id(Z):', id(Z)) # 输出:id(Z): 140225804033376
Z[:] = X + Y
print('id(Z):', id(Z)) # 输出:id(Z): 140225804033376,可见内存位置未改变
  • 也可以通过X[:] = X + YX += Y的方式实现原地操作,减小内存开销
1
2
3
before = id(X)
X += Y
print(id(X) == before) # 输出:True

Pytorch张量可以使用numpy()tensor()和Numpy张量进行相互转化

1
2
3
A = X.numpy()
B = torch.tensor(A)
print(type(A), type(B)) # 输出:<class 'numpy.ndarray'> <class 'torch.Tensor'>

大小为1的张量可以使用不同手段转化为Python标量

1
2
a = torch.tensor([3.5])
print(a, a.item(), float(a), int(a)) # 输出:tensor([3.5000]) 3.5 3.5 3

数据预处理实现

数据预处理用于读取原始数据,使之能够被后续处理。在本例中,首先创建一个人工数据集,并使用一个csv(每一行是一个数据,不同域由逗号分隔)文件进行储存:

1
2
3
4
5
6
7
8
9
10
import os

os.makedirs(os.path.join('..', 'data'), exist_ok=True) # 构建文件路径,创建文件夹
data_file = os.path.join('..', 'data', 'house_tiny.csv') # 定义文件路径
with open(data_file, 'w') as f: # 写入csv数据
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库,使用其read_csv()方法,由创建的csv文件中读取数据集:

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd

data = pd.read_csv(data_file)
print(data)
""" 输出:
NumRooms Alley Price
0 NaN Pave 127500
1 2.0 NaN 106000
2 4.0 NaN 178100
3 NaN NaN 140000
"""

为了处理丢失的数据(NaN),可以采取插值或删除(删除整行或整列)的方法。此处采取插值法。对于属性值为数的行,可以采用均值差值:

1
2
3
4
5
6
7
8
9
10
11
12
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] 
# 此处使用iloc属性分别将文件前两列和第三列存入inputs,outputs
inputs.iloc[:, 0] = inputs.iloc[:, 0].fillna(inputs.iloc[:, 0].mean())
# 对于inputs第一行,现有属性全为数,因此可以使用mean方法将现有数平均后填入该行无数据位置
print(inputs)
""" 输出:
NumRooms Alley
0 3.0 Pave
1 2.0 NaN
2 4.0 NaN
3 3.0 NaN
"""

对于属性值为类别值或离散值(如Alley列中的值为字符串),可以使用Pandas的get_dummies方法,将每个离散值视为一个新属性,并用True、False(0、1)表示该行数据是否拥有该属性:

1
2
3
4
5
6
7
8
9
10
inputs = pd.get_dummies(inputs, dummy_na=True, dtype=int) 
# 表示将NaN也视作一个新属性,且用整形0、1进行表征
print(inputs)
""" 输出:
NumRooms Alley_Pave Alley_nan
0 3.0 1 0
1 2.0 0 1
2 4.0 0 1
3 3.0 0 1
"""

至此inputs与outputs中包含所有内容均为数值类型,可以使用torch.tensor()转化为张量格式:

1
2
3
4
5
6
7
8
9
10
import torch
X, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
print(X, y)
""" 输出(python默认浮点数为float64):
tensor([[3., 1., 0.],
[2., 0., 1.],
[4., 0., 1.],
[3., 0., 1.]], dtype=torch.float64)
tensor([127500, 106000, 178100, 140000])
"""