自学内容网 自学内容网

【大模型实战篇】deep-ml项目深度学习关键模块代码练习题库及部分题目解法的分析

1. 背景说明

        近期关注到一个deep-ml的项目,展示了关于深度学习、机器学习、线性代数关键模块、算法的编程实践。项目类似于leetcode,模块以问题的形式提出,分为Hard、Medium、Easy三个难度层次。该项目还在持续更新中,作为DL代码学习实践很不错。

2. 关键算法/模块的代码示例

2.1 Implementation of Log Softmax Function

        在机器学习和统计学中,softmax 函数是逻辑函数的广义形式,它将一个分数向量转换为概率。log-softmax 函数是 softmax 函数的对数,常用于在计算大数的 softmax 时提高数值稳定性。        

        给定一个 1D numpy 数组的分数,实现一个 Python 函数来计算该数组的 log-softmax。

import numpy as np

def log_softmax(scores: list) -> np.ndarray:
    # 为了数值稳定性,减去最大值
    scores = scores - np.max(scores)
    return scores - np.log(np.sum(np.exp(scores)))

2.2 Linear Regression Using Normal Equation

        编写一个Python函数,使用正规方程执行线性回归。该函数应接受矩阵X(特征)和向量y(目标)作为输入,并返回线性回归模型的系数。将结果四舍五入到小数点后四位,对于非常小的数值,-0.0是有效的四舍五入结果。

        正规方程 是一种解析求解线性回归系数\theta 的方法,它的公式如下:

       \theta = (X^T X)^{-1} X^T y

        其中:

  • X^T 是矩阵 X 的转置。
  •  是矩阵 X^T X 的逆矩阵。
  • 通过这个公式可以直接解出回归系数\theta,不需要像梯度下降那样迭代。
import numpy as np
def linear_regression_normal_equation(X: list[list[float]], y: list[float]) -> list[float]:
    X = np.array(X)
    y = np.array(y).reshape(-1, 1)
    X_transpose = X.T
    theta = np.linalg.inv(X_transpose.dot(X)).dot(X_transpose).dot(y)
    theta = np.round(theta, 4).flatten().tolist()
    return theta

2.3 Linear Regression Using Gradient Descent

        编写一个Python函数,使用梯度下降执行线性回归。该函数应接收NumPy数组X(特征矩阵,包含用于截距的全为1的列)和y(目标向量)作为输入,此外还需提供学习率alpha和迭代次数。函数应返回线性回归模型的系数,结果为NumPy数组,并将系数四舍五入到小数点后四位。对于非常小的数值,-0.0是有效的四舍五入结果。

import numpy as np
def linear_regression_gradient_descent(X: np.ndarray, y: np.ndarray, alpha: float, iterations: int) -> np.ndarray:
    m, n = X.shape
    theta = np.zeros((n, 1))
    for _ in range(iterations):
        predictions = X @ theta
        errors = predictions - y.reshape(-1, 1)
        updates = X.T @ errors / m
        theta -= alpha * updates
    return np.round(theta.flatten(), 4)

2.4 Single Neuron

        编写一个Python函数,模拟一个具有 sigmoid 激活函数的单神经元,用于二元分类,能够处理多维输入特征。该函数应接受特征向量列表(每个向量表示一个示例的多个特征)、相关的真实二元标签,以及神经元的权重(每个特征一个权重)和偏置作为输入。函数应返回经过 sigmoid 激活后的预测概率和预测概率与真实标签之间的均方误差,均四舍五入到小数点后四位。

import math
def single_neuron_model(features, labels, weights, bias):
    probabilities = []
    for feature_vector in features:
        z = sum(weight * feature for weight, feature in zip(weights, feature_vector)) + bias
        prob = 1 / (1 + math.exp(-z))
        probabilities.append(round(prob, 4))
    
    mse = sum((prob - label) ** 2 for prob, label in zip(probabilities, labels)) / len(labels)
    mse = round(mse, 4)
    
    return probabilities, mse

2.5 Single Neuron with Backpropagation

        编写一个Python函数,模拟一个具有sigmoid激活函数的单神经元,并实现反向传播以更新神经元的权重和偏置。该函数应接受特征向量列表、相关的真实二元标签、初始权重、初始偏置、学习率和训练轮数作为输入。函数应使用基于均方误差(MSE)损失的梯度下降更新权重和偏置,并返回更新后的权重、偏置以及每个训练轮的MSE值列表,均四舍五入到小数点后四位。

        因为是MSE作为损失函数,所以梯度因子形式为:

