Bootstrap

【张量学习|TRPCA】Tensor Robust Principal Component Analysis with A New Tensor Nuclear Norm

具有新的张量核范数的张量鲁棒主成分分析

摘要

提出了一种利用新张量核范数从其和中精确恢复低秩和稀疏成分的方法。引入了张量谱范数、张量核范数和张量平均秩的定义,并展示了它们之间的关系。证明了张量核范数是张量平均秩在张量谱范数单位球内的凸包。通过求解一个凸规划来解决TRPCA问题,并提供了精确恢复的理论保证。数值实验验证了该方法的有效性,特别是在图像恢复和背景建模中的应用。

引言

  • 主成分分析(PCA):PCA是数据分析的基本方法,用于在高维数据中发现低维结构。它对轻微噪声的处理效率很高,但对严重噪声的数据处理效果不好。
  • 鲁棒PCA(RPCA):RPCA是PCA的扩展,能够处理严重噪声的数据。它将观察矩阵分解为低秩和稀疏成分。
  • RPCA的局限性:RPCA仅处理二维数据(矩阵),而现实世界中的数据通常具有多维结构(张量)。

问题定义

本文旨在扩展RPCA以处理张量数据,从而引入TRPCA。提出了一个模型,以从稀疏误差中恢复低秩张量。这是通过 t-product 启发的新张量范数来实现的。

TRPCA模型

TRPCA模型公式如下:

min ⁡ L , E ∥ L ∥ ∗ + λ ∥ E ∥ 1 s.t. X = L + E \min_{L,E} \|L\|_* + \lambda \|E\|_1 \quad \text{s.t.} \quad X = L + E L,EminL+λE1s.t.X=L+E

其中, ∥ L ∥ ∗ \|L\|_* L 是张量核范数,具有低秩性, ∥ E ∥ 1 \|E\|_1 E1 ℓ 1 \ell_1 1-范数用于稀疏性。

TRPCA 代码分析与实验结果

小结

TRPCA 模型公式如下:

min ⁡ L , S ∥ L ∥ ∗ + λ ∥ S ∥ 1 s.t. X = L + S \min_{L,S} \|L\|_* + \lambda \|S\|_1 \quad \text{s.t.} \quad X = L + S L,SminL+λS1s.t.X=L+S

其中, ∥ L ∥ ∗ \|L\|_* L 是张量核范数,具有低秩性 ∥ S ∥ 1 \|S\|_1 S1 ℓ 1 \ell_1 1-范数用于稀疏性

  • prox_l1:function x = prox_l1(b,lambda)

    min ⁡ x λ ∥ x ∥ 1 + 0.5 ∥ x − b ∥ 2 2 \min_x \lambda \|x\|_1 + 0.5 \|x - b\|_2^2 xminλx1+0.5∥xb22

    输入:

    b:有待迭代优化的稀疏矩阵 S S S

    lambda:控制稀疏性的程度【越大越稀疏】

    输出:x

    • xb 之间的差异小。
    • x 尽可能稀疏。

    **作用:**使 x x x 尽量稀疏 又 尽量接近 b b b

  • prox_tnn:function [X,tnn,trank] = prox_tnn(Y,rho)

    min ⁡ X r h o ∥ X ∥ ∗ + 0.5 ∥ X − Y ∥ F 2 \min_{X} rho \|X\|_*+0.5\|X-Y\|_F^2 XminrhoX+0.5∥XYF2

    输入:

    Y:有待迭代优化的低秩张量 L L L

    rho:用于进行软阈值处理,使 Y 更具有低秩性,rho 的值越大,就越低秩

    输出:

    X核范数尽量小,且尽量与 Y Y Y 近似的张量

    tnn X X X 的核范数

    trank X X X 的张量的管秩(tubal rank)

    作用:通过对三维张量进行分片处理软阈值化,计算张量的核范数近似解管秩

  • psnr:function psnr = PSNR(Xfull,Xrecover,maxP)

    PSNR = 10 log ⁡ 10 ( maxP 2 MSE ) \text{PSNR} = 10 \log_{10} \left( \frac{{\text{maxP}^2}}{\text{MSE}} \right) PSNR=10log10(MSEmaxP2)

    输入:

    Xfull原始图像或数据张量。

    Xrecover恢复的图像或数据张量。

    maxP:图像或数据的最大可能值,确保所有恢复值都在合理范围内

    输出:

    PSNR:峰值信噪比,值越大说明恢复的效果越好

    **作用:**衡量重建图像与原始图像的差异

  • tprod:function C = tprod(A,B)

    输入:

    A、B:高维张量

    输出:

    C:张量做乘法运算的结果张量

    **作用:**实现了高维张量的乘法

  • trpca_tnn:function [L,S,obj,err,iter] = trpca_tnn(X,lambda,opts)

    min ⁡ L , S ∥ L ∥ ∗ + λ ∥ S ∥ 1 , s.t. X = L + S \min_{L, S} \|L\|_* + \lambda \|S\|_1, \quad \text{s.t.} \quad X = L + S L,SminL+λS1,s.t.X=L+S

    输入:

    X:待分解的三维张量。

    lambda:控制低秩部分和稀疏部分的权重。

    opts:包含多个选项的结构体,用于控制算法的参数。

    输出:

    L:分解得到的低秩张量。

    S:分解得到的稀疏张量。

    obj:目标函数值。

    err:残差。

    iter:迭代次数。

    **作用:**把一个张量分解成两个部分:一个是低秩张量 L L L(数据中有规律的部分),另一个是稀疏张量 S S S(数据中的异常或噪声)

  • tubalrank:function trank = tubalrank(X,tol)

    输入:

    X:待计算管秩的张量

    tol:容差值, 决定了奇异值向量 s 中哪些奇异值被认为是重要的,哪些可以忽略

    输出:

    trank:张量的管秩值

    **作用:**计算输入张量的管秩大小

Functions

prox_l1

function x = prox_l1(b,lambda)

% The proximal operator of the l1 norm
% 
% min_x lambda*||x||_1+0.5*||x-b||_2^2

x = max(0,b-lambda)+min(0,b+lambda);
% The proximal operator of the l1 norm
%
% min_x lambda*||x||_1+0.5*||x-b||_2^2

这部分注释解释了函数的作用:计算 L 1 L1 L1 范数的近似算子。其目标是最小化以下目标函数:

min ⁡ x λ ∥ x ∥ 1 + 0.5 ∥ x − b ∥ 2 2 \min_x \lambda \|x\|_1 + 0.5 \|x - b\|_2^2 xminλx1+0.5∥xb22

其中, ∥ x ∥ 1 \|x\|_1 x1 x x x L 1 L1 L1 范数, ∥ x − b ∥ 2 \|x - b\|_2 xb2 x x x b b b L 2 L2 L2 范数。

直观理解近似算子

可以把近似算子想象成一个“调节器”,它帮助我们在数据拟合和稀疏性之间找到一个平衡。

假设你有一组数据 b,你希望找到一个新的数据 x,使得:

  1. xb 之间的差异尽量小。
  2. x 的很多值是零(即稀疏)

近似算子就是用来实现这个目标的工具。它会根据参数 lambda 的值来调节 x

  • 如果 lambda,它会更强调稀疏性,更多的值会被“推向”零。
  • 如果 lambda,它会更强调 xb相似性,解 x 会更接近原始数据 b

