Power Iteration for Dominant Eigenvector
Implement the power iteration algorithm to find the dominant eigenvector of a matrix.
Power iteration is an iterative algorithm for finding the eigenvector corresponding to the largest eigenvalue of a matrix. It's used in PageRank, PCA, and spectral clustering.
Algorithm:
- Start with random vector v0​
- Iterate: vk+1​=∥Avk​∥Avk​​
- Converges when vk+1​≈vk​
Convergence: The rate depends on ∣λ1​/λ2​∣ - larger ratio means faster convergence.
After convergence, the dominant eigenvalue is: λ1​=vTvvTAv​=vTAv (when v is normalized)
Example:
matrix = [[2, 1], [1, 2]] max_iter = 100
{'eigenvector': [0.7071, 0.7071], 'eigenvalue': 3.0}Matrix analysis: The matrix [[2,1],[1,2]] has eigenvalues 3 and 1. Dominant eigenvalue: λ1​=3 Corresponding eigenvector: [1,1] (normalized: [0.7071,0.7071])
Power iteration: Starting with v0​=[1,0] (arbitrary):
Iteration 1: Av0​=[2,1], normalized: [0.894,0.447] Iteration 2: Av1​=[2.236,1.789], normalized: [0.781,0.625] ... Converges to [0.7071,0.7071]
Eigenvalue: vTAv=[0.7071,0.7071]â‹…[2.12,2.12]=3.0
Constraints:
- matrix: Square numpy array
- max_iter: Maximum iterations (default 100)
- tol: Convergence tolerance (default 1e-6)
- Return: Dict with 'eigenvector' (unit norm) and 'eigenvalue'
- Round to 4 decimal places
To solve this problem, you mainly need to understand what eigenvalues/eigenvectors are and why repeated matrix–vector multiplication reveals the dominant one, then translate the math iteration into safe, numerically stable code.
1. Background Knowledge
An eigenvector of a square matrix A∈Rn×n is a nonzero vector v such that
Av=λv,where λ is the corresponding eigenvalue. The dominant eigenvalue λ1​ is the eigenvalue with the largest magnitude ∣\lambda1​∣; its eigenvector is the dominant eigenvector.
Power iteration relies on the fact that any vector can be written as a linear combination of eigenvectors (for diagonalizable A). If we write
v0​=c1​u1​+c2​u2​+⋯+cn​un​,where ui​ are eigenvectors and λ1​ is dominant, then after k multiplications:
Akv0​=c1​λ1k​u1​+c2​λ2k​u2​+…The term with λ1k​ dominates as k grows (assuming c1â€‹î€ =0), so the direction of Ak\mathbf{v}0​ approaches the dominant eigenvector. Normalizing at each step prevents the vector from blowing up and keeps the iteration numerically stable.
The convergence rate depends on the eigenvalue gap: roughly proportional to ∣\lambda2​/\lambda1​∣k, where λ2​ is the second-largest eigenvalue in magnitude. If this ratio is close to 1, convergence is slow; if it is small, convergence is fast.
2. Algorithm / General Approach
General pattern for power iteration:
- Initialize with a random nonzero vector v0​.
- Repeat:
- Multiply by A to get wk​=A\mathbf{v}k​.
- Normalize: vk+1​=\mathbf{w}k​/∥\mathbf{w}k​∥2​.
- Check convergence by comparing vk+1​ and vk​.
- After convergence, estimate the dominant eigenvalue λ1​ using the Rayleigh quotient:
when v is normalized.
You are essentially doing iterative matrix–vector multiplication plus normalization until the direction stabilizes.
3. Step-by-Step Strategy (Implementation-Oriented)
Assume you’re given a 2D array or tensor A (shape (n, n)), and you must return (eigenvalue, eigenvector).
- Choose hyperparameters
- max_iters: maximum number of iterations (e.g., 1000).
- tol: tolerance for convergence (e.g., 1e-6).
- Initialize vector
- Create a random vector:
v = np.random.randn(n)
- Normalize:
v = v / np.linalg.norm(v)
- Iterative loop
for _ in range(max_iters):
w = A @ v # matrix-vector product
w_norm = np.linalg.norm(w)
if w_norm == 0:
# A sent v to zero; choose a new random vector or handle as special case
break
v_next = w / w_norm # normalize
# convergence check using vector difference
if np.linalg.norm(v_next - v) < tol:
v = v_next
break
v = v_next
Notes on convergence check:
- You can use np.linalg.norm(v_next - v) or check 1 - |v_next·v| (since eigenvectors are up to sign).
- Optionally store an error and stop when it’s below tol.
- Compute dominant eigenvalue
- Using Rayleigh quotient with normalized v:
Av = A @ v
lambda_est = v.T @ Av
- Return lambda_est and v.
- Package as a function
- Wrap the above into something like:
def power_iteration(A, max_iters=1000, tol=1e-6):
#... return lambda_est, v
4. Common Pitfalls
-
No normalization each step:
-
If you skip normalization, values can overflow or underflow, and the algorithm becomes unstable.
-
Bad convergence check:
-
Comparing eigenvalues too early is unstable; use vector change (‖v_{k+1} - v_k‖) or cosine similarity.
-
Remember that eigenvectors are defined up to sign: v and -v are equivalent. A sign flip can make v_{k+1} - v_k large even if you’re converged. Using abs(v_next · v) or min of both norms can help:
if min(np.linalg.norm(v_next - v), np.linalg.norm(v_next + v)) < tol:
# converged
-
Starting with an unlucky vector:
-
If the initial vector is orthogonal to the dominant eigenvector, the method will fail. In practice, a random initialization makes this event probability zero in continuous spaces.
-
Multiple eigenvalues with same magnitude:
-
If the largest magnitude eigenvalue is repeated (e.g., ±same magnitude), the method may not converge to a unique direction, or may oscillate.
-
Non-symmetric / pathological matrices:
-
For general matrices, convergence can be slower or more complex, but for most typical CV/PCA-like problems (symmetric positive semidefinite matrices), it behaves well.
-
Numerical precision:
-
Using a very small tol (e.g., 1e-15) with float32 may never trigger; pick tolerance suited to your data type.
5. Time & Space Complexity
Let n be the dimension of the matrix and T the number of iterations until convergence.
- Per iteration:
- Matrix–vector multiplication Av: O(n2) for dense A.
- Normalization and norms: O(n).
- Total time complexity:
For sparse matrices with m nonzero entries, the matvec cost is O(m), so time is O(Tâ‹…m).
- Space complexity:
- Storing A: O(n2) for dense, O(m) for sparse.
- Storing a few vectors (v, w, Av): O(n).
- Additional overhead is negligible.
- So extra space beyond the input matrix is O(n).
This complexity is why power iteration is attractive in large-scale settings: it uses only matrix–vector products and O(n) extra memory.