我正在尝试根据以下公式在R中实施NMF:
H最初是猜测,然后基于该公式迭代更新。我写了这段代码,但它执行起来就像以前一样。我该如何重写这段代码? W是相似矩阵。
sym.nmf <- function ( W )
{
N <- ncol(W)
set.seed(1234)
H <- matrix(runif(N * k, 0, 1),N,k)
J1 <- 0
while (0 < 1)
{
HT <- t(H)
A <- W %*% H
B <- H %*% HT %*% H
H <- 0.5 * ( H * ( 1 + ( A / B )))
J = W - (H %*% t(H))
J = sum (J^2)
if ( (J1 != 0 ) && (J > J1) )
return (H1)
H1 <- H
J1 <- J
}
}
答案 0 :(得分:2)
这是sym.nmf
函数的重做,在此过程中进行了一些具有统计意义的重要改进并提高了速度。
添加一个相对公差(rel.tol
)参数,以在J [i]占J [i-1]的百分之rel.tol
时打破循环。设置方式只有在0 == 1或机器精度变得比拟合本身可变时,才停止循环。从理论上讲,您的功能将永远不会收敛。
添加种子,因为可重复性很重要。沿着这条思路,您可能会考虑使用非负双SVD进行初始化以取得领先。但是,根据您的应用程序,这可能会将您的NMF驱动到不代表全局最小值的局部最小值中,因此可能很危险。就我而言,我陷入了类似于SVD的最小值,而NMF最终收敛于完全不同于随机初始化的因式分解的状态。
添加一个最大迭代次数(max.iter
),因为有时您不想运行一百万次迭代来达到您的容忍阈值。
用 crossprod
和tcrossprod
函数代替基本的%*%
函数。根据矩阵大小,这可以带来约2倍的速度增益。
减少检查收敛的次数,因为在减去W
之后在HH^T
中计算残差信号将花费近一半的计算时间。您可以假设收敛需要数百至数千次迭代,因此只需每100个周期检查一次收敛。
更新的功能:
sym.nmf <- function (W, k, seed = 123, max.iter = 10000, rel.tol = 1e-10) {
set.seed(seed)
H <- matrix(runif(ncol(W) * k, 0, 1),ncol(W),k)
J <- c()
for(i in 1:max.iter){
H <- 0.5*(H*(1+(crossprod(W,H)/tcrossprod(H,crossprod(H)))))
# check for convergence every 100 iterations
if(i %% 100 == 0){
J <- c(J,sum((W - tcrossprod(H))^2))
plot(J, xlab = "iteration", ylab = "total residual signal", log = 'y')
cat("Iteration ",i,": J =",tail(J)[1],"\n")
if(length(J) > 3 && (1 - tail(J, 1)/tail(J, 2)[1]) < rel.tol){
return(H)
}
}
if(i == max.iter){
warning("Max.iter was reached before convergence\n")
return(H)
}
}
}
目标函数也可以隔离,Rfast也可以用于Rfast::Crossprod()
和Rfast::Tcrossprod()
的并行计算。
sym.nmf <- function (W, k, seed = 123, max.iter = 100, rel.tol = 1e-10) {
set.seed(seed)
require(Rfast)
H <- matrix(runif(ncol(W) * k, 0, 1),ncol(W),k)
J <- c()
for(i in 1:max.iter){
H <- 0.5 * fit_H(W,H, num.iter = 100)
J <- c(J,sum((W - tcrossprod(H))^2))
plot(J, xlab = "iteration", ylab = "total residual signal", log = 'y')
cat("Iteration ",i,": J =",tail(J, n = 1),"\n")
if(length(J) > 3 && (1 - tail(J, 1)/tail(J, 2)[1]) < rel.tol){
return(H)
}
if(i == max.iter){
warning("Max.iter was reached before convergence\n")
return(H)
}
}
}
fit_H <- function(W,H, num.iter){
for(i in 1:num.iter){
H <- 0.5*(H*(1+(Rfast::Crossprod(W,H)/Rfast::Tcrossprod(H,Rfast::Crossprod(H,H)))))
}
H
}
现在,该目标函数可以转换为Rcpp,以进一步提高速度。并行化还可以在目标函数内(并行化crossprod
和tcrossprod
)或通过并行运行多个分解来实现进一步的收益(因为通常需要多次重新启动才能发现可靠的解决方案)。