一个具体的例子

依据代码:

x = max(0,b-lambda)+min(0,b+lambda);

可以由此推导出:

b > λ : x = ( b − λ ) + 0 = b − λ ; (1) b> \lambda:x=(b- \lambda)+0=b-\lambda; \tag{1} b>λ:x=(bλ)+0=bλ;(1)

b < λ : x = 0 + ( b + λ ) = b + λ ; (2) b< \lambda:x=0+(b+ \lambda)=b+ \lambda; \tag{2} b<λ:x=0+(b+λ)=b+λ;(2)

b = λ : x = 0 ; (3) b=\lambda:x=0;\tag{3} b=λ:x=0;(3)

假设你有一个向量 b = [3, -1, 0.5, 2],并且 lambda = 1。你希望找到一个新的向量 x,使得:

  • xb 之间的差异小。
  • x 尽可能稀疏。

使用近似算子计算时,会对 b 中的每个元素进行如下操作:

  • 如果某个元素大于 lambda,减去 lambda(推向零)。
  • 如果某个元素小于 lambda,加上 lambda(推向零)。
  • 如果某个元素在 lambdalambda 之间,直接置为零。

对于 b = [3, -1, 0.5, 2]lambda = 1

  • 对于 3,减去 1 得到 2。
  • 对于 -1,加上 1 得到 0。
  • 对于 0.5,在 -1 和 1 之间,直接置为 0。
  • 对于 2,减去 1 得到 1。

因此,结果 x 将是 [2, 0, 0, 1]

通过近似算子得到的 x 有以下几个特点:

1. 稀疏性(Sparsity)

x 的许多元素变为零。在我们的示例中,原始向量 b = [3, -1, 0.5, 2] 通过近似算子得到的 x = [2, 0, 0, 1],其中有两个元素被置为零。稀疏性是L1正则化的一个重要特性,能使得结果更简单,更容易解释。

2. 接近原始数据(Closeness to Original Data)

尽管 x 中有很多元素变为零,剩下的非零元素尽可能接近原始数据 b。在我们的例子中,原始的非零元素 3 和 2 被减去 lambda 之后变为 2 和 1,这使得新的 x 尽可能地保持与 b 的相似性。

3. 控制影响(Controlled Influence of Lambda)

通过调整参数 lambda 的值,可以控制稀疏性的程度:

  • 较大的 lambda:会使更多的元素变为零,稀疏性更强。
  • 较小的 lambda:会使 x 更接近 b,稀疏性减弱。

4. 平衡(Balance)

x 是一个在稀疏性和数据拟合之间找到平衡的结果。即使 x 变得稀疏了,它仍然尽量保持和原始数据 b 的差异最小。

示例总结

总结一下,通过近似算子得到的 x 具有以下特点:

  • 更加稀疏:非零元素更少。
  • 接近原始数据:非零元素尽可能接近原始数据的值。
  • 可以通过调整 lambda 来控制稀疏性和数据拟合的平衡。

prox_tnn

function [X,tnn,trank] = prox_tnn(Y,rho)

% The proximal operator of the tensor nuclear norm of a 3 way tensor
%
% min_X rho*||X||_*+0.5*||X-Y||_F^2
%
% Y     -    n1*n2*n3 tensor
%
% X     -    n1*n2*n3 tensor
% tnn   -    tensor nuclear norm of X
% trank -    tensor tubal rank of X

[n1,n2,n3] = size(Y);
X = zeros(n1,n2,n3);
Y = fft(Y,[],3);
tnn = 0;
trank = 0;
        
% first frontal slice
[U,S,V] = svd(Y(:,:,1),'econ');
S = diag(S);
r = length(find(S>rho));
if r>=1
    S = S(1:r)-rho;
    X(:,:,1) = U(:,1:r)*diag(S)*V(:,1:r)';
    tnn = tnn+sum(S);
    trank = max(trank,r);
end
% i=2,...,halfn3
halfn3 = round(n3/2);
for i = 2 : halfn3
    [U,S,V] = svd(Y(:,:,i),'econ');
    S = diag(S);
    r = length(find(S>rho));
    if r>=1
        S = S(1:r)-rho;
        X(:,:,i) = U(:,1:r)*diag(S)*V(:,1:r)';
        tnn = tnn+sum(S)*2;
        trank = max(trank,r);
    end
    X(:,:,n3+2-i) = conj(X(:,:,i));
end

% if n3 is even
if mod(n3,2) == 0
    i = halfn3+1;
    [U,S,V] = svd(Y(:,:,i),'econ');
    S = diag(S);
    r = length(find(S>rho));
    if r>=1
        S = S(1:r)-rho;
        X(:,:,i) = U(:,1:r)*diag(S)*V(:,1:r)';
        tnn = tnn+sum(S);
        trank = max(trank,r);
    end
end
tnn = tnn/n3;
X = ifft(X,[],3);

这段代码实现了一个三维张量(tensor)的核范数(nuclear norm)的近似算子。

function [X,tnn,trank] = prox_tnn(Y,rho)

% The proximal operator of the tensor nuclear norm of a 3 way tensor
%
% min_X rho*||X||_*+0.5*||X-Y||_F^2
%
% Y     -    n1*n2*n3 tensor
%
% X     -    n1*n2*n3 tensor
% tnn   -    tensor nuclear norm of X
% trank -    tensor tubal rank of X

min ⁡ X r h o ∥ X ∥ ∗ + 0.5 ∥ X − Y ∥ F 2 \min_{X} rho \|X\|_*+0.5\|X-Y\|_F^2 XminrhoX+0.5∥XYF2

这部分定义了函数 prox_tnn,其作用是计算一个三维张量的核范数的近似算子。

  • 输入参数 Y 是一个 n1*n2*n3三维张量【原始张量】
  • rho 是一个正则化参数
  • 函数返回近似的张量 X核范数 tnn,以及张量的管秩(tubal rank)trank

初始化部分

[n1,n2,n3] = size(Y); % 获取张量 Y 的尺寸
X = zeros(n1,n2,n3); % 将 X 初始化为一个全零张量
Y = fft(Y,[],3); % 对 Y 进行快速傅里叶变换(FFT)【具有对称性】

% 初始化核范数 tnn 和管秩 trank 为零
tnn = 0;
trank = 0;

处理第一个前切片

% 通过对 Y(:,:,1) 进行奇异值分解(SVD),得到 U、S 和 V
[U,S,V] = svd(Y(:,:,1),'econ'); 

% 将矩阵 S 转换为向量 S【提取这个对角矩阵 S 的对角线元素,并将它们放到一个列向量中】
S = diag(S); 

% 计算一个标量 r,它表示矩阵 S 中大于阈值 rho 的奇异值的数量。
r = length(find(S>rho));

if r>=1
    S = S(1:r)-rho; % 对奇异值**进行软阈值处理**
    X(:,:,1) = U(:,1:r)*diag(S)*V(:,1:r)'; % 重构第一个前切片
    tnn = tnn+sum(S); % 计算并累加当前处理过的奇异值的总和,更新核范数
    trank = max(trank,r); % 更新管秩
