文章目录
- 奇异值分解SVD 原理+作业
- 1. 简介
- 1.1 简介
- 1.2 正交变换
- 1.3 特征值和特征向量
- 1.4 SVD
- 2. 流程
- 3. 作业
- 3.1 手动实现SVD
- 要求
- 代码
- 3.2 人脸识别
- 要求
- 数据集介绍
- 代码
奇异值分解SVD 原理+作业
1. 简介
1.1 简介
矩阵分解在机器学习领域有着广泛应用,是降维相关算法的基本组成部分。
矩阵分解的定义
把一个矩阵表示成多个矩阵连乘的形式。
矩阵分解主要有两个作用
- 分解后的每个小矩阵能够更容易的求逆
- 分解后的每个小矩阵有特殊的物理意义
常见的矩阵分解方式有以下两种
- 特征分解Eigendecomposition, 也叫作谱分解Spectral decomposition
- 奇异值分解Singular Value decompositon(SVD)
特征值分解是一个提取矩阵特征很不错的方法,可是它只是对方阵而言的,在现实的世界中,咱们看到的大部分矩阵都不是方阵,好比说有个学生,每一个学生有科成绩,这样造成的一个的矩阵就不多是方阵,咱们怎样才能描述这样普通的矩阵呢的重要特征呢?奇异值分解能够用来干这个事情,奇异值分解是一个能适用于任意的矩阵的一种分解的方法:
想要理解SVD,必须理解的数学知识
1.2 正交变换
正交变换公式
上式表示:是的正交变换,其中是正交矩阵,和为列向量。
下面用─个例子说明正交变换的含义∶
假设有两个单位列向量和,两向量的夹角为,如下图:
现对向量a,b进行正交变换:
的模:
由上式可知的模为1
和的内积为
由上式可知,正交变换前后的内积相等。
和的夹角
正交变换前后的夹角相等,即
因此,正交变换的性质可用下图来表示:
正交变换的两个重要性质:
1)正交变换不改变向量的模。
2)正交变换不改变向量的夹角。
如果向量和是基向量,那么正交变换的结果如下图:
基向量正交变换后的结果仍是基向量 。基向量是表示向量最简洁的方法,向量在基向量的投影就是所在基向量的坐标,我们通过这种思想去理解特征值分解和推导SVD分解。
正交变换只是将变换向量用另一组正交基表示,在这个过程中并没有对向量做拉伸,也不改变向量的空间位置,假如对两个向量同时做正交变换,那么变换前后这两个向量的夹角显然不会改变。
1.3 特征值和特征向量
我们首先回顾下特征值和特征向量的定义如下:
是一个的矩阵,是一个维向量,则我们说是矩阵的一个特征值,而是矩阵的特征值所对应的特征向量。
求出特征值和特征向量有什么好处呢?
我们可以将矩阵特征分解,如果我们求出了矩阵的个特征值,以及这个特征值对应的特征向量,如果这个特征向量线性无关,那么矩阵就可以用下式的特征分解表示
其中是这个特征向量所张成的维矩阵,而为这个特征值为主对角线的维矩阵。
为了可视化特征值分解,假设是的对称矩阵,。把上式展开
用图形表示为
由上图可知,矩阵A没有旋转特征向量,它只是对特征向量进行了拉伸或缩短(取决于特征值的大小),因此,对称矩阵对其特征向量(基向量)的变换仍然是基向量(单位化) 。
特征向量和特征值的几何意义:若向量经过矩阵变换后保持方向不变,只是进行长度上的伸缩,那么该向量是矩阵的特征向量,伸缩倍数是特征值。
一般我们会把的这个个特征向量标准化,即满足,或者说.此时的个特征向量为标准正交基。满足,即,也就是说为酉矩阵,在实数矩阵中,酉矩阵指的是转置矩阵与逆矩阵相等的矩阵;在复数矩阵中,酉矩阵指的是共轭转置矩阵(矩阵中各元素实部不变,虚部相反数)与逆矩阵相等的矩阵。
这样我们的特征分解表达式可以写成:
注意到要进行特征分解,矩阵A必须为方阵。那么如果A不是方阵,即行和列不相同时,我们还可以对矩阵进行分解吗?答案是可以,此时我们的SVD登场了。
1.4 SVD
假设是一个的矩阵,那么可以定义的SVD为
其中,对角矩阵,主对角线上的元素为奇异值,和都是酉矩阵,即满足
证明如下
先回顾一下正交变换的思想:基向量正交变换后的结果仍是基向量 。
我们用正交变换的思想来推导SVD分解:
假设是的矩阵,秩为,。
存在一组正交基V:
矩阵对其变换后仍是正交基,记为:
由正交基定义,得:
上式展开:
如果我们将和做矩阵乘法,那么会得到的一个方阵.既然是方阵,那么我们就可以进行特征分解,得到特征向量和特征向量。当是的特征向量时,有:
根据,得到
即假设成立 。
图形表示如下:
正交向量的模:
单位化正交向量,得:
结论:当基向量是的特征向量时,矩阵A转换后的向量也是基向量 。用矩阵的形式表示为
其中
V是矩阵,U是矩阵,是M*K的矩阵,需要扩展成方阵形式:
两式右乘可得矩阵的奇异值分解
2. 流程
用一个简单的例子来说明矩阵是如何进行奇异值分解的。矩阵A定义为:
首先求出和
求的特征向量和特征值
对应的特征值为,对应的平方根
求的特征向量
所以的奇异值分解
3. 作业
3.1 手动实现SVD
要求
根据课堂讲授的SVD分解方法,用 Python 语言编程实现矩阵的SVD 分解和 thin SVD 表达。
要求:
代码要有规范的注释, 输出以下两个测试例的结果
第二个
代码
import numpy as np
def get_data2():
return np.array([[2, 0],
[0, 1 / np.sqrt(2)],
[0, 1 / np.sqrt(2)], ])
def get_data():
return np.array([[1, 0, 0, 0, 2],
[0, 0, 3, 0, 0],
[0, 0, 0, 0, 0],
[0, 4, 0, 0, 0]])
def get_eigenvalue(A):
return np.linalg.eigvals(A)
def get_U(A):
return np.linalg.eig(A.dot(A.T))
def get_V(A):
return np.linalg.eig(A.T.dot(A))
def SVD():
sigma, V = get_V(A)
sigma, V, length = bubble_sort(sigma, V)
sigma2, U = get_U(A)
sigma, U, _ = bubble_sort(sigma2, U)
S = np.zeros((m, n))
for i in range(length):
S[i][i] = np.sqrt(sigma[i])
# print(S)
# U = np.zeros((m, m))
# print(U.shape)
for i in range(length):
# print((1 / np.sqrt(sigma[i])) * np.dot(A, V[:, i]))
U[:, i] = (1 / np.sqrt(sigma[i])) * np.dot(A, V[:, i])
return U, S, V
def bubble_sort(sigma, B):
"""
对奇异值进行排序
:param sigma:
:param B:
:return:
"""
length = len(sigma)
for i in range(length - 1):
# i表示比较多少轮
for j in range(length - i - 1):
if sigma[j] < sigma[j + 1]:
sigma[j], sigma[j + 1] = sigma[j + 1], sigma[j]
B[:, [j, j + 1]] = B[:, [j + 1, j]]
temp = 0
for i in range(length):
if sigma[i] > 0:
temp = temp + 1
return sigma, B, temp
# 获取数据
A = get_data2()
m = A.shape[0]
n = A.shape[1]
k = min(m, n)
U, S, V = SVD()
temp2 = np.dot(np.dot(U, S), V.T)
print("SVD-- \n U=\n{}\n Z=\n{}\n V=\n{}".format(U, S, V))
print("*" * 10)
print(temp2)
结果
SVD--
U=
[[ 1. 0. 0. ]
[ 0. 0.70710678 0.70710678]
[ 0. 0.70710678 -0.70710678]]
Z=
[[2. 0.]
[0. 1.]
[0. 0.]]
V=
[[1. 0.]
[0. 1.]]
**********
[[2. 0. ]
[0. 0.70710678]
[0. 0.70710678]]
3.2 人脸识别
要求
作业2:实现基于奇异值分解的人脸识别。
要求:
- 提交可运行的python源码(包括数据集、运行代码、运行结果)。
- 撰写WORD文档,说明与分析所用人脸识别方法、原理,实现,测试
数据集介绍
数据集如下:ORL数据集
目录结构如下
数据集下载
我自己上传的 ORL数据集attrface | Kaggle
官网网址,貌似不能用了https://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html
代码
# 人脸识别是一个分类问题
# svc 支持向量解决分类问题
import cv2
import numpy as np
import os
data_path = r"att_faces"
train_data = []
test_data = []
image = []
sigma_data = []
# 使用50个特征值作为该图像的特征信息
K = 80
print("选取了{}个特征".format(K))
def read_image():
"""
加载图片信息
:return:
"""
global train_data, test_data
root_path = os.listdir(data_path)
for d in root_path:
s_path = os.path.join(data_path, d)
for idx, image_path in enumerate(os.listdir(s_path)):
# 因为是二维图,所以是二维的 [112,92]
img = cv2.imread(os.path.join(s_path, image_path), cv2.IMREAD_GRAYSCALE)
image.append(img)
train_data = image[0::2]
test_data = image[1::2]
def train():
"""
进行训练,训练的目的就是把40 张图片的所有 特征值提取出来,放进sigma_data
:return:
"""
for image in train_data:
# 数据进行展示
u, sigma, v = np.linalg.svd(image)
# 对sigma 从小到大排序
sigma = np.sort(sigma)
# 选取特征值K个最大特征值作为Sm_i的特征值
sigma_data.append(sigma[-K:])
print("训练结束")
def test():
"""
用提前准备好的图片 做最近邻算法的比较,和谁的最近邻 小
:return:
"""
# 分类正确的个数
acc = 0
for idx, image in enumerate(test_data):
u, sigma, v = np.linalg.svd(image)
# 对sigma 从小到大排序
sigma = np.sort(sigma)
# 选取特征值K个最大特征值作为Sm_i的特征值
sigma = sigma[-K:]
m = get_index(sigma)
raw = int(idx / 5) + 1
predict = int(m / 5) + 1
if raw == predict:
acc += 1
print("这张图预测为{},预测正确".format(predict, raw))
else:
print("这张图预测为{},预测错误,本来是{}".format(predict, raw))
print("最终准确率{}%,--[{}/{}]".format(100 * acc / len(test_data), acc, len(test_data)))
# 使用
def get_index(sigma):
"""
求出最小近邻的 所在是索引值
:param sigma: 测试图像的前 K 个最大特征值
:return:
"""
index = []
for i in sigma_data:
# 最小近邻,其实就是求第二范数
index.append(np.linalg.norm(i - sigma))
return np.argmin(index)
# 加载数据
read_image()
# print(test_data)
# 训练
train()
# 测试
test()
结果如下
这张图预测为21,预测错误,本来是38
这张图预测为38,预测正确
这张图预测为39,预测正确
这张图预测为39,预测正确
这张图预测为39,预测正确
这张图预测为39,预测正确
这张图预测为39,预测正确
这张图预测为34,预测错误,本来是40
这张图预测为40,预测正确
这张图预测为40,预测正确
这张图预测为40,预测正确
这张图预测为40,预测正确
最终准确率73.0%,--[146/200]