数学与算法基础-线性代数部分-代码实战

代码实战环节

 import torch
 import numpy as np
 ​
 class LinearAlgebra():
     @staticmethod
     def svd(matrix):
         if isinstance(matrix,torch.Tensor):
             matrix = matrix.numpy()
         
         # 求左右矩阵
         left = matrix @ matrix.T
         right = matrix.T @ matrix
 ​
         # 求特征值和特征向量
         left_val , left_ = np.linalg.eigh(left)
         right_val , right_ = np.linalg.eigh(right)
 ​
         # 降序操作
         left_idx = left_val.argsort()[::-1]
         right_idx = right_val.argsort()[::-1]
 ​
         # 调整特征值和特征向量顺序
         left_val = left_val[left_idx]
         right_val = right_val[right_idx]
         left_ = left_[:,left_idx]
         right_ = right_[:,right_idx]
 ​
         # 求奇异值,记住用右边的矩阵,别用左边的
         mid_ = np.sqrt(np.abs(right_val))
 ​
         # 返回  U * Σ * V^T
         return left_ , mid_ , right_.T
     
     @staticmethod
     def tensor_einsum(op,*tensors):
         # 张量的einsum操作
         return torch.einsum(op,*tensors)
 ​
     @staticmethod
     def chain_rule(x):
         # 创建需要计算梯度的张量
         x = torch.tensor(x,requires_grad=True)
         y = torch.sin(x)
         z = y ** 2
         # 自动计算x的梯度
         z.backward()
         # 返回x的梯度
         return x.grad
 ​
     @staticmethod
     def svd_dimension_reduction(matrix,k):
         # 进行svd分解,得到 U * Σ * V^T
         u , s , vT = LinearAlgebra.svd(matrix)
         
         # 保留前k个奇异值和对应的向量
         u_k = u[:,:k] # 保留所有行,保留前k列
         s_k = s[:k] # 保留前k个奇异值
         vT_k = vT[:k,:] # 保留前k行,保留所有列
 ​
         # 用保留的部分重构矩阵
         # 理论上重构结果应该最大程度保留原矩阵的重要信息
         last = u_k @ np.diag(s_k) @ vT_k
         return last, u_k, s_k, vT_k
 ​
 def test():
     matrix = np.array([[1,2,3],
                       [4,5,6],
                       [7,8,9],
                       [10,11,12]])
     # 测试svd分解
     u, s, vT = LinearAlgebra.svd(matrix)
     print("奇异值:",s)
 ​
     # 测试svd降维 保留前2个维度
     k = 2 
     last, u_k, s_k, vT_k = LinearAlgebra.svd_dimension_reduction(matrix,k)
     print("降维重构后的矩阵:",last)
 ​
     print("重构误差:")  # 误差越小说明保留的信息越多
     print(np.sum(np.abs(matrix - last)))
 ​
     # 测试 einsum
     # 创建随机张量(tensors)
     a = torch.randn(2,3)
     b = torch.randn(3,4)
     c = LinearAlgebra.tensor_einsum("ij,jk->ik",a,b)
     print("矩阵a:",a)
     print("矩阵b:",b)
     print("ab矩阵相乘结果:",c)
 ​
     # 测试chain_rule
     grad = LinearAlgebra.chain_rule(1.0)
     print("在x=1处的梯度值:", grad)
 ​
 if __name__ == '__main__':
     test()

1.为什么要降序排序:

  • 奇异值通常按从大到小排序
  • 较大的奇异值代表更重要的特征或信息
  • 在进行降维时,我们通常保留最大的几个奇异值
  • 这是一个标准约定,使结果更容易解释和使用

2.left_[:,left_idx] 的含义:

  • :表示选择所有行
  • left_idx 选择指定的列
  • 整体表示"保留所有行,但重新排列列的顺序"

3.保留前k个奇异值和对应的向量

 u_k = u[:,:k] # 保留所有行,保留前k列
 s_k = s[:k] # 保留前k个奇异值
 vT_k = vT[:k,:] # 保留前k行,保留所有列

u_k = u[:,:k]

  • 保留U的所有行(m行)
  • 只取前k列
  • 结果是**m×k**矩阵

s_k = s[:k]

  • 只保留前k个最大奇异值
  • 用于构造**k×k**的对角矩阵

vT_k = vT[:k,:]

  • 保留V^T的前k行
  • 保留所有列(n列)
  • 结果是**k×n**矩阵