end
  • ‘econ’ 的含义:

    【 "economy-size”】表示进行经济型奇异值分解只返回非零奇异值对应的奇异向量,节省内存和计算资源。

  • S = diag(S); 的作用:

    将对角矩阵 S 转换为向量 S 的原因是为了简化对奇异值的处理。在许多算法中,我们只需要操作这些奇异值,例如进行阈值处理或计算范数。将 S 转换为向量后,可以更方便地进行这些操作。

  • S = S(1:r) - rho 的作用:

    • S(1:r) 提取向量 S 中前 r 个元素【1 到 r】,这些元素是大于 rho 的奇异值。
    • S(1:r) - rho 对这些奇异值进行软阈值处理,即每个奇异值减去 rho。结果仍然是一个向量。
  • X(:,:,1) = U(:,1:r) * diag(S) * V(:,1:r) 的作用:

    • diag(S) 将处理后的奇异值向量 S 转换为对角矩阵。
    • U(:,1:r) 提取 U 矩阵的前 r 列,V(:,1:r) 提取 V 矩阵的前 r 列。
    • U(:,1:r) * diag(S) * V(:,1:r)' 使用截断的奇异值和奇异向量重构矩阵 X(:,:,1)

通过这段代码,函数 prox_tnn 实现了以下功能:

  • 软阈值处理:通过减去 rho 来实现奇异值的软阈值处理,使得结果具有一定的稀疏性。进行去噪、降维等。
  • 矩阵重构:使用处理过的奇异值和奇异向量重构矩阵 X 的第一个切片 X(:,:,1)
  • 核范数计算:累加所有处理过的奇异值,计算并更新张量的核范数 tnn
  • 管秩计算:更新张量的管秩 trank,用于表示张量的秩。

处理其他前切片

% i=2,...,halfn3
halfn3 = round(n3/2); % 傅里叶变换具有对称性 | round 四舍五入
for i = 2 : halfn3
    [U,S,V] = svd(Y(:,:,i),'econ');
    S = diag(S);
    r = length(find(S>rho));
    if r>=1
        S = S(1:r)-rho;
        X(:,:,i) = U(:,1:r)*diag(S)*V(:,1:r)';
        tnn = tnn+sum(S)*2;
        trank = max(trank,r);
    end
    X(:,:,n3+2-i) = conj(X(:,:,i)); % 共轭处理
end
  • 为什么第一个切片的处理要和后面的分开?

    1. 初始化 tnntrank:
      在处理第一个切片时,tnntrank 还没有被初始化。通过处理第一个切片,代码初始化了这两个变量。在后面的切片处理中,tnntrank 的更新都是基于这些初始值进行的。
    2. 对称性和效率:
      第一个切片没有对应的共轭对称性部分。因此,第一个切片独立处理,确保初始结果的准确性,并为后续的对称性处理提供基础。
  • 为什么第一个切片没有对应的共轭对称性部分?

    1. 第一个切片的独特性

    在处理三维张量时,通常第三维度代表频率。当应用傅里叶变换时,第一个切片(即零频率分量)通常是实数,因为它代表的是信号的直流成分或平均值。零频率分量不需要与任何其他分量成对或对称,因为它本身是一个实值,不涉及共轭对称性。

    2. 傅里叶变换的对称性

    在傅里叶变换中,对于一个实值信号,频域的正频率和负频率是共轭对称的。但零频率分量是一个例外,因为它没有对应的负频率分量。这个性质使得零频率分量在处理时可以独立进行,而不需要共轭对称。

处理偶数长度的第三维度

% if n3 is even 特殊情况处理
if mod(n3,2) == 0
    i = halfn3+1;
    
    % 单独处理中间的切片
    [U,S,V] = svd(Y(:,:,i),'econ');
    S = diag(S);
    r = length(find(S>rho));
    if r>=1
        S = S(1:r)-rho;
        X(:,:,i) = U(:,1:r)*diag(S)*V(:,1:r)';
        tnn = tnn+sum(S);
        trank = max(trank,r);
    end
end

tnn = tnn/n3; % 将核范数 tnn 归一化
X = ifft(X,[],3); % 对结果 X 进行逆快速傅里叶变换(IFFT)

总结

这段代码的主要作用是通过对三维张量进行分片处理软阈值化,计算张量的核范数近似解管秩。这对于稀疏表示、降维等问题非常有用。

PSNR

function psnr = PSNR(Xfull,Xrecover,maxP)

Xrecover = max(0,Xrecover);
Xrecover = min(maxP,Xrecover);
[n1,n2,n3] = size(Xrecover);
MSE = norm(Xfull(:)-Xrecover(:))^2/(n1*n2*n3);
psnr = 10*log10(maxP^2/MSE);

计算了两个三维张量 XfullXrecover 之间的峰值信噪比(PSNR)。PSNR 是衡量图像重建质量的重要指标之一,常用于图像处理领域。它的单位是分贝(dB),值越高表示重建质量越好。

function psnr = PSNR(Xfull, Xrecover, maxP)
  • Xfull原始图像或数据张量。
  • Xrecover重建的图像或数据张量。
  • maxP:图像或数据的最大可能值
Xrecover = max(0, Xrecover);
Xrecover = min(maxP, Xrecover);

这两行代码**将 Xrecover 限制在 [0, maxP] 范围内,确保所有恢复值都在合理范围内。**具体步骤:

  • max(0, Xrecover):将 Xrecover 中小于 0 的值替换为 0。
  • min(maxP, Xrecover):将 Xrecover 中大于 maxP 的值替换为 maxP
[n1, n2, n3] = size(Xrecover);

获取 Xrecover 的尺寸,将其大小分配给 n1n2n3,分别对应三维张量的各个维度。

MSE = norm(Xfull(:)-Xrecover(:))^2/(n1*n2*n3);

计算均方误差(MSE),步骤如下: MSE = 1 N ∑ i = 1 N ( y i − y ^ i ) 2 \text{MSE} = \frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2 MSE=N1i=1N(yiy^i)2

  1. Xfull(:)Xrecover(:) 将三维张量展平成一维向量。
  2. Xfull(:) - Xrecover(:) 计算两个向量的差值。
  3. norm(..., 2) 计算差值向量的 L 2 L2 L2 范数,即欧几里得范数。
  4. norm(..., 2)^2 计算范数的平方,即差值向量元素平方和。
  5. 除以 n1*n2*n3 得到均方误差。
psnr = 10 * log10(maxP^2 / MSE);

计算 PSNR:

  1. maxP^2 是信号的最大值平方。
  2. maxP^2 / MSE 是信噪比(SNR)。
  3. 10 * log10(...) 将 SNR 转换为分贝(dB)。

PSNR 计算公式

PSNR 的公式为:

PSNR = 10 log ⁡ 10 ( maxP 2 MSE ) \text{PSNR} = 10 \log_{10} \left( \frac{{\text{maxP}^2}}{\text{MSE}} \right) PSNR=10log10(MSEmaxP2)

结论

这段代码通过将重建图像限制在合理范围内,计算均方误差(MSE),然后根据 PSNR 公式计算出峰值信噪比,衡量重建图像与原始图像的差异。

tprod

function C = tprod(A,B)

% Tensor-tensor product of two 3 way tensors: C = A*B
% A - n1*n2*n3 tensor
% B - n2*l*n3  tensor
% C - n1*l*n3  tensor

[n1,n2,n3] = size(A);
[m1,m2,m3] = size(B);

if n2 ~= m1 || n3 ~= m3 
    error('Inner tensor dimensions must agree.');
end

A = fft(A,[],3);
B = fft(B,[],3);
C = zeros(n1,m2,n3);

% first frontal slice
C(:,:,1) = A(:,:,1)*B(:,:,1);
% i=2,...,halfn3
halfn3 = round(n3/2);
for i = 2 : halfn3
    C(:,:,i) = A(:,:,i)*B(:,:,i);
    C(:,:,n3+2-i) = conj(C(:,:,i));
end

% if n3 is even
if mod(n3,2) == 0
    i = halfn3+1;
    C(:,:,i) = A(:,:,i)*B(:,:,i);
end
C = ifft(C,[],3);

这段代码实现了两个三维张量 A \mathcal{A} A B \mathcal{B} B 的张量乘积得到结果张量 C \mathcal{C} C 。具体来说,这是利用傅里叶变换在频域进行乘法运算,然后再逆傅里叶变换回时域。

function C = tprod(A, B)

函数 tprod 计算两个三维张量 A 和 B 的张量乘积,结果为 C 。

[n1, n2, n3] = size(A);
[m1, m2, m3] = size(B);

获取输入张量 A 和 B 的尺寸。

if n2 ~= m1 || n3 ~= m3
    error('Inner tensor dimensions must agree.');
end

**检查张量维度是否匹配【做张量乘法需要满足的要求】。**如果 A 的第二维度不等于 B 的第一维度,或者 A 的第三维度不等于 B 的第三维度,则抛出错误。

A = fft(A, [], 3);
B = fft(B, [], 3);
C = zeros(n1, m2, n3); 

对张量 A 和 B 的第三维度进行傅里叶变换,将结果存储在 A 和 B 中。然后,初始化结果张量 C 。

% first frontal slice
C(:,:,1) = A(:,:,1) * B(:,:,1);

计算第一个前视切片的乘积 C(:,:,1) = A(:,:,1) * B(:,:,1) 。

% i=2,...,halfn3
halfn3 = round(n3 / 2);
for i = 2 : halfn3
    C(:,:,i) = A(:,:,i) * B(:,:,i);
    C(:,:,n3+2-i) = conj(C(:,:,i));
end

计算从第 2 2 2 halfn3 \text{halfn3} halfn3 的切片乘积,并利用共轭对称性计算对应的对称切片:

  • C ( : , : , i ) = A ( : , : , i ) ∗ B ( : , : , i ) C(:,:,i) = A(:,:,i) * B(:,:,i) C(:,:,i)=A(:,:,i)B(:,:,i)
  • C ( : , : , n 3 + 2 − i ) = conj ( C ( : , : , i ) ) C(:,:,n3+2-i) = \text{conj}(C(:,:,i)) C(:,:,n3+2i)=conj(C(:,:,i)) 确保结果张量 C C C 的共轭对称性。
% if n3 is even
if mod(n3, 2) == 0
    i = halfn3 + 1;
    C(:,:,i) = A(:,:,i) * B(:,:,i);
end

如果 n 3 n3 n3 是偶数,单独处理中间切片(对应于奈奎斯特频率),因为它没有对应的对称切片。

C = ifft(C, [], 3);

对结果张量 C 进行逆傅里叶变换,将其从频域转换回时域。

总结

这段代码通过以下步骤实现张量乘积:

  1. 对输入张量的第三维度进行傅里叶变换。
  2. 在频域中逐切片进行矩阵乘积。
  3. 利用共轭对称性简化计算。
  4. 对结果进行逆傅里叶变换,得到最终结果。

trpca_tnn

function [L,S,obj,err,iter] = trpca_tnn(X,lambda,opts)

% Solve the Tensor Robust Principal Component Analysis based on Tensor Nuclear Norm problem by ADMM
%
% min_{L,S} ||L||_*+lambda*||S||_1, s.t. X=L+S
%
% ---------------------------------------------
% Input:
%       X       -    d1*d2*d3 tensor
%       lambda  -    >0, parameter
%       opts    -    Structure value in Matlab. The fields are
%           opts.tol        -   termination tolerance
%           opts.max_iter   -   maximum number of iterations
%           opts.mu         -   stepsize for dual variable updating in ADMM
%           opts.max_mu     -   maximum stepsize
%           opts.rho        -   rho>=1, ratio used to increase mu
%           opts.DEBUG      -   0 or 1
%
% Output:
%       L       -    d1*d2*d3 tensor
%       S       -    d1*d2*d3 tensor
%       obj     -    objective function value
%       err     -    residual 
%       iter    -    number of iterations

tol = 1e-8; 
max_iter = 500;
rho = 1.1;
mu = 1e-4;
max_mu = 1e10;
DEBUG = 0;

if ~exist('opts', 'var')
    opts = [];
end    
if isfield(opts, 'tol');         tol = opts.tol;              end
if isfield(opts, 'max_iter');    max_iter = opts.max_iter;    end
if isfield(opts, 'rho');         rho = opts.rho;              end
if isfield(opts, 'mu');          mu = opts.mu;                end
if isfield(opts, 'max_mu');      max_mu = opts.max_mu;        end
if isfield(opts, 'DEBUG');       DEBUG = opts.DEBUG;          end

dim = size(X);
L = zeros(dim);
S = L;
Y = L;

iter = 0;
for iter = 1 : max_iter
    Lk = L;
    Sk = S;
    % update L
    [L,tnnL] = prox_tnn(-S+X-Y/mu,1/mu);
    % update S
    S = prox_l1(-L+X-Y/mu,lambda/mu);
  
    dY = L+S-X;
    chgL = max(abs(Lk(:)-L(:)));
    chgS = max(abs(Sk(:)-S(:)));
    chg = max([ chgL chgS max(abs(dY(:))) ]);
    if DEBUG
        if iter == 1 || mod(iter, 10) == 0
            obj = tnnL+lambda*norm(S(:),1);
            err = norm(dY(:));
            disp(['iter=' num2str(iter) ', mu=' num2str(mu) ...
                    ', obj=' num2str(obj) ', err=' num2str(err)]); 
        end
    end
    
    if chg < tol
        break;
    end 
    Y = Y + mu*dY;
    mu = min(rho*mu,max_mu);    
end
obj = tnnL+lambda*norm(S(:),1);
err = norm(dY(:));

这段代码实现了基于张量核范数的张量鲁棒主成分分析(Tensor Robust Principal Component Analysis, TRPCA)。具体地,它通过交替方向乘子法(Alternating Direction Method of Multipliers, ADMM)来解决以下优化问题:

min ⁡ L , S ∥ L ∥ ∗ + λ ∥ S ∥ 1 , s.t. X = L + S \min_{L, S} \|L\|_* + \lambda \|S\|_1, \quad \text{s.t.} \quad X = L + S L,SminL+λS1,s.t.X=L+S

其中, L L L 是低秩张量, S S S 是稀疏张量, ∥ L ∥ ∗ \|L\|_* L 是张量核范数, ∥ S ∥ 1 \|S\|_1 S1 S S S 的元素的 ℓ 1 \ell_1 1 范数, λ \lambda λ 是权衡参数。