(predictions−labels) ⋅ predictions⋅(1−predictions)

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def train_neuron(features, labels, initial_weights, initial_bias, learning_rate, epochs):
    weights = np.array(initial_weights)
    bias = initial_bias
    features = np.array(features)
    labels = np.array(labels)
    mse_values = []

    for _ in range(epochs):
        z = np.dot(features, weights) + bias
        predictions = sigmoid(z)
        
        mse = np.mean((predictions - labels) ** 2)
        mse_values.append(round(mse, 4))

        # Gradient calculation for weights and bias
        errors = predictions - labels
        weight_gradients = (2/len(labels)) * np.dot(features.T, errors * predictions * (1 - predictions))
        bias_gradient = (2/len(labels)) * np.sum(errors * predictions * (1 - predictions))
        
        # Update weights and bias
        weights -= learning_rate * weight_gradients
        bias -= learning_rate * bias_gradient

        # Round weights and bias for output
        updated_weights = np.round(weights, 4)
        updated_bias = round(bias, 4)

    return updated_weights.tolist(), updated_bias, mse_values

2.6 Implementing Basic Autograd Operations

        编写一个Python 类,实现基本的自动求导操作:加法、乘法和 ReLU 激活。该类应处理标量值,并正确计算这些操作的梯度,使用自动微分。

class Value:
    def __init__(self, data, _children=(), _op=''):
'''data:保存当前节点的数值。
grad:保存当前节点的梯度。
_backward:一个函数,用于在反向传播过程中更新梯度。
_prev:保存与当前节点相连的父节点,形成计算图。
_op:记录当前操作的类型。'''
        self.data = data
        self.grad = 0
        self._backward = lambda: None
        self._prev = set(_children)
        self._op = _op

    def __add__(self, other):
'''创建一个新的 Value 对象 out,保存加法的结果。
定义 _backward 函数,该函数在反向传播时被调用,负责将当前节点的梯度(out.grad)传播到参与加法操作的两个节点(self 和 other)。
self.grad 和 other.grad 分别加上 out.grad,实现了梯度的累加。'''
        other = other if isinstance(other, Value) else Value(other)
        out = Value(self.data + other.data, (self, other), '+')
        def _backward():
            self.grad += out.grad
            other.grad += out.grad
        out._backward = _backward
        return out

    def __mul__(self, other):
'''与加法操作类似,乘法操作也创建了一个新的 Value 对象 out,保存乘法的结果。
_backward 函数的计算根据乘法的链式法则实现:self.grad 加上 other.data * out.grad,other.grad 加上 self.data * out.grad。'''
        other = other if isinstance(other, Value) else Value(other)
        out = Value(self.data * other.data, (self, other), '*')
        def _backward():
            self.grad += other.data * out.grad
            other.grad += self.data * out.grad
        out._backward = _backward
        return out

    def relu(self):
        '''该方法实现了ReLU激活函数,输出为 0 或 self.data。
_backward 函数根据ReLU的特性更新梯度,仅在 out.data 大于 0 时将 out.grad 传播回 self.grad。'''
        out = Value(0 if self.data < 0 else self.data, (self,), 'ReLU')
        def _backward():
            self.grad += (out.data > 0) * out.grad
        out._backward = _backward
        return out

    def backward(self):
'''topo 列表存储计算图中的节点,按照反向传播的顺序。
build_topo 函数用于构建一个拓扑排序,确保在反向传播时,从叶节点(输出)到根节点(输入)进行。
在反向传播开始时,将当前节点的梯度设为1(即输出层的梯度)。
最后,遍历拓扑排序的节点,依次调用每个节点的 _backward 函数,从而实现梯度的传播。'''
        topo = []
        visited = set()
        def build_topo(v):
            if v not in visited:
                visited.add(v)
                for child in v._prev:
                    build_topo(child)
                topo.append(v)
        build_topo(self)
        self.grad = 1
        for v in reversed(topo):
            v._backward()
    def __repr__(self):
        return f"Value(data={self.data}, grad={self.grad})"

