以下将会讨论成本函数(cost function)和梯度下降(gradient descent)的相关问题。
给出一份数据,能够拟合出多条直线,有着多组θ_0, θ_1,但为了更精准地预测,需要找到其中一组,使得数据点尽可能多地落在直线上或者其附近,因此需要成本函数。

** 举例**:单变量线性回归(univariate linear regression)


x_i, y_i为第i组的样本
1/2m 作用仅仅是对后面整体求导时,作为指数的2乘下来与1/2抵消,不会影响到求θ的最小值
"To make the derivations mathematically more convenient"

** 几何理解:**
对于简化版h(x)=θ_1*x,

实质上是各组h(x_1)=θ_1*x_1与真实值y_1之间高度差绝对值的平方之和乘1/2样本数量
** 可视化:**
对于h(x)=θ_0+θ_1*x,

对于线性回归总会是这样的凸函数,只有全局最优解

右图线上的点虽然有着不同的θ_1, θ_0,但对应的J值是相同的
可看做上方凸函数图像的各个截面
椭圆最中心的点即为成本函数值最小处
计算cost(梯度下降步骤1):

def compute_cost(x,y,w,b): #equation 2 (for univariate)
m = x.shape[0]
cost = 0
for i in range(m):
f_wb = w * x[i] + b #one feature
cost = cost + (f_wb - y[i])** 2
total_cost = 1/(2 * m) * cost
return total_cost
def compute_cost(X, y, w, b): #equation 2 (for multi-var)
m = X.shape[0]
cost = 0.0
for i in range(m):
f_wb_i = np.dot(X[i], w) + b #multiple features
cost = cost + (f_wb_i - y[i]) **2
cost = cost / (2 * m)
return cost
这里只能算出数据集中总的cost值,但如何具体得到使J值最小的θ_1, θ_0,需要用到梯度下降

** 可视化说明1:**
想要从山顶想要最快速地走到山脚,因此需要找到最陡峭的路前进,对于代价函数来说,路即为斜率k

但存在问题:出发点稍微偏离一点,便可以得到完全不同的局部最优解
“实际应用中,评估所有可能的参数通常不能找到全局最优解。而是使用优化算法找到解,虽然可能不一定是全局最优的,但足够满足目标。另一种办法是用更高级的技术,如SGD或者Adam。”
** 具体算法**:

α为学习速率(learning rate),改变该值对应着下坡时的跨度
对***θ_1, θ_0 (w,b)的同步更新*(simultaneously update)
个人理解: θ_1, θ_0可以看作下坡时的向左向右,减去的α和导数项之积对于θ_1, θ_0是完全相同的,即重新调整的方向的跨度是相同的。


计算gradient(梯度下降步骤2):

def compute_gradient (x,y,w,b): #equation 4,5 (for univariate)
m = x.shape[0]
dj_dw = 0
dj_db = 0
for i in range(m):
f_wb = w * x[i] + b
dj_dw_i = (f_wb - y[i]) * x[i]
dj_db_i = f_wb - y[i]
dj_db += dj_db_i
dj_dw += dj_dw_i
dj_dw = dj_dw /m
dj_db = dj_db /m
return dj_dw , dj_db
def compute_gradient(X, y, w, b): #equation 4,5 (for multi-var)
m,n = X.shape #(# of examples, # of features)
dj_dw = np.zeros((n,))
dj_db = 0.
for i in range(m):
err = (np.dot(X[i], w) + b) - y[i]
for j in range(n):
dj_dw[j] = dj_dw[j] + err * X[i, j]
dj_db = dj_db + err
dj_dw = dj_dw / m
dj_db = dj_db / m
return dj_db, dj_dw
现在有了计算cost和gradient的函数,就可以进行descent的过程
梯度下降步骤3:

