Numpy
(使用numpy函数规避for循环,numpy自动实现并行运算,节省时间)
创建
# create
arr1 = np.array([1,2,3,4,5])
arr2 = np.zeros([2,2,3], dtype = np.float32)
arr3 = np.ones([2,2])
print(arr1,type(arr1))
print(arr2)
print(arr3)
层数是由外向内数的
修改
# modify
arr1 = np.zeros([1,1,4], dtype = np.int32)
arr2 = np.ones([5,1,4], dtype = np.int32)
arr = np.concatenate([arr1,arr2],axis=0)
print('concatenate\n', arr)
print('arr1\n',arr1)
print('expand1\n', np.expand_dims(arr1,3))
print('2', arr2[:, :, np.newaxis, :])
axis=3 的含义:
在原数组的第4个维度(因为 Python 的索引从0开始)上添加一个新的轴。 如果原数组的维度小于3,新的轴会被添加到最后。
获取数组属性
# 获取数组属性
arr = np.array([[1,1,4,5,1,4],
[1,9,1,9,8,10]])
print(arr.size)
print(arr.shape)
12
(2, 6)
切片和筛选
arr = np.array([[1,2,3,4,5],
[6,7,8,9,0],
[10,11,12,13,14],
[15,16,17,18,19],
[9,1,5,4,6]])
print(arr.shape)
print(arr[[1,0], [2,3]])
print('\nslice\n')
print(arr[:2,:3])
print('\nfilter\n')
print(arr>5)
print(arr[arr>7])
print(arr[[1,0], [2,3]])是 NumPy 的高级索引方法。
[1,0] 是第一个索引列表,对应行索引。
[2,3] 是第二个索引列表,对应列索引。
这两个列表是一一对应的,意味着我们要取:
第 1 行(索引 1)的第 2 列(索引 2)的元素
第 0 行(索引 0)的第 3 列(索引 3)的元素
arr[1,2] 是第 2 行第 3 列的元素,即 8
arr[0,3] 是第 1 行第 4 列的元素,即 4
因此,结果是 [8, 4]。
按条件选择、替换数据
arr = np.array([[1,2,3,4,5],
[6,7,8,9,0],
[10,11,12,13,14],
[15,16,17,18,19],
[9,1,5,4,6]])
condition = arr>5
print(condition)
print(np.where(condition, -1, arr))
print(np.where(condition, -1, 2))
rra = -arr
print(np.where(condition, arr, rra))
数据的保存和加载
os.makedirs("result", exist_ok=True)
# 数据的保存和加载
a = np.array([1,2,3,4,5,6])
b = np.array([[1,9,1],[9,8,10]])
print(b.shape)
np.save("result/a",a)
np.savez("result/ab",a=a,b=b)
# numpy.save(file, arr, allow_pickle=True, fix_import=True)
# file: the file need save, expanded-name .npy
# numpy.savez(file, *args, **kwds)
# file: .npz
# kwds: 要保存的数据使用关键字的名称
a = np.load('result/a.npy')
ab = np.load('result/ab.npz')
print(a)
print(ab)
# 通过保存时指定的名字来访问对应的元素
print(ab['a'])
print(ab['b'])
Broadcast
如何让矩阵乘2?
由于访存连续性,最高效的方法是把2变成同形矩阵,然后把矩阵拆成几块,丢给不同的核做并行运算。
访存连续性问题是计算机系统中一个重要的性能考量因素。它主要关注的是程序在访问内存时的模式,特别是连续访问内存的能力。理解这个问题对于优化程序性能和提高系统效率非常重要。
-
定义: 访存连续性指的是程序在访问内存时,能够连续地访问相邻的内存位置的程度。高度的访存连续性意味着程序更多地访问彼此靠近的内存位置。
-
为什么重要:
- 缓存效率:现代计算机系统使用多级缓存来加速内存访问。连续的内存访问模式更容易被缓存预取机制预测和优化,从而提高缓存命中率。
- 内存带宽利用:连续的内存访问可以更好地利用内存总线带宽,因为可以一次性传输更多相关数据。
- 页面访问:在虚拟内存系统中,连续访问可以减少页面错误,提高性能。
- 影响因素:
- 数据结构设计:如数组vs链表
- 内存分配策略
- 算法设计:如遍历方式
- 编程语言和编译器优化
- 常见问题:
- stride访问:非连续的、固定步长的内存访问模式
- 随机访问:完全不连续的内存访问
- 数据结构不当设计导致的碎片化访问
- 优化策略:
- 使用适当的数据结构(如数组代替链表)
- 合理安排数据布局
- 利用缓存友好的算法
- 预取技术
- 编译器优化(如循环展开)
- 测量和分析:
- 使用性能分析工具
- 缓存miss率分析
- 内存访问模式可视化
- 在不同系统中的考虑:
- CPU系统:关注缓存层次结构
- GPU系统:关注内存合并访问
- 分布式系统:关注数据局部性
多级缓存是现代计算机架构中一个重要的概念,用于平衡处理器速度和主内存访问速度之间的差异。让我们详细探讨一下多级缓存:
-
定义: 多级缓存是指在处理器和主内存之间设置多个层次的高速缓存存储器。通常包括L1、L2、L3缓存,有时甚至更多层。
-
层次结构:
- L1 缓存:最靠近CPU核心,速度最快,容量最小(通常几十KB)
- L2 缓存:次之,速度稍慢,容量更大(通常几百KB到几MB)
- L3 缓存:更大容量(可达数十MB),但速度相对较慢
- 主内存:容量最大,速度最慢
- 特点:
- 速度:越靠近CPU的缓存速度越快
- 容量:越靠近CPU的缓存容量越小
- 访问延迟:从L1到主内存,访问延迟逐级增加
- 共享性:L1通常每核独占,L2可能独占或共享,L3通常在多核间共享
- 工作原理:
- 当CPU需要数据时,首先查找L1缓存
- 如果L1未命中,则查找L2,以此类推
- 如果所有缓存都未命中,则从主内存读取数据
- 优势:
- 减少平均内存访问时间
- 提高处理器利用率
- 降低内存带宽压力
-
缓存一致性: 在多核处理器中,需要维护缓存一致性,确保不同核心看到的数据是一致的。
-
缓存替换策略: 当缓存满时,需要决定哪些数据被替换。常见策略包括LRU(最近最少使用)、FIFO(先进先出)等。
-
预取机制: 现代缓存系统often包含智能预取机制,尝试预测并提前加载可能需要的数据。
-
写入策略:
- 写直达(Write-through):同时写入缓存和主内存
- 写回(Write-back):仅写入缓存,延迟写入主内存
- 性能考量:
- 缓存命中率:高命中率意味着更好的性能
- 缓存一致性开销:在多核系统中需要权衡
- 缓存大小与延迟的权衡
- 编程考虑:
- 数据局部性:时间局部性和空间局部性
- 缓存友好的算法和数据结构设计
- 避免频繁的缓存行颠簸(Cache line thrashing)
广播的规则如下:
从后向前,如果两数组对应维度上轴的长度相同或其中一个的轴长度为1,广播兼容,可在轴长度为1的轴上进行广播机制处理。
如果两个数组的维度不同导致某个数组的前方没有维度,那么给低维度的数组前扩展提升一维,扩展维的轴长度为1,然后在扩展出的维上进行广播机制处理。
a = np.arange(1, 16).reshape([3,5])
print(a)
# b = np.array([2,3,4,5,6])
b = np.arange(2,7).reshape([5])
print(b)
print(a+b)
a(3, 5) b(5),从后往前比(5对应5)(3对应?,没有维度)(扩展一个维度)(b(1, 5))(b(3, 5))
print(2*a)
2:( ) a:(3, 5)
2:(1) (1对应5) 2:(5) (?对应3) 2:(1, 5) (1对应3) 2:(3, 5)
a = np.arange(1,5)
print(a.shape)
b = np.arange(0,12).reshape((3,4,1))
print(a+b)
请自行思考,结果:(3,4,4)
线性代数库
a = np.random.randint(5, size=(3, 3))
b = np.random.randint(5, size=(3, 3))
print(f'a:{a}\nb:{b}')
print(f'dot:\n {np.dot(a,b)}')
# 计算逐元素乘积再求和
print(f"vdot:\n {np.vdot(a,b)}")
# 计算最后一维内积
print(f"inner:\n {np.inner(a,b)}")
# 于计算两个向量的外积
print(f"outer:\n {np.outer(a,b)}")
print(f"matmul:\n {a@b}")
inner计算过程:
(0,0): [2 4 4] · [1 1 1] = 21 + 41 + 4*1 = 10
(0,1): [2 4 4] · [4 3 0] = 24 + 43 + 4*0 = 20
(0,2): [2 4 4] · [2 0 3] = 22 + 40 + 4*3 = 16
(1,0): [4 3 3] · [1 1 1] = 41 + 31 + 3*1 = 10
(1,1): [4 3 3] · [4 3 0] = 44 + 33 + 3*0 = 25
(1,2): [4 3 3] · [2 0 3] = 42 + 30 + 3*3 = 17
(2,0): [0 1 1] · [1 1 1] = 01 + 11 + 1*1 = 2
(2,1): [0 1 1] · [4 3 0] = 04 + 13 + 1*0 = 3
(2,2): [0 1 1] · [2 0 3] = 02 + 10 + 1*3 = 3
outer计算过程:
np.outer() 通常用于一维数组。
对于二维数组,我们需要先将它们展平(flatten)成一维数组,然后再计算外积。
a_flat = [2, 4, 4, 4, 3, 3, 0, 1, 1]
b_flat = [1, 1, 1, 4, 3, 0, 2, 0, 3]
a = np.arange(0,9).reshape(3,3)
print(a)
print(np.einsum("ij->ji",a))
A = torch.tensor(np.random.randint(5,size=(2,2,2,5))).reshape(2,2,1,2,5)
B = torch.tensor(np.random.randint(5,size=(2,2,2,5))).reshape(2,1,2,2,5)
print(A.shape, B.shape)
print(A)
print(B)
res = torch.einsum("ijk..., iko...->ijo",[A,B])
print(res.shape)
print(res)
关于einsum,分享一个不错的博客einsum 入门指南 - 楷哥 - 博客园 (cnblogs.com)
res = torch.einsum("ijk..., iko...->ijo",[A,B])
i:批次维度,在这里是2
这个运算实际上是在进行批量矩阵乘法。
对于每个批次(i),我们将 A 的一个 (j,k) (2,1)矩阵与 B 的一个 (k,o)(1,2) 矩阵相乘,得到一个 (2,2) 矩阵。
这个过程对所有 i(2) 个批次都进行。
具体的数学运算如下:
对于每个 i(从 0 到 1):
A[i] 是一个 (2,1) 矩阵
B[i] 是一个 (1,2) 矩阵
结果[i] = A[i] @ B[i],得到一个 (2,2) 矩阵
最终结果是一个 (2,2,2) 的张量,其中包含了 2 个 (2,2) 矩阵。
SciPy
SciPy 用户指南 — SciPy v1.14.0 手册 --- SciPy User Guide — SciPy v1.14.0 Manual
SciPy 模块列表 | 菜鸟教程 (runoob.com)
Pytorch
自动微分系统的计算图在运行中构建(即动态图系统,区别于tensorflow先定义再建图的静态图系统)
- 准备数据集 (tqdm() 进度条)
- 定义模型
- 计算梯度
- 优化
- 清空梯度
- 定义损失函数和优化器
- 训练(使用wandb可视化)
- 保存
# 准备数据集
class MyDataset(Dataset):
def __init__(self, *args):
def __getitem__(self, index):
def __len__(self):
dataset = MyDataset(*args)
dataloader = DataLoader(dataset, *args)
train_loader = DataLoader(dataset = train_set, batch_size = 32, shuffle = True)
test_loader = DataLoader(dataset = test_set, batch_size = 32, shuffle = True)
# 注:
# 定义模型
class Model(nn.Modules):
def __init__(self, *args):
super().__init__()
def forward(self, x):
return output
model = Model(*args)
# 定义损失函数和优化器
lossfn = Loss()
optimizer = Optimizer(model.parameters(), lr = LEARNING_RATE)
# 训练
for epoch_num in range(EPOCH):
for (data, label) in dataloader:
output = model(data)
loss = lossfn(output, label)
loss.backward()
optimizer.step()
optimizer.zero_grad()
# 保存
torch.save(model, MODEL_SAVE_PATH)