1 聚类(Clustering)

1.1 K-means

1.1.1 K-means直观理解

绘制一个数据集,其中有30个未标记的训练实例。

第一步:将每个点分配给其最近的质心。随机选取两个点,分别作为两个不同簇的质心,在图中用蓝色和红色的叉号表示。然后,将数据集中的每个点分配给离其最近的质心所在的簇。其中,簇的质心被称为簇质心。

第二步:重新计算质心。针对每个簇,查看该簇内的所有点,并计算这些点的平均值,以此将簇中心移动到所得的平均位置,重新计算质心。

不断重复上述两个步骤,当点所属的簇不再发生显著变化(即点的颜色基本稳定)时,说明 K - means 算法已经收敛。

1.1.2 K-means算法

初始化质心:随机初始化 K 个簇质心\mu _{1}\mu _{2},…,\mu _{K}

迭代过程

  • 分配点到簇质心:对于从 1 到 m 的每个数据点x^{(i)},将其分配到离它最近的簇质心对应的簇索引 c^{(i)}(范围是从 1 到 K)。
  • 移动簇质心:对于从 1 到 K 的每个簇,将该簇的质心\mu _{K}更新为分配到该簇的所有点的平均值。

即使簇之间不是完全分离,K-means算法也能有效地将数据点分组。 

1.1.3 优化目标 

最小化代价函数 

  • c^{(i)}:表示示例x^{(i)}当前被分配到的簇的索引(范围是 1 到 K)。
  • \mu _{K}:表示第 k 个簇的质心。
  • \mu_{c^{(i)}}:表示示例x^{(i)}已被分配到的簇的质心。


def find_closest_centroids(X, centroids):
    """
    计算每个样本的最近质心索引
    参数:
        X (ndarray): (m, n) 输入数据
        centroids (ndarray): k 个质心
    返回:
        idx (array_like): (m,) 最近质心索引
    """
    K = centroids.shape[0]
    idx = np.zeros(X.shape[0], dtype=int)

    # 遍历输入数据中的每个样本
    for i in range(X.shape[0]):
        # 初始化最小距离为正无穷大
        min_distance = float('inf')
        # 遍历每个质心
        for j in range(K):
            # 计算当前样本 X[i] 到第 j 个质心的欧几里得距离
            distance = np.linalg.norm(X[i] - centroids[j])
            # 如果当前距离小于最小距离
            if distance < min_distance:
                # 更新最小距离
                min_distance = distance
                # 更新当前样本对应的最近质心的索引
                idx[i] = j

    return idx
def compute_centroids(X, idx, K):
    """
    通过计算分配给每个质心的数据点的均值,返回新的质心。
    参数:
        X (ndarray):   (m, n) 数据点
        idx (ndarray): (m,) 数组,包含 X 中每个示例对应的最近质心的索引。
                       具体而言,idx[i] 包含距离示例 i 最近的质心的索引
        K (int):       质心的数量

    返回:
        centroids (ndarray): (K, n) 计算得到的新质心
    """

    m, n = X.shape

    centroids = np.zeros((K, n))

    for k in range(K):
        # 找出所有分配到第 k 个质心的数据点
        points_in_cluster = X[idx == k]
        # 如果该簇中有数据点,则计算它们的均值作为新的质心
        if len(points_in_cluster) > 0:
            centroids[k] = np.mean(points_in_cluster, axis=0)

    return centroids

1.1.4 初始化K-means

随机初始化:

  1. 选择簇的数量 K,且 K 要小于训练实例的总数 m。
  2. 从训练实例中随机挑选 K 个实例。
  3. 将 K 个簇的初始质心\mu _{1}\mu _{2},…,\mu _{K}设置为随机挑选出的这 K 个训练实例。
def kMeans_init_centroids(X, K):
    """
    此函数用于初始化 K 个质心,这些质心将在数据集 X 上的 K-means 算法中使用。
    参数:
        X (ndarray): 数据点
        K (int): 质心/簇的数量

    返回:
        centroids (ndarray): 初始化后的质心
    """

    # 随机打乱样本的索引顺序
    randidx = np.random.permutation(X.shape[0])

    # 选取前 K 个样本作为质心
    centroids = X[randidx[:K]]

    return centroids

尝试多个随机初始化,试图找到最好的局部最优解。如下图所示,K=3,运行K-means算法得到三个不同的簇划分。计算这三个解的成本函数 J,用K-means算法找到的所有这三种簇的选择中,J1 点距簇中心的平方距离相对较小,所以成本 J 相对较小。

对于到i=1到100 {

  • 随机初始化 K - means 算法
  • 运行 K - means 算法
  • 计算代价函数。

}

选择使代价最低的簇集合。

1.1.5 选择聚类的个数

肘部法则

随着 K 值的增加,代价函数 J 的值逐渐减小。在 K = 3 附近,曲线出现明显的拐点,类似人的肘部,这个拐点被称为 “elbow”(肘部)。根据肘部法则,此时的 K = 3 可能是一个合适的簇数量选择。

有时,在使用 K - means 算法进行聚类以服务于某些后续/下游用途时,需要根据衡量其在后续目的中的表现优劣的指标来评估 K - means 算法的效果。

如下图,如果后续对 T 恤尺码分类的精细度要求较高,可能 K = 5 更合适;如果要求相对简单,K = 3 可能就已足够。

2 异常检测(Anomaly detection)

2.1 发现异常事件

异常检测示例

如图介绍了飞机发动机的特征,x_{1}表示产生的热量,x_{2}表示振动强度。

数据集包含多个样本,新引擎的特征表示为 x_{test},这是一个需要被评估的新样本。

