Во-первых, одним из способов упрощения построения матрицы коэффициентов A
может быть использование np.diag
и суммирование каждой отдельной диагональной матрицы k
вместо использования нескольких циклов for
, поэтому взяв n=10
например
import numpy as np
# gradient descent parameters
gamma = 0.01 # step size multiplier
tol = 1e-5 # convergence tolerance for stopping criterion
max_iters = 1e6 # maximum number of iterations
# dimension of the problem
n = 10
A = np.diag(np.ones(n-2), k=-2) + np.diag(-4*np.ones(n-1), k=-1) + \
np.diag(6*np.ones(n), k=0) + \
np.diag(-4*np.ones(n-1), k=1) + np.diag(np.ones(n-2), k=2)
b = np.zeros(n)
b[0] = 3 ; b[1] = -1 ; b[n-1] = 3 ; b[n-2] = -1
Теперь, градиентный спуск (самый крутой спуск) гласит, что вектор x_sol
, который является решением системы Ax=b
, где A
есть положительно определено и симметрия c, является минимальным значением квадратичной c формы
def f(A,b,x):
return 0.5*np.dot(np.dot(x,A),x)-np.dot(x,b)
Поэтому убедитесь, что вы выполняете правильное матричное умножение (с np.dot
функция или @
оператор), а не python поэлементное умножение (с оператором *
) при оценке градиента df
этой функции f
, так что
def df(A,b,x):
return np.dot(A,x)-b
Тем не менее, обратите внимание, что поскольку A
является симметрией c, df
может быть упрощено и просто возвращает точный остаток np.dot(A,x)-b
вместо 0.5*np.dot(A.T,x) + 0.5*np.dot(A,x)-b
. Вам также необходимо указать адекватный критерий остановки, например, путем тестирования евклидовой нормы шага по отношению к вашему коэффициенту толерантности tol
. Давайте запустим эту
# int: you chose 6 as initial guess
x0 = 6
def gradient_descent(A,b,x0):
# initial guess vector x
next_x = x0*np.ones(n)
# print initial f value
print('i = 0 ; f(x)= '+str(f(A,b,next_x)))
i=1
# convergence flag
cvg = False
print('Starting descent')
while i <= max_iters:
curr_x = next_x
next_x = curr_x - gamma * df(A,b,curr_x)
step = next_x - curr_x
# convergence test
if np.linalg.norm(step,2)/(np.linalg.norm(next_x,2)+np.finfo(float).eps) <= tol:
cvg = True
break
# print optionnaly f values while searching for minimum
print('i = '+str(i)+' ; f(x)= '+str(f(A,b,next_x)))
i += 1
if cvg :
print('Minimum found in ' + str(i) + ' iterations.')
print('x_sol =',next_x)
else :
print('No convergence for specified parameters.')
return next_x
, которая дает
x_sol = gradient_descent(A,b,x0)
>>> i = 0 ; f(x)= 48.0
>>> Starting descent
>>> i = 1 ; f(x)= 43.20999999999996
>>> i = 2 ; f(x)= 39.17663099999998
>>> i = 3 ; f(x)= 35.75573883350001
>>> i = 4 ; f(x)= 32.83351344396835
>>> i = 5 ; f(x)= 30.319690909679018
>>> i = 6 ; f(x)= 28.1423440799636
>>> ...
>>> i = 19144 ; f(x)= -1.99977775801747
>>> i = 19145 ; f(x)= -1.9997778660326588
>>> i = 19146 ; f(x)= -1.99977797399535
>>> i = 19147 ; f(x)= -1.99977808190557
>>> i = 19148 ; f(x)= -1.999778189763341
>>> Minimum found in 19149 iterations.
>>> x_sol = [1.00991626 1.02509014 1.04110403 1.05415009 1.0614195 1.0614195
1.05415009 1.04110403 1.02509014 1.00991626]
Наконец, давайте сравним с прямым решателем np.linalg.solve
:
np.allclose(np.linalg.solve(A,b),x_sol)
>>> True
Также я бы использовал scipy.linalg.solveh_banded если градиентный спуск не является обязательным, или обновите его для лучшей конвергенции в метод сопряженных градиентов, используя A
-ортогональные векторы, заданные в качестве направлений поиска с хорошим предварительным условием (см. этот превосходный pdf imo). Надеюсь, это поможет.