线性回归与梯度下降

10 线性回归与梯度下降

1950年天才科学家图灵在论文《计算机与智能》中首次提出一个问题,机器能思考吗?  这个问题开启了AI这个领域的研究,也引发了人们对AI的无限想象。 图灵测试: 判断一台机器能否思考,图灵设计了一个测试:发问人C同时对机器A与人类B持续发问,只要C无法分辨AB谁是电脑,谁是人类,就可以宣称机器A是一台能思考的机器。

人类学习的特点 人类智慧的累积方式,是一个很好的参考对象,人类的智慧来自经验,也就是不断的学习与吸取教训,在一次次的尝试错误当中,调整自我对外界的认知,如此一来,当下次遇到类似的状况,我们就能轻易的利用过往的经验,来判断与应对未知的未来。 同时,为了大幅减少所需的记忆和要处理的内容,人们也很擅长把类似的东西分类贴标签,把大量的信息归纳为几类。套用同样的概念,我们能不能把经验,也就是历史数据,喂给机器去学习,从而自动找出事件特征呢?

10.1 机器学习的分类:

监督学习:有历史数据,历史数据已标记 监督学习是一种机器学习范式,其中模型从带有标签的训练数据中学习,目标是找到输入特征和输出标签之间的映射关系。在这种学习模式下,算法被训练以识别数据中的模式,并能够预测新的、未见过的数据点的标签。这种方法广泛应用于分类、回归、物体检测等多种任务,涉及从图像识别到股票价格预测等多个领域。监督学习的关键优势在于其能够从历史数据中提取知识,并应用于新情况的预测,但其性能受限于训练数据的质量和标签的准确性。

无监督学习:有历史数据,但是数据没有标记,需要算法从数据中归纳特征 无监督学习是机器学习中的一种方法,它处理的是未标记的数据,目的是在没有任何先验标签或指导的情况下,从数据中发现模式、结构或分布。这种学习方式广泛应用于聚类、关联规则学习、异常检测和降维等任务,旨在揭示数据的内在性质和关系,帮助我们理解数据的本质特征,以及在没有明确指导信号的情况下做出决策或发现新的洞见。

强化学习:无历史数据,根据反馈(评分)调整算法行为 强化学习是一种机器学习范式,其中智能体(agent)通过与环境的交互来学习如何做出决策,以最大化某种累积奖励。在这个过程中,智能体不断试错,根据当前状态选择行动,接收环境的反馈(奖励或惩罚),并更新其策略以优化未来的决策。强化学习广泛应用于游戏、机器人控制、自动驾驶车辆和推荐系统等领域。

10.2 常见的机器学习算法

10.2.1 决策树

决策树是一种模仿人类决策过程的监督学习算法,它通过学习简单的决策规则从数据特征中推断出目标值。这种模型将数据集递归地分割成更小的子集,每个分割决策基于特征值的测试,形成树状结构,最终每个叶节点代表一个预测结果,适用于分类和回归任务。

历史经验

天气

是否有风

温度

经验

晴朗

有风

温暖

不下雨

晴朗

有风

寒冷

不下雨

晴朗

无风

温暖

不下雨

晴朗

无风

寒冷

不下雨

阴天

有风

寒冷

不下雨

阴天

有风

温暖

不下雨

阴天

无风

寒冷

下雨

阴天

无风

温暖

不下雨

根据经验,决策出门要不要带雨伞

天气

是否有风

温度

预测

晴朗

有风

温暖

 

阴天

无风

温暖

 

#解决kaggle中文显示乱码
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager

# Path to the custom font
font_path = '/kaggle/input/chinese-fonts/NotoSansSC-VariableFont_wght.ttf'

# Add the custom font to the font manager
font_manager.fontManager.addfont(font_path)

# After adding the font, search for it by filename to get the correct font name
for font in font_manager.fontManager.ttflist:
    if font.fname == font_path:
        print(f"Found font: {font.name}")
        plt.rcParams['font.family'] = font.name
        break



import matplotlib.pyplot as plt

# 定义树形结构的递归绘制函数
def plot_tree(node, x, y, dx, dy, tree_structure, ax):
    # 绘制节点,增大字体和背景框
    ax.text(x, y, node, ha='center', va='center', fontsize=12, bbox=dict(facecolor='lightblue', edgecolor='black', boxstyle='round,pad=0.5'))

    # 获取当前节点的子节点
    children = tree_structure.get(node, [])
    num_children = len(children)
   
    for i, child in enumerate(children):
        # 计算子节点位置
        child_x = x + (i - (num_children - 1) / 2) * dx
        child_y = y - dy
       
        # 绘制连接线
        ax.plot([x, child_x], [y - 0.1, child_y + 0.1], 'k-')
       
        # 递归绘制子节点
        plot_tree(child, child_x, child_y, dx / 2, dy, tree_structure, ax)

# 树结构数据
tree_structure = {
    '开始': ['晴朗', '阴天'],
    '晴朗': ['不下雨'],
    '阴天': ['有风', '无风'],
    '有风': ['不下雨'],
    '无风':['温暖', '寒冷'],
    '温暖': ['不下雨'],
    '寒冷': ['下雨'],
}