图中有多个红色叉号表示数据集中的样本点,其中绿色标注为 “ok” 的点表示正常的数据点,符合数据的一般分布;绿色标注为 “anomaly” 的点表示异常点,其位置明显偏离其他大部分数据点。

进行异常检测最常见的方法是密度估计

蓝色的同心椭圆表示通过模型p(x)估计出的密度分布,越靠近中心密度越高。

2.2 高斯(正态)分布

高斯分布是一种常见的连续概率分布,其形状为钟形曲线。

x是一个数,如果x服从均值为\mu、方差为\sigma ^{2}的高斯分布。

概率密度函数:

p(x) = \frac{1}{\sqrt{2\pi\sigma}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}

  • μ:表示均值,是曲线的中心位置。
  • σ:表示标准差,决定了曲线的宽度。标准差越大,曲线越宽;标准差越小,曲线越窄。

 高斯分布中的参数估计

\mu = \frac{1}{m} \sum_{i=1}^{m} x^{(i)}

\sigma^2 = \frac{1}{m} \sum_{i=1}^{m} (x^{(i)} - \mu)^2

def estimate_gaussian(X): 
    """
    计算数据集中所有特征的均值和方差
    参数:
        X (ndarray): (m, n) 数据矩阵
    
    返回:
        mu (ndarray): (n,) 所有特征的均值
        var (ndarray): (n,) 所有特征的方差
    """

    m, n = X.shape
    mu = np.mean(X, axis=0)
    var = np.var(X, axis=0)
        
    return mu, var

2.3 异常检测算法

  1. 选择特征:选择n个特征 x_{i}​,这些特征被认为可能指示异常样本。
  2. 拟合参数:\mu _{1},…,\mu _{n}\sigma _{1}^{2},…,\sigma _{n}^{2}其中,\mu _{j}是第j个特征的均值
  3. 计算概率密度
  4. 判断异常:如果 p(x)<ϵ,则认为该样本是异常的。

2.4 开发和检测异常检测系统

2.4.1 实数评估(real-number evaluation)

当开发学习算法(选择特征等)时,如果我们有一种方法来评估我们的学习算法,做出决策会更容易。

假设我们有一些标记数据,包括异常和非异常样本,y=0表示正常样本,y=1表示异常样本。

2.4.2 划分数据集

飞机发动机监测中划分数据集,如下图有两种方法,当数据集很小,尤其是当数据集中的异常数量很少时,采用第二种方法。

2.4.3 算法评估

在训练集 x(1),x(2),…,x(m)上拟合模型 p(x)

对于交叉验证或测试集中的每个样本 x,预测结果 y 为1或0

可能的评估指标包括:(具体参考【吴恩达机器学习】高级学习算法-CSDN博客 7 倾斜数据集)

  • 真正例、假正例、假负例、真负例。这些指标用于描述模型预测结果与实际情况的符合程度。
  • 精确率 / 召回率。精确率衡量预测为正例的样本中实际为正例的比例,召回率衡量实际正例中被正确预测为正例的比例。
  • F1 值,是精确率和召回率的调和平均数,能够综合评估模型的性能。

可以使用交叉验证集来选择合适的参数ε

select_threshold 函数用于在给定验证集的预测概率 p_val 和真实标签 y_val 的情况下,通过遍历一系列可能的阈值 epsilon,计算每个阈值对应的 F1 分数,最终返回能使 F1 分数达到最大的阈值 epsilon 以及对应的最大 F1 分数。

def select_threshold(y_val, p_val): 
    """
    基于验证集的结果(p_val)和真实标签(y_val),找到用于选择异常值的最佳阈值。
    参数:
        y_val (ndarray):验证集上的真实标签
        p_val (ndarray):验证集上的结果

    返回:
        epsilon (float):选择的阈值
        F1 (float):选择epsilon作为阈值时的F1分数
    """

    # 初始化最佳阈值和最佳 F1 分数为 0
    best_epsilon = 0
    best_F1 = 0
    F1 = 0
    
    # 计算步长,用于遍历阈值范围
    step_size = (max(p_val) - min(p_val)) / 1000
    
    # 遍历从最小预测概率到最大预测概率的所有可能阈值,步长为 step_size
    for epsilon in np.arange(min(p_val), max(p_val), step_size):
            
        # 根据当前阈值 epsilon 进行预测
        predictions = (p_val < epsilon).astype(int)
        
        # 计算真正例(True Positives):预测为正例且实际为正例的数量
        tp = np.sum((predictions == 1) & (y_val == 1))
        
        # 计算假正例(False Positives):预测为正例但实际为负例的数量
        fp = np.sum((predictions == 1) & (y_val == 0))
        
        # 计算假负例(False Negatives):预测为负例但实际为正例的数量
        fn = np.sum((predictions == 0) & (y_val == 1))
        
        # 计算精确率(Precision)
        prec = tp / (tp + fp) if (tp + fp) > 0 else 0
        
        # 计算召回率(Recall)
        rec = tp / (tp + fn) if (tp + fn) > 0 else 0
        
        # 计算 F1 分数
        F1 = 2 * prec * rec / (prec + rec) if (prec + rec) > 0 else 0
        
        # 如果当前 F1 分数大于之前记录的最佳 F1 分数
        if F1 > best_F1:
            # 更新最佳 F1 分数
            best_F1 = F1
            # 更新最佳阈值
            best_epsilon = epsilon
        
    return best_epsilon, best_F1