#equation 3
def gradient_descent(x, y, w_in, b_in, alpha, num_iters, cost_function, gradient_function):
"""
Args:
x (ndarray (m,)) : Data, m examples
y (ndarray (m,)) : target values
w_in,b_in (scalar): initial values of model parameters
Returns:
w (scalar): Updated value
b (scalar): Updated value
J_history (List): History of cost values
p_history (list): History of parameters [w,b]
"""
w = copy.deepcopy(w_in) # 避免影响w_in。深拷贝,确保每个对象的独立性,从而避免在原对象和副本之间共享对象所导致的潜在问题)
J_history = []
p_history = []
b = b_in
w = w_in
for i in range(num_iters):
dj_dw, dj_db = gradient_function(x, y, w , b)
# Update Parameters using equation (3) above
b = b - alpha * dj_db
w = w - alpha * dj_dw
# Save cost J at each iteration
if i<100000: # prevent resource exhaustion
J_history.append( cost_function(x, y, w , b)) #返回total_cost
p_history.append([w,b])
# 控制代码的执行频率,确保某些代码只在特定的迭代次数执行。
if i % math.ceil(num_iters/10) == 0:
print(f"Iteration {i:4}: Cost {J_history[-1]:0.2e} ",
f"dj_dw: {dj_dw: 0.3e}, dj_db: {dj_db: 0.3e} ",
f"w: {w: 0.3e}, b:{b: 0.5e}")
return w, b, J_history, p_history #return for graphing
def gradient_descent(X, y, w_in, b_in, cost_function, gradient_function, alpha, num_iters):
"""
Args:
X (ndarray (m,n)) : Data, m examples with n features
y (ndarray (m,)) : target values
w_in (ndarray (n,)), b_in (scalar) : initial parameters
Returns:
w (ndarray (n,)) : Updated values
b (scalar) : Updated value
"""
# An array to store cost J and w's at each iteration primarily for graphing later
J_history = []
w = copy.deepcopy(w_in) #avoid modifying global w within function
b = b_in
for i in range(num_iters):
dj_db,dj_dw = gradient_function(X, y, w, b)
w = w - alpha * dj_dw
b = b - alpha * dj_db
# Save cost J at each iteration
if i<100000: # prevent resource exhaustion
J_history.append( cost_function(X, y, w, b))
# 控制代码的执行频率,确保某些代码只在特定的迭代次数执行。
if i% math.ceil(num_iters / 10) == 0:
print(f"Iteration {i:4d}: Cost {J_history[-1]:8.2f} ")
return w, b, J_history #return for graphing
可视化说明2:
a)第一种情况:斜率为正值

对θ进行更新时,由于斜率为正,对应的导数为正数,因此减去该值后是在对θ向后调整
每次调整后会得到新的斜率,新的斜率相对于调整前的会更小,因此新的调整幅度会更小最终找到局部最优解
b)第二种情况:斜率为负值

对θ进行更新时,由于斜率为负,对应的导数为负数,因此减去该值后是在对θ向前调整
新的调整幅度同样会越来越小,最终逼近目标
c)第三种情况:斜率为0,已经处在局部或者全局最优处时

此时对θ更新时,后面的减数由于斜率为0,因此θ的值不变,这也能说明已经达到最优点
** 对α的选取:**
a)不应过小

b)不应过大

假设绿点为初始点,此时的导数值为一个较小的负值,但由于α过大,导致绿点对应的θ加上了一个比较大的数,得到紫点;
紫点对应的导数值为一个较大的正值,同样由于α过大,使得此时的θ减去一个更大的数字,得到橘点;
不断地滚雪球最终离目标点越来越远
(本来想着具体算算,纸上画图、用desmos画图,结果研究了半天,纸上演算一整发现好麻烦,结果用脑子想想道理其实好简单==)
c) 可能的问题

因此可以先使用非常小的α迭代数次,确定图像趋势正确后,再提高

对梯度下降的优化:
特征放缩(Feature Scaling):
使下降速度更快
能够消除特征之间的量纲差异性,使得不同特征更具有可比性
(size in feet量级可能是k,而#bedrooms则是个位数,前者会对结果产生较大影响,后者则有可能会被忽略)
同时也能让数据足够离散(不是主要目的)

方案1:除以取值范围最大值

方案2:均值归一化(Mean Normalization)

方案3:Z-score 标准化

但放缩结果并无强制要求,接近-1≤ x_j ≤ 1的范围

c)固定?不固定?
α不变的情况下随着与其相乘的导数值不断变化,整体是一直动态调整的,所以在最开始设置好后不会出现上述过大过小的情况的话,其实没有必要在训练过程中调整α?
“通常在训练开始前设置好并在过程中保存不变,但对于某些情况可能需要在期间进行调整。”
梯度下降的种类:
a) 批量梯度下降法(Batch Gradient Descent)
对整个样本集进行遍历,更新参数时使用所以的样本来完成这一操作
batch *n.*一批,一批生产量;*v.*分批处理
b)随机梯度下降法(Stochastic Gradient Descent)
选用一个样本进行梯度下降,对于过大样本使用a方法训练速度过慢
但解不一定最优
c)小批量梯度下降法(Mini-batch Gradient Descent)
上述两种的折衷
细分的种类以后课程内或者课程外学到了再新补充吧
参考来源:
