首页 AI发展前景内容详情

设置随机种子,让每次生成的噪音一样,便于复现

2026-03-01 344 AI链物

别光调包了,动手从零搓一个AI模型,Python代码实战走起!**


最近和几个搞开发的朋友聊天,发现一个挺有意思的现象:现在玩AI,大家开口闭口就是“调个API”、“用个HuggingFace的模型”、“跑个Fine-tuning”,这当然没问题,效率高嘛,但聊深一点,问到“那模型里面到底是怎么一步步学出来的?”很多人就有点含糊了,感觉中间隔了一层毛玻璃。

这让我想起早年学开车,如果只会用自动挡,虽然能上路,但万一有点小状况,或者想真正理解车的脾气,可能就差点意思,咱们就抛开那些现成的、封装好的高级框架,不用TensorFlow,不用PyTorch,甚至不用Scikit-learn,就只用最原始的Python,加上NumPy,来亲手“搓”一个最简单的神经网络模型,目的不是造个多厉害的东西,而是把“黑盒子”打开一条缝,看看里面的齿轮是怎么咬合的,相信我,这个过程之后,你再去看那些高级框架,感觉会完全不一样。

咱们想干嘛?

任务要足够简单,才能聚焦过程,我们就搞一个经典的“线性回归”问题,但用神经网络的思想来做,说白了,就是教电脑找规律,假设我们有一堆数据:房子的面积(输入x)和对应的房价(目标y),我们知道它们之间大概存在一种“正比”关系(面积越大,房价越贵),但具体公式不知道,有些许偏差,我们的目标就是让模型自己找出这个“比例系数”(权重)和可能的“基础价”(偏置)。

设置随机种子,让每次生成的噪音一样,便于复现 第1张

第一步:准备“食材”——数据

咱们自己造点数据,干净清楚,假设真实的规律是:房价 = 2.5 * 面积 + 8.0,再加上一点随机的噪音(模拟现实中的各种因素)。

import numpy as np
np.random.seed(42)
# 生成100个房子的面积,范围在30到150平米之间
X_raw = np.random.rand(100, 1) * 120 + 30
# 真实的规律:y = 2.5 * x + 8.0,再加点正态分布的噪音
true_weight = 2.5
true_bias = 8.0
y_raw = true_weight * X_raw + true_bias + np.random.randn(100, 1) * 10 # 噪音标准差10万
# 数据标准化:这不是必须,但能让训练更稳、更快,把数据缩放到均值0,标准差1附近。
X_mean, X_std = np.mean(X_raw), np.std(X_raw)
y_mean, y_std = np.mean(y_raw), np.std(y_raw)
X = (X_raw - X_mean) / X_std
y = (y_raw - y_mean) / y_std

看,数据有了,标准化就像把不同尺度的东西放到同一个操场上赛跑,公平。

第二步:搭建最简陋的“模型架子”

我们的模型简单到令人发指:就一个神经元,输入是面积x,输出是预测的房价y_pred,计算就是:y_pred = w * x + b,其中w是权重(我们想找的2.5),b是偏置(我们想找的8.0),一开始,我们随便猜一个w和b,比如0和0。

# 初始化参数:随机小值是个好习惯,这里我们从标准正态分布取样
w = np.random.randn(1, 1) * 0.01
b = np.zeros(1)

第三步:定义“错得多离谱”——损失函数

模型预测的y_pred和真实的y肯定有差距,我们需要一个量来衡量这个差距,最常用的就是均方误差(MSE):把所有样本的(预测值-真实值)的平方加起来再求平均,这个值越小,说明模型预测得越准。

def compute_loss(y, y_pred):
    return np.mean((y - y_pred) ** 2)

第四步:核心中的核心——反向传播与梯度下降

这是AI学习(训练)的“灵魂”,现在模型不是预测不准吗?损失函数值很大,我们怎么调整w和b让它变准呢?