2.5 异常检测 vs 监督学习 

  • 异常检测
    • 样本数量:正样本(y=1,代表异常样本)数量极少,一般在 0 到 20 个较为常见,而负样本(y=0,代表正常样本)数量庞大。
    • 样本特征:异常情况种类繁多,算法很难从少量的正样本中学习到异常的特征模式,而且未来出现的异常样本可能与已见过的异常样本完全不同。
    • 应用场景:以欺诈检测(Fraud)为例,欺诈行为往往是少数且形式多样,难以通过少量的欺诈样本准确建模。
  • 监督学习
    • 样本数量:拥有大量的正样本和负样本。
    • 样本特征:正样本数量足够,使得算法能够学习到正样本的特征模式,并且未来遇到的正样本很可能与训练集中的正样本相似。
    • 应用场景:以垃圾邮件检测(Spam)为例,由于存在大量的垃圾邮件(正样本)和正常邮件(负样本),模型可以通过学习这些样本的特征,对新邮件进行准确分类 。

异常检测和监督学习在不同应用场景中的应用 

  • 异常检测
    • 欺诈检测
    • 制造业 - 发现新的未知缺陷
    • 数据中心机器监控
  • 监督学习
    • 垃圾邮件分类
    • 制造业 - 发现已知的缺陷
    • 天气预测
    • 疾病分类

2.6 选择要使用的特征

将非高斯分布的数据转换为更接近高斯分布的数据,有助于后续机器学习模型的应用。

异常检测中的错误分析

最常见问题:正常样本和异常样本计算出的概率p(x)相近(例如都较大)。这就导致模型难以通过概率值区分正常样本和异常样本,无法准确进行异常检测。

假设我们正在进行欺诈行为检测,x1代表用户的交易数量。有时,某个用户的交易数量可能与其他正常用户相似,仅依据此特征很难判断其是否存在欺诈行为。但如果我们发现这个用户有一些疯狂的打字速度,添加一个新功能x2,即用户的打字速度,情况就会有所不同。当发现某个用户的打字速度异常高时,异常检测算法便能更容易地识别出该异常用户。新增的打字速度这一特征,能让学习算法拟合高斯分布,为正常区域的点赋予较高的概率,从而凸显异常点。

因此,我们可以通过训练模型,观察交叉验证集中未被成功检测出的异常样本。分析这些样本,思考能否由此创建新的特征,使得算法在这些新特征上,能够发现异常样本具有异常大或异常小的值,进而准确地将它们标记为异常 。

如果建立一个异常系统,监测数据中心的计算机。

选择那些在异常情况下可能会出现异常大或异常小值的特征。

基于p(x)的特征选择:对于正常样本,p(x)的值应该较大;而在交叉验证集中,当出现异常样本时,p(x)的值应该变小 。

3 推荐系统

3.1 进行推荐

在电影评分预测场景中,存在一定数量的用户和电影项目,我们的目标是向用户推荐他们可能感兴趣的电影。

  • n_{u} :用户数量。
  • n_{m} :电影数量。
  • r(i,j) :用户 j 是否对电影 i 进行了评分,如果进行了评分则为 1,否则为 0。
  • y^{(i,j) }:用户 j 对电影 i 的评分,仅当 r(i,j)=1时定义。

通过分析用户已有的评分数据,对于那些用户尚未评分的电影,我们尝试预测用户对它们的评分。基于这些预测,我们可以向用户推荐那些预测评分较高,例如可能被评为 5 星的电影 。

3.2 协同过滤(Collaborative Filtering)

3.2.1 使用每项特征

每部电影有两个特征:浪漫和动作。特征值范围从0到1,表示电影在该特征上的程度。

  •  x^{(i)}:每部电影的特征向量,其中 i 是电影的索引。
  • w(j),b(j):用户 j 的参数向量和偏置参数。

对于用户1(Alice),预测电影 i 的评分为:w^{(1)} \cdot x^{(i)} + b^{(1)}(类似于线性回归)

对于电影 "Cute puppies of love"(特征向量 x^{(3)}=[0.99,0]^{T}),预测评分为:

w^{(1)} \cdot x^{(3)} + b^{(1)} = 4.95

对于用户 j,预测电影 i 的评分为:w^{(j)} \cdot x^{(i)} + b^{(j)}

代价函数
n_{u}用户数量
n_{m}电影数量
r(i,j)如果用户 j 评价了电影 i,则 r(i,j)=1;否则为 0。
y^{(i,j)}用户 j 对电影 i 的评分(如果已定义)
w^{(j)}用户 j 的参数向量
b^{(j)}用户 j 的偏置项
x^{i}电影 i 的特征向量

3.2.2 协同过滤算法

x1和x2未知,可以根据已知评分,借助用户参数向量和特征向量的计算,来推断电影特征向量,进而预测未知评分。

已知用户的参数向量和偏置  ,目的是学习电影的特征向量。

未知用户的参数向量和偏置,第一个代价函数用于学习用户的参数向量和偏置,第二个代价函数用于学习电影的特征向量,最后将它们整合。

