本节视频链接:
- p1 https://www.bilibili.com/video/BV1CV411Y7i4?p=1&vd_source=901b5111a53e52641fb11df13be3b7d1
- p2 https://www.bilibili.com/video/BV1CV411Y7i4?p=2&vd_source=901b5111a53e52641fb11df13be3b7d1
p1:
1.N维数组
N维数组是机器学习和神经网络的主要数据结构
- 0dim(标量):
1.0
:一个类别 - 1dim(向量):
[1.0,2.7,3.4]
:一个特征向量 - 2dim(矩阵)(矩阵是二维张量,张量不一定是矩阵):
[[1.0,2.7,3.4][5.0,0.2,4.6][4.3,8.5,0.2]]
:一个样本——特征矩阵
2.创建数组
创建数组需要:
- 形状:例如3*4矩阵
- 每个元素的数据类型:例如32位浮点数
- 每个元素的值。例如全是0,或者随机数
3.访问元素
-
访问一个元素:
[1,2]
:访问第2行第3个元素 -
访问一行元素:
[1,:]
:访问第2行的所有列(就是访问第2行元素) -
访问一列元素:
[:,1]
:访问第2列的所有行(就是访问第2列元素) -
访问子区域:
[1:3,1:]
:访问第2到3(左闭右开)行,第2列开始往后的所有列
-
访问子区域:
[::3,::2]
:访问第0行到最后一行,每3行取一次,访问第0列到最后一列,每2列取一次
p2:数据操作
导入torch库
import torch
- 张量:表示一个数值组成的数组,这个数组可能有很多个维度
x=torch.arange(12)
print(x)
#tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
- 通过
shape
属性来访问张量的形状
print(x.shape)
#torch.Size([12])
- 张量中元素的总数(永远是一个标量)
print(x.numel())
#12
- 通过
reshape
函数改变张量的形状而不改变元素数量和元素值
y=x.reshape(3,4)
print(y)
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
- 使用
0
、全1
、其他常量或者从特定分布中随机采样的数字
x=torch.zeros((2, 3, 4))
print(x)
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.]]])
x=torch.ones(2, 3, 4)
print(x)
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.]]])
关于x=torch.zeros((2, 3, 4))
和x=torch.ones(2, 3, 4)
的区别:
-
在PyTorch中,
torch.ones
函数用于创建一个填充了1的张量。对于你提到的两个用法,x=torch.ones(2,3,4)
和x=torch.ones((2,3,4))
,它们在功能上是等效的,没有实质性的区别。 -
torch.ones(2,3,4)
中的参数是以逗号分隔的多个整数,表示张量的形状。这种方式比较直观,将形状的各个维度依次传递给函数。 -
torch.ones((2,3,4))
中的参数是一个元组(2,3,4)
,表示张量的形状。使用元组的方式可以将形状以一个整体的方式传递给函数,这在一些情况下更加方便,特别是当形状较复杂或者通过变量动态指定形状时。 -
两种用法最终都会创建一个形状为
(2, 3, 4)
的张量,并且将其中的元素都设置为1。所以,在这种情况下,可以根据个人的喜好和代码的可读性来选择使用哪种方式。
- 通过提供包含数值的Python列表(或嵌套列表)来为所需张量中的每个元素赋值
x=torch.tensor([[1,2,3],[4,5,6],[7,8,9]])
print(x)
tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
如果再加一个[]
就变成3
维
x=torch.tensor([[[1,2,3],[4,5,6],[7,8,9]]])
print(x.shape)
torch.Size([1, 3, 3])
- 常见的标准算术运算符
(+、-、*、/、**)
都可以升级为按元素运算
x=torch.tensor([1,2,3,4.0])
y=torch.tensor([2,2,2,2])
print(x+y)
print(x-y)
print(x*y)
print(x/y)
print(x**y)
tensor([3., 4., 5., 6.])
tensor([-1., 0., 1., 2.])
tensor([2., 4., 6., 8.])
tensor([0.5000, 1.0000, 1.5000, 2.0000])
tensor([ 1., 4., 9., 16.])
- 指数运算
x=torch.tensor([1,2,3,4.0])
print(torch.exp(x))
tensor([ 2.7183, 7.3891, 20.0855, 54.5981])
- 把多个张量拼接在一起
x=torch.arange(12,dtype=torch.float32).reshape((3,4))
print(torch.cat((x, y), dim=0))
dim=0
表示在y
轴方向上拼接
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.]])
y=torch.tensor([[2.0,1,4,3,],[1,2,3,4],[4,3,2,1]])
print(torch.cat((x, y), dim=1))
dim=1
表示x
轴方向上的拼接
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.]])
- 通过逻辑运算符构建二元张量
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(y==x)
tensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])
- 即使形状不同,我们仍然可以通过调用广播机制来执行按元素操作
a=torch.arange(3).reshape((3,1))
b=torch.arange(2).reshape((1,2))
print(a)
print(b)
tensor([[0],
[1],
[2]])
tensor([[0, 1]])
print(a + b)
a
复制成3*2
的矩阵
tensor([[0],[0]
[1],[1]
[2],[2]])
b
复制成3*2
的矩阵
tensor([[0, 1]
[0, 1]
[0, 1]])
然后再相加,得
tensor([[0, 1],
[1, 2],
[2, 3]])
- 用
[-1]
访问最后一个元素
x=torch.arange(12,dtype=torch.float32).reshape((3,4))
print(x[-1])
访问最后一行
tensor([ 8., 9., 10., 11.])
- 用
[1:3]
访问第2
个和第3
个元素
x=torch.arange(12,dtype=torch.float32).reshape((3,4))
print(x[1:3])
访问第2
行和第3
行
tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]])
- 通过索引将指定的元素写入矩阵
x=torch.arange(12,dtype=torch.float32).reshape((3,4))
x[1,2]=9
print(x)
第2
行第3
个元素改为9
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
- 为多个元素赋相同的值
x=torch.arange(12,dtype=torch.float32).reshape((3,4))
x[0:2,:]=12
print(x)
第1
行和第2
行,所有列,赋值为12
tensor([[12., 12., 12., 12.],
[12., 12., 12., 12.],
[ 8., 9., 10., 11.]])
- 运行一些操作可能会导致为新结果分配内存
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]])
before=id(y)
y=y+x
print(id(y) == before)
id()
是取地址操作
False
新的y
的id
不等于以前的,产生了一个新的y
,因为这里y=y+x
的等式左右都边使用到了y
,变量名重复使用了。所以不是原地操作
- 执行原地操作
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]])
z=torch.zeros_like(y)
print('id(z):',id(z))
z[:]=x+y
print('id(z):',id(z))
z=torch.zeros_like(y)
表示z
和y
的shape
和数据类型是一样的,所有的元素值为0
这里必须是z[:]=
,该操作只改变了z
中的元素值,未改变z
的地址
id(z): 1515049846816
id(z): 1515049846816
- 原地操作
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]])
before=id(x)
x+=y
print(id(x) == before)
True
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]])
before=id(x)
x[:]=x+y
print(id(x) == before)
True
x[:]=x+y
只改变了值
当执行 x = x + y
时,它实际上是创建了一个新的张量对象来存储 x + y
的结果,并将该新张量赋值给变量 x
。因此,x
引用的是一个全新的张量对象,其身份标识发生了变化。
相比之下,x[:] = x + y
是一种原地操作。它将 x + y
的结果存储回 x 张量,而不创建新的张量对象。因此,x
仍然引用同一个张量对象,其身份标识保持不变。
- 转换为
Numpy
张量
import numpy
x=torch.arange(12,dtype=torch.float32).reshape((3,4))
a=x.numpy()
b=toorch.tensor(a)
print(type(a))
print(type(b))
<class 'numpy.ndarray'>
<class 'torch.Tensor'>
- 将大小为
1
的张量转换为Python标量
a=torch.tensor([3.5])
print(a,a.item(),float(a),int(a))
tensor([3.5000]) 3.5 3.5 3
P3:数据预处理
- 创建一个人工数据集,并存储在csv(逗号分隔值)文件中
import torch
import numpy
import os
import pandas as pd
os.makedirs(os.path.join('D:\PycharmProject\TorchStudy', 'data'), exist_ok=True)
data_file = os.path.join('D:\PycharmProject\TorchStudy', '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')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
data = pd.read_csv(data_file)
print(data)
NumRooms Alley Price
0 NA Pave 127500
1 2 NaN 106000
2 4 NaN 178100
3 NA NaN 140000
这段代码的主要功能是创建一个名为 house_tiny.csv
的CSV
文件,并写入一些数据。
-
首先,
os.makedirs(os.path.join('D:\PycharmProject\TorchStudy', 'data'), exist_ok=True)
用于创建一个名为data
的文件夹,其相对路径是上一级目录('D:\PycharmProject\TorchStudy
)下的data
。exist_ok=True
表示如果文件夹已经存在,不会引发错误。 -
接下来,
data_file = os.path.join(''D:\PycharmProject\TorchStudy', 'data', 'house_tiny.csv')
将文件路径'D:\PycharmProject\TorchStudy/data/house_tiny.csv
存储在变量data_file
中。 -
然后,使用
open(data_file, 'w') as f
打开文件data_file
,以写入模式('w')
。这将创建一个文件对象f
,可以通过该对象进行写入操作。 -
在 with 语句的代码块中,进行以下写入操作:
f.write('NumRooms,Alley,Price\n')
写入一行包含列名的标题行。
f.write('N,Pave,127500 \n ')
写入第一个数据样本的行,包含三个值:NumRooms= N,Alley= Pave,Price= 127500。
f.write(' 2,NA,106000\n ')
写入第二个数据样本的行,包含三个值:NumRooms= 2,Alley= NA,Price= 106000。
f.write(' 4,NA,178100\n ')
写入第三个数据样本的行,包含三个值:NumRooms= 4,Alley= NA,Price= 178100。
f.write('NA,NA,14000o\n ')
写入第四个数据样本的行,包含三个值:NumRooms= NA,Alley= NA,Price= 140000。
通过这些写入操作,数据文件house_tiny.csv
被创建并填充了一些数据。每一行表示一个数据样本,包含三个特征(NumRooms、Alley、Price)
和对应的值。
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:
-
首先通过
os.makedirs
创建了一个名为data
的文件夹(如果该文件夹不存在的话),然后将文件路径 house_tiny.csv 存储在变量data_file
中。 -
下来使用
open(data_file, 'w')
打开文件,模式设置为写入模式。这将创建一个空的house_tiny.csv
文件,如果该文件已经存在,会被覆盖。 -
因此,运行代码时,文件夹
data
和文件house_tiny.csv
会被自动创建,然后数据会被写入house_tiny.csv
文件中。
with open(data_file, 'w') as f:
是Python中用于处理文件的常见语法,它被称为上下文管理器(Context Manager)。这种用法可以确保在处理完文件后,无论是否发生异常,都会自动关闭文件,释放相关资源。
具体解释如下:
-
open(data_file, 'w')
打开一个文件,返回一个文件对象。data_file
是要打开的文件的路径,'w'
表示以写入模式打开文件。 -
as f
将文件对象赋值给变量f
,以便在代码块中使用。 -
冒号表示开始一个代码块,下面的代码将在文件对象
f
的上下文中执行。 -
在
with
代码块中,可以执行读取或写入文件的操作,例如使用f.write()
写入数据。 -
当代码块结束时,无论是否发生异常,文件对象
f
将自动关闭,确保文件资源被正确释放。
使用 with open() as f
的好处是,不需要手动调用 f.close()
来关闭文件。当代码块退出时,文件会自动关闭,即使发生异常也不会影响文件的关闭操作。这种方式更加简洁和安全,同时也减少了出错的可能性。
2. 为了处理缺失的数据,典型的方法包括插值和删除,这里我们将考虑插值
机器学习就是为了处理缺失的数据(预测未来)
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = inputs.fillna(inputs.mean(numeric_only=True))
print(inputs)
NumRooms Alley
0 3.0 Pave
1 2.0 NaN
2 4.0 NaN
3 3.0 NaN
data.iloc[:, 0:2]
表示从 data 数据框中选取所有行和第 0
列至第 1
列(总共两列)的数据,作为 inputs
的值。
data.iloc[:, 2]
表示从 data 数据框中选取所有行和第 2 列的数据,作为 outputs
的值。
inputs.fillna(inputs.mean())
使用 inputs
中的均值来填充缺失值。
fillna()
是一个Pandas函数,用于填充缺失值。
这里使用 inputs.mean()
计算 inputs
列的均值,并将缺失值用均值填充。
numeric_only=True
参数表示仅考虑数值类型的列来计算均值。
对于inputs中的类别值或离散值(不是数值),我们将‘NaN’视为一个类别
inputs=pd.get_dummies(inputs,dummy_na=True)
print(inputs)
NumRooms Alley_Pave Alley_nan
0 3.0 True False
1 2.0 False True
2 4.0 False True
3 3.0 False True
pd.get_dummies()
函数用于对分类变量进行独热编码(One-Hot Encoding),创建虚拟变量。
独热编码是将分类变量转换为二进制向量的过程,使得机器学习模型能够更好地处理分类数据。
具体解释如下:
-
pd.get_dummies(inputs, dummy_na=True)
对inputs
数据框中的分类变量进行独热编码。get_dummies()
是 Pandas 的函数,接受一个数据框或数据列作为输入,并返回独热编码后的结果。 -
inputs
数据框中的分类变量会被转换为一组二进制变量,每个变量表示一个可能的分类值。如果dummy_na=True
,还会为缺失值创建一个虚拟变量。 -
最后,将独热编码后的结果赋值给
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=pd.get_dummies(inputs,dummy_na=True)
inputs = inputs.replace({True: 1, False: 0})
print(inputs)
使用 replace()
方法将布尔值 True
替换为 1
,将布尔值 False
替换为 0
。
replace()
方法接受一个字典作为参数,其中键表示要替换的值,值表示替换后的值。
在这里,使用 {True: 1, False: 0}
字典将布尔值替换为整数值。
3. 现在inputs和outputs中的所有条目都是数值类型,它们都可以转换为张量格式
x,y=torch.tensor(inputs),torch.tensor(outputs)
print(x)
print(y)
会报错:ValueError: could not determine the shape of object type 'DataFrame'
原因:torch.tensor()
函数用于将数据转换为张量形式,但它需要处理的是具有确定形状的数据,而不是 DataFrame
对象。
正确代码:
x,y=torch.tensor(inputs.values),torch.tensor(outputs.values)
print(x)
print(y)
tensor([[3., 1., 0.],
[2., 0., 1.],
[4., 0., 1.],
[3., 0., 1.]], dtype=torch.float64)
tensor([127500, 106000, 178100, 140000])
64位浮点数计算较慢,深度学习一般用32位
NaN 是一个缩写,表示 “Not a Number”,即"不是一个数字"。
它是一种特殊的数值表示,在计算机科学和数据分析中常常用于表示缺失值或无效的数值。
QA:
a=torch.arange(12)
b=a.reshape((3,4))
b[:]=2
print(a)
tensor([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
为什么改b
,a
也会变?
在PyTorch中,张量对象是通过引用传递的,而不是通过值传递。当你创建一个张量,并将其赋值给另一个变量时,它们实际上共享相同的底层数据存储。这种共享存储的机制导致了这种情况。
在代码中,执行 b = a.reshape((3, 4))
时,b
实际上成为了 a
的一个视图(view)。这意味着 b
和 a
引用相同的存储空间,它们是同一个张量的不同表示。
因此,当修改 b
中的元素时,实际上也会修改共享的底层存储,从而影响到 a
。这是因为 b[:] = 2
语句将所有元素都设置为 2
,同时在底层存储中进行了相应的更改。由于 a
和 b
共享相同的存储,因此对 b
所做的更改也会反映在 a
上。
如果想要创建一个与 a
独立的副本,而不共享底层存储,可以使用 clone()
方法来复制张量。例如,可以使用 b = a.clone().reshape((3, 4))
来创建一个新的张量 b
,它与 a
拥有相同的形状,但是是独立的对象,修改 b
不会影响到 a
。
总结起来,当你对一个张量进行切片或重塑操作时,要注意是否会影响到原始张量,因为它们可能共享相同的底层存储。如果需要避免这种共享,可以使用 clone()
方法来创建副本。