"""
梯度下降与代价函数 —— 纯代码解析
以房价预测(线性回归)为例
"""

import numpy as np
import matplotlib.pyplot as plt

# ========================================
# 1. 准备数据
# ========================================
# 特征:面积 (平方英尺)
X = np.array([1500, 2000, 1200, 1800, 2500])
# 标签:房价 (美元)
y = np.array([300000, 450000, 250000, 380000, 550000])

m = len(y)  # 样本数量

print(f"样本数量: {m}")
print(f"面积范围: {X.min()} - {X.max()}")
print(f"房价范围: {y.min()} - {y.max()}\n")

# ========================================
# 2. 特征缩放(标准化)
# ========================================
# 原因:面积是上千的数值,如果不缩放,梯度下降会跑得很慢
X_mean = np.mean(X)
X_std = np.std(X)
X_scaled = (X - X_mean) / X_std  # 标准化公式: (x - μ) / σ

print(f"面积 - 均值: {X_mean:.2f}, 标准差: {X_std:.2f}")
print(f"缩放后面积范围: {X_scaled.min():.2f} - {X_scaled.max():.2f}\n")

# ========================================
# 3. 代价函数(Cost Function)
# ========================================
def compute_cost(X, y, w, b):
    """
    计算均方误差的一半(代价函数)
    
    参数:
        X: 特征数据(标准化后)
        y: 真实标签
        w: 权重(斜率)
        b: 偏置(截距)
    
    返回:
        cost: 代价函数值(越小代表预测越准)
    """
    m = len(y)
    
    # 预测值 = 权重 × 特征 + 偏置
    predictions = w * X + b
    
    # 误差 = 预测值 - 真实值
    errors = predictions - y
    
    # 平方误差求和,取平均,再除以2(方便后面求导)
    cost = (1 / (2 * m)) * np.sum(errors ** 2)
    
    return cost


# ========================================
# 4. 计算梯度(Gradient)
# ========================================
def compute_gradient(X, y, w, b):
    """
    计算代价函数对 w 和 b 的偏导数(梯度)
    梯度告诉我们应该往哪个方向、走多远来调整参数
    
    参数:
        X, y, w, b: 同代价函数
    
    返回:
        dw: 权重 w 的梯度
        db: 偏置 b 的梯度
    """
    m = len(y)
    
    # 预测值
    predictions = w * X + b
    
    # 误差
    errors = predictions - y
    
    # dw:所有误差乘以各自特征值的累加,再取平均
    dw = (1 / m) * np.sum(errors * X)
    
    # db:所有误差的累加,再取平均
    db = (1 / m) * np.sum(errors)
    
    return dw, db


# ========================================
# 5. 梯度下降(Gradient Descent)
# ========================================
def gradient_descent(X, y, w_init, b_init, learning_rate, num_iterations):
    """
    梯度下降算法:反复用梯度来更新 w 和 b,让代价越来越小
    
    参数:
        X, y: 数据
        w_init, b_init: w 和 b 的初始值(通常从0开始)
        learning_rate: 学习率(每一步迈多大)
        num_iterations: 迭代次数(跑多少步)
    
    返回:
        w, b: 优化后的参数
        cost_history: 每次迭代的代价(用来画下降曲线)
    """
    w = w_init
    b = b_init
    cost_history = []  # 记录每一步的代价
    
    for i in range(num_iterations):
        # 1. 先算出当前的梯度(方向和步长)
        dw, db = compute_gradient(X, y, w, b)
        
        # 2. 往梯度的反方向走一小步(因为梯度指向上升,我们要下降)
        w = w - learning_rate * dw
        b = b - learning_rate * db
        
        # 3. 记录这一步的代价,方便后面看下降曲线
        cost = compute_cost(X, y, w, b)
        cost_history.append(cost)
        
        # 每100步打印一次进度
        if i % 200 == 0:
            print(f"迭代 {i:4d}: cost = {cost:.2f}, w = {w:.4f}, b = {b:.2f}")
    
    return w, b, cost_history


# ========================================
# 6. 开始训练
# ========================================
print("开始训练...")
print("-" * 50)

# 初始化参数:w 和 b 都从 0 开始
w_init = 0
b_init = 0

# 超参数
learning_rate = 0.1    # 学习率:步子太大容易跑过头,太小跑得太慢
num_iterations = 1000   # 迭代次数

# 执行梯度下降
w_final, b_final, cost_history = gradient_descent(
    X_scaled, y, w_init, b_init, learning_rate, num_iterations
)

print("-" * 50)
print(f"\n最终结果: w = {w_final:.4f}, b = {b_final:.2f}")
print(f"最终代价: {cost_history[-1]:.2f}")
print(f"代价下降倍数: {cost_history[0] / cost_history[-1]:.0f} 倍\n")

# ========================================
# 7. 可视化代价下降曲线
# ========================================
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 左图:代价下降曲线
axes[0].plot(range(len(cost_history)), cost_history, 'b-', linewidth=2)
axes[0].set_xlabel('迭代次数', fontsize=12)
axes[0].set_ylabel('代价函数值', fontsize=12)
axes[0].set_title('梯度下降:代价随迭代的变化', fontsize=14)
axes[0].grid(True, linestyle='--', alpha=0.6)

# 右图:拟合直线 vs 原始数据
# 把缩放后的特征还原,方便画图
X_plot = np.linspace(X.min(), X.max(), 100)
X_plot_scaled = (X_plot - X_mean) / X_std
y_plot = w_final * X_plot_scaled + b_final

axes[1].scatter(X, y, color='red', s=80, label='真实数据', zorder=5)
axes[1].plot(X_plot, y_plot, 'b-', linewidth=2, label='拟合直线')
axes[1].set_xlabel('面积 (平方英尺)', fontsize=12)
axes[1].set_ylabel('房价 (美元)', fontsize=12)
axes[1].set_title('线性回归:找到最佳直线', fontsize=14)
axes[1].legend()
axes[1].grid(True, linestyle='--', alpha=0.6)

plt.tight_layout()
plt.show()

# ========================================
# 8. 预测新房价
# ========================================
print("\n预测新房价:")
new_areas = np.array([1600, 2200, 3000])
for area in new_areas:
    # 记住:预测前要先做同样的标准化
    area_scaled = (area - X_mean) / X_std
    price_pred = w_final * area_scaled + b_final
    print(f"面积 {area:4d} 平方英尺 → 预测房价 ${price_pred:,.0f}")

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