AI算法 - 逻辑回归 logistics

72 阅读3分钟
import numpy as np
# 科学计算
from sklearn.datasets import load_breast_cancer
# 乳腺癌数据
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
# PolynomialFeatures(多项式特征扩展,增加特征非线性组合(如x1², x1x2, x1x2x3),提高模型的表达能力)
# StandardScaler 归一化
from sklearn.metrics import accuracy_score
# 计算分类准确率

n_epoches = 1000
# 训练轮次
batch_size = 32
# 每轮的训练批次
learning_rate = 0.1
# 学习率(控制每次参数更新的步长)

X, y = load_breast_cancer(return_X_y=True)
# X(569条数据, 30个特征) y(一维数组569个元素(结果(0/1)))
poly = PolynomialFeatures(include_bias=False, degree=3, interaction_only=True)
# include_bias: 是否添加一列全为1,下面手动添加了
# degree: 生成的多项式最高次数为3 (x1, x1³, x1²..)
# interaction_only: 是否只生成交互项(去掉幂次项)
X = poly.fit_transform(X)
# 经过多项式后, 特征维度变成了(569, 4525)
# 多项式特征后数值差异很大(x1=100 x1³ = 1000000)
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 所以需要归一化缩放到均值为0, 方差为1的分布
X = np.c_[np.ones(X.shape[0]), X]
# np.c_ 等价于 np.concatenate(axis=1) 给数据集第一列添加一列全为[1,1...]
y = y.reshape(-1, 1)
# 将一维数组(m,)变成二维数组(m,1)

m, n = X.shape
# 取出数据X的行列
theta = np.random.randn(n, 1)
# 随机生成(n行1列) 均值为0 标准差为1的参数
best_loss = float('inf')
# 创建一个最优loss变量

# sigmoid 核心激活函数, 将任意z映射到(0,1)之间
def sigmoid(z):
    z = np.clip(z, -500, 500)
    # 当z的绝对值极大时, np.exp(-z)会返回0或inf,导致计算结果不准确或报错
    # 所以将输入z剪裁到[-500, 500]之间, 防止数值溢出
    return 1 / (1 + np.exp(-z))

# 交叉熵损失函数(Cross-Entropy Loss) 用于衡量模型预测值y_hat与真实值y之间的差异
def compute_loss(theta, X, y):
    y_hat = sigmoid(X @ theta)
    # @矩阵相乘 等价于 np.dot()
    epsilon = 1e-15
    # 极小常数, 防止log(0)
    return -np.mean(y * np.log(y_hat + epsilon) + (1 - y) * np.log(1 - y_hat + epsilon))
    # 这就是交叉熵公式

for epoch in range(n_epoches):
    indices = np.random.permutation(m)
    # 随机排列一个序列
    X_shuffled = X[indices]
    y_shuffled = y[indices]
    # 打乱顺序取数据

    # 小批量梯度下降(Mini - Batch Gradient Descent)
    # 用于更新模型参数 theta, 使预测结果尽可能接近真实标签
    # batch_size=32时, i= 0, 32, 64, 96... 所以每次切割都是不重复的
    # [0:32] [32:64] [64:96] [96:100]..
    for i in range(0, m, batch_size):
        X_batch = X_shuffled[i:i + batch_size]
        y_batch = y_shuffled[i:i + batch_size]
        # 从打乱后的数据选取一个小批量的数据
        y_hat = sigmoid(X_batch @ theta)
        # 预测这一小批数据的概率
        gradient = X_batch.T @ (y_hat - y_batch) / batch_size
        # 求这一小批数的梯度
        theta -= learning_rate * gradient
        # 更新参数(注意是-= 不是 =)

    current_loss = compute_loss(theta, X, y)
    # 根据更新后的参数求当前损失

    learning_rate *= 0.995
    # 按指数衰减策略逐步降低学习率(优化算法的常见技巧)

    # 提前停止(Early Stopping) 当损失值小于1e-6, 且至少训练10轮, 终止训练
    if epoch > 10 and abs(best_loss - current_loss) < 1e-6:
        break
    # 更新最佳损失值, best_loss初始无限大, 只有当前损失更优时才会更新
    if current_loss < best_loss:
        best_loss = current_loss

    if epoch % 50 == 0:
        print(f"Epoch {epoch}: loss = {current_loss:.6f}")

y_pred = (sigmoid(X @ theta) > 0.5).astype(int)
# 这里是逐元素与0.5比较, 不是整体比较
accuracy = accuracy_score(y, y_pred)
# 计算分类准确率
print(f"\nFinal Accuracy:, {accuracy:.4f}")
print(f"Final Loss:, {best_loss:.4f}")