# 设置画布
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 0.5)
ax.axis('off'# 隐藏坐标轴

# 绘制树形图
plot_tree('开始', 0, 0, 0.9, 0.3, tree_structure, ax)

plt.title("树形图示例")
plt.show()

10.2.2 朴素贝叶斯分类

历史数据:  是否堵车 | 是否下雨 | 交通事故 — | :–: | — 堵车 | 不下雨 | 发生交通事故 不堵车 | 下雨 | 无交通事故 堵车 | 不下雨 | 发生交通事故 堵车 | 下雨 | 发生交通事故 堵车 | 不下雨 | 发生交通事故 堵车 | 不下雨 | 无交通事故 不堵车 | 不下雨 | 发生交通事故 堵车 | 不下雨 | 无交通事故 不堵车 | 不下雨 | 无交通事故 堵车 | 下雨 | 发生交通事故

预测: 是否堵车 | 是否下雨 | 交通事故 — | :–: | — 堵车 | 下雨 |

贝叶斯定理:

事件AB同时发生的概率 = 事件B发生的情况下,事件A发生的概率 x 事件B发生的概率 = 事件A发生的情况下,事件B发生的概率 x 事件A发生的概率 PAB=PA|B*PB=PB|A*PA

我们要对比:

堵车、下雨条件下,发生事故的概率和在堵车、下雨条件下,未发生事故的概率

即对比:

P发生事故|堵车下雨P未发生事故|堵车下雨

根据贝叶斯定理:

P发生事故|堵车下雨*P堵车下雨=P堵车下雨|发生事故*P发生事故

P发生事故|堵车下雨=P堵车下雨|发生事故*P发生事故P堵车下雨=P堵车|发生事故P下雨|发生事故*P发生事故P堵车下雨

P堵车|发生事故=56:发生交通事故6次,其中堵车5次。

P下雨|发生事故=26:发生交通事故6次,下雨2次。

P发生事故=61010笔历史数据中,发生6次事故。

P发生事故|堵车下雨=5626610P堵车下雨

P未发生事故|堵车下雨=P堵车下雨|未发生事故*P未发生事故P堵车下雨=P堵车|未发生事故P下雨|未发生事故*P发生事故P堵车下雨

P堵车|未发生事故=24:未发生交通事故4次,其中堵车2次。

P下雨|发生事故=36:未发生交通事故4次,下雨1次。

P发生事故=41010笔历史数据中,4次未发生事故。

P未发生事故|堵车下雨=2436410P堵车下雨

P5626610P堵车下雨>2436410P堵车下雨

P发生事故|堵车下雨5626610=0.17

P未发生事故|堵车下雨2436410=0.1

归一化后验概率:

P发生事故|堵车下雨=0.170.17+1=0.63

P未发生事故|堵车下雨=0.10.17+1=0.37

所以,根据历史数据,在堵车下雨的情况下,发生事故的概率为0.63

10.2.3 k最近邻

K最近邻(K-Nearest NeighborsKNN 是一种简单且直观的机器学习算法,广泛应用于分类和回归任务。其基本原理是:对于一个待分类或预测的样本,KNN会根据该样本与训练集中所有样本的距离,找到距离最小的 K 个邻居,并根据这些邻居的类别或数值进行决策。在分类任务中,KNN使用多数投票原则,即选择K个邻居中出现次数最多的类别作为预测结果;在回归任务中,则通常取这K个邻居的平均值作为预测值。KNN算法的优点是易于理解和实现,但其计算复杂度较高,特别是在数据集较大的时候,因为它需要计算每个样本之间的距离。

数据集:  月均登录次数 | 月访问时长 | 用户类型 :–: | :–: | — 50 | 1000 | 忠实用户 80 | 500 | 普通用户 90 | 2400 | 忠实用户 20 | 6800 | 忠实用户 10 | 400 | 普通用户 60 | 5000 | 忠实用户 70 | 4000 | 忠实用户 40 | 800 | 普通用户

预测: 月均登录次数 | 月访问时长 | 用户类型 :–: | :–: | — 50 | 5000 |

import matplotlib.pyplot as plt

# 数据
login_times = [50, 80, 90, 20, 10, 60, 70, 40]
visit_times = [1000, 500, 2400, 6800, 400, 5000, 4000, 800]
user_types = ['忠实用户', '普通用户', '忠实用户', '忠实用户', '普通用户', '忠实用户', '忠实用户', '普通用户']

# 新用户
new_user = [50, 5000]

# 创建数据框
data = {
    '登录次数': login_times,
    '访问时长': visit_times,
    '用户类型': user_types
}

# 绘制散点图
plt.figure(figsize=(10, 6))

# 绘制忠实用户
plt.scatter(
    [login_times[i] for i in range(len(user_types)) if user_types[i] == '忠实用户'],
    [visit_times[i] for i in range(len(user_types)) if user_types[i] == '忠实用户'],
    c='blue',
    label='忠实用户'
)

# 绘制普通用户
plt.scatter(
    [login_times[i] for i in range(len(user_types)) if user_types[i] == '普通用户'],
    [visit_times[i] for i in range(len(user_types)) if user_types[i] == '普通用户'],
    c='red',
    label='普通用户'
)

# 绘制新用户
plt.scatter(
    new_user[0],
    new_user[1],
    c='green',
    marker='x',
    s=100,
    label='新用户'
)

# 添加标签和标题
plt.xlabel('登录次数')
plt.ylabel('访问时长')
plt.title('用户类型分布')
plt.legend()

# 显示图表
plt.show()

import numpy as np

# 数据
login_times = [50, 80, 90, 20, 10, 60, 70, 40]
visit_times = [1000, 500, 2400, 6800, 400, 5000, 4000, 800]
user_types = ['忠实用户', '普通用户', '忠实用户', '忠实用户', '普通用户', '忠实用户', '忠实用户', '普通用户']

# 新用户
new_user = [50, 5000]

# 计算欧式距离的函数
def euclidean_distance(user1, user2):
    return np.sqrt((user1[0] - user2[0])**2 + (user1[1] - user2[1])**2)

# 计算新用户与每个已知用户的距离
distances = []
for i in range(len(login_times)):
    user_data = [login_times[i], visit_times[i]]
    distance = euclidean_distance(new_user, user_data)
    distances.append((distance, user_types[i]))

# 输出每个已知用户的距离和用户类型
for i, (distance, user_type) in enumerate(distances):
    print(f"新用户与用户{i+1}(登录次数={login_times[i]}, 访问时长={visit_times[i]})的距离:{distance:.2f},类型:{user_type}")

# 根据距离进行排序,选择距离最近的3个邻居
distances.sort(key=lambda x: x[0])  # 按距离升序排序
nearest_neighbors = distances[:3# 取最近的3个邻居

# 输出最近3个邻居的类型
print("\n最近的3个邻居及其类型:")
for distance, user_type in nearest_neighbors:
    print(f"距离:{distance:.2f}, 用户类型:{user_type}")

# 进行投票,预测新用户的类别
# 统计最近3个邻居中出现最多的用户类型
user_types_nearby = [user_type for _, user_type in nearest_neighbors]
predicted_type = max(set(user_types_nearby), key=user_types_nearby.count)

print(f"\n预测的新用户类型为: {predicted_type}")

10.2.4 K平均(K-Means):

K平均(K-Means)是一种常见的聚类算法,是一种非监督式学习的算法。 K平均算法计算步骤: 1、将数据分成K群,确定K的值。 2、随机选择K个中心。 3、计算每个元素到K个中心的距离,将元素划分到K个族群中。 4、重新计算K个族群的中心。 5、计算每个元素到新中心的距离,重新划分族群,如果有变动,则重复第4步,直到每个元素的族群不在发生变动。

历史数据:

月消费频次

消费金额(百元)

2

1

3

1.5

4

2

10

5

12

7

15

10

我们要将这些用户分为两类,以便设计更有针对性的营销方案。

import numpy as np
import matplotlib.pyplot as plt

# 数据
purchase_freq = np.array([9, 3, 4, 5, 7.5, 10, 10, 12, 10])
spending_amount = np.array([4, 6, 6, 5, 7, 10, 5, 7, 10])

# 随机选择两个中心
random_centers = np.array([[5, 7], [10, 5.5]])

# 创建 2x2 的子图布局
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 第一个子图:灰色的散点图
axes[0, 0].scatter(purchase_freq, spending_amount, color='gray')
axes[0, 0].set_title('数据分布')
axes[0, 0].set_xlabel('购买频率 (/)')
axes[0, 0].set_ylabel('消费金额 (百元)')

# 第二个子图:添加两个随机的中心,分别用紫色和橙色
axes[0, 1].scatter(purchase_freq, spending_amount, color='gray', label='数据点')
axes[0, 1].scatter(random_centers[0, 0], random_centers[0, 1], marker='*', color='purple', s=100, label='紫色中心')
axes[0, 1].scatter(random_centers[1, 0], random_centers[1, 1], marker='*', color='orange', s=100, label='橙色中心')
axes[0, 1].set_title('散点图 + 产生两个随机中心')
axes[0, 1].set_xlabel('购买频率 (/)')
axes[0, 1].set_ylabel('消费金额 ()')
axes[0, 1].legend()

# 第三个子图:添加一条连接两个中心的直线
#axes[1, 0].scatter(purchase_freq, spending_amount, color='gray', label='数据点')
axes[1, 0].scatter(random_centers[0, 0], random_centers[0, 1], marker='*', color='purple', s=100, label='紫色中心')
axes[1, 0].scatter(random_centers[1, 0], random_centers[1, 1], marker='*', color='orange', s=100, label='橙色中心')

# 根据距离决定每个点的颜色

dist_to_purple = np.sqrt((purchase_freq - random_centers[0, 0])**2 + (spending_amount - random_centers[0, 1])**2)
dist_to_orange = np.sqrt((purchase_freq - random_centers[1, 0])**2 + (spending_amount - random_centers[1, 1])**2)
colors = ['purple' if dist_to_purple[i] < dist_to_orange[i] else 'orange' for i in range(len(purchase_freq))]

axes[1, 0].scatter(purchase_freq, spending_amount, c=colors, s=100, label='数据点')

# 绘制连接两个中心的直线
# 计算中点
mid_x = (random_centers[0, 0] + random_centers[1, 0]) / 2
mid_y = (random_centers[0, 1] + random_centers[1, 1]) / 2

# 计算两个点之间的斜率
if random_centers[1, 0] != random_centers[0, 0]:
    slope = (random_centers[1, 1] - random_centers[0, 1]) / (random_centers[1, 0] - random_centers[0, 0])
else:
    slope = float('inf'# 如果两点在同一条垂直线上

if slope == 0:
    vertical_slope = float('inf')
elif slope == float('inf'):
    vertical_slope = 0
else:
    vertical_slope = -1 / slope

# 定义垂直线的范围
x_range = np.linspace(mid_x - 2, mid_x + 2, 100)
if vertical_slope == float('inf'):
    y_range = np.linspace(mid_y - 3, mid_y + 3, 100)
else:
    y_range = vertical_slope * (x_range - mid_x) + mid_y


axes[1, 0].plot([random_centers[0, 0], random_centers[1, 0]],
                [random_centers[0, 1], random_centers[1, 1]], color='black', linestyle='--', label='中心连线')

# 绘制中点
axes[1, 0].plot(mid_x, mid_y, 'ro', label='Midpoint')

# 绘制垂直线
axes[1, 0].plot(x_range, y_range, 'g-', label='Perpendicular line')

axes[1, 0].set_title('按随机中心分群')
axes[1, 0].set_xlabel('购买频率 (/)')
axes[1, 0].set_ylabel('消费金额 ()')
axes[1, 0].grid(True)
axes[1, 0].legend()
axes[1, 0].set_aspect('equal')

# 第四个子图:根据距离将点变色

#更新中心点
random_centers = np.array([[5, 6], [10, 7]])
axes[1, 1].scatter(random_centers[0, 0], random_centers[0, 1], marker='*', color='purple', s=100, label='紫色中心')
axes[1, 1].scatter(random_centers[1, 0], random_centers[1, 1], marker='*', color='orange', s=100, label='橙色中心')

# 根据距离决定每个点的颜色
colors = ['purple' if dist_to_purple[i] < dist_to_orange[i] else 'orange' for i in range(len(purchase_freq))]

axes[1, 1].scatter(purchase_freq, spending_amount, c=colors, s=100, label='数据点')

# 绘制连接两个中心的直线
# 计算中点
mid_x = (random_centers[0, 0] + random_centers[1, 0]) / 2
mid_y = (random_centers[0, 1] + random_centers[1, 1]) / 2

# 计算两个点之间的斜率
if random_centers[1, 0] != random_centers[0, 0]:
    slope = (random_centers[1, 1] - random_centers[0, 1]) / (random_centers[1, 0] - random_centers[0, 0])
else:
    slope = float('inf'# 如果两点在同一条垂直线上

if slope == 0:
    vertical_slope = float('inf')
elif slope == float('inf'):
    vertical_slope = 0
else:
    vertical_slope = -1 / slope

# 定义垂直线的范围
x_range = np.linspace(mid_x - 2, mid_x + 2, 100)
if vertical_slope == float('inf'):
    y_range = np.linspace(mid_y - 3, mid_y + 3, 100)
else:
    y_range = vertical_slope * (x_range - mid_x) + mid_y


axes[1, 1].plot([random_centers[0, 0], random_centers[1, 0]],
                [random_centers[0, 1], random_centers[1, 1]], color='black', linestyle='--', label='中心连线')

# 绘制中点
axes[1, 1].plot(mid_x, mid_y, 'ro', label='Midpoint')

# 绘制垂直线
axes[1, 1].plot(x_range, y_range, 'g-', label='Perpendicular line')

axes[1, 1].set_title('计算新的中心,并重新分群,直到分群不在变化')
axes[1, 1].set_xlabel('购买频率 (/)')
axes[1, 1].set_ylabel('消费金额 ()')
axes[1, 1].grid(True)
#axes[1, 1].legend()
axes[1, 1].set_aspect('equal')

# 调整子图之间的间距
plt.tight_layout()
plt.show()

10.3 线性回归与逻辑回归

线性回归:预测连续值

线性回归是一种预测分析方法,用于建立一个或多个自变量(解释变量)与因变量(响应变量)之间的线性关系模型。该模型假设因变量和自变量之间存在线性关系,即因变量Y可以表示为自变量X的线性组合加上一个随机误差项。线性回归的目标是确定最佳拟合线(在二维空间中是直线,在多维空间中是超平面),这条线能够最小化实际观测值和模型预测值之间的差异。从而能够根据自变量的值预测因变量的连续值。线性回归模型简单、易于理解,并且广泛应用于经济学、社会科学、生物学和工程等领域,用于预测房价、销售额、温度变化等连续数值。

房价历史成交数据 面积(平方) | 房价(万元) — | :–: 100 | 105 120 | 125 140 | 145

预测 面积(平方) | 房价(万元) — | :–: 110 |

import matplotlib.pyplot as plt

# 历史房价数据
x_points = [100, 120, 140]
y_points = [105, 125, 145]

# 绘制历史房价数据
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
ax[0].plot(x_points, y_points, 'o'# 'o'表示用圆圈标记点
ax[0].plot(x_points, y_points, '-'# '-'表示绘制实线

# 添加标题和轴标签
ax[0].legend(labels=['房价', '房价曲线'])
ax[0].axis('equal')
ax[0].set_title('历史房价曲线')
ax[0].set_xlabel('面积')
ax[0].set_ylabel('房价')


# 绘制历史房价数据
ax[1].plot(x_points, y_points, 'o'# 'o'表示用圆圈标记点
ax[1].plot(x_points, y_points, '-'# '-'表示绘制实线

# 绘制要预测房价的房屋面积
ax[1].axvline(x=110, color='r', linestyle='--')

# 添加标题和轴标签
ax[1].legend(labels=['房价', '需要预测的面积', '房价曲线'])
ax[1].axis('equal')
ax[1].set_title('根据历史房价预测')
ax[1].set_xlabel('面积')
ax[1].set_ylabel('房价')

# 显示图表
plt.show()

在上面的例子中,用于训练模型的房价历史成交数据称为训练集,而要预测的房子,由于还没有销售,所以它并不在房价历史成交数据中,我们也不知道它的售价是多少,想要预测这个房子的售价,我们需要训练模型从房价历史成交数据学习,然后利用这个模型来进行预测。

在上面的数据集中,房子的面积,会影响房子的售价,我们需要使用房子的面积来预测房子的售价,因此我们也把面积称为输入特征,把房价称为输出目标

逻辑回归:预测离散值

逻辑回归是一种统计学方法,用于模拟二分类问题中某个事件发生的概率,即预测一个结果为离散值(通常是01)的因变量。尽管名为回归,逻辑回归实际上是一种分类算法,它通过使用逻辑函数(如Sigmoid函数)将线性回归模型的输出映射到01之间,从而预测特定类别的概率。在实际应用中,逻辑回归常用于信用评分、疾病诊断、电子邮件过滤等领域,以确定某个实例属于特定类别的可能性。

肿瘤诊断成交数据

肿瘤尺寸(cm

肿瘤性质

1.8

良性

1.5

良性

2.2

良性

1.2

良性

1.3

良性

2.9

恶性

2.0

恶性

2.0

恶性

3.0

恶性

2.4

恶性

预测 肿瘤尺寸(cm | 肿瘤性质 — | :–: 1.6 |

import matplotlib.pyplot as plt

# 绘制良性肿瘤分布图
x1 = [1.8, 1.5, 2.2, 1.2, 1.3]
y1 = [0] * len(x1)

# 绘制恶性肿瘤分布图
x2 = [2.9, 2.0, 2.0, 3.0, 2.4]
y2 = [1] * len(x2)

# 创建一个图形和轴对象
fig, ax = plt.subplots(figsize=(6, 6))

# 在轴对象上绘制数值点
ax.plot(x1, y1, 'o', label='Values'# 'o-'表示用圆圈标记点,并连接线
ax.plot(x2, y2, 'go', label='Values'# 'o-'表示用圆圈标记点,并连接线
# 绘制要预测的肿瘤的尺寸大小
#ax.plot(1.6, 0, 'x', label='Values')  # 'o-'表示用圆圈标记点,并连接线

# 设置y轴刻度标签
ax.set_yticks([0, 1])
ax.set_yticklabels(['良性肿瘤', '恶性肿瘤'])

# 添加图例
ax.legend(labels=['良性肿瘤', '恶性肿瘤', '要预测的尺寸'])

# 添加标题和轴标签
ax.set_ylim(-0.2,2)
ax.set_title('肿瘤性质预测')
ax.set_xlabel('肿瘤尺寸')
ax.set_ylabel('肿瘤性质')

# 显示图表
plt.show()

10.4 线性回归:更真实的例子

直线方程为:y = wx + b,其中w为直线的斜率,b为直线的截距,要确定一条直线,需要知道斜率a和截距b,刚才线性回归的例子中,房价数据(100105),(120125),(140145)恰好都是直线: y = x + 5上的点,这是一个为了说明线性回归的原理而简化的例子,现在我们来看一个更接近实际的数据:

房价数据 面积(平方) | 房价(万元) — | :–: 100 | 90 115 | 95 140 | 100 65 | 50 80 | 80

import matplotlib.pyplot as plt
import numpy as np

# 历史房价数据
x_true = [100, 115, 140, 65, 80]
y_true = [90, 95, 100, 50, 80]

# 绘制历史房价数据
fig, ax = plt.subplots(figsize=(6, 6))
ax.plot(x_true, y_true, 'o'# 'o'表示用圆圈标记点

x = np.arange(60,140)
pre_line1 = 0.6 * x + 23
pre_line2 = 0.8 * x + 3
ax.plot(x, pre_line1, '-'# 绘制预测线1
ax.plot(x, pre_line2, '-'# 绘制预测线2


# 添加标题和轴标签
ax.set_xlim(55,145)
ax.legend(labels=['真实房价','预测直线1', '预测直线2'])
ax.axis('equal')
ax.set_title('那条预测直线更准确?')
ax.set_xlabel('面积')
ax.set_ylabel('房价')

# 显示图表
plt.show()

上图绘制了2条预测直线,这两条预测直线那条能更好的拟合历史数据,我们有应该如果评估呢?

10.5 损失函数(Loss functions

损失函数(Loss Function):用于评估预测值与真实值的误差 损失函数也称为代价函数或目标函数,在机器学习中用于衡量模型预测值与实际值之间的差异,其目的是通过优化算法最小化这个差异,从而提高模型的预测准确性。常见的损失函数包括均方误差(Mean Squared Error, MSE)用于回归问题,交叉熵损失(Cross-Entropy Loss)用于分类问题,以及 hinge loss 用于支持向量机(SVM)等。损失函数的选择取决于具体问题的性质和模型的目标。 注:在吴恩达的机器学习教程中称为代价函数(Cost functions

import matplotlib.pyplot as plt
import numpy as np

# 历史房价数据
x_true = [100, 115, 140, 65, 80]
y_true = [90, 95, 100, 50, 80]

fig, ax = plt.subplots(1, 2, figsize=(10, 6))

# 绘制预测线1

# 绘制历史房价数据
ax[0].plot(x_true, y_true, 'o'# 'o'表示用圆圈标记点

x = np.arange(60,140)
pre_line1 = 0.6 * x + 23
ax[0].plot(x, pre_line1, '-')

#绘制预测值与真实值的差
y_pre1 = 0.6 * np.array(x_true) + 23
ax[0].plot(x_true, y_pre1, 'o'# 'o'表示用圆圈标记点
ax[0].plot((x_true, x_true), (y_true, y_pre1), 'r-')

# 添加标题和轴标签
ax[0].set_xlim(55,145)
ax[0].legend(labels=['真实房价', '预测直线', '预测房价', '真实值与预测值误差'])
ax[0].axis('equal')
ax[0].set_title('历史房价曲线')
ax[0].set_xlabel('面积')
ax[0].set_ylabel('房价')

 # 绘制预测线2

# 绘制历史房价数据
ax[1].plot(x_true, y_true, 'o'# 'o'表示用圆圈标记点

x = np.arange(60,140)
pre_line2 = 0.8 * x + 3
ax[1].plot(x, pre_line2, 'g-')

#绘制预测值与真实值的差
y_pre2 = 0.8 * np.array(x_true) + 3
ax[1].plot(x_true, y_pre2, 'o'# 'o'表示用圆圈标记点
ax[1].plot((x_true, x_true), (y_true, y_pre2), 'r-')

#for i in range(len(y_true)):
#    print((y_true[i]  - y_pre2[i]) ** 2)
#print(y_pre2)

# 添加标题和轴标签
ax[1].set_xlim(55,145)
ax[1].legend(labels=['真实房价', '预测直线', '预测房价', '真实值与预测值误差'])
ax[1].axis('equal')
ax[1].set_title('历史房价曲线')
ax[1].set_xlabel('面积')
ax[1].set_ylabel('房价')

# 显示图表
plt.show()

假设图一中的直线方程为:y = 0.6x + 23  我们将面积代入方程,可以计算出预测房价,如下表所示: 房价数据

面积

真实房价

预测房价

真实值与预测值之差的平方

100

90

83

49

115

95

92

9

140

100

107

49

65

50

62

144

80

80

71

81

 由于真实房价与预测房价的差值,有正有负,为了不让正负差值互相抵消,我们使用真实值与预测值的平方差之和来描述真实值与预测值的差异。 我们使用真实值与预测值之差的平方之和,来评估预测值和真实值的差异: 预测直线1的方差之和的平均值为 = (49+9+49+144+81)/5 = 332/5 = 66.4

图二同理,假设图二的直线方程为:y = 0.8x + 3  我们将面积代入方程,可以计算出预测房价,如下表所示: 房价数据

面积

真实房价

预测房价

真实值与预测值之差的平方

100

90

83

49

115

95

95

0

140

100

115

225

65

50

55

25

80

80

67

169

预测直线2的方差之和的平均值为 = (49+0+225+25+169)/5 = 468/5 = 93.6

通过对比两条预测直线的方差之和的平均值,可以得到预测直线1预测的数值更为准确。

根据以上例子归纳,假设预测直线为:y=wx+b

房子面积x1,x2,x3...xm,

房子售价真实值y1,y2,y3...ym,

房子售价预测值y1,y2,y3...ym,

则该预测直线的方差之和的平均值为:

Loss = 1my1-y12+y2-y22+y3-y32+...+ym-ym2

Loss = 1mwx1+b-y12+wx2+b-y22+wx3+b-y32+...+wxm+b-ym2

也可以写做:

Loss = 1mi=0mwxi+b-yi2

为了让计算更简便,我们通常使用系数12乘以方差之和,这样不会影响公式的结果,但是求导时,可以消去系数,便于计算。

Loss = 12mi=0mwxi+b-yi2

10.6 梯度下降

梯度下降是一种优化算法,通过迭代地沿着目标函数梯度的反方向调整参数,以最小化损失函数并找到最优解。

我们可以把梯度下降想像为下山的过程,第一步是寻找下坡的方向,第二步是移动一段距离,直到走到最低点。

10.6.1 梯度下降的直观表示

import numpy as np
import matplotlib.pyplot as plt

# 定义函数和其导数(用于计算切线斜率)
def func(x):
    return 0.25 * x**2

def derivative(x):
    return 0.5 * x

# x 范围
x = np.linspace(-6, 6, 400)
y = func(x)

# 计算 x = 0 x = 1 处的切线
x0 = -4
x1 = 4
slope_0 = derivative(x0)
slope_1 = derivative(x1)
y_tangent_0 = slope_0 * (x - x0) + func(x0)  # 切线方程 y = slope * (x - x0) + func(x0)
y_tangent_1 = slope_1 * (x - x1) + func(x1)

# 创建包含两个子图的图表
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

# 子图1:函数 y = x^2 和切线
ax1.plot(x, y, label="y = 0.25*x^2", color="blue")
ax1.plot(x, y_tangent_0, label="Tangent at x = -4", color="red", linestyle="--")
#ax1.plot(x, y_tangent_1, label="Tangent at x = 1", color="green", linestyle="--")
ax1.scatter([x0], [func(x0)], color="black"# 标出切点
ax1.annotate('原函数:$y = \\frac{1}{4}x^2$\n导数为:$y = \\frac{1}{2}x$\n$x = -4$\n梯度为:$\\nabla = \\frac{1}{2}x = -2$\nx的值调整为:$x = x - \\nabla = -2$',
             xy=(x0, func(x0)), xytext=(-4, 8),
            arrowprops=dict(facecolor='black', shrink=0.05), fontsize=20, ha='left')
#ax1.set_title("y = x^2 with Tangents at x=0 and x=1")
ax1.set_ylim(-0.5,10)
ax1.set_xlabel("x")
ax1.set_ylabel("y")
ax1.legend()
ax1.grid(True)

# 子图2:切线的单独展示
ax2.plot(x, y, label="y = x^2", color="blue")
#ax2.plot(x, y_tangent_0, label="Tangent at x = 0", color="red", linestyle="--")
ax2.plot(x, y_tangent_1, label="Tangent at x = 1", color="green", linestyle="--")
ax2.scatter([x1], [func(x1)], color="black"# 标出切点
ax2.annotate('原函数:$y = \\frac{1}{4}x^2$\n导数为:$y = \\frac{1}{2}x$\n$x = 4$\n梯度为:$\\nabla = \\frac{1}{2}x = 2$\nx的值调整为:$x = x - \\nabla = 2$',
             xy=(x1, func(x1)), xytext=(-4, 8),
            arrowprops=dict(facecolor='black', shrink=0.05), fontsize=20, ha='left')
ax2.set_title("Tangents at x=0 and x=1")
ax2.set_ylim(-0.5,10)
ax2.set_xlabel("x")
ax2.set_ylabel("y")
ax2.legend()
ax2.grid(True)

plt.tight_layout()
plt.show()

10.6.2 学习率(Learning Rate)

尝试将学习率分别设置为: learning_rate = 0.8 learning_rate = 1.005 learning_rate = 1 learning_rate = 0.1 运行程序,看看结果会如何

import numpy as np
import matplotlib.pyplot as plt

# 定义损失函数和它的导数
def loss_function(x):
    return x**2  # 一个简单的二次损失函数 y = x^2

def gradient(x):
    return 2 *# 导数 dy/dx = 2x

# 初始化参数
x = -1.5  # 起始点
learning_rate = 0.8  # 学习率
iterations = 20  # 迭代次数
x_values = [x]  # 记录每次迭代的x
loss_values = [loss_function(x)]  # 记录每次迭代的损失值

# 梯度下降
for _ in range(iterations):
    x = x - learning_rate * gradient(x)  # 更新参数
    x_values.append(x)
    loss_values.append(loss_function(x))

# 绘制损失函数和梯度下降路径
x_range = np.linspace(-1.7, 1.7, 400)
y_range = loss_function(x_range)

plt.figure(figsize=(10, 6))
plt.plot(x_range, y_range, label="Loss Function $y = x^2$")
plt.scatter(x_values, loss_values, color="red", label="Gradient Descent Steps")
plt.plot(x_values, loss_values, color="red", linestyle="--", alpha=0.6)

# 添加标签和图例
plt.title("Gradient Descent Optimization")
plt.xlabel("Parameter x")
plt.ylabel("Loss")
plt.legend()
plt.grid(True)
plt.show()

10.7 简化的损失函数,只考虑斜率w,暂时忽略截距b

现在我们使用梯度下降的方法,来找到使得Loss函数最小的w

现在我们使用Loss函数来描述真实值与预测值之间的差异,那么我们的目标可以转化为: wb的值,使得Loss函数的值最小: #### Loss = 12mi=0mwxi+b-yi2  式中xy训练集中的数据,均为已知数。

简化的Loss函数:为了便于理解,我们先忽略参数b的值: #### Loss = 12mi=0mwxi-yi2

我们将训练集中的数据代入:

Loss = 12*5w*100-902+w*115-952+w*140-1002+w*65-502+w*80-802

整理得:

Loss = 11053450w2-87150w+36025

10.7.1 简化的损失函数图像,只考虑斜率,忽略截距

绘制损失函数图像,得到关于w的损失函数,图像的X轴表示直线方程的斜率,Y轴表示预测值于真实值的差异(Loss值):

import numpy as np
import matplotlib.pyplot as plt

# 定义 w 的范围
w = np.linspace(-100, 100, 400# 0100,生成400个点

# 计算对应的 y
y = (53450 * w**2 - 87150 * w + 36025) / 10

# 绘制函数图像
plt.plot(w, y, label='Loss')

# 添加图例
plt.legend()

# 添加标题和轴标签
plt.title('关于w的损失函数图像')
plt.xlabel('w')
plt.ylabel('Loss')

# 显示图表
plt.show()

10.7.2 简化的损失函数梯度下降的直观表示

目标:找到使得Loss值最小的w的值 现在我们可以使用梯度下降的方法,来找到使得Loss值最小的w的值,我们先给w一个随机的初始位置,然后让w沿着梯度的方向,不断下降,直到收敛于Loss值的最低点。

import numpy as np
import matplotlib.pyplot as plt
import random

# 定义损失函数和它的导数
def loss_function(x):
    return (53450 * x * x - 87150 * x + 36025) / 10  # Loss函数

def gradient(x):
    return (53450 * x - 43575) / 5  # Loss函数的导数

# 初始化参数
x = random.randint(-100, 100# 起始点
x = 100
learning_rate = 0.00001  # 学习率
iterations = 100 # 迭代次数
x_values = [x]  # 记录每次迭代的x
loss_values = [loss_function(x)]  # 记录每次迭代的损失值

# 梯度下降
for _ in range(iterations):
    x = x - learning_rate * gradient(x)  # 更新参数
    x_values.append(x)
    loss_values.append(loss_function(x))

# 绘制损失函数和梯度下降路径
x_range = np.linspace(-100, 100, 400)
y_range = loss_function(x_range)

#x_values = np.round(x_values, 1)
#loss_values = np.round(loss_values, 1)
plt.figure(figsize=(10, 6))
plt.plot(x_range, y_range, label="Loss Function")

print(x_values[-20:])
plt.scatter(x_values, loss_values, color="red", label="Gradient Descent Steps")
plt.plot(x_values, loss_values, color="red", linestyle="--", alpha=0.6)

# 添加标签和图例
#plt.xlim(-100, 100)
#plt.ylim(-100, 100)
plt.title("Gradient Descent Optimization")
plt.xlabel("Parameter x")
plt.ylabel("Loss")
plt.legend()
plt.grid(True)
plt.show()

10.8 完整的损失函数

损失函数的公式为:

Loss = 12mi=0mwxi+b-yi2

我们将数据集中的数据带入: #### Loss = 12mw100+b-902+w115+b-952+w140+b-1002+w65+b-502+w80+b-802 整理得: #### Loss = 53450w2+5b2+1000wb+87150w+730b+36025

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 数据
X = np.array([1, 1.15, 1.40, 0.65, 0.80])  # 面积,单位/百平方
y = np.array([0.90, 0.95, 1.00, 0.50, 0.80])  # 房价:单位/百万元

# 梯度下降参数
learning_rate = 0.1
iterations = 1000
n = len(X)

# 初始化参数
w = 0.0  # 斜率
b = 0.0  # 截距

# 记录每次迭代的wbloss
ws, bs, losses = [], [], []

# 计算损失函数(均方误差)
def compute_loss(w, b):
    y_pred = w * X + b
    return np.mean((y - y_pred) ** 2)

# 梯度下降过程
for i in range(iterations):
    # 计算预测值
    y_pred = w * X + b
   
    # 计算损失
    loss = compute_loss(w, b)
    losses.append(loss)
   
    # 计算梯度
    dw = -2/n * np.sum(X * (y - y_pred))
    db = -2/n * np.sum(y - y_pred)
   
    # 更新参数
    w -= learning_rate * dw
    b -= learning_rate * db
   
    # 记录当前的w, bloss
    ws.append(w)
    bs.append(b)

# 绘制梯度下降的过程
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')

# 创建网格用于绘制损失函数
w_range = np.linspace(-1, 2, 100)
b_range = np.linspace(-2, 2, 100)
W, B = np.meshgrid(w_range, b_range)
Z = np.array([compute_loss(w_, b_) for w_, b_ in zip(np.ravel(W), np.ravel(B))])
Z = Z.reshape(W.shape)

# 绘制损失函数表面
ax.plot_surface(W, B, Z, cmap='viridis', alpha=0.6)

# 绘制梯度下降路径
ax.plot(ws, bs, losses, color="red", marker="o", markersize=5, label="Gradient Descent Path")

# 设置标签
ax.set_xlabel('w')
ax.set_ylabel('b')
ax.set_zlabel('Loss')
ax.set_title('Gradient Descent Process')

# 显示图例
ax.legend()

plt.show()

import numpy as np
import matplotlib.pyplot as plt

# 数据
X = np.array([1, 1.15, 1.40, 0.65, 0.80])  # 面积
y = np.array([0.90, 0.95, 1.00, 0.50, 0.80])  # 房价

# 梯度下降参数
learning_rate = 0.01
iterations = 10000
n = len(X)

# 初始化参数
w = 0.0  # 斜率
b = 0.0  # 截距

# 记录损失值
losses = []

# 梯度下降过程
for i in range(iterations):
    # 计算预测值
    y_pred = w * X + b
   
    # 计算损失函数(均方误差)
    loss = np.mean((y - y_pred) ** 2)
    losses.append(loss)
   
    # 计算梯度
    dw = -2/n * np.sum(X * (y - y_pred))
    db = -2/n * np.sum(y - y_pred)
   
    # 更新参数
    w -= learning_rate * dw
    b -= learning_rate * db

# 输出最终的斜率和截距
print(f"斜率 (w): {w}")
print(f"截距 (b): {b}")

# 绘制损失函数变化
plt.plot(losses)
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.title('Loss Function Convergence')
plt.show()

# 绘制回归直线
plt.scatter(X, y, color='blue', label='Data')
plt.plot(X, w * X + b, color='red', label='Regression Line')
plt.xlabel('Area (sq ft)')
plt.ylabel('Price')
plt.title('Linear Regression Fit')
plt.legend()
plt.show()

10.9 梯度下降总结

根据上述分析,我们可以推导出权重更新的规则。

损失函数:

wb对于Loss(预测误差)的函数,其中xy为数据集,是已知的,m为数据集的样本数量。

Lw,b=12mi=0mwxi+b-yi2

损失函数对w方向的偏导数:

wLw,b=1mi=0mxiwxi+byi

bLw,b=1mi=0myiwxi+byi

每次更新梯度:

$w^`:=w_0-\alpha\frac{\partial}{\partial w}L(w,b)=w_0-\alpha\frac{1}{m}\sum_{i = 0}^{m}{x}_{i}{(wx_{i}+by_{i})}$

$b^`:=b_0-\alpha\frac{\partial}{\partial b}L(w,b)=b_0-\alpha\frac{1}{m}\sum_{i = 0}^{m}{y}_{i}{(wx_{i}+by_{i})}$

式中xy为已知的数据集,w0,b0的初值为随机值

10.10 线性回归举例

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

#读取数据
df = pd.read_csv('/kaggle/input/new-york-housing-market/NY-House-Dataset.csv')
df = df.head(20)
description = df.describe()
print(description)

area = df.iloc[:, 5].values.reshape(-1, 1# 面积作为特征
price = df.iloc[:, 2].values  # 价格作为目标变量

# 创建线性回归模型并拟合数据
model = LinearRegression()
model.fit(area, price)

# 获取线性回归模型的参数(斜率和截距)
slope = model.coef_[0]
intercept = model.intercept_

# 打印斜率和截距
print(f"斜率(Slope: {slope:.2f}")
print(f"截距(Intercept: {intercept:.2f}")

# 绘制数据散点图
plt.scatter(area, price, color='blue', label='Data Points')

# 绘制线性回归直线
area_line = np.linspace(area.min(), area.max(), 100).reshape(-1, 1)
price_line = model.predict(area_line)
plt.plot(area_line, price_line, color='red', label=f'Regression Line: price = {slope:.2f}*area + {intercept:.2f}')

# 添加图例
plt.legend()

plt.xlim(0,5000)


# 添加标题和轴标签
plt.title('House Price vs Area')
plt.xlabel('Area (PROPERTYSQFT)')
plt.ylabel('Price')

# 显示图表
plt.show()

10.11 多元线性方程的向量表示:

假设我们使用3个特征(如面积、楼层、房龄)来预测房价:

特征

数值

面积

100

楼层

20

房龄

5

那么线性函数可表示为: #### fx=w0+w1x1+w2x2+w3x3 为方便表示,令x0=1 权重向量W W=w0w1w2w3 特征向量X X=x0x1x2x3

则: $ W^TX ==$

使用向量的形式来表示主要有两个优点: 1、可以让我们的表达式看起来更简洁,便于我们理解和推理。 2、便于矩阵运算和优化,在编程中,使用矩阵运行可以加速计算的过程。

 

notebook链接:https://www.kaggle.com/code/jeanshendev/linear-regression-and-gradient-descent

下载本节的示例代码及文件:linear-regression-and-gradient-descent.ipynb