def cofi_cost_func(X, W, b, Y, R, lambda_):
    """
    返回协同过滤算法的代价函数值
    参数:
      X (ndarray (电影数量, 特征数量)): 电影特征矩阵
      W (ndarray (用户数量, 特征数量)): 用户参数矩阵
      b (ndarray (1, 用户数量)): 用户参数向量
      Y (ndarray (电影数量, 用户数量)): 用户对电影的评分矩阵
      R (ndarray (电影数量, 用户数量)): 矩阵,若第 i 部电影被第 j 个用户评分,则 R(i, j) = 1
      lambda_ (float): 正则化参数
    返回:
      J (float): 代价函数值
    """
    
    # 获取评分矩阵 Y 的行数(电影数量)和列数(用户数量)
    nm, nu = Y.shape
    J = 0
    
    # 计算预测评分矩阵
    prediction = np.dot(X, W.T) + b
    # 仅计算有评分的电影的平方误差
    error = (prediction - Y) ** 2
    error = error * R
    # 计算未正则化的代价函数值
    J_unregularized = np.sum(error) / 2
    # 计算正则化项
    reg_W = (lambda_ / 2) * np.sum(np.square(W))
    reg_X = (lambda_ / 2) * np.sum(np.square(X))
    # 计算总代价函数值
    J = J_unregularized + reg_W + reg_X

    return J
梯度下降

3.2.3 二进制标签:收藏、点赞和点击

二进制标签的一些应用示例

  1. 用户 j 在看到某个项目后是否购买了它?

  2. 用户 j 是否收藏或喜欢某个项目?

  3. 用户 j 是否在某个项目上花费了至少30秒的时间?

  4. 用户 j 是否点击了某个项目

评分的含义

  • 1:用户在看到项目后进行了互动。
  • 0:用户在看到项目后没有进行互动。
  • ?:项目尚未展示给用户。

在线性回归中,我们直接预测一个连续值 y^{(i,j)},通过线性组合来实现。

在二分类问题中,我们需要预测一个概率值,表示某个事件发生的可能性。使用 sigmoid 函数 g(z)将线性组合映射到 [0, 1] 区间内,表示 y^{(i,j)}=1 的概率。

二元应用场景下协同过滤的代价函数

3.2.4 寻找相关项目

项目 i 的特征 x^{(i)} 很难解释。

为了找到与项目 i 相关的其他项目,需要找到特征 x^{(k)}  与 x^{(i)} 相似的项目 k 。

计算项目 k 和项目 i 特征向量之间的欧几里得距离,距离越小,说明两个项目的特征越相似,也就认为它们是相关项目。

协同过滤的局限性

冷启动问题

  • 如何对很少有用户评分的新项目进行排名?
  • 如何向新用户展示合理的内容,这些用户只对少数项目进行了评分?

利用辅助信息

  • 项目:类型、电影明星、制片公司等。
  • 用户:人口统计信息(年龄、性别、地点)、表达的偏好等。

3.3 推荐系统实现

3.3.1 均值归一化(Mean Normalization)

如图用户 Eve 是一个尚未对任何电影进行评分的新用户。在训练协同过滤算法时,最小化目标函数意味着要让参数w尽可能小。虽然我们没有对参数b进行正则化约束,但在初始化时将其设为 0,最终其值也会趋近于 0。当w和b都等于 0 时,算法会预测新用户对所有电影的评分均为 0,然而这样的预测结果实际帮助不大。

我们可以通过均值归一化来优化预测效果。

原始评分矩阵:左侧的矩阵表示五个用户对五部电影的评分。问号“?”表示该用户未对该电影进行评分。

均值向量 μ:每部电影的平均评分。

归一化后的评分矩阵:右侧的矩阵是经过均值归一化处理后的评分矩阵。每个单元格的数值是原始评分减去对应电影的平均评分。

用户5(Eve)没有对任何电影进行过评分,因此她的权重向量和偏置项都初始化为0。

对于用户5的预测评分公式简化为:y^{(i, 5)} = 0 \cdot x^{(i)} + 0 + \mu_i = \mu_i

这意味着用户5对所有电影的预测评分等于这些电影的平均评分。

以上例子中我们所做的是将每一行归一化为平均值为0,以便为新用户给出合理的评分。

我们还可以将每一列归一化为平均值为0,以帮助处理那些没有被观看或评分的电影的情况。

3.3.2  协同过滤的TensorFlow 实现

使用 TensorFlow 实现自定义训练循环,如图展示了梯度下降算法的TensorFlow实现。

  1. 变量定义:w = tf.Variable(3.0) 将 w 定义为一个 TensorFlow 变量,这是需要优化的参数;x = 1.0 是输入值,y = 1.0 是目标值,alpha = 0.01 是学习率,iterations = 30 表示训练迭代次数。
  2. 记录计算步骤:使用 TensorFlow 的梯度带(Gradient Tape)来记录用于计算损失值 J 的步骤,以实现自动求导。
  3. 计算梯度:使用梯度带计算损失相对于参数 w 的梯度,即 dJdw
  4. 更新参数:通过更新参数 w 的值(用参数 w 减去学习率与梯度的乘积)来执行一次梯度下降步骤,以减少损失。TensorFlow 变量需要使用特殊的函数才能进行修改。
  • Auto Diff:自动微分(Automatic Differentiation)
  • Auto Grad:自动梯度(Automatic Gradient)

使用优化算法,如Adam优化算法。如图展示了在 TensorFlow 中实现梯度下降算法的过程。

  • 指定使用的是 Keras 优化器。这里实例化了一个 Adam 优化器,并将学习率设置为 0.1。
  • Ynorm:经均值归一化后的评分矩阵。
  • R:通常表示一个用户-项目(User-Item)评分矩阵。
  • zip(grads, [X, W, b]) 将梯度列表 grads 和变量列表 [X, W, b] 中对应位置的元素组合成一个个元组,每个元组包含一个梯度和对应的变量。
  • optimizer.apply_gradients() 是优化器对象的一个方法,它接受一个由梯度和变量组成的元组列表作为输入,根据优化器的更新规则,使用这些梯度来更新对应的变量。

3.4 基于内容的过滤(Content-based Filtering) 

3.4.1 协同过滤 vs 基于内容的过滤

