Below is a 1-D sparse recovery problem: the algorithm must recover a sparse vector x from noisy measurements. Use the sliders to change the L1 penalty λ and the step size t, then press Run to watch ISTA and FISTA converge. The left panel tracks the objective value; the right panel shows the current estimate vs. the true sparse signal.
<!-- Proximal Gradient demo: ISTA vs FISTA on a sparse 1-D recovery problem -->
<div class="controls">
<label>λ (L1 penalty): <span id="lval">0.10</span>
<input type="range" id="lambda" min="0.01" max="0.5" step="0.01" value="0.10">
</label>
<label>t (step size): <span id="tval">0.10</span>
<input type="range" id="step" min="0.01" max="0.19" step="0.01" value="0.10">
</label>
<label>Iterations: <span id="kval">60</span>
<input type="range" id="iters" min="10" max="150" step="5" value="60">
</label>
</div>
<div class="btn-row">
<button id="run">Run</button>
<button id="reset" class="ghost">Reset</button>
</div>
<div class="charts">
<canvas id="objChart" width="280" height="200" title="Objective value vs iterations"></canvas>
<canvas id="sigChart" width="280" height="200" title="Estimated vs true signal"></canvas>
</div>
<div id="status" class="status"></div>
/* Layout and chart container styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 4px; }
.controls { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .6rem; }
label { font-size: .85rem; display: flex; align-items: center; gap: .5rem; }
input[type=range] { flex: 1; accent-color: #1d3557; }
.btn-row { display: flex; gap: .5rem; margin-bottom: .7rem; }
button { font: 600 14px system-ui; padding: .4rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.charts { display: flex; gap: 8px; flex-wrap: wrap; }
canvas { border: 1px solid #d0d7de; border-radius: 6px; background: #fafbfc; }
.status { font-size: .88rem; font-weight: 600; margin-top: .5rem; min-height: 1.3em; color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Sparse 1-D recovery: minimize (1/2)||Ax-b||^2 + lambda*||x||_1
// True signal TRUE_X is sparse; A is a random measurement matrix with M < N rows.
// Ground-truth sparse signal (10 coordinates, 3 nonzero)
const TRUE_X = [0, 0, 1.5, 0, 0, -2.0, 0, 0, 0.8, 0];
const N = TRUE_X.length;
// Random measurement matrix A (M x N) with M < N (underdetermined).
// We use a simple LCG for reproducibility (no external library).
const SEED = 42;
function lcg(s){ return (s * 1664525 + 1013904223) & 0xffffffff; }
let rng = SEED;
function rand(){ rng = lcg(rng); return (rng >>> 0) / 0xffffffff - 0.5; }
const M = 7; // 7 measurements for 10 unknowns: underdetermined system
const A = [];
for(let i = 0; i < M; i++){ const row = []; for(let j = 0; j < N; j++) row.push(rand()); A.push(row); }
// Normalize columns of A to unit L2 norm for conditioning
for(let j = 0; j < N; j++){
let norm = 0; for(let i = 0; i < M; i++) norm += A[i][j]*A[i][j]; norm = Math.sqrt(norm);
for(let i = 0; i < M; i++) A[i][j] /= norm;
}
// Compute noisy measurements b = A * TRUE_X + small noise
const B = [];
for(let i = 0; i < M; i++){
let s = 0; for(let j = 0; j < N; j++) s += A[i][j] * TRUE_X[j];
B.push(s + 0.02 * rand());
}
// Matrix-vector product helper
function matvec(mat, x){ return mat.map(row => row.reduce((s,v,j) => s + v*x[j], 0)); }
// Residual r = A*x - b
function residual(x){ return matvec(A, x).map((v,i) => v - B[i]); }
// Gradient of smooth part f(x) = (1/2)||Ax-b||^2
function grad_f(x){ const r = residual(x); const g = new Array(N).fill(0);
for(let i=0; i<M; i++) for(let j=0; j<N; j++) g[j] += A[i][j]*r[i]; return g; }
// L1 norm (sum of absolute values)
function l1(x){ return x.reduce((s,v) => s + Math.abs(v), 0); }
// Full composite objective F(x) = f(x) + lambda*||x||_1
function objective(x, lam){ const r = residual(x); const sq = r.reduce((s,v) => s+v*v, 0);
return 0.5*sq + lam*l1(x); }
// Soft-thresholding: proximal operator for lambda*||.||_1
function softThreshold(x, thresh){ return x.map(v => Math.sign(v) * Math.max(Math.abs(v) - thresh, 0)); }
// One ISTA step: gradient step on f, then soft-threshold for g
function istaStep(x, t, lam){
const g = grad_f(x);
const y = x.map((v,j) => v - t*g[j]);
return softThreshold(y, lam*t);
}
// Run ISTA and FISTA for K iterations, return objective histories and final iterates
function runFISTA(lam, t, K){
let x = new Array(N).fill(0);
let xPrev = new Array(N).fill(0);
let theta = 1;
const objISTA = [], objFISTA = [];
let xISTA = new Array(N).fill(0);
for(let k = 0; k < K; k++){
// ISTA plain gradient step
xISTA = istaStep(xISTA, t, lam);
objISTA.push(objective(xISTA, lam));
// FISTA momentum update: extrapolate with previous iterate
const xNew = istaStep(x, t, lam);
const thetaNew = (1 + Math.sqrt(1 + 4*theta*theta)) / 2;
const beta = (theta - 1) / thetaNew;
const y = xNew.map((v,j) => v + beta*(v - xPrev[j]));
xPrev = xNew; x = y; theta = thetaNew;
objFISTA.push(objective(xNew, lam));
}
return { objISTA, objFISTA, xISTA, xFISTA: xPrev };
}
let currentResult = null;
function draw(lam, t, K){
const res = runFISTA(lam, t, K);
currentResult = res;
drawObj(res.objISTA, res.objFISTA, K);
drawSig(res.xFISTA);
const finalISTA = res.objISTA[K-1].toFixed(4);
const finalFISTA = res.objFISTA[K-1].toFixed(4);
document.getElementById('status').textContent =
fmt('After %K% iterations — objective:: ISTA=%finalISTA% FISTA=%finalFISTA%', { K: K, finalISTA: finalISTA, finalFISTA: finalFISTA });
}
function drawObj(istaVals, fistaVals, K){
const canvas = document.getElementById('objChart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0,0,W,H);
const pad = { l:38, r:8, t:18, b:28 };
const pw = W - pad.l - pad.r, ph = H - pad.t - pad.b;
const allVals = [...istaVals, ...fistaVals];
const minV = Math.min(...allVals), maxV = Math.max(...allVals);
const range = maxV - minV || 1;
// Draw coordinate axes
ctx.strokeStyle = '#888'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, pad.t+ph); ctx.lineTo(pad.l+pw, pad.t+ph); ctx.stroke();
// Draw ISTA convergence curve (blue)
ctx.strokeStyle = '#1d6aaa'; ctx.lineWidth = 1.5;
ctx.beginPath();
istaVals.forEach((v,i) => { const x = pad.l + (i/(K-1))*pw; const y = pad.t + ph - ((v-minV)/range)*ph;
i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y); });
ctx.stroke();
// Draw FISTA convergence curve (orange)
ctx.strokeStyle = '#e07b39'; ctx.lineWidth = 1.5;
ctx.beginPath();
fistaVals.forEach((v,i) => { const x = pad.l + (i/(K-1))*pw; const y = pad.t + ph - ((v-minV)/range)*ph;
i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y); });
ctx.stroke();
// Draw axis labels and legend
ctx.fillStyle = '#444'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
ctx.fillText('Iteration', pad.l + pw/2, H - 4);
ctx.save(); ctx.translate(10, pad.t + ph/2); ctx.rotate(-Math.PI/2);
ctx.fillText('F(x)', 0, 0); ctx.restore();
ctx.fillStyle = '#1d6aaa'; ctx.fillText('ISTA', pad.l + pw*0.25, pad.t+12);
ctx.fillStyle = '#e07b39'; ctx.fillText('FISTA', pad.l + pw*0.65, pad.t+12);
}
function drawSig(xFISTA){
const canvas = document.getElementById('sigChart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0,0,W,H);
const pad = { l:38, r:8, t:18, b:28 };
const pw = W - pad.l - pad.r, ph = H - pad.t - pad.b;
const allVals = [...TRUE_X, ...xFISTA];
const minV = Math.min(...allVals) - 0.3, maxV = Math.max(...allVals) + 0.3;
const range = maxV - minV || 1;
const bar = pw / (N * 2.5);
// Draw horizontal zero line
ctx.strokeStyle = '#ccc'; ctx.lineWidth = 1;
const y0 = pad.t + ph - ((-minV)/range)*ph;
ctx.beginPath(); ctx.moveTo(pad.l, y0); ctx.lineTo(pad.l+pw, y0); ctx.stroke();
for(let j = 0; j < N; j++){
const cx = pad.l + (j+0.5)/(N) * pw;
// True signal bar (dark blue, semi-transparent)
ctx.fillStyle = 'rgba(29,53,87,0.35)';
const yt = pad.t + ph - ((TRUE_X[j]-minV)/range)*ph;
ctx.fillRect(cx - bar - 1, Math.min(yt, y0), bar, Math.abs(yt - y0));
// FISTA estimate bar (orange)
ctx.fillStyle = 'rgba(224,123,57,0.85)';
const ye = pad.t + ph - ((xFISTA[j]-minV)/range)*ph;
ctx.fillRect(cx + 1, Math.min(ye, y0), bar, Math.abs(ye - y0));
}
// Draw coordinate axes for signal chart
ctx.strokeStyle = '#888'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, pad.t+ph); ctx.lineTo(pad.l+pw, pad.t+ph); ctx.stroke();
ctx.fillStyle = '#444'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
ctx.fillText('Coordinate index', pad.l + pw/2, H - 4);
ctx.fillStyle = 'rgba(29,53,87,0.7)'; ctx.fillText('True', pad.l + pw*0.25, pad.t+12);
ctx.fillStyle = 'rgba(224,123,57,0.9)'; ctx.fillText('FISTA est.', pad.l + pw*0.7, pad.t+12);
}
function readParams(){ return {
lam: parseFloat(document.getElementById('lambda').value),
t: parseFloat(document.getElementById('step').value),
K: parseInt(document.getElementById('iters').value)
}; }
document.getElementById('lambda').addEventListener('input', e => {
document.getElementById('lval').textContent = parseFloat(e.target.value).toFixed(2); });
document.getElementById('step').addEventListener('input', e => {
document.getElementById('tval').textContent = parseFloat(e.target.value).toFixed(2); });
document.getElementById('iters').addEventListener('input', e => {
document.getElementById('kval').textContent = e.target.value; });
document.getElementById('run').addEventListener('click', () => {
const p = readParams(); draw(p.lam, p.t, p.K); });
document.getElementById('reset').addEventListener('click', () => {
document.getElementById('lambda').value = 0.10;
document.getElementById('step').value = 0.10;
document.getElementById('iters').value = 60;
document.getElementById('lval').textContent = '0.10';
document.getElementById('tval').textContent = '0.10';
document.getElementById('kval').textContent = '60';
document.getElementById('status').textContent = '';
const c1 = document.getElementById('objChart'), c2 = document.getElementById('sigChart');
c1.getContext('2d').clearRect(0,0,c1.width,c1.height);
c2.getContext('2d').clearRect(0,0,c2.width,c2.height);
});
// Run automatically on page load with default parameters
draw(0.10, 0.10, 60);
<!-- Proximal Gradient demo: ISTA vs FISTA on a sparse 1-D recovery problem -->
<div class="controls">
<label>λ (L1 penalty): <span id="lval">0.10</span>
<input type="range" id="lambda" min="0.01" max="0.5" step="0.01" value="0.10">
</label>
<label>t (step size): <span id="tval">0.10</span>
<input type="range" id="step" min="0.01" max="0.19" step="0.01" value="0.10">
</label>
<label>Iterations: <span id="kval">60</span>
<input type="range" id="iters" min="10" max="150" step="5" value="60">
</label>
</div>
<div class="btn-row">
<button id="run">Run</button>
<button id="reset" class="ghost">Reset</button>
</div>
<div class="charts">
<canvas id="objChart" width="280" height="200" title="Objective value vs iterations"></canvas>
<canvas id="sigChart" width="280" height="200" title="Estimated vs true signal"></canvas>
</div>
<div id="status" class="status"></div>
/* Layout and chart container styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 4px; }
.controls { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .6rem; }
label { font-size: .85rem; display: flex; align-items: center; gap: .5rem; }
input[type=range] { flex: 1; accent-color: #1d3557; }
.btn-row { display: flex; gap: .5rem; margin-bottom: .7rem; }
button { font: 600 14px system-ui; padding: .4rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.charts { display: flex; gap: 8px; flex-wrap: wrap; }
canvas { border: 1px solid #d0d7de; border-radius: 6px; background: #fafbfc; }
.status { font-size: .88rem; font-weight: 600; margin-top: .5rem; min-height: 1.3em; color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Sparse 1-D recovery: minimize (1/2)||Ax-b||^2 + lambda*||x||_1
// True signal TRUE_X is sparse; A is a random measurement matrix with M < N rows.
// Ground-truth sparse signal (10 coordinates, 3 nonzero)
const TRUE_X = [0, 0, 1.5, 0, 0, -2.0, 0, 0, 0.8, 0];
const N = TRUE_X.length;
// Random measurement matrix A (M x N) with M < N (underdetermined).
// We use a simple LCG for reproducibility (no external library).
const SEED = 42;
function lcg(s){ return (s * 1664525 + 1013904223) & 0xffffffff; }
let rng = SEED;
function rand(){ rng = lcg(rng); return (rng >>> 0) / 0xffffffff - 0.5; }
const M = 7; // 7 measurements for 10 unknowns: underdetermined system
const A = [];
for(let i = 0; i < M; i++){ const row = []; for(let j = 0; j < N; j++) row.push(rand()); A.push(row); }
// Normalize columns of A to unit L2 norm for conditioning
for(let j = 0; j < N; j++){
let norm = 0; for(let i = 0; i < M; i++) norm += A[i][j]*A[i][j]; norm = Math.sqrt(norm);
for(let i = 0; i < M; i++) A[i][j] /= norm;
}
// Compute noisy measurements b = A * TRUE_X + small noise
const B = [];
for(let i = 0; i < M; i++){
let s = 0; for(let j = 0; j < N; j++) s += A[i][j] * TRUE_X[j];
B.push(s + 0.02 * rand());
}
// Matrix-vector product helper
function matvec(mat, x){ return mat.map(row => row.reduce((s,v,j) => s + v*x[j], 0)); }
// Residual r = A*x - b
function residual(x){ return matvec(A, x).map((v,i) => v - B[i]); }
// Gradient of smooth part f(x) = (1/2)||Ax-b||^2
function grad_f(x){ const r = residual(x); const g = new Array(N).fill(0);
for(let i=0; i<M; i++) for(let j=0; j<N; j++) g[j] += A[i][j]*r[i]; return g; }
// L1 norm (sum of absolute values)
function l1(x){ return x.reduce((s,v) => s + Math.abs(v), 0); }
// Full composite objective F(x) = f(x) + lambda*||x||_1
function objective(x, lam){ const r = residual(x); const sq = r.reduce((s,v) => s+v*v, 0);
return 0.5*sq + lam*l1(x); }
// Soft-thresholding: proximal operator for lambda*||.||_1
function softThreshold(x, thresh){ return x.map(v => Math.sign(v) * Math.max(Math.abs(v) - thresh, 0)); }
// One ISTA step: gradient step on f, then soft-threshold for g
function istaStep(x, t, lam){
const g = grad_f(x);
const y = x.map((v,j) => v - t*g[j]);
return softThreshold(y, lam*t);
}
// Run ISTA and FISTA for K iterations, return objective histories and final iterates
function runFISTA(lam, t, K){
let x = new Array(N).fill(0);
let xPrev = new Array(N).fill(0);
let theta = 1;
const objISTA = [], objFISTA = [];
let xISTA = new Array(N).fill(0);
for(let k = 0; k < K; k++){
// ISTA plain gradient step
xISTA = istaStep(xISTA, t, lam);
objISTA.push(objective(xISTA, lam));
// FISTA momentum update: extrapolate with previous iterate
const xNew = istaStep(x, t, lam);
const thetaNew = (1 + Math.sqrt(1 + 4*theta*theta)) / 2;
const beta = (theta - 1) / thetaNew;
const y = xNew.map((v,j) => v + beta*(v - xPrev[j]));
xPrev = xNew; x = y; theta = thetaNew;
objFISTA.push(objective(xNew, lam));
}
return { objISTA, objFISTA, xISTA, xFISTA: xPrev };
}
let currentResult = null;
function draw(lam, t, K){
const res = runFISTA(lam, t, K);
currentResult = res;
drawObj(res.objISTA, res.objFISTA, K);
drawSig(res.xFISTA);
const finalISTA = res.objISTA[K-1].toFixed(4);
const finalFISTA = res.objFISTA[K-1].toFixed(4);
document.getElementById('status').textContent =
fmt('After %K% iterations — objective:: ISTA=%finalISTA% FISTA=%finalFISTA%', { K: K, finalISTA: finalISTA, finalFISTA: finalFISTA });
}
function drawObj(istaVals, fistaVals, K){
const canvas = document.getElementById('objChart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0,0,W,H);
const pad = { l:38, r:8, t:18, b:28 };
const pw = W - pad.l - pad.r, ph = H - pad.t - pad.b;
const allVals = [...istaVals, ...fistaVals];
const minV = Math.min(...allVals), maxV = Math.max(...allVals);
const range = maxV - minV || 1;
// Draw coordinate axes
ctx.strokeStyle = '#888'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, pad.t+ph); ctx.lineTo(pad.l+pw, pad.t+ph); ctx.stroke();
// Draw ISTA convergence curve (blue)
ctx.strokeStyle = '#1d6aaa'; ctx.lineWidth = 1.5;
ctx.beginPath();
istaVals.forEach((v,i) => { const x = pad.l + (i/(K-1))*pw; const y = pad.t + ph - ((v-minV)/range)*ph;
i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y); });
ctx.stroke();
// Draw FISTA convergence curve (orange)
ctx.strokeStyle = '#e07b39'; ctx.lineWidth = 1.5;
ctx.beginPath();
fistaVals.forEach((v,i) => { const x = pad.l + (i/(K-1))*pw; const y = pad.t + ph - ((v-minV)/range)*ph;
i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y); });
ctx.stroke();
// Draw axis labels and legend
ctx.fillStyle = '#444'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
ctx.fillText('Iteration', pad.l + pw/2, H - 4);
ctx.save(); ctx.translate(10, pad.t + ph/2); ctx.rotate(-Math.PI/2);
ctx.fillText('F(x)', 0, 0); ctx.restore();
ctx.fillStyle = '#1d6aaa'; ctx.fillText('ISTA', pad.l + pw*0.25, pad.t+12);
ctx.fillStyle = '#e07b39'; ctx.fillText('FISTA', pad.l + pw*0.65, pad.t+12);
}
function drawSig(xFISTA){
const canvas = document.getElementById('sigChart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0,0,W,H);
const pad = { l:38, r:8, t:18, b:28 };
const pw = W - pad.l - pad.r, ph = H - pad.t - pad.b;
const allVals = [...TRUE_X, ...xFISTA];
const minV = Math.min(...allVals) - 0.3, maxV = Math.max(...allVals) + 0.3;
const range = maxV - minV || 1;
const bar = pw / (N * 2.5);
// Draw horizontal zero line
ctx.strokeStyle = '#ccc'; ctx.lineWidth = 1;
const y0 = pad.t + ph - ((-minV)/range)*ph;
ctx.beginPath(); ctx.moveTo(pad.l, y0); ctx.lineTo(pad.l+pw, y0); ctx.stroke();
for(let j = 0; j < N; j++){
const cx = pad.l + (j+0.5)/(N) * pw;
// True signal bar (dark blue, semi-transparent)
ctx.fillStyle = 'rgba(29,53,87,0.35)';
const yt = pad.t + ph - ((TRUE_X[j]-minV)/range)*ph;
ctx.fillRect(cx - bar - 1, Math.min(yt, y0), bar, Math.abs(yt - y0));
// FISTA estimate bar (orange)
ctx.fillStyle = 'rgba(224,123,57,0.85)';
const ye = pad.t + ph - ((xFISTA[j]-minV)/range)*ph;
ctx.fillRect(cx + 1, Math.min(ye, y0), bar, Math.abs(ye - y0));
}
// Draw coordinate axes for signal chart
ctx.strokeStyle = '#888'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, pad.t+ph); ctx.lineTo(pad.l+pw, pad.t+ph); ctx.stroke();
ctx.fillStyle = '#444'; ctx.font = '10px system-ui'; ctx.textAlign = 'center';
ctx.fillText('Coordinate index', pad.l + pw/2, H - 4);
ctx.fillStyle = 'rgba(29,53,87,0.7)'; ctx.fillText('True', pad.l + pw*0.25, pad.t+12);
ctx.fillStyle = 'rgba(224,123,57,0.9)'; ctx.fillText('FISTA est.', pad.l + pw*0.7, pad.t+12);
}
function readParams(){ return {
lam: parseFloat(document.getElementById('lambda').value),
t: parseFloat(document.getElementById('step').value),
K: parseInt(document.getElementById('iters').value)
}; }
document.getElementById('lambda').addEventListener('input', e => {
document.getElementById('lval').textContent = parseFloat(e.target.value).toFixed(2); });
document.getElementById('step').addEventListener('input', e => {
document.getElementById('tval').textContent = parseFloat(e.target.value).toFixed(2); });
document.getElementById('iters').addEventListener('input', e => {
document.getElementById('kval').textContent = e.target.value; });
document.getElementById('run').addEventListener('click', () => {
const p = readParams(); draw(p.lam, p.t, p.K); });
document.getElementById('reset').addEventListener('click', () => {
document.getElementById('lambda').value = 0.10;
document.getElementById('step').value = 0.10;
document.getElementById('iters').value = 60;
document.getElementById('lval').textContent = '0.10';
document.getElementById('tval').textContent = '0.10';
document.getElementById('kval').textContent = '60';
document.getElementById('status').textContent = '';
const c1 = document.getElementById('objChart'), c2 = document.getElementById('sigChart');
c1.getContext('2d').clearRect(0,0,c1.width,c1.height);
c2.getContext('2d').clearRect(0,0,c2.width,c2.height);
});
// Run automatically on page load with default parameters
draw(0.10, 0.10, 60);
Notice how FISTA (orange) consistently reaches low objective values in far fewer steps than ISTA (blue). The gap widens when the problem is poorly conditioned. Soft-thresholding is what drives the sparsity: components smaller than λt are zeroed out at every iteration, and the rest are shrunk toward zero by the same amount.
Comments
Loading comments...