2.7 Implementing a Custom Dense Layer in Python

        一个基础的 Layer 类,该类定义了神经网络层的结构。任务是实现一个名为 Dense 的子类,它表示一个全连接神经网络层。Dense 类应扩展 Layer 类并实现以下方法:

  1. 初始化(init

    • 定义层,指定神经元数量(n_units)和可选的输入形状(input_shape)。
    • 设置层的权重(W)、偏置(w0)和优化器的占位符。
  2. 权重初始化(initialize)

    • 使用均匀分布初始化权重 W,限制为 1 / sqrt(input_shape[0]),偏置 w0 应设置为零。
    • 为 W 和 w0 初始化优化器。
  3. 参数计数(parameters)

    • 返回层中可训练参数的总数,包括 W 和 w0 中的参数。
  4. 前向传播(forward_pass)

    • 通过对输入 X 和权重矩阵 W 进行点积,然后加上偏置 w0 来计算层的输出。
  5. 反向传播(backward_pass)

    • 计算并返回相对于输入的梯度。
    • 如果层是可训练的,则使用优化器的更新规则更新权重和偏置。
  6. 输出形状(output_shape)

    • 返回通过前向传播生成的输出的形状,应该是 (self.n_units,)

目标:通过实现 Dense 类扩展 Layer 类,以确保它在神经网络框架中正常工作。

class Dense(Layer):
    def __init__(self, n_units, input_shape=None):
        self.layer_input = None
        self.input_shape = input_shape
        self.n_units = n_units
        self.trainable = True
        self.W = None
        self.w0 = None

    def initialize(self, optimizer):
        limit = 1 / math.sqrt(self.input_shape[0])
        self.W  = np.random.uniform(-limit, limit, (self.input_shape[0], self.n_units))
        self.w0 = np.zeros((1, self.n_units))
        self.W_opt  = copy.copy(optimizer)
        self.w0_opt = copy.copy(optimizer)

    def parameters(self):
        return np.prod(self.W.shape) + np.prod(self.w0.shape)

    def forward_pass(self, X, training=True):
        self.layer_input = X
        return X.dot(self.W) + self.w0

    def backward_pass(self, accum_grad):
        W = self.W
        if self.trainable:
            grad_w = self.layer_input.T.dot(accum_grad)
            grad_w0 = np.sum(accum_grad, axis=0, keepdims=True)
            self.W = self.W_opt.update(self.W, grad_w)
            self.w0 = self.w0_opt.update(self.w0, grad_w0)
        accum_grad = accum_grad.dot(W.T)
        return accum_grad

    def output_shape(self):
        return (self.n_units, )
    

2.8 Simple Convolutional 2D Layer

        在 Python 中实现一个 2D 卷积层。该函数将使用指定的卷积核、填充和步幅处理输入矩阵。

import numpy as np

def simple_conv2d(input_matrix: np.ndarray, kernel: np.ndarray, padding: int, stride: int):
    input_height, input_width = input_matrix.shape
    kernel_height, kernel_width = kernel.shape
    
    '''使用 np.pad 函数在输入矩阵的四周添加零填充,填充的数量由 padding 参数决定。'''
    padded_input = np.pad(input_matrix, ((padding, padding), (padding, padding)), mode='constant')
    input_height_padded, input_width_padded = padded_input.shape

    output_height = (input_height_padded - kernel_height) // stride + 1
    output_width = (input_width_padded - kernel_width) // stride + 1

    output_matrix = np.zeros((output_height, output_width))
    
    '''使用两个嵌套的循环遍历输出矩阵的每一个位置。
region 变量代表当前卷积核在填充输入矩阵上覆盖的区域。通过步幅(stride)移动卷积核。
计算当前区域(region)和卷积核(kernel)的逐元素乘积,并对结果求和,得到输出矩阵对应位置的值。'''
    for i in range(output_height):
        for j in range(output_width):
            region = padded_input[i*stride:i*stride + kernel_height, j*stride:j*stride + kernel_width]
            output_matrix[i, j] = np.sum(region * kernel)

    return output_matrix

2.9 Implement Adam Optimization Algorithm

        在 Python 中实现 Adam(自适应矩估计)优化算法。Adam 是一种能够为每个参数自适应调整学习率的优化算法。任务是编写一个名为 adam_optimizer 的函数,该函数使用 Adam 算法更新目标函数的参数。

该函数应接受以下参数:

  • f:需要优化的目标函数
  • grad:用于计算目标函数梯度的函数
  • x0:初始参数值
  • learning_rate:学习率(默认值:0.001)
  • beta1:用于计算一阶矩估计的指数衰减率(默认值:0.9)
  • beta2:用于计算二阶矩估计的指数衰减率(默认值:0.999)
  • epsilon:用于数值稳定性的一个小常数(默认值:1e-8)
  • num_iterations:运行优化器的迭代次数(默认值:1000)

该函数应返回优化后的参数。

import numpy as np

def adam_optimizer(f, grad, x0, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8, num_iterations=10):
    x = x0
    m = np.zeros_like(x)
    v = np.zeros_like(x)

    for t in range(1, num_iterations + 1):
        g = grad(x)
        m = beta1 * m + (1 - beta1) * g
        v = beta2 * v + (1 - beta2) * g**2
        m_hat = m / (1 - beta1**t)
        v_hat = v / (1 - beta2**t)
        x = x - learning_rate * m_hat / (np.sqrt(v_hat) + epsilon)

    return x

2.10 Implement Self-Attention Mechanism

        任务是实现自注意力机制,transformer模型的基本组成部分,有兴趣可以阅读下我们的文章《Transformer原理及关键模块深入浅出》,广泛应用于自然语言处理和计算机视觉任务。自注意力机制允许模型在生成上下文化表示时动态关注输入序列的不同部分。函数返回自注意力输出,格式为 NumPy 数组。

import numpy as np

def compute_qkv(X, W_q, W_k, W_v):
    Q = np.dot(X, W_q)
    K = np.dot(X, W_k)
    V = np.dot(X, W_v)
    return Q, K, V

def self_attention(Q, K, V):
    d_k = Q.shape[1]
    scores = np.matmul(Q, K.T) / np.sqrt(d_k)
    attention_weights = np.exp(scores) / np.sum(np.exp(scores), axis=1, keepdims=True)
    attention_output = np.matmul(attention_weights, V)
    return attention_output

2.11 Implementing a Simple RNN

        编写一个 Python 函数,实现一个简单的循环神经网络(RNN)单元。该函数应处理一系列输入向量并生成最终的隐藏状态。使用 tanh 激活函数进行隐藏状态更新。该函数应接收输入序列、初始隐藏状态、输入到隐藏层和隐藏层到隐藏层的权重矩阵,以及偏置向量作为输入。函数应返回处理完整个序列后的最终隐藏状态,结果四舍五入到小数点后四位。

        关于RNN,可以参考我们的文章《精简循环序列模型(minGRU/minLSTM)性能堪比Transformer以及对循环神经网络的回顾》。

import numpy as np

def rnn_forward(input_sequence, initial_hidden_state, Wx, Wh, b):
    h = np.array(initial_hidden_state)
    Wx = np.array(Wx)
    Wh = np.array(Wh)
    b = np.array(b)
    for x in input_sequence:
        x = np.array(x)
        h = np.tanh(np.dot(Wx, x) + np.dot(Wh, h) + b)
    final_hidden_state = np.round(h, 4)
    return final_hidden_state.tolist()

2.12 Optimal String Alignment Distance

        实现一个函数来计算两个给定字符串之间的最优字符串对齐(OSA)距离。OSA 距离表示将一个字符串转换为另一个字符串所需的最小编辑次数。允许的编辑操作包括:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符
  • 转置两个相邻的字符

每个操作的费用为 1 个单位。任务是找到将第一个字符串(s1)转换为第二个字符串(s2)所需的最小编辑次数。

例如,字符串 "caper" 和 "acer" 之间的 OSA 距离是 2:一次删除(去掉 "p"),一次转置(交换 "a" 和 "c")。

import numpy as np

def OSA(source: str, target: str) -> int:
    source_len, target_len = len(source), len(target)

    # Initialize matrix with zeros
    osa_matrix = [[0] * (target_len + 1) for _ in range(source_len + 1)]

    # Fill the first row and first column with index values
    for j in range(1, target_len + 1):
        osa_matrix[0][j] = j
    for i in range(1, source_len + 1):
        osa_matrix[i][0] = i

    # Compute the OSA distance
    for i in range(1, source_len + 1):
        for j in range(1, target_len + 1):
            osa_matrix[i][j] = min(
                osa_matrix[i - 1][j] + 1,  # Deletion
                osa_matrix[i][j - 1] + 1,  # Insertion
                osa_matrix[i - 1][j - 1] + (1 if source[i - 1] != target[j - 1] else 0)  # Substitution
            )
            if i > 1 and j > 1 and source[i - 1] == target[j - 2] and source[i - 2] == target[j - 1]:
                osa_matrix[i][j] = min(osa_matrix[i][j], osa_matrix[i - 2][j - 2] + 1)  # Transposition

    return osa_matrix[-1][-1]

        动态规划是一种通过将复杂问题分解成更小的子问题并存储这些子问题的解决方案来减少计算复杂度的算法。OSA 距离的计算可以看作是一个动态规划的过程,具体体现在以下几个方面:

  1. 定义状态:

    • 状态表示为 osa_matrix[i][j],其中 ij 分别表示 sourcetarget 字符串的前 i 个字符和前 j 个字符之间的最小编辑距离。
  2. 状态转移方程:

    • 为了计算 osa_matrix[i][j],我们可以从以下几种操作中选择最小的代价:
      • 删除: 将 source 的第 i 个字符删除,剩下的部分与 target 的前 j 个字符的最小编辑距离为 osa_matrix[i - 1][j] + 1
      • 插入: 在 source 中插入 target 的第 j 个字符,使其与 source 的前 i 个字符相等,剩下的部分的最小编辑距离为 osa_matrix[i][j - 1] + 1
      • 替换: 如果 source 的第 i 个字符与 target 的第 j 个字符不同,可以通过替换来减少编辑距离,剩下的部分的最小编辑距离为 osa_matrix[i - 1][j - 1] + 1;如果相同,则不需要替换,代价为 osa_matrix[i - 1][j - 1]
      • 交换: 如果 source 的最后两个字符与 target 的最后两个字符相同但顺序相反,可以通过交换这两个字符来减少编辑距离,代价为 osa_matrix[i - 2][j - 2] + 1
  3. 初始化:

    • 初始化第一行和第一列是为了设置基本情况:
      • osa_matrix[0][j] 表示从空字符串转换到 target 的前 j 个字符所需的插入次数。
      • osa_matrix[i][0] 表示从 source 的前 i 个字符转换到空字符串所需的删除次数。
  4. 计算顺序:

    • 使用两个嵌套循环,先遍历 source(行),再遍历 target(列),确保每次计算 osa_matrix[i][j] 时,osa_matrix 的前一个状态已经被计算出来。

2.13 Implement Lasso Regression using Gradient Descent

        使用梯度下降法实现 Lasso 回归算法。Lasso 回归(L1 正则化)在损失函数中添加一个等于系数绝对值的惩罚项。任务是使用损失函数的梯度和 L1 惩罚项迭代更新权重和偏置。

Lasso 回归的目标函数为:

L = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 + \lambda \sum_{j=1}^{m} |w_j|

其中:

  • y_i 是第 i 个样本的实际值
  • \hat{y}_i 是第 i 个样本的预测值
  • w_j 是与第 j 个特征相关的权重
  • \lambda 是正则化参数
  • b 是偏置

        任务是在梯度下降过程中使用 L1 惩罚项将一些特征系数缩小到零,从而帮助进行特征选择。

import numpy as np

def l1_regularization_gradient_descent(X: np.array, y: np.array, alpha: float = 0.1, learning_rate: float = 0.01, max_iter: int = 1000, tol: float = 1e-4) -> tuple:
    n_samples, n_features = X.shape
    # 初始化权重和偏置为零
    weights = np.zeros(n_features)
    bias = 0
    
    for iteration in range(max_iter):
        # 预测值
        y_pred = np.dot(X, weights) + bias
        # 计算误差
        error = y_pred - y
        # 计算带 L1 惩罚的权重梯度
        grad_w = (1 / n_samples) * np.dot(X.T, error) + alpha * np.sign(weights)
        # 计算偏置的梯度(偏置没有惩罚项)
        grad_b = (1 / n_samples) * np.sum(error)
        
        # 更新权重和偏置
        weights -= learning_rate * grad_w
        bias -= learning_rate * grad_b
        
        # 检查是否收敛
        if np.linalg.norm(grad_w, ord=1) < tol:
            break
    
    return weights, bias

2.14 K-Means Clustering

        任务是编写一个实现k均值聚类算法的Python函数。该函数应接受特定的输入,并生成最终质心的列表。k均值聚类是一种将n个点划分为k个簇的方法,目标是将相似的点归为一组,并用每组的中心(称为质心)来表示。

        函数输入:

  • points:一个点的列表,每个点是一个坐标元组(例如,二维点的形式为 (x, y))。
  • k:一个整数,表示要形成的聚类数量。
  • initial_centroids:一个初始质心点的列表,每个质心是一个坐标元组。
  • max_iterations:一个整数,表示要执行的最大迭代次数。
import numpy as np

def euclidean_distance(a, b):
    return np.sqrt(((a - b) ** 2).sum(axis=1))

def k_means_clustering(points, k, initial_centroids, max_iterations):
    points = np.array(points)
    centroids = np.array(initial_centroids)
    
    for iteration in range(max_iterations):
        # Assign points to the nearest centroid
        distances = np.array([euclidean_distance(points, centroid) for centroid in centroids])
        assignments = np.argmin(distances, axis=0)

        new_centroids = np.array([points[assignments == i].mean(axis=0) if len(points[assignments == i]) > 0 else centroids[i] for i in range(k)])
        
        # Check for convergence
        if np.all(centroids == new_centroids):
            break
        centroids = new_centroids
        centroids = np.round(centroids,4)
    return [tuple(centroid) for centroid in centroids]

2.15 Implement AdaBoost Fit Method

        编写一个 Python 函数 adaboost_fit,实现 AdaBoost 分类器的拟合方法。该函数应接受一个形状为 (n_samples, n_features) 的二维 numpy 数组 X,表示数据集;一个形状为 (n_samples,) 的一维 numpy 数组 y,表示标签;以及一个整数 n_clf,表示分类器的数量。该函数应初始化样本权重,找到每个特征的最佳阈值,计算误差,更新权重,并返回包含其参数的分类器列表。

        首先介绍下adaboost原理:

        AdaBoost(Adaptive Boosting)是一种集成学习方法,通过组合多个弱分类器(通常是决策树)来构建一个强分类器。其基本原理和工作步骤如下:

1. 弱分类器的概念

        弱分类器是指在分类任务中,表现略好于随机猜测的分类器。AdaBoost 通过加权组合多个弱分类器,形成一个强分类器。

2. 初始权重分配

        AdaBoost 从每个训练样本开始,初始化每个样本的权重,使所有样本的权重相等。假设有 n 个样本,初始权重为 w_i = \frac{1}{n}

3. 分类器训练        

        迭代训练:对每个迭代 t:

                选择一个特征并在特征上寻找最佳阈值(threshold),以最小化分类错误率。

                对于每个特征的每个可能阈值,进行预测,并计算当前分类器的误差。

                误差是指加权错误分类样本的总和,可以表示为: \text{error} = \sum_{i=1}^{n} w_i \cdot \mathbb{1}(y_i \neq \hat{y}_i) 其中 \mathbb{1}是指示函数,如果样本 i 被错误分类,则返回 1,否则返回 0。

4. 分类器的权重

        根据误差计算每个弱分类器的权重\alpha_t,反映分类器在最终预测中的重要性。权重的计算公式为:\alpha_t = 0.5 \cdot \log\left(\frac{1 - \text{error}}{\text{error} + \epsilon}\right)其中 \epsilon 是一个小常数,防止除以零。

5. 更新样本权重

        使用当前分类器的预测结果来更新样本的权重。误分类的样本权重会增加,而正确分类的样本权重会降低。更新的公式为: w_i \leftarrow w_i \cdot \exp(-\alpha_t y_i \hat{y}_i)

        最后,对所有权重进行归一化,使得它们的总和为 1: w \leftarrow \frac{w}{\sum_{i=1}^{n} w_i}

6. 迭代过程

        重复步骤 3 到步骤 5,直到达到预设的分类器数量或其他停止条件。

7. 最终模型

        在所有迭代后,AdaBoost 的最终预测模型是各个弱分类器加权的组合:

\hat{y} = \text{sign}\left(\sum_{t=1}^{T} \alpha_t \cdot h_t(x)\right)

        其中h_t(x) 是第 t 个弱分类器的预测结果。

import numpy as np
import math


'''X: 输入特征矩阵,形状为 (n_samples, n_features)。
y: 标签向量,包含样本的真实分类(通常是 1 或 -1)。
n_clf: 要训练的弱分类器的数量。'''
def adaboost_fit(X, y, n_clf):
    '''n_samples 和 n_features 是样本数量和特征数量。
w 是样本权重,初始时每个样本的权重相同。
clfs 用于存储每个训练的弱分类器。'''
    n_samples, n_features = np.shape(X)
    w = np.full(n_samples, (1 / n_samples))
    clfs = []
    
    for _ in range(n_clf):
        clf = {}
        min_error = float('inf')
        
        for feature_i in range(n_features):
            feature_values = np.expand_dims(X[:, feature_i], axis=1)
            unique_values = np.unique(feature_values)
            
            for threshold in unique_values:
                '''对于每个阈值,初始化一个预测向量 prediction,其值为 1。
根据当前特征值和阈值,更新预测向量:当特征值小于阈值时,预测为 -1。
计算错误率 error,这是权重下的误分类样本的加权和。'''
                p = 1
                prediction = np.ones(np.shape(y))
                prediction[X[:, feature_i] < threshold] = -1
                error = sum(w[y != prediction])
                
                if error > 0.5:
                    '''如果误差大于 0.5,则表明分类器效果较差,调整误差并反转预测的极性。'''
                    error = 1 - error
                    p = -1
                
                if error < min_error:
                    '''如果当前阈值的误差小于已知的最小误差,则更新最佳分类器的信息。
'''
                    clf['polarity'] = p
                    clf['threshold'] = threshold
                    clf['feature_index'] = feature_i
                    min_error = error
        
'''计算分类器的权重 alpha,这是根据其准确性计算的,权重越大表示分类器的准确性越高。
'''
        clf['alpha'] = 0.5 * math.log((1.0 - min_error) / (min_error + 1e-10))
'''计算当前分类器的预测。
根据预测结果更新样本权重,误分类样本的权重会增加,而正确分类的样本权重则会降低。
最后,通过归一化确保权重和为 1。'''
        predictions = np.ones(np.shape(y))
        negative_idx = (clf['polarity'] * X[:, clf['feature_index']] < clf['polarity'] * clf['threshold'])
        predictions[negative_idx] = -1
        w *= np.exp(-clf['alpha'] * y * predictions)
        w /= np.sum(w)
        clfs.append(clf)

    return clfs
    

2.16 Calculate Covariance Matrix (medium)

        编写一个Python函数,用于从向量列表中计算协方差矩阵。假设输入列表表示一个数据集,其中每个向量是一个特征,且所有向量的长度相同。

def calculate_covariance_matrix(vectors: list[list[float]]) -> list[list[float]]:
    n_features = len(vectors)
    n_observations = len(vectors[0])
    covariance_matrix = [[0 for _ in range(n_features)] for _ in range(n_features)]

    means = [sum(feature) / n_observations for feature in vectors]

    for i in range(n_features):
        for j in range(i, n_features):
            covariance = sum((vectors[i][k] - means[i]) * (vectors[j][k] - means[j]) for k in range(n_observations)) / (n_observations - 1)
            covariance_matrix[i][j] = covariance_matrix[j][i] = covariance

    return covariance_matrix

2.17 Solve Linear Equations using Jacobi Method

        编写一个Python函数,使用Jacobi方法求解由Ax = b表示的线性方程组。该函数应迭代n次,每次将中间解四舍五入到小数点后四位,并返回近似解x。Jacobi方法是一种迭代算法,它逐步逼近线性方程组的解。每次迭代中,使用上一轮解的值来计算每个未知数的新值。该算法的优点是简单直观,但在某些情况下,收敛速度可能较慢,需要较多的迭代次数。

import numpy as np

def solve_jacobi(A: np.ndarray, b: np.ndarray, n: int) -> list:
    d_a = np.diag(A)
    nda = A - np.diag(d_a)
    x = np.zeros(len(b))
    x_hold = np.zeros(len(b))
    for _ in range(n):
        for i in range(len(A)):
            x_hold[i] = (1/d_a[i]) * (b[i] - sum(nda[i]*x))
        x = x_hold.copy()
    return np.round(x,4).tolist()

3. 参考材料

【1】ML Code Challenges


原文地址:https://blog.csdn.net/weixin_65514978/article/details/142910605

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!