深度学习中避免不了矩阵运算,或者张量(其实是矩阵数组)运算。卷积是矩阵加、乘法,注意力也是一样。本质都一样,所谓注意力,卷积、滤波,是对不必了解数学的人说的,底层都是矩阵运算,线性变换。
任何一个向量都可以用基向量的线性组合来表示。
即任何一个向量都可以用基向量通过线性变换得到。
矩阵相乘的几何意义就是两个线性变换的相继作用。
通过线性变换,不仅可以得到这一组合的变换结果,也可以有效地计算任意向量的线性变换结果。
看看旋转变换:
比如上图所示,向量R逆时针旋转角度B前后的情况。
在左图中,有:
x0 = |R| * cosA => cosA = x0 / |R|
y0 = |R| * sinA => sinA = y0 / |R|
同样的,在右图中:
x1 = |R| * cos(A+B)
y1 = |R| * sin(A+B)
其中(x1, y1)就是(x0, y0)旋转角B后得到的向量,展开cos(A+B)和sin(A+B):
x1 = |R| * (cosAcosB - sinAsinB)
y1 = |R| * (sinAcosB + cosAsinB)
把 cosA = x0 / |R| 和 sinA = y0 / |R| 代入上面的式子,得到:
x1 = |R| * (x0 * cosB / |R| - y0 * sinB / |R|) => x1 = x0 * cosB - y0 * sinB
y1 = |R| * (y0 * cosB / |R| + x0 * sinB / |R|) => y1 = x0 * sinB + y0 * cosB
这样就得到了二维坐标下向量的逆时针旋转公式。顺时针旋转就把角度变为负:
x1 = x0 * cos(-B) - y0 * sin(-B) => x1 = x0 * cosB + y0 * sinB
y1 = x0 * sin(-B) + y0 * cos(-B)=> y1 = -x0 * sinB + y0 * cosB
把这个公式写成矩阵的形式,每个线性变换(这里是旋转变换)都对应一个矩阵,叫做变换矩阵。
线性变换的性质
- 唯一性:每个线性变换由其在基向量上的值唯一确定。
- 矩阵表示:每个线性变换可以用矩阵表示,通过矩阵与向量的乘法来实现变换。
- 复合性:两个线性变换的复合依然是线性变换。
- 零向量的映射:线性变换总是将零向量映射为零向量,即 T(0)=0T(0)=0。
- 负元素的象为原来元素的象的负元素。
- 线性变换把线性相关的元素组仍变为线性相关的元素组。
- 特征值和特征向量:线性变换的特征值和特征向量可帮助理解变换性质,如缩放、旋转等。
注: 线性无关的元素组经过线性变换不一定再是线性无关的, 变换后的情况与元素组和线性变换有关。若线性变换 T 将所有的元素组仍变换为线性无关的元素组,则称之为满秩的线性变换,其变换矩阵为满秩矩阵。
常见变换:平移变换、 旋转变换、 缩放变换、 反射变换、 投影变换。
1、线性变换是自身空间下的变换,而仿射变换是不同空间下的变换。线性变换作用后,空间维数不变,仿射变换作用后空间维数可能改变。
2、仿射变换与线性变换的区别在于变换前后是否改变原点。仿射变换=线性变换+平移变换
3、旋转,伸缩改变向量的方法,伸缩不改变方向
4、正交变换是指通过正交矩阵进行的线性变换。可以简单理解为矩阵的行(或列)向量是正交的,并且每个向量的长度为1。
性质:
保持角度和长度:正交变换保持向量之间的夹角和长度,意味着不改变几何形状。
几何意义:正交变换可以看作是对空间的旋转或反射。
几种特殊矩阵:对称矩阵、正交矩阵、单位矩阵、酉矩阵、 Hermite矩阵、上下三角矩阵等。
向量完成变换的关键是:要找到那个变换矩阵T
变换矩阵T的本质是:找到一组基向量
找到一组基向量等价于:找到了一个坐标系
找到一组基向量意味着:找到了一个向量空间
PyTorch 张量操作函数:
a. 张量创建
torch.zeros(size):创建全0张量。
torch.ones(size):创建全1张量。
torch.empty(size):创建未初始化的张量。
torch.rand(size):创建随机值张量。
torch.eye(n):创建单位矩阵。
b. 张量形状操作
tensor.view(shape):重塑张量。
tensor.reshape(...):重塑张量,注意跟view的区别。
tensor.permute(dims):改变张量的维度顺序。
tensor.unsqueeze(dim):在指定维度上增加一个维度。
tensor.squeeze(dim):去除指定维度的大小为1的维度。
torch.cat(tensors, dim):沿指定维度拼接多个张量。
c. 张量运算
torch.matmul(a, b):矩阵乘法。
torch.add(a, b):张量加法。
torch.subtract(a, b):张量减法。
torch.multiply(a, b):张量逐元素相乘。
torch.divide(a, b):张量逐元素相除。
d. 张量统计
tensor.mean(dim):计算均值。
tensor.sum(dim):计算和。
tensor.max(dim):计算最大值。
tensor.min(dim):计算最小值。
e. 张量索引与切片
tensor[index]:索引单个元素。
tensor[start:end]:切片操作。
f. 其他操作
transpose(dim0, dim1):交换两个维度。
transpose(-2, -1)交换最后两个维度,负数表示从里层开始算,正数表示从外层开始算。
torch.stack(tensors, dim):在新维度上堆叠多个张量。
tensor.clone():克隆一个张量。
tensor.contiguous() :使张量在内存里连续,一般用在view()函数之前。
张量运算常见错误:
python
import torch
import torch.nn as nn
import torch.nn.functional as F
input_tensor = torch.randn(2, 3, 4, 5)
print(input_tensor)
print("----------------")
y=input_tensor.permute(0,2,3,1)
y=y.view(4,3,2,5)
print(y.shape)
运行错误:
y=y.view(4,3,2,5)
RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
解析错误原因
在 PyTorch 中,view 方法要求输入张量的内存是连续的(contiguous)。当你调用 permute 方法时,它会改变张量的维度顺序,但不会改变其在内存中的存储顺序。
因此,经过 permute 后的张量可能不是连续的,这就导致了在使用 view 时出现了错误。在 permute 之后,y 的形状变为 (2, 4, 5, 3),但是这个张量在内存中并不连续。
当你尝试执行 y = y.view(4, 3, 2, 5) 时,PyTorch 检测到 y 的内存布局不符合 view 的要求,因此抛出了 RuntimeError。
解决:view替换为reshape 或者先调用contiguous()
y=y.reshape(3,4,5,2)
y=y.contiguous().view(4,3,2,5)
这同时也展示了view、permute、reshape用法的不同。view和reshape的输入参数是张量的每个维度的大小,在不关心每个维度是多长时,可以用permute,输入原张量维度的序号(0,1,2,3,4...)他们的用途都是重塑张量的形状,但是,view需要张量的内存是连续的,如果一个张量发生了形状的改变,在内存里可能就不是连续的,此时调用view前,先用contiguous()。
在 PyTorch 中, torch.nn.Linear
是用于构建线性变换(又称为全连接层或线性层)的类,常用在神经网络中。原型:torch.nn.Linear(self, in_features: int, out_features: int, bias: bool = True,device=None, dtype=None)
输入参数解析
1.in_features (int):
这是输入特征的维度,即输入向量的大小。例如,如果输入数据是一个具有 10 个特征的样本,则 in_features
应设置为 10。
2.out_features (int):
这是输出特征的维度,即线性变换后输出向量的大小。例如,如果希望输出数据具有 5 个特征,则 out_features
应设置为 5。
3.bias (bool, optional):
默认值为 True
。这个参数指示是否包含偏置项(bias term)。如果设置为 True
,则该线性层在计算时会添加一个偏置项;如果设置为 False
,则省略偏置项。线性变换的公式一般为:
output=input⋅weightT+biasoutput=input⋅weightT+bias
- 当
bias
为False
时,公式简化为:output=input⋅weightToutput=input⋅weightT
4.device (torch.device, optional):
这个参数用于指定张量要分配到的设备(例如,CPU 或 GPU)。
5.dtype (torch.dtype, optional):
这是指定张量数据类型的参数。例如 torch.float32
或 torch.float64
。
简单例子:
python
import torch
import torch.nn as nn
class Example(nn.Module):
def __init__(self, d_model, num_heads):
super(Example, self).__init__()
self.d_model = d_model
self.num_heads = num_heads
# 两个参数分别是输入
self.W_q = nn.Linear(d_model, d_model)
def forward(self, x):
# 输入 x, shape = (batch_size, seq_length, d_model)
# 假设我们有 batch_size=2, seq_length=3, d_model=4
x = torch.arange(24).view(2, 3, 4).float() # Shape: (2, 3, 4)
print(x)
# 执行线性变换
Q = self.W_q(x) # 仍然是(2, 3, 4)
print(Q)
# 对Q进行reshape操作
Q = Q.view(2, 3, 2, 2) # Shape: (2, 3, 2, 2), 假设 num_heads=2, d_k=2
Q = Q.transpose(1, 2) # Shape: (2, 2, 3, 2)
# 进行一些示例
print("原始Q:", x)
print("线性变换后的Q:", Q)
model = Example(4, 2)
model(torch.randn(2, 3, 4))
# 输入维度为3,输出维度为2
linear_layer = nn.Linear(3, 3)
# 随机输入一个batch大小为1,特征维度为3的张量
input_tensor = torch.tensor([[1.0, 2.0, 3.0]]) # Batch size = 1, Features = 3
# 线性层前向传播
output = linear_layer(input_tensor)
print(output)
print("---------------")
# in_features=4,out_features=3
m = nn.Linear(4, 3)
# 只要最后一维的大小等于 in_features(这里是4)
input = torch.randn(2,3,5,4)
print(input)
output = m(input)
print(output)
print(output. Size())
总之线性变换对应一个变换矩阵,变换矩阵也是一个函数(映射)。