想象一下,你蒙着眼站在山坡上(损失函数构成的地形),想走到山谷最低点(损失最小),你该怎么办?你会用脚感受一下哪个方向是下坡的,然后往那个方向迈一小步,在数学上,这个“下坡的方向”就是梯度——损失函数对每个参数(w和b)的偏导数。

  1. 前向传播:用当前的w和b,计算预测值和损失。y_pred = X * w + bloss = MSE(y, y_pred)
  2. 反向传播(计算梯度):这是手动推导公式的地方,也是理解的关键。
    • 损失对预测值y_pred的导数:dL_dy_pred = -2 * (y - y_pred) / n_samples (n_samples是样本数,这里是100)。
    • y_pred是由 w*X + b 得到的。
      • 损失对w的梯度:dL_dw = np.dot(X.T, dL_dy_pred) (可以理解为,每个样本的输入X对误差的“贡献”程度,汇总起来就是w该调整的方向)。
      • 损失对b的梯度:dL_db = np.sum(dL_dy_pred) (偏置b对每个样本的贡献都一样,所以直接把所有误差导数加起来就行)。
  3. 梯度下降(下山迈步):知道了下坡方向(梯度),我们就朝着反方向(因为梯度指向上升最快的方向,我们要下降)迈一小步,这个步长叫做学习率,是个超参数,要手动设,比如0.01。
    • w = w - learning_rate * dL_dw
    • b = b - learning_rate * dL_db
def train_one_step(X, y, w, b, learning_rate):
    n_samples = len(X)
    # 1. 前向传播
    y_pred = np.dot(X, w) + b
    loss = compute_loss(y, y_pred)
    # 2. 反向传播
    dL_dy_pred = -2 * (y - y_pred) / n_samples
    dL_dw = np.dot(X.T, dL_dy_pred)
    dL_db = np.sum(dL_dy_pred)
    # 3. 梯度下降更新参数
    w = w - learning_rate * dL_dw
    b = b - learning_rate * dL_db
    return w, b, loss

第五步:循环“学习”

我们就把上面这个“计算损失-求梯度-更新参数”的步骤,重复很多很多次(比如1000轮,也叫epoch)。

learning_rate = 0.01
epochs = 1000
loss_history = [] # 记录每轮损失,看看学习曲线
for epoch in range(epochs):
    w, b, loss = train_one_step(X, y, w, b, learning_rate)
    loss_history.append(loss)
    if epoch % 100 == 0: # 每100轮打印一次损失
        print(f"Epoch {epoch}, Loss: {loss:.6f}")
print(f"训练结束!最终参数 -> w: {w[0][0]:.4f}, b: {b[0]:.4f}")

跑起来之后,你会看到损失loss在一轮轮地下降,最后稳定在一个比较小的值,打印出来的w和b,就是我们模型学到的参数,注意,这是标准化之后的数据上的参数,我们需要转换回原始尺度才能和真实的2.5、8.0比较。

第六步:看看“学习成果”

# 将学到的参数转换回原始数据尺度
w_original = w[0][0] * (y_std / X_std)
b_original = y_mean - w_original * X_mean
print(f"原始尺度下的参数 -> 权重(每平米单价): {w_original:.4f}, 偏置(基础价): {b_original:.4f}")
print(f"真实规律是 -> 权重: {true_weight}, 偏置: {true_bias}")

你会发现,w_originalb_original会非常接近2.5和8.0!模型通过一遍遍看数据,自己找到了这个规律。

最后唠两句

整个过程写下来,代码量不大,但把监督学习的核心闭环走通了:模型、损失、优化,你亲手实现了梯度下降,感受到了参数是如何被误差“拉扯”着走向正确位置的。

经过这么一遭,你再回去用model.fit()的时候,心里就有底了:哦,它背后大概就是在循环干这些事,只不过做得更高效、更复杂、支持各种网络结构,当模型训练出现问题时(比如损失不下降、爆炸了),你可能会更有思路:是不是学习率太大了?数据要不要标准化?初始化有没有问题?

这就好比,你亲手拆装过一次发动机,虽然慢,虽然脏,但以后听引擎声就知道大概有没有毛病,在AI越来越“傻瓜式”应用的今天,保留一点“手搓”的笨功夫和理解,或许能让你走得更稳、更远,下次,我们可以试试给它加个激活函数,弄个隐藏层,让它从“线性回归”升级成真正的“神经网络”,那会更有意思。

(免费申请加入)AI工具导航网

AI出客网

相关标签: # ai训练模型python

  • 评论列表 (0条)

 暂无评论,快来抢沙发吧~

发布评论