Gradient programming

Posted by Hao Do on July 29, 2022

Gradient programming

Cách thức implement gradient bằng ngôn ngữ python, biểu diễn từ công thức toán học. Và ứng dụng của nó trong một số bài toán. Import thư viện trước khi thực hiện code

1
2
import numpy as np
import matplotlib.pyplot as plt

Công thức tính gradient phổ biến

img

Code thực hiện gradient function

1
2
3
''' Gradient Func '''
def gradient (f, x, epsilon = 1.0e-9):
    return (f(x + epsilon/2) - f(x - epsilon/2)) / epsilon

Một số hàm dùng để gọi và test thử function gradient

1
2
3
4
5
6
7
8
9
10
11
12
def f1(x):
    return 3*x**2 - 2*x
def f2(x):
    return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
def f3(x):
    return (x + 4)*(x + 6) * (x - 2) * (x - 12) - 25 * x**2
def f4(w):
    return 2 * w[0]**4 + w[1]**4 - 4 * w[0]**2 + 2 * w[1]**2
def f5(w):
    x = w[0]
    y = w[1]
    return 2 * x**2 + 3 * y**2 - np.exp(-(x**2 + y**2))

Test function gradient

1
2
''' test Func'''
print(gradient(f2, 2.0))

Plot function f and gradient f

x = np.arange(-10, 10, 0.01) #3x^2 - 2x
plt.plot(x, f1(x), x, gradient(f1, x))
plt.show()

img

x = np.arange(-10, 20, 0.01) #3x^2 - 2x
plt.plot(x, f3(x), x, gradient(f3, x))
plt.show()

img

Chuyển list sang array và reshape

1
2
3
4
5
6
7
8
9
w = [3.4, 7.1, 2.9]
print(type(w))
w = np.array(w, dtype=np.float64)
print(type(w))
''''''
print(len(w.shape))
print(w.shape)
print(w)
print(w.reshape(-1, 1)) # only 1 column, ignore rows

Cài đặt tổng quát (w: có thể là giá trị hoặc vector)

1
2
3
4
5
6
7
8
9
10
def d_vec2vec(f, w, epsilon=1.0e-9):
    res = []
    w = np.array(w, dtype=np.float64).reshape(-1, 1)
    for i in range(w.shape[0]):
        w_t = w.copy()
        w_p = w.copy()
        w_t[i] += epsilon / 2
        w_p[i] -= epsilon / 2
        res.append((f(w_t) - f(w_p)) / epsilon)
    return np.concatenate(res, -1)

Test kết quả của hàm d_vec2vec

1
2
w = [1, 2, 3, 4]
print(d_vec2vec(f1, w))

Ứng dụng tìm nghiệm của phương trình

1
2
3
4
5
6
7
8
9
#tim nghiem cua ham f (khi no bang 0)
# f = (x + 4)*(x + 6) * (x - 2) * (x - 12) - 25 * x**2
epochs = 1000
x = 9 # chon diem roi --> tim nghiem --> x1 = -8
x = -5 # chon diem roi --> tim nghiem --> x2 = -3
x = 100 # chon diem roi --> tim nghiem --> x3 = 13.17
for _ in range(epochs):
    x = x - f3(x) / gradient(f3, x) # tim nghiem.
print(x)

Ứng dụng tối ưu hàm f

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#optimize (tim min, max)
# function (f4, f5)
# f4 = 2 * w[0]**4 + w[1]**4 - 4 * w[0]**2 + 2 * w[1]**2
# f5 = 2 * x**2 + 3 * y**2 - np.exp(-(x**2 + y**2))

x = 1.0
y = 1.0
lr = 0.01
epochs = 1000
res = []
for _ in range(epochs):
    z = f5([x, y])
    dx, dy = d_vec2vec(f5, [x, y])
    x -= dx * lr
    y -= dy * lr
    res.append(z)
plt.plot(res)
print(f'min = {res[-1]} with x, y = {x, y}')

img

Cách triển khai đạo hàm (cách khác)

Sử dụng phân tích Taylor

img

Sau đó, chuyển nó về dạng “giống với số phức”, trong đó (epsilon)^2 = 0

img

Code cài đặt của nó như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class DualNumberGradient:
    def __init__(self, v, p):
        self.v = v
        self.p = p
    def __add__(self, other):
        if not isinstance(other, DualNumberGradient):
            other = DualNumberGradient(other, 0)
        return DualNumberGradient(self.v + other.v, self.p + other.p)
    def __radd__(self, other):
        return DualNumberGradient(other, 0) + self
    
    def __sub__(self, other):
        if not isinstance(other, DualNumberGradient):
            other = DualNumberGradient(other, 0)
        return DualNumberGradient(self.v - other.v, self.p - other.p)
    def __rsub__(self, other):
        return DualNumberGradient(other, 0) - self
    def __mul__(self, other):
        if not isinstance(other, DualNumberGradient):
            other = DualNumberGradient(other, 0)
        return DualNumberGradient(self.v * other.v, self.v * other.p + self.p * other.v)
    def __rmul__(self, other):
        return DualNumberGradient(other, 0) * self
    def __truediv__(self, other):
        if not isinstance(other, DualNumberGradient):
            other = DualNumberGradient(other, 0)
        return DualNumberGradient(self.v/other.v,
                                 (self.p * other.v - self.v*other.p)/(other.v**2))
    def __rtruediv__(self, other):
        return DualNumberGradient(other, 0) / self
    def __pow__(self, other):
        if not isinstance(other, DualNumberGradient):
            other = DualNumberGradient(other, 0)
        return DualNumberGradient(self.v**other.v, np.exp(other.v*np.log(self.v)) * (other.p * np.log(self.v) + (other.v * self.p * 1.0 / self.v)))
    def __repr__(self):
        return repr(self.v) + ' + ' + repr(self.p) + '*epsilon'
    def __neg__(self):
        return DualNumberGradient(-self.v, -self.p)
    def __pos__(self):
        return DualNumberGradient(self.v, self.p)
    
    def func(f):
        def fdeclare(dark):
            if not isinstance(dark, DualNumberGradient):
                def wrapper(x):
                    return f(dark(x))
                return wrapper
            return f(dark)
        return fdeclare
    @func
    def exp(x):
        return DualNumberGradient(np.exp(x.v), x.p * np.exp(x.v))
    @func
    def sin(x):
        return DualNumberGradient(np.sin(x.v), x.p * np.cos(x.v))
    @func
    def cos(x):
        return DualNumberGradient(np.cos(x.v), -x.p * np.sin(x.v))
    @func
    def tan(x):
        return DualNumberGradient(np.tan(x.v), x.p / np.cos(x.v)**2)
    @func
    def log(x):
        return DualNumberGradient(np.log(x.v), x.p * (1.0 / x.v))
    @func
    def pow(x, n):
        return DualNumberGradient(x.v**n, x.p * n * (x.v**(n-1)))
    @func
    def fabs(x):
        return DualNumberGradient(np.abs(x.v), x.v / (x.v**2)**0.5)
    @func
    def fsum(x, axis=None, dtype=None, keepdims=np._NoValue):
        return DualNumberGradient(np.sum(x.v, axis=axis, dtype=dtype, keepdims = keepdims), x.p)

def auto_diff(f, x):
    if isinstance(x, int):
        x = [x]
    x = np.array(x, dtype=np.float64)
    return f(DualNumberGradient(x, np.ones(x.shape))).p

def fff(x):
    return x * (3*x+2) ** (1/2)

auto_diff(fff, [2.0])

Một số công thức đạo hàm đáng nhớ

img

Full ipynb

Tài liệu tham khảo

AI Việt Nam

Hết.