我们要解决一个问题:把一个张量分解成两个部分:一个是低秩张量 L L L(数据中有规律的部分),另一个是稀疏张量 S S S(数据中的异常或噪声)。这个过程叫做张量鲁棒主成分分析(TRPCA)。

目标

我们的目标是找到 L L L S S S,使得 X = L + S X = L + S X=L+S L L L低秩的, S S S稀疏的。为了达到这个目标,我们要最小化两个部分的总和:

  • L L L 的秩(用核范数【秩越小,核范数越小】来表示)。
  • S S S 的元素总数(用 ℓ 1 \ell_1 1 范数【越稀疏, ℓ 1 \ell_1 1 范数越小】来表示)。

初始化

这些选项 (opts) 用于控制 ADMM 算法的各种参数,以便灵活地调整算法的性能和收敛行为。以下是每个选项的详细解释:

opts.tol - Termination Tolerance

  • 描述: 终止容差,表示当迭代过程中变量的变化量小于这个值时,算法认为已经收敛,可以终止。
  • 作用: 控制算法的收敛精度。较小的 tol 会导致更高的精度,但可能需要更多的迭代次数;较大的 tol 会加快收敛,但可能导致精度下降。
  • 默认值: 1e-8

opts.max_iter - Maximum Number of Iterations

  • 描述: 最大迭代次数,表示算法允许的最大迭代步数。
  • 作用: 防止算法陷入无限循环。如果在达到最大迭代次数之前没有收敛,算法会强制终止。
  • 默认值: 500

opts.mu - Stepsize for Dual Variable Updating in ADMM

  • 描述: ADMM 中更新对偶变量时的步长。
  • 作用: 控制对偶变量 Y 的更新速度。较小的 mu 可能导致收敛较慢,较大的 mu 可能导致不稳定。
  • 默认值: 1e-4

opts.max_mu - Maximum Stepsize

  • 描述: mu 的最大值。
  • 作用: 限制步长的最大值,防止步长变得过大导致算法不稳定。
  • 默认值: 1e10

opts.rho - Ratio Used to Increase mu

  • 描述: 用于增加 mu 的比率,rho >= 1
  • 作用: 每次迭代后,将 mu 乘以 rho 来逐步增加步长。这有助于加快收敛速度,但必须谨慎选择,以防止 mu 增长过快导致不稳定。
  • 默认值: 1.1

opts.DEBUG - Debugging Flag

  • 描述: 调试标志,取值为 01
  • 作用: 控制是否在每次迭代中打印调试信息。如果设置为 1,则会在每次迭代中输出当前的目标函数值和误差等信息,有助于监控算法的进展和诊断问题。
  • 默认值: 0

检查 opts 是否提供这些参数,并进行覆盖:

% 是检查变量 opts 是否存在,如果不存在,则初始化为一个空结构体 []
if ~exist('opts', 'var')
    opts = [];
end

% 检查 opts 结构体中是否包含指定字段 tol,如果包含,则将 opts.tol 的值赋给变量 tol
if isfield(opts, 'tol');         tol = opts.tol;              end
if isfield(opts, 'max_iter');    max_iter = opts.max_iter;    end
if isfield(opts, 'rho');         rho = opts.rho;              end
if isfield(opts, 'mu');          mu = opts.mu;                end
if isfield(opts, 'max_mu');      max_mu = opts.max_mu;        end
if isfield(opts, 'DEBUG');       DEBUG = opts.DEBUG;          end

初始化变量

初始化低秩张量 L、稀疏张量 S拉格朗日乘子 Y

dim = size(X);
L = zeros(dim);
S = L;
Y = L;

迭代更新

使用 ADMM 迭代方法更新 LSY

iter = 0;
for iter = 1 : max_iter
		% 保存当前的 L 和 S 以便稍后计算变化量
    Lk = L;
    Sk = S;
    
    
    %% 根据 ADMM 的迭代格式开始对 L 和 S 进行迭代优化
    
    % 更新 L
    % [X,tnn,trank] = prox_tnn(Y,rho):求 L 的核范数的近似算子,这里想让 L 尽量低秩
    [L, tnnL] = prox_tnn(-S + X - Y/mu, 1/mu); 
    
    % 更新 S
    % x = prox_l1(b,lambda):求向量 b 的l1范数的近似算子,这里想让 S 尽量稀疏
    S = prox_l1(-L + X - Y/mu, lambda/mu);

    dY = L + S - X; % 计算当前残差,即 L 和 S 的和与原始数据 X 之间的差
    
    % chgL 和 chgS 计算 L 和 S 的变化量
    chgL = max(abs(Lk(:) - L(:)));
    chgS = max(abs(Sk(:) - S(:)));
    
    % chg 计算所有变化量中的最大值
    chg = max([chgL, chgS, max(abs(dY(:)))]);

		% 每 10 次迭代输出一些调试信息,包括当前的目标函数值和误差。
    if DEBUG
        if iter == 1 || mod(iter, 10) == 0
            obj = tnnL + lambda * norm(S(:), 1);  % 用于调试和收敛判断
            err = norm(dY(:));
            
            % 示例输出:iter=10, mu=0.1, obj=0.005, err=0.002 
            disp(['iter=' num2str(iter) ', mu=' num2str(mu) ...
                  ', obj=' num2str(obj) ', err=' num2str(err)]);
        end
    end
		
		% 我们检查最大变化量 chg 是否小于设定的容差 tol。如果小于,则认为算法收敛,停止迭代
    if chg < tol
        break;
    end
		
		% 调整拉格朗日乘子 Y,以逐步逼近优化问题的解
    Y = Y + mu * dY;
    mu = min(rho * mu, max_mu);
end

这段代码实现了基于交替方向乘子法(ADMM)的张量鲁棒主成分分析(Tensor Robust Principal Component Analysis, TRPCA)。它通过迭代更新低秩张量 L L L、稀疏张量 S S S 和拉格朗日乘子 Y Y Y,求解以下优化问题:

