今天是2025年6月1日,从今天起,我打算新开一个系列,将自己学习《动手学深度学习》的心得和笔记发在掘金上面。
目标是日更,就以这种形式,今天是2.1和2.2,明天就应该是2.3和2.4。我计算过,只有以这种速度更新,才能在两个月内搞定这本书。
本篇文章包括后续的所有文章,都是我在Jupyter Notebook中先自己过了一遍,然后把笔记和代码以markdown的形式放到掘金上。一方面是为了分享,另一方面也是为了督促自己完成。
其实我自己知道真正每天按照计划走很难,达标不容易,但是如果能坚持走下来,相信自己人工智能的实践水平能提升一个档次。
2.1.1 入门
numpy里面的数组只能在CPU上操作,而且不支持自动微分。深度学习框架(Pytorch, Tensorflow, MXnet)支持GPU计算和自动微分因此想做深度学习只用numpy是不够的。
import torchx = torch.arange(12)x
X = x.reshape(3, 4) 只改变形状,不改变元素的值和数量
既然数量不变,因此reshape以后的数量要和原来对的上,否则会报错
X = x.reshape(3, 4)X
无论是一维数组还是二维数组,还是其他维数组,在深度学习框架里面,数组统称为张量Tensor
f"string"这种结构,具体的变量值就放在{}里面,\n放在外面即可
y = torch.arange(22)Y = y.reshape(2, 11)print(f"{y} \n\n {Y}")
无论使不使用-1,用reshape的关键都是前后元素的数量要对齐。
Y2 = y.reshape(-1, 11)Y3 = y.reshape(2, -1)print(f"{Y2}\n\n{Y3}")
torch.tensor()才是本源像什么torch.arange(), torch.randn(), torch.zeros(), torch.ones()这些只是对torch.tensor()的快捷操作罢了。
torch.tensor([[1, 2], [3, 4]])
2.1.2 运算符
''' tensor的加减乘除也是得元素数量的对齐tensor级别的加减乘除本质上就是针对其中相应位置元素的加减乘除。'''x = torch.tensor([3.4, 2.1, 5, 6, 7])y = torch.tensor([1, 2, 3, 4, 5])ans1, ans2, ans3, ans4, ans5 = x+y, x-y, x*y, x/y, x**y print(f"{ans1}\n\n{ans2}\n\n{ans3}\n\n{ans4}\n\n{ans5}")
''' 事实证明,reshape(3, 4)和reshape((3, 4))效果是一样的。'''x = torch.arange(12).reshape(3, 4)x2 = torch.arange(12).reshape((3, 4))print(f"{x}\n\n{x2}")
x = torch.arange(12).reshape(3, 4)y = torch.zeros(3, 4)ans1, ans2 = torch.cat((x, y), dim = 0), torch.cat((x, y), dim = 1)print(f"{ans1}\n\n{ans2}")
''' 0竖1横 cat是contactnate的缩写,意思是拼接dim = 0是竖着拼,dim = 1是横着拼注意x和y要用一个小括号括起来,相当于cat()只用两个参数,一个是(x, y),还有一个是dim'''x = torch.arange(12).reshape(3, 4)y = torch.zeros(3, 1)ans = torch.cat((x, y), dim = 1)print(ans)
''' 使用torch.cat()的使用只要拼接处对齐就行,没必要要求tensor形状完全一样。但是使用x == y判断两个tensor中每个位置上的元素是否对应相等,那就形状必须得完全一样了。'''x = torch.tensor([1, 2, 3, 4])y = torch.tensor([1, 1, 3, 3])x == y
2.1.3 广播机制
import torchx = torch.tensor([[1, 1, 1], [2, 2, 2]])y = torch.arange(6).reshape(2, 3)print(f"{x+y}\n\n{x-y}")
''' 广播机制有两种应用场景:1. 至少有一个维度要对齐2. 行和列相加'''x = torch.arange(15).reshape(3, 5)y = torch.ones(5)x+y
2.1.4 索引和切片
''' 操作逻辑基本上和传统的数组是一样的。'''x = torch.arange(12).reshape(3, 4)x
''' 所以tensor的索引有两种方式,一种是x[1, 2],另一种是x[1][2]这两种效果是等价的。'''x[1, 1] = 100x
2.1.5 节省内存
x = torch.tensor([[1, 2], [1, 3]])y = torch.ones(2, 2)before_y = id(y)y = x + yafter_y = id(y)print(f"{before_y} \n\n{after_y}")before_y == after_y
''' 原地更新有两种方法:1. 切片2. +='''x = torch.tensor([[1, 2], [1, 3]])y = torch.ones(2, 2)before_y = id(y)y += xafter_y = id(y)print(f"{before_y} \n\n{after_y}")before_y == after_y
x = torch.tensor([[1, 2], [1, 3]])y = torch.ones(2, 2)before_y = id(y)y[:, :] = x + yafter_y = id(y)print(f"{before_y} \n\n{after_y}")before_y == after_y
2.1.6 转换为其他Python对象
path = []def dfs(self, root, target_node): path.append(root) if root.val == target_node.val: return True if root.left: dfs(self, root.left, target_node) if root.right: dfs(self, root.right, target_node) path.pop() return Falsedef dfs(self, root, target_node, path): if not root: return False path.append(root) if root.val == target_node.val: return True if dfs(self, root.left, target_node, path) or dfs(self, root.right, target_node, path): return True path.pop() return False
import numpy as npimport torch''' list不能直接通过numpy()转换为numpy.ndarray, 但是tensor可以直接通过numpy()转换为numpy.ndarray如果想把tensor转换为numpy.ndarray,就直接用torch.tensor'''X = torch.tensor([1, 2, 3])X_numpy = X.numpy()X_torch = torch.tensor(X_numpy)print(f"{type(X_numpy)}\n\n{type(X_torch)}")
''' int(a)的效果是直接下取整。只有当tensor中只含有一个元素时,才能直接用int()和float()'''a = torch.tensor([3.8])a, a.item(), float(a), int(a)
2.1 练习
X = torch.arange(20).reshape(4, 5)Y = torch.ones(4, 5)print(f"{X>Y}\n\n\n{X<Y}")
问题:广播机制的使用到底可以适用于哪些情况?
回答:从右到左比较:每一对维度之间必须满足以下三个条件中的任意一个
- 相等其中一个为一不存在
''' 我大概能体会为什么要从右到左比较了。因为这三个条件:等;二选一为1;不存在从右到左和从左到右是不一样的。更深层次的原因是从里往外,内层必须对的上,外层可以扩展'''X = torch.ones(3, 4, 5)Y = torch.ones(1, 5)X+Y
上半截是2.1数据操作,下半截是2.2数据预处理
2.2.1 读取数据集
os.path.join()会根据不同的操作系统创建路径。
os.path.join()不会去创建一个文件或文件夹,创建文件夹用os.makedirs(),创建文件用 with open
data的地位应该是和章节并列的
import osos.makedirs(os.path.join("..", "data"), exist_ok = True)data_file = os.path.join("..", "data", "house_tiny.csv")
用os.path.join()指定路径就是为了使代码在windows和linux之间通用
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')
读csv文件最有效的方法就是pandas里面的read_csv
在用with open()往csv文件里面写东西的时候,第一个写入的就是列名,剩下的就是具体的值。
写入的使用是NA,用pd.read_csv()识别的使用会识别成NaN, NaN代表缺失值
import pandas as pd data = pd.read_csv(data_file)print(data)
2.2.2 处理缺失值
csv文件被read_csv以后,就成了pandas里面的DataFrame
iloc[slice1, slice2],slice1指定行,slice2再指定列
iloc的参数可以是切片,还可以是具体的数字
如果你用inputs.mean()去填充NaN,那么只会影响数值列,不会影响别的列
单列的dataframe会被视为series,列名在下方;多列的dataframe就是dataframe,列名在上方
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]inputs = inputs.fillna(inputs.mean())print(inputs)print()print(outputs)
pd.get_dummies() 根据值的不同生成不同的列,新值是不同值的有无。
dummy_na = True 的意思是NaN也看作是不同的值中的一个dummy_na就决定了是否有Alley_nan这一行。如果dummy_na = True,那么Alley_nan就会单独作为一行。但是如果dummy_na = False,那么Alley_nan就没有这一行
get_dummies()只会对object类型有用,对数值不起作用
inputs = pd.get_dummies(inputs, dummy_na = True) print(inputs)
2.2.3 转换为张量格式
数值类型才能转换为张量形式,因此必须先把Object对象,比如字符串用get_dummies改成数值形式再去转换为tensor
pandas里面有一个函数:to_numpy()可以把dataframe转换为numpynumpy又可以进一步通过torch.tensor转换为tensor
import torch X = torch.tensor(inputs.to_numpy(dtype = float))Y = torch.tensor(outputs.to_numpy(dtype = float))print(f"{X}\n\n{Y}")
练习
1.创建包含更多行和列的原始数据集
定义csv文件时,直接加后缀.csv就行
往csv文件里面逐行写内容,分隔一定要用英文逗号,中文逗号识别不出来,无法起到分隔效果。
太细节了。一般来说写代码的时候英文逗号后面还会额外空一格,但是在往CSV文件里面写东西的时候就不能空格了因为 NA, 这种写法会导致NA无法被正确得被识别为NaN,只有前后无空格才能被识别为NaN
import osimport pandas as pdfile_path = os.path.join("..", "data", "2.2数据预处理_练习.csv")with open(file_path, "w") as f: f.write("物品,价格,年龄\n") f.write("iphone10,7999,5\n") f.write("篮球,NA, 4\n") f.write("戴尔笔记本,5000,4\n") f.write("倍思电子笔,NA,NA\n")data = pd.read_csv(file_path)data
2. 删除缺失值最多的列
方法一:肉眼看,直接删除
df2 = df1.drop(columns = "balabalabala")通过这种方式可以删除某列
data = data.drop(columns = "价格")data
方法二:写代码自动判断哪一列的NaN是最多的
isna() 缺失了就是True, 没有缺失就是False
is_NaN = data.isna()NaN_num = is_NaN.sum()Most_NaN_column = NaN_num.idxmax()data = data.drop(columns = Most_NaN_column)data
3. 将预处理后的数据集转换为张量
data = data.fillna(data.mean())data
data = pd.get_dummies(data)data
ts = torch.tensor(data.to_numpy(dtype = int))ts