
import tensorflow as tf
import numpy as np

# set y = wx + b
# loss func: (1/N) * sum[((wx + b) - y) ^ 2]
# update w: (1/N) * x


# generate random data [x, y]
def initial_random(N, w, b):
    res = []
    for i in range(N):
        x = np.random.uniform(-10, 10)
        noise = np.random.normal(0, 0.01)
        y = w * x + b + noise  # x in -10, 10, and get y by w, b
        res.append([x, y])
    res = np.array(res)
    return res

# points are sample point
# w b is value when updating(training)
# according loss func to get error
def mse(b, w, points) :
    N = len(points)

    error = 0
    for i in range(N):
        x = points[i, 0]
        ry = points[i, 1]
        py = w * x + b
        error += (ry - py) ** 2
    return error / float(N)

# update w and b
def update_gradient(b_current, w_current, points, lr):
    b_grad = 0
    w_grad = 0
    N = float(len(points))
    for i in range(int(N)):
        x = points[i, 0]
        y = points[i, 1]
        b_grad += (2 / N) * ((w_current * x + b_current) - y)
        w_grad += (2 / N) * x * ((w_current * x + b_current) - y)
    new_b = b_current - (lr * b_grad)
    new_w = w_current - (lr * w_grad)

    return [new_b, new_w]

def gradient_descent(points, starting_b, starting_w, lr, num_iteration):
    b = starting_b
    w = starting_w
    for step in range(num_iteration):
        b, w = update_gradient(b, w, points, lr)
        loss = mse(b, w, points)

        if step % 50 == 0:
            print(f"iter:{step}, loss:{loss}, w:{w}, b:{b}")

    return [b, w]

if __name__ == "__main__":
    data = initial_random(100, 2.232, 1.432)

    lr = 0.01
    initial_b = 0
    initial_w = 0
    num_iter = 1000

    [b, w] = gradient_descent(data, initial_b, initial_w, lr, num_iter)

    loss = mse(b, w, data)

    print(f"final loss:{loss}, w:{w}, b:{b}")