协同过滤:推荐项目是基于与你给出相似评分的其他用户的评分。

基于内容的过滤:推荐项目是基于用户和项目的特征来寻找合适的匹配。

用户特征和物品特征的例子

用户特征:年龄、性别、国家、观看过的电影数量、每个类型的平均评分

电影特征:上映年份、类型、评论、平均评分

用户特征向量和电影特征向量的大小可能不相同

预测用户 j 对电影 i 评分的公式:w^{(j)} \cdot x^{(i)} + b^{(j)},在基于内容的过滤中,去掉 b^{(j)} 通常不会损坏其性能。

V_{u}^{(j)} 代替w^{(j)}V_{u}^{(j)}是一个向量,是从用户特征x_{u}^{(j)}中计算出的数字列表,u 下标代表一个用户; 用 V_{m}^{(i)} 代替x^{(i)}V_{m}^{(i)} 也是一个向量,是从电影特征x_{m}^{(i)}中计算出的数字列表 ,m下标代表一个电影。

V_{u}^{(j)}捕获用户的偏好,表示用户对不同类型的喜好程度;V_{m}^{(i)}表示电影的不同属性值,如多少钱一部爱情片,多少钱一部动作片等。

尽管x_{u}^{(j)}x_{m}^{(i)}的维度可能不同,V_{u}^{(j)}V_{m}^{(i)}必须维度相同,这样才能对它们取点积,通过点积运算来寻找两者之间的匹配度。

3.4.2 深度学习在基于内容的过滤中的应用

神经网络架构:

  • 用户网络:输入是用户特征向量 X_{u} (如年龄、性别、职业等),经过三个隐藏层,神经元数量依次为 128、64、32 ,输出用户特征表示向量 V_{u} 。
  • 电影网络:输入为电影特征向量 X_{m} (如类型、导演、演员等),经过三个隐藏层,神经元数量依次是 256、128、32,输出电影特征表示向量 V_{m}  。

预测过程:

将用户网络输出的 V_{u}^{(j)}和电影网络输出的V_{m}^{(i)} 做点积运算,通过函数 g(v_u^{(j)} \cdot v_m^{(i)}) 来预测y^{(i,j)} 为1的概率,可以代表用户 j 对电影 i 的是否喜欢。

代价函数部分:

使用学习到的用户和物品(电影)向量来找到相似的电影

为了找到与电影 i 相似的电影,可以计算其他电影 k 的向量 V_{m}^{(k)}与电影 i 的向量 V_{m}^{(i)}之间的平方距离,如果这个距离很小,则说明电影 k 与电影 i 很相似。

 两个向量 V_{m}^{(k)}​ 和 V_{m}^{(i)} 之间的平方距离:\left\| \mathbf{v}_m^{(k)} - \mathbf{v}_m^{(i)} \right\|^2 = \sum_{l=1}^n \left( v_{ml}^{(k)} - v_{ml}^{(i)} \right)^2

def sq_dist(a, b):
    """
    返回两个向量之间的平方距离
    参数:
      a (ndarray (n,)): 具有 n 个特征的向量
      b (ndarray (n,)): 具有 n 个特征的向量
    返回:
      d (float): 距离
    """

    # 计算两个向量之间的差值
    diff = a - b
    # 计算差值的平方
    squared_diff = np.square(diff)
    # 计算平方差值的总和
    d = np.sum(squared_diff)
   
    return d

3.4.3 基于内容过滤的TensorFlow 实现

# 创建用户神经网络模型
user_NN = tf.keras.models.Sequential([
    Dense(256, activation='relu'),  
    Dense(128, activation='relu'),  
    Dense(32) 
])

# 创建项目神经网络模型
item_NN = tf.keras.models.Sequential([
    Dense(256, activation='relu'), 
    Dense(128, activation='relu'), 
    Dense(32)  
])

# 创建用户特征输入层
input_user = Input(shape=(num_user_features,))
# 将用户特征输入传递给用户神经网络,并进行L2归一化
vu = user_NN(input_user)
vu = tf.linalg.l2_normalize(vu, axis=1)

# 创建项目特征输入层
input_item = Input(shape=(num_item_features,))
# 将项目特征输入传递给项目神经网络,并进行L2归一化
vm = item_NN(input_item)
vm = tf.linalg.l2_normalize(vm, axis=1)

# 计算用户特征向量和项目特征向量的点积,衡量相似度
output = tf.keras.layers.Dot(axes=1)([vu, vm])

# 定义整个模型,指定输入和输出
model = Model([input_user, input_item], output)

# 选择均方误差作为损失函数
cost_fn = tf.keras.losses.MeanSquaredError()

3.5 高级实现

3.5.1 从大目录中推荐

从大量项目中高效地找到推荐的两个关键步骤:检索和排序

检索:

  • 生成一个包含大约100个项目的候选列表。
    • 例如:
      1. 对于用户最近观看的 10 部电影中的每一部,找到 10 部最相似的电影。
      2. 针对用户观看次数最多的 3 种电影类型,分别找出排名前 10 的电影。
      3. 找出该国最热门的前 20 部电影。
  • 将检索到的项目合并成一个列表,同时去除重复的项目以及用户已经观看过或购买过的项目。

排序:

  • 获取检索到的列表,并使用训练好的模型进行排序。

  • 向用户展示排好序的项目。

检索步骤

  • 检索的项目越多,推荐性能越好,但推荐速度会变慢。
  • 为分析和优化这种权衡关系,需进行离线实验,查看检索更多的项目是否能得出更相关的推荐结果(即向用户展示的项目中,p(y^{(i,j)}) = 1 的情况更多 ) 。