min ⁡ L , S ∥ L ∥ ∗ + λ ∥ S ∥ 1 s.t. X = L + S \min_{L, S} \|L\|_* + \lambda \|S\|_1 \quad \text{s.t.} \quad X = L + S L,SminL+λS1s.t.X=L+S

  • 为什么 -S + X - Y/mu 是更新 L L L S S S 时的输入数据

    ADMM 的思路

    ADMM(交替方向乘子法)的思路是把复杂的问题分解成几个简单的小步骤,每一步都相对容易解决。我们每次只处理一个变量(L 或 S),然后逐步逼近最优解

    增广拉格朗日函数

    为了求解这个问题,我们构造一个包含 L 和 S 的函数。这个函数的形式有点复杂,但你可以把它看作是一个"惩罚"函数,用来确保 L + S 逼近 X。我们引入一个辅助变量 Y 来帮助这个过程。

    更新步骤的来源

    ADMM 的更新步骤包括以下几部分:

    1. 更新 L:找到新的低秩部分。
    2. 更新 S:找到新的稀疏部分。
    3. 更新 Y:调整辅助变量,确保 L + S 逼近 X。

    现在,重点是更新 L 和 S 的步骤。

    更新 L 的输入数据

    X = L + S ⇒ L = − S + X X = L + S \Rightarrow L = -S+X X=L+SL=S+X

    当我们更新 L 时,我们希望新的 L 能够尽可能满足 L + S ≈ X L + S \approx X L+SX 。为了达到这个目标,我们构造一个临时变量 -S + X - Y/mu,这个变量包含了当前的 S 、 X S、X SX Y Y Y

    临时变量 = − S + X − Y μ \text{临时变量} = -S + X - \frac{Y}{\mu} 临时变量=S+XμY

    为什么要这样构造呢?这实际上是为了平衡两个方面:

    1. 逼近原始数据 X:我们希望 L + S L + S L+S 尽可能接近 X X X
    2. 调整 Y 的影响 Y Y Y 是一个帮助我们逼近 X X X 的变量,通过除以 μ \mu μ 来调节其影响力。

    这个临时变量综合了当前的稀疏部分 S S S、原始数据 X X X 和辅助变量 Y Y Y 的信息,帮助我们更好地更新 L L L

    更新 S 的输入数据

    X = L + S ⇒ S = − L + X X = L + S \Rightarrow S = -L+X X=L+SS=L+X

    更新 S S S 的道理是类似的。我们希望新的 S S S 能够尽可能满足 L + S ≈ X L + S \approx X L+SX。为了更新 S,我们构造另一个临时变量 -L + X - Y/mu

    临时变量 = − L + X − Y μ \text{临时变量} = -L + X - \frac{Y}{\mu} 临时变量=L+XμY

    这个变量包含了当前的 L 、 X L、X LX Y Y Y

    1. 逼近原始数据 X:我们希望 L + S L + S L+S 尽可能接近 X X X
    2. 调整 Y 的影响:同样,通过除以 μ \mu μ 来调节 Y Y Y 的影响力。

    总结

    S + X - Y/muL + X - Y/mu 是更新 L 和 S 时的输入数据,因为它们综合了当前状态下的稀疏部分 S、低秩部分 L、原始数据 X 和辅助变量 Y 的信息。这些临时变量帮助我们在每一步中更好地逼近 L + S = X L + S = X L+S=X,从而逐步逼近最优解。

  • 解释一下:obj = tnnL + lambda * norm(S(:), 1);

    目标函数的组成部分

    在稀疏低秩矩阵分解问题中,目标函数通常由两个主要部分组成:

    1. 低秩项:希望矩阵 L 尽量低秩,这里用核范数 tnnL 表示。
    2. 稀疏项:希望矩阵 S 尽量稀疏,这里用 S L 1 L1 L1 范数表示,即 norm(S(:), 1)

    具体部分解释

    1. tnnL:这是通过 prox_tnn 函数更新 L 时计算得到的 L 的核范数(即 tnnL)。
    2. lambda * norm(S(:), 1):这是矩阵 S L 1 L1 L1 范数乘以正则化参数 lambda

    计算目标函数值的目的

    计算目标函数值的目的是**为了衡量当前迭代状态下的解 LS 是否满足优化目标。**目标函数 obj 的值用于评估以下方面:

    • 当前解 L 的低秩程度(通过核范数 tnnL)。
    • 当前解 S 的稀疏程度(通过 L1 范数 norm(S(:), 1))。
    • 两者之间的权衡(由正则化参数 lambda 控制)。

    在迭代过程中的作用

    在迭代过程中,每隔若干次(比如每 10 次)迭代,算法会输出当前的目标函数值和误差,以便调试和监控收敛过程。这有助于:

    • 了解算法的收敛速度。
    • 确认算法是否逐步接近最优解。
    • 评估参数选择(如 lambdamu)的效果。
  • 解释一下:err = norm(dY(:));

    具体解释

    1. dY(:):这是将矩阵 dY 展开为一个列向量。MATLAB 中的 (:) 操作符可以将任何矩阵转换为一个列向量。
    2. norm(dY(:)):这是计算 dY 的向量范数。默认情况下,norm 函数计算的是 L2 范数,即欧几里得范数。这一范数是所有元素平方和的平方根。具体来说,它衡量了重建误差的整体大小。

    计算误差的目的

    计算误差 err 的目的是评估当前迭代步中重建矩阵 L + S 与原始矩阵 X 的差距。这有助于判断迭代过程中的收敛情况。

  • 解释一下:mu = min(rho*mu,max_mu);

    具体解释

    1. rho:这是一个用来增加步长参数 mu 的比例因子,通常取值 rho >= 1。在每次迭代中,通过将 mu 乘以 rho 来增加 mu 的值,从而加快收敛速度。
    2. mu:这是当前的步长参数,用于更新双重变量 Y。在每次迭代中,mu 会根据 rho 的值进行调整。
    3. max_mu:这是步长参数 mu 的最大允许值。为了避免 mu 增长得过快导致不稳定,设置了一个上限 max_mu
    4. min(rho * mu, max_mu):这一部分代码的作用是计算 rho * mumax_mu 中的较小值。这样确保 mu 在每次迭代中增加,但不会超过 max_mu。具体的计算步骤如下:
      • 首先计算 rho * mu,这是根据比例因子 rho 增加后的新步长参数。
      • 然后比较 rho * mumax_mu 的大小,选择其中的较小值作为新的 mu

    作用和目的

    1. 自适应调整步长:通过每次迭代增加 mu 的值,可以加快算法的收敛速度。较大的步长可以使算法更快地逼近最优解。
    2. 防止不稳定:设置一个最大值 max_mu 可以防止步长参数 mu 增长得过快,避免算法出现不稳定或震荡的情况。
    3. 提高收敛效率:逐步增加 mu 可以使算法在初期收敛较慢时逐渐加速,最终达到快速收敛的效果。

总结

  1. 初始化:设定初始值和参数。
  2. 迭代更新
    • 更新 L:计算新的低秩矩阵。
    • 更新 S:计算新的稀疏矩阵。
    • 计算变化量和残差:判断当前结果与原始数据的差距,以及变量的变化程度。
    • 输出调试信息:可选,输出当前迭代状态。
    • 检查收敛:如果变化量小于设定值,停止迭代。
  3. 更新参数:调整拉格朗日乘子和步长。

通过这些步骤,算法逐步逼近最优解,将原始数据 X X X 分解成低秩部分 L L L 和稀疏部分 S S S

tubalrank

function trank = tubalrank(X,tol)

% The tensor tubal rank of a 3 way tensor
%
% X     -    n1*n2*n3 tensor
% trank -    tensor tubal rank of X

X = fft(X,[],3);
[n1,n2,n3] = size(X);

% 初始化一个列向量 s
s = zeros(min(n1,n2),1);

% i=1 
% 计算张量第一张切片的奇异值并累加到 `s` 中
s = s + svd(X(:,:,1),'econ');

% i=2,...,halfn3
% 对于从第二到 halfn3 的切片,计算奇异值并累加到 s 中,由于对称性,需要乘以 2
halfn3 = round(n3/2);
for i = 2 : halfn3
    s = s + svd(X(:,:,i),'econ')*2;
end

% if n3 is even
% 如果 n3 是偶数,则还需要处理第 halfn3 + 1 个切片,并累加奇异值
if mod(n3,2) == 0
    i = halfn3+1;
    s = s + svd(X(:,:,i),'econ');
end

% 将累加后的奇异值向量 s 除以 n3 **进行归一化**
s = s/n3;

