从线性回归到波士顿房价——一个老程序员的机器学习入门实战

学完TensorFlow基础,下一步就是拿它做点什么。线性回归、波士顿房价预测、MNIST手写数字——经典三件套。这篇记录我的学习过程,重点关注每个实验教会了我什么,不是教程。


一、一元线性回归:y = 2x + 1

TF 1.x版本

生成100个带噪声的数据点,真实关系是y = 2x + 1,让模型自己学出来。

x_data = np.linspace(-1, 1, 100)
y_data = 2 * x_data + 1.0 + np.random.randn(*x_data.shape) * 0.4

w = tf.Variable(1.0, name="w0")
b = tf.Variable(0.0, name="b0")

def model(x, w, b):
    return tf.multiply(x, w) + b

loss_function = tf.reduce_mean(tf.square(y - pred))
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)

训练100轮,每轮逐样本喂数据。训练过程中每轮画一条拟合线,可以看到线从歪到正的过程——第一次看到这个动画的时候,直观理解了"梯度下降在做什么"。

训练完支持交互式输入:输入一个x,输出预测值和目标值,直观对比。

学到的东西

  • 损失函数:均方误差(MSE)——预测值和真实值差的平方再求平均。差距越大,梯度越大,参数调整越多。
  • 学习率:0.01太小收敛慢,0.1太大可能震荡。这个参数要调。
  • 训练方式:逐样本训练(SGD),简单但慢。后来波士顿房价用了mini-batch。

TF 2.x版本

同样的任务,用GradientTape重写:

w = tf.Variable(np.random.randn())
b = tf.Variable(0.0)

def loss(x, y, w, b):
    err = model(x, w, b) - y
    return tf.reduce_mean(tf.square(err))

def grad(x, y, w, b):
    with tf.GradientTape() as tape:
        loss_ = loss(x, y, w, b)
    return tape.gradient(loss_, [w, b])

for epoch in range(train_epochs):
    loss_ = loss(x_data, y_data, w, b)
    deltaW, deltaB = grad(x_data, y_data, w, b)
    w.assign_sub(deltaW * learning_rate)
    b.assign_sub(deltaB * learning_rate)

对比1.x版本:

  • 不需要Session、placeholder、feed_dict
  • 梯度计算显式写在代码里,能看清每一步在干什么
  • w.assign_sub()手动更新参数,比1.x的optimizer.minimize()更透明

二、波士顿房价:从一维到多维

一元线性回归只有一个x,波士顿房价有12个特征(犯罪率、房间数、年龄等),预测房价。

TF 1.x版本(boson1.0)

# 12个特征的权重矩阵
w = tf.Variable(tf.random.normal([12, 1], stddev=0.01))
b = tf.Variable(1.0)

def model(x, w, b):
    return tf.matmul(x, w) + b  # 矩阵乘法,不再是标量乘法

数据归一化:

for i in range(12):
    data[:, i] = data[:, i] / (data[:, i].max() - data[:, i].min())

这是手写的min-max归一化。12个特征量级差很大,不归一化训练不收敛。训练1000轮,每轮打乱数据顺序。

问题:没有验证集,只有训练集和测试集,无法判断过拟合。

TF 2.x版本(boson2.0)

升级点:

# sklearn标准化代替手写归一化
x_train = tf.cast(scale(x_train), dtype=tf.float32)

# 三分数据集:训练300条、验证100条、测试剩余
train_num = 300
valid_num = 100

# mini-batch训练
batch_size = 20
total_step = int(train_num / batch_size)

for step in range(total_step):
    xs = x_train[step*batch_size:(step+1)*batch_size, :]
    ys = y_train[step*batch_szie:(step+1)*batch_size]
    grads = grad(xs, ys, w, b)
    optimizer.apply_gradients(zip(grads, [w, b]))

同时记录训练loss和验证loss,画两条曲线:

loss_list_train.append(loss_train)
loss_list_valid.append(loss_valid)
plt.plot(loss_list_train, label="train")
plt.plot(loss_list_valid, label="valid")

这一版教会我

  1. 数据集要三分——训练集训练参数,验证集调超参数,测试集最终评估。如果只有训练和测试,你不知道模型是学到了规律还是背了答案。
  2. mini-batch——不是一条一条喂,也不是一把全喂,每次喂一小批(20条)。收敛速度和稳定性的折中。
  3. 标准化比归一化好——sklearn的scale()做的是Z-score标准化(减均值除标准差),比min-max更鲁棒。
  4. 看两条loss曲线——训练loss在降但验证loss开始升了,就是过拟合的信号。

三、MNIST手写数字:自己解析二进制

Keras可以一行加载MNIST,但我额外写了一个从二进制文件手动解析的版本:

def load_images(file_name):
    binfile = open(file_name, 'rb')
    buffers = binfile.read()
    magic, num, rows, cols = struct.unpack_from('>IIII', buffers, 0)
    bits = num * rows * cols
    images = struct.unpack_from('>' + str(bits) + 'B', buffers, struct.calcsize('>IIII'))
    images = np.reshape(images, [num, rows * cols])
    return images

MNIST的二进制格式是IDX格式:前4个整数是魔数+数量+行数+列数(大端序),后面是像素数据。用Python的struct模块逐字节解析。

解析完后画出来看看:

for i in range(30):
    images = np.reshape(train_images[i], [28, 28])
    ax = fig.add_subplot(6, 5, i+1, xticks=[], yticks=[])
    ax.imshow(images, cmap=plt.cm.binary, interpolation='nearest')
    ax.text(0, 7, str(train_labels[i]))

28×28的灰度图,手写的0-9数字。人一眼就能认出来,但要让机器认出来,需要卷积神经网络(CNN),那是后面的事了。

为什么要手写解析? 因为真实项目的数据不会是Keras帮你准备好的CSV。你的数据可能是Oracle里的表、日志文件、二进制协议、API返回的JSON。手写解析IDX格式就是练习"从原始数据到可用数据"这个能力。


四、三个实验的递进关系

实验 维度 教会我什么
一元线性回归 1个特征 梯度下降的基本过程、损失函数、学习率
波士顿房价 12个特征 多维权重、数据标准化、训练/验证/测试划分、mini-batch、过拟合
MNIST二进制解析 784维 数据不是现成的、二进制解析、图像表示

一元→多元→图像,复杂度逐步上升,但核心都是同一件事:定义模型 → 定义损失 → 求梯度 → 更新参数 → 循环

这个循环就是机器学习最核心的套路。后面的CNN、Transformer,换了模型结构,没换这个循环。


相关阅读:

  • 《一个Java老鸟的TensorFlow入门——从计算图到GradientTape》
  • 《一个46岁架构师的AI实战经验总结》
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