3.5.2 推荐系统的道德使用

推荐系统的目标是什么?

推荐内容:

  • 最有可能被用户评为五星的电影
  • 最有可能被购买的产品
  • 最有可能被点击的广告 + 高竞价
  • 能产生最大利润的产品
  • 能让观看时长达到最长的视频

推荐系统中的伦理考量

  • 旅游行业:推荐系统为更多用户提供良好的旅游体验,这使得业务更有利可图。利润增加后,商家愿意为广告支付更高的竞价,进而又能进一步推广,形成良性循环。
  • 发薪日贷款:发薪日贷款业务通过推荐系统压榨客户以获取更多利润。利润增加后同样会提高广告竞价,持续这一不良循环。

改进建议:不接受来自剥削性企业的广告。

其他问题案例:

  • 最大化用户参与度(例如观看时间)导致大型社交媒体/视频分享网站放大了阴谋论和仇恨/毒性内容。

    改进措施:过滤掉有问题的内容,如仇恨言论、欺诈、诈骗和暴力内容。

  • 一个旨在最大化利润而非用户利益的排名系统能否以透明的方式呈现?

        改进措施:对用户保持透明。

4 强化学习

4.1 强化学习介绍

图中以直升机控制为例,利用强化学习让直升机自主飞行。任务是根据直升机的位置决定如何移动控制杆,因此需要找到一个函数,将直升机的状态映射到一个动作,即确定两个控制杆应该推多远,以保持直升机在空中和飞行中的平衡。

为了实现这一目标,你可以尝试使用监督学习方法,但事实证明,这种方法并不是自主直升机飞行的理想选择。这是因为很难获得足够准确的数据集来训练模型,特别是难以确定在给定状态下正确的动作是什么。因此,在很多控制机器人的任务中,监督学习算法并不适用。

相反,我们使用强化学习。强化学习的一个关键输入是奖励函数,它告诉直升机何时做得好,何时做得不好。强化学习算法的训练过程是这样的:找出如何获得更多的正向奖励和更少的负向奖励的结果。

强化学习之所以强大,是因为我们无需告知直升机具体该怎么做,只需设定奖励函数。这种方式不像指定最优行动那样受限,而是给予了更多的灵活性。在该例中,若直升机飞行状态良好,奖励函数会给予 +1 的正向奖励;若飞行不佳,则给予 -1000 的负向奖励。

强化学习的应用领域

  • 控制机器人
  • 工厂优化
  • 金融(股票)交易
  • 玩游戏(包括电子游戏)

4.2 强化学习形式化