% 如果没有提供 tol 参数【终止容差】,则根据最大奇异值和浮点精度计算一个默认的 tol
% nargin 是 MATLAB 内置变量,表示传递给函数的输入参数数量
% 只有一个输入参数表示调用时没有提供 tol 参数,没有提供的时候,计算并设置 tol 的默认值
if nargin==1 
   tol = max(n1,n2) * eps(max(s));
end
trank = sum(s > tol);

计算了一个 3 3 3 维张量的管状秩(tubal rank)

  • 如何理解:tol = max(n1,n2) * eps(max(s));

    • max(n1, n2):取 n1n2 的较大值,其中 n1n2 是张量 X 的前两个维度。
    • max(s):取向量 s 中的最大值,s 包含了张量 X 在傅里叶变换后每个切片的奇异值。
    • eps(max(s)):计算最大奇异值的机器精度。eps(x) 返回与 x 同数量级的浮点数精度。
    • max(n1, n2) * eps(max(s)):将机器精度乘以 max(n1, n2) 得到一个合理的容差值 tol。这个值根据张量的大小和奇异值的规模动态调整。

    容差 tol 在计算管状秩中的作用

    在计算管状秩时,容差 tol 决定了奇异值向量 s 中哪些奇异值被认为是重要的,哪些可以忽略。具体来说:

    • trank = sum(s > tol);:统计 s 中大于 tol 的奇异值数量,作为张量的管状秩。

    通过设置一个合适的 tol,可以确保管状秩计算的准确性。

demo_trpca_image

主要目的是对图像进行降噪处理,并使用张量鲁棒主成分分析方法来恢复被噪声污染的图像。

%% 添加路径和清理工作区
% 添加当前目录及其子目录到 MATLAB 的搜索路径,并清除工作区中的所有变量。
addpath(genpath(cd))
clear

%% 读取和标准化图像
pic_name = './testimg.jpg'; % pic_name 指定图像文件的路径
X = double(imread(pic_name)); % imread 读取图像并转换为双精度类型存储在变量 X 中

X = X/255; % X 被标准化为 [0, 1] 之间的值
maxP = max(abs(X(:))); % maxP 记录图像像素值的最大绝对值

%% 引入噪声
[n1,n2,n3] = size(X);
Xn = X;
rhos = 0.1 % rhos 定义噪声比例
ind = find(rand(n1*n2*n3,1) < rhos); % ind 找到满足随机值小于 rhos 的像素索引
Xn(ind) = rand(length(ind),1); % 在这些索引位置引入随机噪声

%% 设置 TRPCA 参数
opts.mu = 1e-4;
opts.tol = 1e-5;
opts.rho = 1.1;
opts.max_iter = 500;
opts.DEBUG = 1;

%% ****计算 lambda
[n1,n2,n3] = size(Xn);
lambda = 1/sqrt(max(n1,n2)*n3); % 根据图像尺寸计算正则化参数 lambda。

%% 执行 TRPCA 算法
% 恢复图像 Xhat【低秩张量】,并输出噪声矩阵 E、误差 err 和迭代次数 iter
[Xhat,E,err,iter] = trpca_tnn(Xn,lambda,opts);
 
%% 调整恢复后的图像
% 将恢复后的图像像素值限制在合理范围内
Xhat = max(Xhat,0);
Xhat = min(Xhat,maxP);

%% 计算峰值信噪比 (PSNR)
psnr = PSNR(X,Xhat,maxP)

%% 显示结果
% 使用 figure 和 subplot 显示原始图像、噪声图像和恢复后的图像
figure(1) % 创建或激活一个编号为 1 的图形窗口
subplot(1,3,1) % 将图形窗口分为 1 行 3 列,并激活第 1 个子图
imshow(X/max(X(:))) % 显示原始图像 X。在显示前,图像被 max(X(:)) 归一化到 [0, 1] 范围内
subplot(1,3,2)
imshow(Xn/max(Xn(:))) % 显示添加噪声后的图像 Xn
subplot(1,3,3)
imshow(Xhat/max(Xhat(:))) % 显示通过 TRPCA 恢复后的图像 Xhat
  • lambda 的计算依据

    1. 平衡数据尺度
      • 图像或张量的数据规模不同,合理设置 lambda 可以平衡低秩和稀疏项
      • 采用 1/sqrt(max(n1, n2) * n3) 这种方法有助于根据张量的维度自动调整 lambda,使得其值适合特定数据集。
    2. 数据规模的影响
      • 如果张量的大小较大,则需要较小的 lambda 来平衡范数项。反之,较小的张量需要较大的 lambda
      • max(n1, n2) 选择了前两个维度中的较大值,确保了 lambda 对于长宽不等的数据也能适应。
      • n3 是张量的第三维度,通常用于处理多通道图像(如 RGB 图像)或时间序列数据。
    3. 经验公式
      • 经验上,lambda = 1/sqrt(max(n1, n2) * n3) 被证明在许多实际问题中表现良好。这种公式确保了 lambda 与张量的总体规模成反比,使得不同规模的数据都能有适当的正则化力度。
  • 实验结果【以 1.testimg.jpg 为例】

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    rhos =
    
        0.1000
    
    iter 1, mu=0.0001, obj=0, err=322.3926
    iter 10, mu=0.00023579, obj=0, err=322.3926
    iter 20, mu=0.00061159, obj=176.61, err=101.8023
    iter 30, mu=0.0015863, obj=191.5663, err=93.894
    iter 40, mu=0.0041145, obj=280.2145, err=74.5437
    iter 50, mu=0.010672, obj=582.3684, err=31.2861
    iter 60, mu=0.02768, obj=719.2263, err=12.8851
    iter 70, mu=0.071795, obj=780.8715, err=4.5385
    iter 80, mu=0.18622, obj=800.6079, err=1.7585
    iter 90, mu=0.483, obj=808.3799, err=0.69129
    iter 100, mu=1.2528, obj=811.5095, err=0.25896
    iter 110, mu=3.2494, obj=812.6566, err=0.08876
    iter 120, mu=8.4281, obj=812.9967, err=0.030065
    iter 130, mu=21.8603, obj=813.0962, err=0.010111
    iter 140, mu=56.7, obj=813.1248, err=0.003305
    iter 150, mu=147.0653, obj=813.1321, err=0.0010538
    
    psnr =
    
       33.2784
    

总结

这段代码的总体流程如下:

  1. 读取并标准化图像—>X。
  2. 向图像中引入噪声—>Xn。
  3. 设置 TRPCA 算法的参数。
  4. 执行 TRPCA 算法以恢复被噪声污染的图像—>Xhat。
  5. 计算恢复后的图像与原始图像之间的 PSNR。
  6. 显示原始图像、噪声图像和恢复后的图像。

demo_trpca_toy

对一个低秩张量和稀疏噪声的合成数据进行恢复

%% 添加路径、清理工作区和关闭所有图形窗口
addpath(genpath(cd))
clear
close all

%% 定义张量的尺寸和管秩
n1 = 30;
n2 = n1;
n3 = 30;
r = 0.1*n1 % 管秩

%% 生成低秩张量 L
L1 = randn(n1,r,n3)/n1;
L2 = randn(r,n2,n3)/n2;
L = tprod(L1,L2); % low rank part

