不用第三方库 从零实现逻辑回归二分类
发布时间:2026/9/28 21:09:35来源:尧图网络
这篇文章使用 Python 标准库实现一个简单的机器学习模型逻辑回归二分类。项目不依赖numpy、scikit-learn等第三方库适合刚开始学习机器学习、希望了解算法内部计算过程的人。完整流程包括数据生成、训练集与测试集划分、特征标准化、批量梯度下降、模型评估、参数保存和手动预测。算法原理逻辑回归虽然名字中有“回归”但它主要用于分类任务。对于二维输入样本先计算再通过 Sigmoid 函数把 z 转换为 0 到 1 之间的概率当 p大于等于0.5时预测为类别 1否则预测为类别 0。模型使用交叉熵作为损失函数并通过批量梯度下降不断更新权重和偏置。为了让训练更加稳定代码还使用训练集的均值和标准差对特征进行了标准化。完整代码# -*- coding: utf-8 -*- 一个从零实现的简单机器学习示例逻辑回归二分类 只使用 Python 标准库不需要安装 numpy / scikit-learn 等任何第三方库。 整体流程 1. 生成数据两个类别二维特征 2. 划分训练集 / 测试集 3. 特征标准化 4. 用梯度下降训练逻辑回归 5. 在测试集上评估准确率 6. 保存模型参数并做一次预测示例 import json import math import os import random # 1. 数据 def make_data(n_per_class200, seed42): 生成两个线性可分带噪声的二维类别数据。 rng random.Random(seed) X, y [], [] for _ in range(n_per_class): # 类别 0中心在 (-1, -1) X.append([rng.gauss(-1.0, 0.8), rng.gauss(-1.0, 0.8)]) y.append(0) for _ in range(n_per_class): # 类别 1中心在 (1, 1) X.append([rng.gauss(1.0, 0.8), rng.gauss(1.0, 0.8)]) y.append(1) # 打乱顺序 idx list(range(len(y))) rng.shuffle(idx) return [X[i] for i in idx], [y[i] for i in idx] def train_test_split(X, y, test_ratio0.2, seed42): 按比例划分训练集与测试集。 rng random.Random(seed) idx list(range(len(y))) rng.shuffle(idx) n_test int(len(y) * test_ratio) test_idx idx[:n_test] train_idx idx[n_test:] X_train [X[i] for i in train_idx] y_train [y[i] for i in train_idx] X_test [X[i] for i in test_idx] y_test [y[i] for i in test_idx] return X_train, X_test, y_train, y_test # 2. 预处理 def mean(values): return sum(values) / len(values) def std(values): mu mean(values) var sum((v - mu) ** 2 for v in values) / len(values) return math.sqrt(var) def standardize(X_train, X_test): 用训练集的均值和标准差做标准化避免测试集信息泄漏。 n_features len(X_train[0]) mu, sigma [], [] for j in range(n_features): col [row[j] for row in X_train] m mean(col) s std(col) if s 0: s 1.0 mu.append(m) sigma.append(s) def transform(X): return [[(row[j] - mu[j]) / sigma[j] for j in range(n_features)] for row in X] return transform(X_train), transform(X_test), mu, sigma # 3. 模型 def sigmoid(z): 数值稳定的 sigmoid 函数。 if z 0: return 1.0 / (1.0 math.exp(-z)) ez math.exp(z) return ez / (1.0 ez) def dot(a, b): return sum(x * y for x, y in zip(a, b)) def compute_loss(w, b, X, y): 交叉熵损失。 eps 1e-12 total 0.0 for xi, yi in zip(X, y): p sigmoid(dot(w, xi) b) total yi * math.log(p eps) (1 - yi) * math.log(1 - p eps) return -total / len(y) def train_logistic_regression(X, y, lr0.1, epochs2000, verboseTrue): 用批量梯度下降训练逻辑回归。 m len(y) n len(X[0]) w [0.0] * n b 0.0 loss_history [] for epoch in range(1, epochs 1): # 计算梯度 dw [0.0] * n db 0.0 for xi, yi in zip(X, y): err sigmoid(dot(w, xi) b) - yi for j in range(n): dw[j] err * xi[j] db err # 更新参数 for j in range(n): w[j] - lr * dw[j] / m b - lr * db / m # 记录损失 if epoch % 200 0 or epoch 1: loss compute_loss(w, b, X, y) loss_history.append(loss) if verbose: print(fepoch {epoch:5d} | loss {loss:.4f}) return w, b, loss_history def predict(w, b, X, threshold0.5): 预测类别0 或 1。 return [1 if sigmoid(dot(w, xi) b) threshold else 0 for xi in X] def accuracy(y_true, y_pred): 计算准确率。 correct sum(1 for a, p in zip(y_true, y_pred) if a p) return correct / len(y_true) # 4. 保存模型 def save_model(path, w, b, mu, sigma): 把模型参数保存成 JSON 文件。 with open(path, w, encodingutf-8) as f: json.dump({w: w, b: b, mu: mu, sigma: sigma}, f, ensure_asciiFalse, indent2) print(f[保存] 模型已保存到 {path}) def load_model(path): 加载模型参数。 with open(path, r, encodingutf-8) as f: data json.load(f) return data[w], data[b], data[mu], data[sigma] # 4.5 交互测试 def interactive(w, b, mu, sigma): 让用户手动输入特征值实时查看预测结果。 print(\n - * 50) print(手动测试输入两个数字用空格分开回车查看预测结果) print(例如0.5 0.5 输入 q 退出) print(- * 50) while True: try: s input( ).strip() except EOFError: break if s.lower() in (q, quit, exit): print(已退出测试) break if s : continue parts s.split() if len(parts) ! 2: print(格式不对请输入两个数字用空格分开如 0.5 0.5) continue try: sample [float(parts[0]), float(parts[1])] except ValueError: print(输入的不是数字请重新输入) continue sample_std [(sample[j] - mu[j]) / sigma[j] for j in range(2)] prob sigmoid(dot(w, sample_std) b) label 1 if prob 0.5 else 0 print(f输入 {sample} → 预测类别 {label}概率 {prob:.4f}) # 5. 主程序 def main(): print( * 50) print(简单机器学习示例逻辑回归二分类) print( * 50) # 1) 生成数据并划分 X, y make_data() X_train, X_test, y_train, y_test train_test_split(X, y) print(f训练集 {len(X_train)} 条测试集 {len(X_test)} 条\n) # 2) 标准化 X_train, X_test, mu, sigma standardize(X_train, X_test) # 3) 训练 print(开始训练) w, b, losses train_logistic_regression(X_train, y_train) # 4) 评估 train_acc accuracy(y_train, predict(w, b, X_train)) test_acc accuracy(y_test, predict(w, b, X_test)) print(f\n训练集准确率{train_acc * 100:.2f}%) print(f测试集准确率{test_acc * 100:.2f}%) # 5) 打印参数 print(f\n权重 w {[round(v, 4) for v in w]}) print(f偏置 b {b:.4f}) # 6) 保存模型保存在脚本所在目录 path os.path.join(os.path.dirname(os.path.abspath(__file__)), model.json) try: save_model(path, w, b, mu, sigma) except OSError as e: print(f[警告] 模型保存失败不影响本示例运行{e}) # 7) 单样本预测示例 sample [0.5, 0.5] sample_std [(sample[j] - mu[j]) / sigma[j] for j in range(len(sample))] prob sigmoid(dot(w, sample_std) b) print(f\n对样本 {sample} 的预测类别 {1 if prob 0.5 else 0}概率 {prob:.4f}) # 8) 进入手动测试模式可反复输入自己的数字 interactive(w, b, mu, sigma) if __name__ __main__: main()最终运行结果结果分析从运行结果可以看出模型的损失值从第 1 轮的 0.6646 下降到第 2000 轮的 0.1073说明梯度下降正在不断优化模型参数。训练集准确率为 95.62%测试集准确率为 97.50%。测试集准确率略高于训练集并不意味着模型出现异常而是因为测试集只有 80 条数据样本数量较少准确率会受到个别样本影响。最终权重约为w [3.6356, 3.2681]两个权重都是正数符合数据设计。因为类别 1 的数据中心在 (1, 1)两个特征值越大样本越可能属于类别 1。偏置约为 0.0020非常接近 0也符合两个类别中心关于原点对称的特点。对于输入样本 [0.5, 0.5]模型给出的类别 1 概率为 0.9394所以最终预测结果为类别 1。这个结果与两类数据的分布规律一致说明模型已经学到了有效的分类边界。总结这个示例完整展示了逻辑回归的基本训练过程生成二维二分类数据。按 8:2 划分训练集和测试集。使用训练集参数完成标准化。使用 Sigmoid 和交叉熵构建分类模型。使用批量梯度下降更新参数。使用准确率评价模型。将模型参数保存为 model.json。支持手动输入数字进行实时预测。整个程序不需要 numpy 或 scikit-learn因此适合用于理解机器学习算法的底层计算过程。后续可以继续增加损失曲线绘制、更多特征、L2 正则化、学习率调整以及使用真实数据集进行训练。
网站建设高端定制企业官网