4.2.1 火星探测车示例

  • 状态:状态 1 和状态 6 是终端状态,分别标记为 100 和 40,状态 2、3、4、5 是中间状态。

  • 动作:探测车可以向左移动或向右移动。

  • 奖励函数:如果探测车到达状态 1,获得 +100 的奖励;如果探测车到达状态 6,获得 +40 的奖励;在其他状态下,奖励为 0。

  • 状态转移:可以用元组 (s, a, R (s), s') 表示,具体到这个例子就是 (4, ←, 0, 3) ,其中 4 是当前状态 s,←是动作 a,0 是在当前状态执行该动作获得的奖励 R (s),3 是转移后的新状态 s'。

4.2.2 强化学习中的回报 

回报是对各个时刻奖励的加权求和,权重由折现系数决定 。折现系数是一个小于1的数值,它体现了强化学习算法对未来奖励的 “不耐烦” 特性。如果没有折现,回报可能会过度依赖于初始奖励;而有了折现,后续时刻的奖励需要乘以一个小于1的系数(如0.9),这就使得随着时间推移,未来奖励在总回报中的权重逐渐降低。因此,越早获得奖励,对总回报的贡献越大。

在很多强化学习算法中,常用的折现系数通常非常接近1。但这次实际使用的是 0.5 的折扣率,这会使未来奖励的价值随时间大幅减少。以图片中的例子来说,当折扣率为0.5时计算出的回报,类似于在金融应用中考虑货币时间价值后的收益情况 。从金融角度理解,折现系数就如同利率,体现了货币的时间价值。

你得到的回报取决于奖励,奖励取决于你所采取的行动, 所以回报取决于你采取的行动。

如果探测车走左边,状态 1 为终端状态,从状态4开始,回报是12.5;

从状态3开始,回报是0+(0.5)0+(0.5)^{2}100=25

从状态2开始,回报是0+(0.5)100=50

从状态1开始,它马上就得到了100的奖励;

从状态5开始,回报是0+(0.5)0+(0.5)^{2}0+(0.5)^{3}0+(0.5)^{4}100=6.25

从状态6开始,是一种终结状态,只是得到了奖励40。

如果探测车走右边,状态 6 为终端状态,从状态4开始,回报是10,其他的状态以此类推。

但事实证明,我们不必总是向左走或者总是向右走,如果从状态2、3、4开始,走左边,如果从状态5开始,走右边。

4.2.3 强化学习中的决策与策略制定

我们的目标是想出一个函数,它被称为策略 \pi,它的作用是将任何状态 s 映射到希望采取的行动a。也就是说,策略应用于状态 s,告诉我们在这种情况下应该采取什么行动 a。

强化学习的目标是找到一个策略 π ,该策略能针对每个状态 s,给出应采取的行动 a(a = π(s)) ,从而使回报最大化 。

4.2.4 回顾关键概念 

  • 状态(states)
  • 行动(actions)
  • 奖励(rewards)
  • 折扣系数(discount factor γ)
  • 回报(return)
  • 策略(policy π)

马尔可夫决策过程(MDP)

MDP是强化学习中一个重要的概念,用于描述智能体在一个环境中采取行动以最大化累积奖励的过程。

  • 智能体(Agent):是做出决策的实体,图中用 “Agent π” 表示,π 代表策略,即智能体根据当前状态选择行动的规则 。
  • 环境(Environment / World):是智能体所处的外部世界,智能体与环境进行交互。
  • 状态(state s):表示环境在某一时刻的状况,智能体能够感知到这些状态。
  • 奖励(reward R):是环境根据智能体的行动反馈给智能体的数值信号,用于衡量行动的好坏。
  • 行动(action a):是智能体在某个状态下采取的行为,行动会使智能体从一个状态转移到另一个状态。

箭头表示了信息和动作的流动方向:

  • 智能体根据当前状态选择一个动作。
  • 动作被发送到环境中。
  • 环境根据动作更新状态,并返回一个新的状态和奖励给智能体。

这个过程不断重复,直到达到某个终止条件。MDP的目标是找到一个最优策略π,使得智能体能够最大化其长期累积奖励。

4.3 状态 - 动作价值函数(Q 函数)

4.3.1 状态 - 动作价值函数定义

状态 - 动作价值函数,也称为 Q 函数(Q - function),表示在状态 s 下采取动作 a 一次,然后以最优策略行动后所能获得的回报。即:

  • 从状态 s  开始;
  • 采取动作 a(仅一次);
  • 之后以最优方式行动。

示例分析

  • Q(2,→)=12.5:2→3→2→1
  • Q(2,←)=50:2→1
  • Q(4,←)=12.5:4→3→2→1

  • 最优返回值:从状态 s 出发的最佳返回值是 \max_{a} Q(s, a)
  • 最优动作:在状态 s 下的最佳动作是能够给出 \max_{a} Q(s, a) 的动作 a。
  • 最优 Q 函数Q^*:表示最优的状态-动作价值函数,即在所有状态下选择最优动作时的 Q(s,a)值。

智能体可以依据 Q 函数在不同状态下做出最优动作选择,以获取最大的长期回报。

4.3.2 贝尔曼方程(Bellman Equation)

贝尔曼方程:表示在状态 s 下采取动作 a 的价值 Q(s,a) 等于即时奖励 R(s)加上折扣因子 γ 乘以在新状态 s′ 下采取最优动作 a′ 的价值 Q(s′,a′)。

  • s:当前状态;
  • a:当前动作;
  • s':采取动作 a 后到达的状态;
  • a':在状态 s' 下采取的动作 ;
  • R(s):当前状态 s 对应的奖励。

终端状态时 Q(s,a)=R(s)

贝尔曼方程的本质

4.3.3 随机(概率性)环境

随机环境

在智能体所在的状态 4 处,标注了其执行向左或向右动作时的转移概率:

  • 当智能体选择向左行动时,有 0.9 的概率成功向左转移,有 0.1 的概率向右转移;
  • 当智能体选择向右行动时,有 0.1 的概率向左转移,有 0.9 的概率成功向右转移。

这体现了随机环境的特点,即智能体采取的行动并不一定能确定性地到达预期的状态,而是以一定概率转移到不同状态,增加了环境的不确定性和复杂性。

预期回报

强化学习算法的工作是选择一个策略,最大化预期回报的平均值。

E\left[\max_{a'} Q(s', a')\right] 是后续状态 s' 下执行最优动作 a ' 时的期望最大价值。

4.4 连续状态空间

4.4.1 连续状态应用示例

离散状态与连续状态对比

  • 离散状态:是由一系列分离的、可数的状态组成。例如,火星探测车可能处于几个特定的位置之一,而不能处于这些位置之间的任何其他位置
  • 连续状态:是指系统状态可以在某个区间内任意取值的状态。例如,卡车的位置、速度和角度等参数可以是连续变化的,而不是跳跃式的。图中给出了一个状态向量 s,它包含多个变量:x,y,θ,x˙,y˙,θ˙。这些变量分别代表位置(x, y)、方向角(θ)、以及它们的变化率(x˙,y˙,θ˙),这些都是连续变化的参数。

直升机向量 s 包含以下元素:

  • x,y,z:直升机在三维空间中的位置坐标。
  • ϕ,θ,ω:直升机的姿态角,分别代表俯仰角、偏航角和滚转角。
  • x˙,y˙,z˙:直升机在三个方向上的速度。
  • ϕ˙,θ˙,ω˙:直升机姿态角的变化率,即角速度。

4.4.2 月球着陆器 

月球着陆器目标是安全降落在两个旗帜之间的平坦区域。

向量 s 包含以下元素:

  • x,y:着陆器在二维空间中的位置坐标。
  • x˙,y˙:着陆器在两个方向上的速度。
  • θ:着陆器的姿态角,即相对于水平面的角度。
  • θ˙:着陆器姿态角的变化率,即角速度。
  • l,r:着陆器左右腿是否接地(二进制值)。

奖励函数 :

  • 成功到达着陆平台可获得 100 到 140 的奖励分数。
  • 朝着或远离着陆平台移动会有额外奖励。
  • 如果着陆器坠毁,会得到 -100 的惩罚分数。
  • 实现软着陆可获得 100 的奖励分数。
  • 着陆器的腿接触地面可获得 10 的奖励分数。
  • 启动主发动机,每次会扣 0.3 分。
  • 启动侧推进器,每次会扣 0.03 分。

4.4.3 学习状态 - 价值函数

使用神经网络来计算状态-动作对的Q值,并选择最优动作。

输入向量 \vec{x}

  • 输入向量 \vec{x}包含两部分:状态 s 和动作 a。
  • 状态 s 包括多个变量,如位置、速度和角度等。
  • 动作 a 是一个离散的动作,采用独热编码,例如“不做任何事”、“向左移动”、“主发动机点火”、“向右移动”。

在状态 s 中,使用神经网络计算: Q(s,nothing),Q(s,left),Q(s,main),Q(s,right)

选择使得 Q(s,a) 最大的动作 a。

使用神经网络 f_{w, b}(x) \approx y 来逼近目标Q值,网络的输入是状态和动作对 (s,a),输出是对应的Q值。

深度Q网络(Deep Q-Network, DQN)算法

随机初始化一个神经网络作为 Q(s,a) 的猜测

重复以下步骤:

  • 在月球着陆器环境中执行动作,获取四元组 (s, a, R(s), s')(分别表示当前状态、执行的动作、在该状态下获得的即时奖励、下一个状态)。
  • 存储最近的 10000 个四元组,这种只存储最新示例的技术有时称为“经验回放缓冲区”或者简称为 “回放缓冲区”。

训练神经网络:

  • 使用回放缓冲区中的 10000 个样本创建训练集。
  • 对于每个样本 (s,a,R(s),s′),定义输入 x=(s,a)和目标输出y = R(s) + \gamma \max_{a'} Q(s', a')
  • 训练新的 Q_{new},使Q_{new}(s,a)  尽可能接近 y。

将 Q 更新为 Q_{new}

4.4.4 算法优化:改进的神经网络架构 

  • 输入层:8 个输入单元,对应状态向量 s 的8 个输入变量。
  • 隐藏层:两个隐藏层,每个隐藏层有 64 个单元。
  • 输出层:4 个输出单元,分别对应四个动作。

神经网络的工作是当处于 s 状态时,同时计算四个可能动作对应的 Q 值,这样做效率更高,因为对于给定状态,仅需一次推理,就能获取这四个 Q 值,随后可快速选择能使状态 s 下 Q 值最大化的动作 a。这种方式也提升了计算 \max_{a'} Q(s', a') 的效率。

4.4.5 算法优化:ε- 贪婪策略

在学习过程中如何选择动作?

  • Option 1:
    • 选择使 Q(s,a)最大的动作 a。
  • Option 2:
    • 以概率 0.95 选择使 Q(s,a)最大的动作 a。这种选择被称为“贪心”或“利用”。
    • 以概率 0.05 随机选择一个动作 a。这种选择被称为“探索”。

ε-贪婪策略:

在某个状态 s 下,初始时 ϵ 设为较高值(例如 1.0),此时随机探索的概率较大;然后逐渐减少到较低值(例如 0.01),这意味着随着时间推移,随机采取行动的可能性降低,智能体更倾向于依据对 Q 函数的改进估计来选择合适的动作。

与监督学习算法相比,强化学习算法对参数选择要挑剔得多。

4.4.6 算法优化: 小批量和软更新

小批量处理

事实证明,这个想法既可以加快强化学习算法,也可以加快监督学习算法。

当训练集规模非常庞大时,传统的梯度下降算法可能会变得相当缓慢。小批量梯度下降提供了一种解决方案:不是在每次迭代中使用全部训练样本 m,而是选择一个较小的子集 m′。因此,在每次迭代中,梯度下降只处理这 m′个样本,而不是整个数据集 m,每一步计算所需的时间大幅减少,进而产生了一个更为高效的算法。

小批量梯度下降所做的是在算法的每一次迭代中只查看数据的一个子集。

  • 批量梯度下降:

    • 使用整个数据集来计算梯度,并更新参数。
    • 路径较为平滑,但每次迭代需要处理大量数据,计算成本较高。
  • 小批量梯度下降:

    • 每次迭代只使用一小部分数据(小批量)来计算梯度,并更新参数。
    • 路径较为曲折,但每次迭代所需的时间较少,计算成本低,整体上可以更快地收敛。

强化学习算法中使用小批量处理:

从经验回放缓冲区中选取一个子集,具体挑选 1000 个元组作为例子,以此来构建包含一千个训练样本的集合,用于训练神经网络。事实证明,这种做法能让训练的每次迭代都对模型进行有效更新。虽然在训练过程中可能会因数据的随机性表现得有些 “波动”,但训练速度会大幅提升。总体而言,小批量处理有助于加快强化学习算法的运行效率。

最后对算法还有个改进,可以使它更可靠地收敛,实践发现,这可能会使 Q 值发生非常突然的变化。当你训练一个新的神经网络时,有可能得到的并非一个性能优良的网络。倘若直接用这个新网络的结果覆盖原有的 Q 函数,就可能引入一个充满噪声、表现更差的神经网络。

软更新方法能有效避免这种情况。它有助于强化学习算法取得更好的效果,逐步趋近于更优的解决方案。

软更新

传统的硬更新方式是直接将的值Q设为Q_{new},即Q=Q_{new},对应的权重更新也是直接用新权重W_{new}完全替换旧权重W),偏置也类似。

软更新方式并非直接替换,而是通过加权平均的方式来更新参数。这种方式使得更新过程更为平滑,新参数对旧参数的影响较为缓和,有助于防止模型因更新幅度过大而出现不稳定的情况 。

4.4.7 强化学习的状态

强化学习的局限性

  • 在模拟环境中运行比在真实机器人上容易得多!
  • 与监督学习和无监督学习相比,应用场景要少得多。
  • 但是…… 这是一个令人兴奋的研究方向,在未来应用方面具有潜力 。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