%% 生成稀疏噪声 S
p = 0.05; % 稀疏噪声的比例
m = p*n1*n2*n3 % 需要添加噪声的元素数量
temp = rand(n1*n2*n3,1); % 生成长度为 n1*n2*n3 的随机数列
[B,I] = sort(temp); % 对其排序
I = I(1:m); % I 获取前 m 个位置
Omega = zeros(n1,n2,n3); 
Omega(I) = 1; % 标记噪声位置
E = sign(rand(n1,n2,n3)-0.5); % 生成随机噪声(-1 或 1)

S = Omega.*E; % 在指定位置添加噪声,生成稀疏噪声张量 S

%% 生成观测张量 Xn 并定义参数
Xn = L+S; % 将低秩张量 L 和稀疏噪声 S 相加,生成观测张量 Xn
lambda = 1/sqrt(n3*max(n1,n2)); % 定义正则化参数 lambda

opts.tol = 1e-8; % 定义收敛阈值
opts.mu = 1e-4;
opts.rho = 1.1;
opts.DEBUG = 1;

%% 执行张量恢复并计算误差
tic
[Lhat,Shat] = trpca_tnn(Xn,lambda,opts); % 使用 trpca_tnn 函数恢复低秩张量 L 和稀疏噪声张量 S
n1
Lr = norm(L(:)-Lhat(:))/norm(L(:)) % 计算恢复的低秩张量与真实低秩张量的相对误差
Sr = norm(S(:)-Shat(:))/norm(S(:)) % 计算恢复的稀疏噪声张量与真实稀疏噪声张量的相对误差

sparsity = m % 实际噪声的稀疏度 
sparsityhat = length(find(Shat~=0)) % 恢复噪声的稀疏度
trank = r % 实际张量的管秩
trankhat = tubalrank(Lhat) % 恢复张量的管秩

toc
  • 实验结果

    r =
    
         3
    
    m =
    
            1350
    
    iter 1, mu=0.0001, obj=0, err=36.775
    iter 10, mu=0.00023579, obj=0, err=36.775
    iter 20, mu=0.00061159, obj=0, err=36.775
    iter 30, mu=0.0015863, obj=0, err=36.775
    iter 40, mu=0.0041145, obj=44.9907, err=1.6795
    iter 50, mu=0.010672, obj=44.9907, err=1.6795
    iter 60, mu=0.02768, obj=44.9907, err=1.6795
    iter 70, mu=0.071795, obj=45.1557, err=1.5768
    iter 80, mu=0.18622, obj=47.7323, err=0.23377
    iter 90, mu=0.483, obj=47.8832, err=0.037091
    iter 100, mu=1.2528, obj=47.9045, err=0.0010766
    iter 110, mu=3.2494, obj=47.9049, err=8.4064e-07
    
    n1 =
    
        30
    
    Lr =
    
       6.4046e-08
    
    Sr =
    
       6.5363e-10
    
    sparsity =
    
            1350
    
    sparsityhat =
    
            1378
    
    trank =
    
         3
    
    trankhat =
    
         3
    
    历时 3.556168 秒。
    
    
  • 如何理解:L1 = randn(n1,r,n3)/n1;

    • randn :每个元素都是从标准正态分布生成的
    • 除以 n1 是一种简单且常用的方法来缩放随机生成的数据,使其均值和方差更适合特定的应用场景。

    为什么选择 n1

    在具体实现中,选择 n1 作为缩放因子的原因可能有几个:

    1. 保持张量元素的尺度:通过将生成的随机数除以 n1,可以确保 L1 中的元素值在较小的范围内,防止数值过大而影响后续的数值计算稳定性。
    2. 一致性:由于 L1L2 的维度和后续操作(如 tprod)中的规模因子 n1n2n3 相关,除以 n1 可以保持计算的一致性,确保生成的低秩张量 L 的数值范围适当。
  • 如何理解:E = sign(rand(n1,n2,n3)-0.5)

    1. rand(n1, n2, n3)
      • rand 是 MATLAB 中用于生成均匀分布的随机数的函数
      • rand(n1, n2, n3) 生成一个大小为 (n1, n2, n3) 的三维张量,其中每个元素都是在 [0, 1] 区间内均匀分布的随机数。
    2. rand(n1, n2, n3) - 0.5
      • 对生成的每个随机数减去 0.5,使其范围变为 [-0.5, 0.5]
      • 这样做的目的是将随机数中心化,使其有一半的值为正数,一半为负数。
    3. sign(rand(n1, n2, n3) - 0.5)
      • sign 是 MATLAB 中的符号函数,返回输入值的符号:
        • 对于正数,返回 1。
        • 对于负数,返回 -1。
        • 对于 0,返回 0(在这种情况下不会出现)。
      • sign(rand(n1, n2, n3) - 0.5) 将生成的中心化随机数转换为 -1 或 1,具体取决于每个值是正的还是负的。

    作用和意义

    生成一个大小为 (n1, n2, n3) 的张量 E,其中每个元素为 -1 或 1。这个操作通常用于创建稀疏噪声或其他需要符号随机分布的场景。

Ex2_trpca_phasetransition

与上述代码的区别主要在于噪声生成的部分

p = 0.05; % 定义稀疏噪声的比例
alphabet = [-1 0 1]; % 定义符号集合
prob = [p/2 1-p 2/p]; % 定义符号概率
m = p*n1*n2*n3; % 计算需要添加噪声的元素数量 m
temp = randsrc(n1*n2*n3, 1, [alphabet; prob]); % 生成具有指定概率分布的随机数列
[B,I] = sort(temp); % 对随机数列 temp 进行排序,并获取排序后的索引 I
I = I(1:m); % 选择前 m 个索引,这些索引对应于 temp 中最小的 m 个值
Omega = zeros(n1,n2,n3); 
Omega(I) = 1; % 在前 m 个位置标记噪声位置
E = sign(rand(n1,n2,n3)-0.5); % 生成随机噪声(-1 或 1)
S = Omega.*E; % 在指定位置添加噪声,生成稀疏噪声张量 S
  • alphabet符号集合,表示噪声可以是 − 1 、 0 -1、0 10 1 1 1
  • prob符号概率,定义了每个符号出现的概率
  • randsrc 函数生成一个尺寸为 (n1*n2*n3, 1)随机数列,其中元素根据 alphabetprob 的指定概率分布生成

上述代码的噪声生成:

  1. 生成一个均匀随机数列。
  2. 对随机数列排序,选择前 m 个索引,这些索引对应于噪声位置。
  3. 在选定的位置上生成随机噪声(-1 或 1)。

当前段代码的噪声生成:

  1. 定义符号集合和对应的概率分布。
  2. 使用 randsrc 函数按照指定概率生成随机数列。
  3. 对随机数列排序,选择前 m 个索引,这些索引对应于噪声位置。
  4. 在选定的位置上生成随机噪声(-1 或 1)。

总结

  • 相同点:两段代码都生成一个低秩张量和稀疏噪声张量,然后进行张量恢复。
  • 不同点:噪声生成部分的符号和分布方式不同。前一段代码生成均匀随机噪声,当前段代码生成具有特定符号分布的噪声

这种区别在于当前段代码中引入了特定的符号概率分布,可能更符合某些实际应用中的噪声分布特征,而前一段代码则使用了更通用的均匀随机噪声生成方式。这可能对张量恢复算法的性能和结果产生不同的影响。

;