本篇讲解机器学习理论。涵盖线性回归与逻辑回归的理论与实践,重点讲解数据预处理、模型构建与训练、性能评估及可视化,结合PaddlePaddle框架,系统学习深度学习开发流程并积累实战经验
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

上一节我们从机器学习的基础出发,逐步实现了线性回归和分类任务。现在我们将通过加州房价预测的线性回归问题,理解了如何使用简单的线性模型进行数值预测。
接着,我们使用Logistic 回归解决了Moon100数据集的分类问题,并通过优化算法提升了模型的准确性。
逐步让大家熟练学习机器学习的理论和实践
我们将使用 加州房价数据集(California Housing Dataset)作为本案例的研究对象。该数据集包含 1990年加州普查区 的房价与其他特征信息,用于研究地区特征与房价之间的关系。数据集共有 20640条样本 和 8个特征,目标变量是该地区的房价中位数。
以下是数据集中的特征描述:
| 特征名称 | 描述 | 单位 |
|---|---|---|
| MedInc | 区域收入中位数 | 万美元 |
| HouseAge | 区域房屋年龄中位数 | 年 |
| AveRooms | 每户平均房间数 | 无单位 |
| AveBedrms | 每户平均卧室数 | 无单位 |
| Population | 区域人口数量 | 人 |
| AveOccup | 每户平均入住人数 | 人 |
| Latitude | 区域纬度 | 度 |
| Longitude | 区域经度 | 度 |
| Target(目标变量) | 房价中位数(待预测) | 万美元 |
本案例的目标是通过 线性回归模型 预测区域的房价中位数(Target),并评估模型的性能。同时,尝试通过特征工程和正则化方法改进模型效果。
# 导入必要库import paddlefrom sklearn.datasets import fetch_california_housingimport pandas as pd# 加载加州房价数据集housing = fetch_california_housing(as_frame=True)# 转换为DataFramedata = housing.frame# 显示数据集基本信息print("数据集基本信息:")print(data.info())# 显示前几行样本print("数据集示例:")print(data.head())# 查看目标变量分布print("目标变量分布(房价中位数):")print(data['MedHouseVal'].describe())/opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages/paddle/utils/cpp_extension/extension_utils.py:686: UserWarning: No ccache found. Please be aware that recompiling all source files may be required. You can download and install ccache from: https://github.com/ccache/ccache/blob/master/doc/INSTALL.md warnings.warn(warning_message)
数据集基本信息: <class 'pandas.core.frame.DataFrame'> RangeIndex: 20640 entries, 0 to 20639 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 MedInc 20640 non-null float64 1 HouseAge 20640 non-null float64 2 AveRooms 20640 non-null float64 3 AveBedrms 20640 non-null float64 4 Population 20640 non-null float64 5 AveOccup 20640 non-null float64 6 Latitude 20640 non-null float64 7 Longitude 20640 non-null float64 8 MedHouseVal 20640 non-null float64 dtypes: float64(9) memory usage: 1.4 MB None 数据集示例: MedInc HouseAge AveRooms AveBedrms Population AveOccup Latitude \ 0 8.3252 41.0 6.984127 1.023810 322.0 2.555556 37.88 1 8.3014 21.0 6.238137 0.971880 2401.0 2.109842 37.86 2 7.2574 52.0 8.288136 1.073446 496.0 2.802260 37.85 3 5.6431 52.0 5.817352 1.073059 558.0 2.547945 37.85 4 3.8462 52.0 6.281853 1.081081 565.0 2.181467 37.85 Longitude MedHouseVal 0 -122.23 4.526 1 -122.22 3.585 2 -122.24 3.521 3 -122.25 3.413 4 -122.25 3.422 目标变量分布(房价中位数): count 20640.000000 mean 2.068558 std 1.153956 min 0.149990 25% 1.196000 50% 1.797000 75% 2.647250 max 5.000010 Name: MedHouseVal, dtype: float64
在机器学习任务中,数据清洗是模型构建的基础环节,目的是处理缺失值、异常值等可能影响模型性能的问题,同时通过可视化分析数据的分布和特征之间的关系。
缺失值可能会导致模型无法正常训练。我们需要检查数据集是否存在缺失值,并采取适当措施进行填补或移除。
# 检查缺失值missing_values = data.isnull().sum()print("各列缺失值情况:\n", missing_values)# 若存在缺失值,可以选择删除或填充# 删除缺失值样本cleaned_data = data.dropna()# 或者使用均值填充filled_data = data.fillna(data.mean())print("清洗后的数据集基本信息:")print(cleaned_data.info())各列缺失值情况: MedInc 0 HouseAge 0 AveRooms 0 AveBedrms 0 Population 0 AveOccup 0 Latitude 0 Longitude 0 MedHouseVal 0 dtype: int64 清洗后的数据集基本信息: <class 'pandas.core.frame.DataFrame'> RangeIndex: 20640 entries, 0 to 20639 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 MedInc 20640 non-null float64 1 HouseAge 20640 non-null float64 2 AveRooms 20640 non-null float64 3 AveBedrms 20640 non-null float64 4 Population 20640 non-null float64 5 AveOccup 20640 non-null float64 6 Latitude 20640 non-null float64 7 Longitude 20640 non-null float64 8 MedHouseVal 20640 non-null float64 dtypes: float64(9) memory usage: 1.4 MB None
异常值是指数据中明显偏离常规范围的值。我们通常通过统计方法或可视化工具(如箱线图)来检测异常值。
IQR方法:利用四分位数间距(IQR)检测异常值。
异常值范围:小于 (Q1−1.5×IQR) 或大于 (Q3+1.5×IQR) 的值被视为异常值。
# 统计每列的上下四分位数和异常值范围for col in data.columns[:-1]: # 遍历所有特征列
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 过滤异常值
outliers = data[(data[col] < lower_bound) | (data[col] > upper_bound)] print(f"{col} 列异常值数量:{len(outliers)}")
# 去除异常值(可选)
data = data[(data[col] >= lower_bound) & (data[col] <= upper_bound)]MedInc 列异常值数量:681 HouseAge 列异常值数量:0 AveRooms 列异常值数量:439 AveBedrms 列异常值数量:1116 Population 列异常值数量:1063 AveOccup 列异常值数量:528 Latitude 列异常值数量:0 Longitude 列异常值数量:0
箱线图是一种直观的统计图表,用于展示数据分布及检测异常值。通过绘制箱线图,我们可以快速发现数据的分布情况和离群点
通过箱线图,我们可以观察: 1. 各特征值的分布范围及中心位置。 2. 是否存在显著的离群点。 3. 不同特征的量级差异。
!pip install seaborn
Looking in indexes: https://mirror.baidu.com/pypi/simple/, https://mirrors.aliyun.com/pypi/simple/ Collecting seaborn Downloading https://mirrors.aliyun.com/pypi/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl (294 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 294.9/294.9 kB 7.5 MB/s eta 0:00:00a 0:00:01Requirement already satisfied: numpy!=1.24.0,>=1.20 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from seaborn) (1.26.4) Requirement already satisfied: pandas>=1.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from seaborn) (2.2.3) Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from seaborn) (3.9.2) Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.0) Requirement already satisfied: cycler>=0.10 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.54.1) Requirement already satisfied: kiwisolver>=1.3.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.7) Requirement already satisfied: packaging>=20.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (24.1) Requirement already satisfied: pillow>=8 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (10.4.0) Requirement already satisfied: pyparsing>=2.3.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.0) Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from pandas>=1.2->seaborn) (2024.2) Requirement already satisfied: tzdata>=2022.7 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from pandas>=1.2->seaborn) (2024.2) Requirement already satisfied: six>=1.5 in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.16.0) Installing collected packages: seaborn Successfully installed seaborn-0.13.2
import matplotlib.pyplot as pltimport seaborn as sns# 设置画图风格sns.set(style="whitegrid")# 选择部分特征绘制箱线图selected_features = ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population']# 绘制箱线图plt.figure(figsize=(12, 6))
sns.boxplot(data=data[selected_features])
plt.title("Boxplot of dataset feature.", fontsize=16)
plt.xticks(fontsize=12)
plt.ylabel("value", fontsize=12)
plt.xlabel("feature", fontsize=12)
plt.show()<Figure size 1200x600 with 1 Axes>
在机器学习任务中,特征归一化 是特征工程的重要步骤。其目的是将不同量纲的特征值映射到相同的尺度范围,从而提高模型的训练效率和性能,尤其是在梯度下降算法和距离相关的模型(如线性回归、逻辑回归和支持向量机)中。
现实数据中,不同特征的取值范围可能存在巨大差异。例如:
这种差异会导致以下问题:
将特征值线性缩放到指定范围(通常为 [0, 1] 或 [-1, 1])。
公式:
[x′=max(x)−min(x)x−min(x)]
优点:
适用场景:
将特征值转化为标准正态分布(均值为0,标准差为1)。
公式: [x′=σx−μ] 其中,(μ) 为均值,(σ) 为标准差。
优点:
适用场景:
对数变换将特征值压缩到较小范围。
公式: [x′=log(x+1)]
适用场景:
| 方法 | 数据分布影响 | 是否受异常值影响 | 适用范围 |
|---|---|---|---|
| Min-Max归一化 | 不改变分布 | 易受异常值影响 | 特征范围已知 |
| 标准化 | 转换为标准分布 | 鲁棒 | 特征范围未知,分布不均 |
| 对数缩放 | 减少长尾效应 | 易受负值影响 | 存在长尾分布 |
# # 使用最大最小归一化import numpy as npfrom sklearn.model_selection import train_test_split# 提取特征与目标变量features = data.iloc[:, :-1].values target = data.iloc[:, -1].values# # 最小-最大归一化实现# def min_max_scaling(features):# min_vals = np.min(features, axis=0)# max_vals = np.max(features, axis=0)# return (features - min_vals) / (max_vals - min_vals)# # # 应用归一化# # normalized_features = min_max_scaling(features)# # 数据集划分# X_train, X_test, y_train, y_test = train_test_split(normalized_features, target, test_size=0.2, random_state=42)
# 标准化from sklearn.preprocessing import StandardScaler# 标准化实现scaler = StandardScaler() standardized_features = scaler.fit_transform(features)# 数据集划分X_train, X_test, y_train, y_test = train_test_split(standardized_features, target, test_size=0.2, random_state=42)
import paddleimport paddle.nn.functional as F# 将数据转换为Paddle张量X_train_tensor = paddle.to_tensor(X_train, dtype='float32')
X_test_tensor = paddle.to_tensor(X_test, dtype='float32')# 使用Paddle实现标准化mean = paddle.mean(X_train_tensor, axis=0)
std = paddle.std(X_train_tensor, axis=0)
X_train_normalized = (X_train_tensor - mean) / std
X_test_normalized = (X_test_tensor - mean) / stdprint("标准化后的训练数据:", X_train_normalized.numpy()[:5])标准化后的训练数据: [[-0.5087326 1.4234632 0.12109426 0.05857366 -0.6497711 0.15639436 1.0031793 -1.2958153 ] [ 1.0481998 -0.8750934 0.9622561 2.2335987 -0.49749768 0.10866082 0.655153 -1.1851951 ] [ 0.66350424 -0.8750934 -0.05120656 0.2758133 -0.42929187 -0.79060733 0.7539172 -1.1751386 ] [ 2.9235477 0.76673275 2.0087693 -0.01296391 0.00373571 -0.74548185 -0.72284335 0.6651809 ] [-1.147807 0.5204589 -0.953951 0.690019 -0.93687 -0.79818255 -0.4500659 0.75066024]]
W0108 10:22:09.703733 294 dygraph_functions.cc:83253] got different data type, run type promotion automatically, this may cause data type been changed.
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.boxplot(features, labels=data.columns[:-1])
plt.title("Feature distribution before normalization") # 归一化前特征分布plt.xticks(rotation=45)
plt.show()/tmp/ipykernel_294/2547550394.py:4: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11. plt.boxplot(features, labels=data.columns[:-1])
<Figure size 1200x600 with 1 Axes>
plt.figure(figsize=(12, 6))
plt.boxplot(standardized_features)
plt.title("Feature distribution after normalization") # 归一化后特征分布(Min-Max)plt.show()<Figure size 1200x600 with 1 Axes>
在完成数据处理后,我们开始进入模型构建部分。本文将使用 PaddlePaddle 框架来自定义一个线性回归模型,并通过最小二乘法的解析解直接求解模型参数,从而避免复杂的迭代训练过程。
PaddlePaddle 提供了灵活的算子设计接口,可以通过继承 paddle.nn.Layer 来定义自定义算子。我们首先构建一个简单的线性算子。
import paddleimport paddle.nn as nnimport paddle.nn.functional as F# 自定义Linear算子class Linear(nn.Layer):
def __init__(self, in_features, out_features):
super(Linear, self).__init__() # 初始化权重和偏置
self.weight = self.create_parameter(
shape=[in_features, out_features],
default_initializer=nn.initializer.XavierUniform())
self.bias = self.create_parameter(
shape=[out_features],
default_initializer=nn.initializer.Constant(0.0)) def forward(self, x):
# 线性变换公式:y = Wx + b
return paddle.matmul(x, self.weight) + self.biasRunner 类是模型训练的核心封装,我们将其用于配置模型、损失函数、优化器,以及完成模型的训练、评价和预测任务。
# Runner类定义class Runner:
def __init__(self, input_dim, regularization_lambda=0.01):
"""
初始化线性回归模型以及其他必需的参数
"""
self.model = Linear(in_features=input_dim, out_features=1)
self.loss_fn = F.mse_loss # 均方误差损失
self.optimizer = paddle.optimizer.SGD(parameters=self.model.parameters(), learning_rate=0.01)
self.regularization_lambda = regularization_lambda # L2正则化项
def prepare_model(self, X_train, y_train):
"""
使用正规方程(带L2正则化)来求解模型的权重和偏置
"""
# 转换为Paddle张量
X_train_tensor = paddle.to_tensor(X_train, dtype='float32')
y_train_tensor = paddle.to_tensor(y_train, dtype='float32') # 添加偏置项到 X_train
X_train_with_bias = paddle.concat([X_train_tensor, paddle.ones([X_train_tensor.shape[0], 1], dtype='float32')], axis=1) # 正规方程解析解:W = (X^T X + λI)^-1 X^T y
X_transpose = paddle.transpose(X_train_with_bias, perm=[1, 0])
# 加上正则化项 (λI)
identity_matrix = paddle.eye(X_train_with_bias.shape[1])
regularization_matrix = self.regularization_lambda * identity_matrix
# 计算正规方程解
weights = paddle.matmul(
paddle.inverse(paddle.matmul(X_transpose, X_train_with_bias) + regularization_matrix),
paddle.matmul(X_transpose, y_train_tensor)
) # 拆分权重和偏置
self.model.weight.set_value(weights[:-1].reshape([X_train.shape[1], 1])) # 权重调整为二维张量
self.model.bias.set_value(weights[-1:].reshape([1])) # 偏置调整为一维张量
print("解析解求得的模型参数:") print("权重:", self.model.weight.numpy()) print("偏置:", self.model.bias.numpy()) def evaluate_model(self, X_test, y_test):
"""
使用测试集进行模型评价
"""
X_test_tensor = paddle.to_tensor(X_test, dtype='float32')
y_test_tensor = paddle.to_tensor(y_test, dtype='float32') # 进行预测
predictions = self.model(X_test_tensor)
loss = self.loss_fn(predictions, y_test_tensor) print("模型评价 - 测试集均方误差:", loss.numpy()) return predictions.numpy() def predict(self, X_new):
"""
对新数据进行预测
"""
X_new_tensor = paddle.to_tensor(X_new, dtype='float32')
predictions = self.model(X_new_tensor) return predictions.numpy()# 加载数据X_train, X_test, y_train, y_test = X_train_normalized, X_test_normalized, y_train, y_test# 创建 Runner 实例runner = Runner(input_dim=X_train.shape[1])# 使用最小二乘法解析解求解模型参数runner.prepare_model(X_train, y_train)# 评价模型predictions = runner.evaluate_model(X_test, y_test)# 对新数据进行预测X_new = X_test[:5] # 假设我们使用测试集的前5条数据进行预测predicted_prices = runner.predict(X_new)print("预测结果(前5条):", predicted_prices)解析解求得的模型参数: 权重: [[ 0.7456653 ] [ 0.16260225] [-0.16771364] [ 0.1250522 ] [ 0.04790141] [-0.27607393] [-0.8478774 ] [-0.7796167 ]] 偏置: [2.025095] 模型评价 - 测试集均方误差: 1.9318573 预测结果(前5条): [[1.5605581] [1.4985472] [1.6648219] [1.4926147] [1.280458 ]]
在前边的学习过程中,通过对线性回归的学习,为我们提供了一种直观且基础的预测模型。通过将输入特征与目标变量之间的关系表示为一个线性方程,线性回归帮助我们理解了如何在多维空间中找到最优的拟合直线。然而,除了回归问题,分类问题也是机器学习中的重要领域。在许多应用场景中,我们需要根据输入数据的特征对样本进行分类。此时,线性分类模型成为了一种非常有效的选择。
与线性回归模型相似,线性分类模型通过线性方程来决定样本所属的类别。然而,线性分类的目标是将数据点分割成不同的类别,而不是预测一个连续的数值。接下来,我们将介绍线性分类的定义及其核心思想,并展示如何使用机器学习方法进行分类。
线性分类是一种基于线性模型的分类方法,旨在通过一个线性决策边界将样本分为不同的类别。其基本思想是利用线性函数来对数据进行分割,从而使得数据能够被映射到不同的类别标签。
具体来说,给定一个输入数据点 (x∈Rn),线性分类模型试图通过一个线性方程进行分类:
[y=sign(w1x1+w2x2+⋯+wnxn+b)]
其中:
在训练过程中,线性分类器的目标是找到最佳的权重 (w) 和偏置 (b),使得决策边界能够最有效地将不同类别的样本分开。
与回归问题不同,线性分类模型的输出通常是一个类别标签,而不是一个连续的数值。根据训练数据的不同,线性分类器会调整其参数,使得分类边界最大程度地正确地划分样本。
常见的线性分类模型包括:
Logistic 回归(Logistic Regression)是一种广泛使用的统计模型,尽管其名称中带有“回归”二字,但它实际上是一种分类模型,主要用于解决二分类问题。Logistic 回归的核心思想是通过一个线性模型预测数据属于某一类别的概率。
在二分类问题中,我们通过线性模型计算一个实数值,然后使用Sigmoid函数(也称为Logistic函数)将其转换为一个0到1之间的概率值,表示数据属于某一类别的可能性。
假设输入特征为 (x∈Rn),模型的输出为类别 (y∈{0,1}),则 Logistic 回归模型的预测概率为:
[P(y=1∣x)=σ(wTx+b)]
其中:
Logistic 回归中使用的核心激活函数是 Sigmoid 函数,它的数学表达式为:
[σ(z)=1+e−z1]
Sigmoid 函数的作用是将输入值 ( z ) 映射到区间 ( (0, 1) ),这使得它非常适合用来表示概率。对于输入值 ( z ) 较大时,Sigmoid 输出接近 1,而当输入值较小时,输出接近 0。
Sigmoid 函数的特性
使用PaddlePaddle框架,实现一个简单的 Logistic 回归模型的实现代码
Logistic 函数实现了标准的 Sigmoid 激活函数 (σ(z)=1+e−z1)
使用 paddle.linspace 生成从 -10 到 10 的等间距数值,并将这些值输入到 Logistic 函数中,计算其对应的输出值。
import paddle# 定义 Logistic 函数def Logistic(x):
return 1 / (1 + paddle.exp(-x))# 创建一组从 -10 到 10 的数值x = paddle.linspace(-10, 10, 10000)# 计算 Logistic 函数值y = Logistic(x)# 打印结果的前几项print(y[:10]) # 输出前10项# 可视化 Logistic 函数import matplotlib.pyplot as plt
plt.plot(x.tolist(), y.tolist(),color = 'red')
plt.title('Sigmoid Function (Logistic)')
plt.xlabel('z')
plt.ylabel('σ(z)')
plt.grid(True)
plt.show()Tensor(shape=[10], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.00004540, 0.00004549, 0.00004558, 0.00004567, 0.00004576, 0.00004585,
0.00004595, 0.00004604, 0.00004613, 0.00004622])<Figure size 640x480 with 1 Axes>
函数解析
我们将构建一个简单的二分类数据集,使用的是 Moon1000 数据集,这是一个由两个半月形状的数据集,常用于测试二分类模型的性能。
make_moons:我们生成了 1000 条带有噪声(noise=0.1)的二分类数据,形成了两个半月形状的数据集。
StandardScaler:对数据进行标准化处理,使其均值为 0,标准差为 1,这有助于提高模型训练的效果。
plt.scatter:通过不同的颜色和标记(Class 0 用蓝色,Class 1 用红色)绘制两类数据点。
# 使用 `sklearn.datasets.make_moons` 来生成一个二分类的数据集。随机抽取其中的 1000 个样本,import numpy as npimport matplotlib.pyplot as pltfrom sklearn.datasets import make_moonsfrom sklearn.preprocessing import StandardScaler# 生成 Moon 数据集X, y = make_moons(n_samples=1000, noise=0.1, random_state=42)# 数据标准化scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)# 可视化数据分布plt.figure(figsize=(8, 6))
plt.scatter(X_scaled[y == 0][:, 0], X_scaled[y == 0][:, 1], color='b', label='Class 0', s=10)
plt.scatter(X_scaled[y == 1][:, 0], X_scaled[y == 1][:, 1], color='r', label='Class 1', s=10)
plt.title('Moon1000 Dataset Visualization')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.grid(True)
plt.show()<Figure size 800x600 with 1 Axes>
# 拆分数据集(训练集、验证集、测试集)from sklearn.model_selection import train_test_split# 第一次拆分:将数据集分为训练集(60%)和临时集(40%)X_train, X_temp, y_train, y_temp = train_test_split(X_scaled, y, test_size=0.4, random_state=42)# 第二次拆分:将临时集分为验证集(20%)和测试集(20%)X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)# 打印数据集形状print(f"训练集特征 X_train 形状: {X_train.shape}")print(f"验证集特征 X_val 形状: {X_val.shape}")print(f"测试集特征 X_test 形状: {X_test.shape}")print(f"训练集标签 y_train 形状: {y_train.shape}")print(f"验证集标签 y_val 形状: {y_val.shape}")print(f"测试集标签 y_test 形状: {y_test.shape}")训练集特征 X_train 形状: (600, 2) 验证集特征 X_val 形状: (200, 2) 测试集特征 X_test 形状: (200, 2) 训练集标签 y_train 形状: (600,) 验证集标签 y_val 形状: (200,) 测试集标签 y_test 形状: (200,)
import paddleimport paddle.nn as nnimport paddle.optimizer as optimimport paddle.nn.functional as F# 自定义 Logistic 回归模型(仅用 paddle.matmul)class LogisticRegression(nn.Layer):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__() # 定义线性层
self.params = {}
self.params['w'] = paddle.zeros([input_dim, 1])
self.params['b'] = paddle.zeros(shape = [1])
self.fc = nn.Linear(input_dim, 1) def forward(self, x):
# 使用 paddle.matmul 计算加权和
logits = paddle.matmul(x, self.params['w']) + self.params['b']
outputs = Logistic(logits) return outputs # 这里没有使用激活函数 sigmoidpaddle.seed(0)input = paddle.randn([3, 4])print(input) model = LogisticRegression(4) output = model(input)print(output)
Tensor(shape=[3, 4], dtype=float32, place=Place(cpu), stop_gradient=True,
[[-0.75711036, -0.38059190, 0.10946669, 1.34467661],
[-0.84002435, -1.27341712, 2.47224617, 0.14070207],
[ 0.60608417, 0.23396523, 1.35604191, 0.10350471]])
Tensor(shape=[3, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0.50000000],
[0.50000000],
[0.50000000]])损失函数(Loss Function),也叫代价函数(Cost Function),在机器学习中是评估模型预测结果与真实结果之间差异的一个函数。损失函数的主要作用是通过量化模型的预测误差来引导优化过程,最终帮助模型调整参数,使得预测结果更加精确。
损失函数是一个数学函数,用来衡量模型的预测输出与真实标签之间的差异。其核心作用是:
通常,损失函数的值越小,表示模型的预测结果与真实值的差距越小,因此,优化算法的目标就是通过不断最小化损失函数来训练模型。
在不同的任务中,损失函数有所不同。常见的损失函数包括:
均方误差(Mean Squared Error,MSE):用于回归问题,公式如下:
L(y,y^)=N1i=1∑N(yi−y^i)2
其中,yi 为真实值,y^i 为预测值,N 为样本数量。
交叉熵(Cross-Entropy):用于分类问题,特别是二分类和多分类任务,公式如下:
对于二分类,交叉熵损失函数可以写为:
L(y,y^)=−N1i=1∑N[yilog(y^i)+(1−yi)log(1−y^i)]
对于多分类,交叉熵损失函数可以写为:
L(y,y^)=−i=1∑Cyilog(y^i)
其中,yi 是实际类别标签(通常是一个 one-hot 向量),y^i 是预测的类别概率,C 是类别数。
交叉熵损失函数主要用于分类问题,尤其是在处理二分类和多分类任务时广泛应用。交叉熵的基本思想是量化两个概率分布之间的差异,通常用于衡量模型输出的概率分布与实际分布之间的差距。
二分类交叉熵:当标签是二分类时,交叉熵损失函数用于度量二进制分类模型输出的概率和真实标签之间的差异。
多分类交叉熵:对于多分类问题,交叉熵损失会计算所有类别的概率差异,并选择具有最大概率的类别作为模型的输出。
以下是一个简单的交叉熵损失函数实现,代码使用了 PaddlePaddle 框架:
import paddleimport paddle.nn as nnclass CrossEntropyLoss(nn.Layer):
def __init__(self):
super(CrossEntropyLoss, self).__init__() def forward(self, logits, labels):
"""
计算交叉熵损失
:param logits: 预测值(通常是模型的输出,没有经过 softmax 的原始值)
:param labels: 真实标签,通常是 one-hot 编码
:return: 交叉熵损失
"""
# 使用 PaddlePaddle 的函数计算交叉熵损失
loss = nn.functional.cross_entropy(logits, labels) return loss# 示例:使用自定义交叉熵损失函数logits = paddle.to_tensor([[1.2, 0.3, -0.5], [0.7, 1.4, -1.0]], dtype='float32') # 模拟模型输出labels = paddle.to_tensor([0, 1], dtype='int64') # 假设有两个样本,标签分别为类别 0 和类别 1loss_fn = CrossEntropyLoss()
loss = loss_fn(logits, labels)print("交叉熵损失:", loss.numpy())交叉熵损失: 0.46265036
梯度优化(Gradient Optimization)是机器学习中最常用的优化方法之一,它基于梯度下降算法(Gradient Descent)来通过调整模型的参数,最小化损失函数,从而提高模型的预测性能。在训练过程中,模型的参数通过计算梯度并朝着损失函数的最小值方向进行更新。
梯度计算是优化算法的核心步骤,它用于计算损失函数相对于模型参数的偏导数。梯度描述了损失函数在某个点上的变化率,并指示了如何调整参数以减少损失。
对于逻辑回归的交叉熵损失函数,损失函数可以表示为:
L(y,y^)=−(ylog(y^)+(1−y)log(1−y^))
其中,y^=σ(Wx+b) 为模型的预测输出,y 为实际标签,W 为权重,b 为偏置,σ 为Sigmoid激活函数。
为了优化权重和偏置,我们需要计算损失函数相对于模型参数(权重 W 和偏置 b)的偏导数。通过链式法则,我们可以得到每个参数的梯度:
对权重 W 的梯度:
∂W∂L=N1i=1∑N(y^i−yi)xi
对偏置 b 的梯度:
∂b∂L=N1i=1∑N(y^i−yi)
为了在逻辑回归模型中实现梯度计算,我们需要增加一个 backward 函数来计算损失函数的梯度。该函数将根据模型的输入数据和实际标签计算出每个参数的梯度,并将其存储在模型的 grads 属性中。
import paddleimport paddle.nn as nn# 自定义 Logistic 回归模型(仅用 paddle.matmul)class LogisticRegression(nn.Layer):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__() # 定义线性层
self.params = {}
self.params['w'] = paddle.zeros([input_dim, 1]) # 初始化权重
self.params['b'] = paddle.zeros(shape=[1]) # 初始化偏置
self.fc = nn.Linear(input_dim, 1) def forward(self, x):
# 使用 paddle.matmul 计算加权和
logits = paddle.matmul(x, self.params['w']) + self.params['b']
outputs = self.sigmoid(logits) return outputs def sigmoid(self, x):
# Sigmoid 激活函数
return 1 / (1 + paddle.exp(-x)) def backward(self, x, y):
"""
计算损失函数对模型参数的梯度,并将其存放在 grads 属性中。
:param x: 输入特征
:param y: 真实标签
:return: None
"""
# 计算预测输出
logits = paddle.matmul(x, self.params['w']) + self.params['b']
y_pred = self.sigmoid(logits) # 计算损失函数对权重的偏导数
dw = paddle.matmul(paddle.transpose(x, [1, 0]), (y_pred - y)) / x.shape[0]
db = paddle.sum(y_pred - y) / x.shape[0] # 将梯度存放在 grads 属性中
self.grads = {}
self.grads['w'] = dw
self.grads['b'] = db # 输出计算的梯度
print("梯度 w:", dw.numpy()) print("梯度 b:", db.numpy())# 使用 backward 函数进行梯度计算# 模型创建与输入数据生成input_dim = 2model = LogisticRegression(input_dim)# 随机生成输入数据和标签x_sample = paddle.to_tensor([[0.5, 1.0], [1.5, -1.0], [1.0, 2.0]], dtype='float32')
y_sample = paddle.to_tensor([[1], [0], [1]], dtype='float32')# 前向计算outputs = model(x_sample)print("模型输出:", outputs.numpy())# 计算梯度model.backward(x_sample, y_sample)# 查看存储的梯度print("存储在 grads 中的权重梯度:", model.grads['w'].numpy())print("存储在 grads 中的偏置梯度:", model.grads['b'].numpy())模型输出: [[0.5] [0.5] [0.5]] 梯度 w: [[ 0. ] [-0.6666667]] 梯度 b: -0.16666667 存储在 grads 中的权重梯度: [[ 0. ] [-0.6666667]] 存储在 grads 中的偏置梯度: -0.16666667
在模型训练过程中,优化器的作用是根据计算得到的梯度来调整模型的参数,逐步最小化损失函数,从而提高模型的预测能力。常见的优化算法有梯度下降(Gradient Descent)及其变种,如随机梯度下降(SGD)、Adam等。
梯度下降法是最基本的优化算法,主要思想是通过计算损失函数的梯度,并按梯度的反方向更新参数,以便使损失函数逐步减小,直至收敛。
对于一个模型的参数 θ,在每一次迭代中,梯度下降法通过以下公式更新参数:
θ:=θ−η∂θ∂L
其中:
假设我们有一个模型 f(x,θ),其损失函数为 L(y,f(x,θ)),我们首先通过反向传播计算出模型的梯度。然后使用梯度下降算法,通过以下步骤更新参数:
计算损失函数 L 对于模型参数 θ 的梯度 ∂θ∂L。
根据梯度更新模型参数:
θnew=θ−η⋅∂θ∂L
在每次迭代中,参数会沿着梯度的反方向进行调整。
在设计优化器时,我们需要使用梯度下降法来更新模型参数。假设我们有一个简单的逻辑回归模型,已经通过 backward 函数计算了梯度,接下来,我们使用优化器来更新模型的权重和偏置。
import paddleimport paddle.nn as nn# 自定义 Logistic 回归模型(仅用 paddle.matmul)class LogisticRegression(nn.Layer):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__() # 定义线性层
self.params = {}
self.params['w'] = paddle.zeros([input_dim, 1]) # 初始化权重
self.params['b'] = paddle.zeros(shape=[1]) # 初始化偏置
self.fc = nn.Linear(input_dim, 1) def forward(self, x):
# 使用 paddle.matmul 计算加权和
logits = paddle.matmul(x, self.params['w']) + self.params['b']
outputs = self.sigmoid(logits) return outputs def sigmoid(self, x):
# Sigmoid 激活函数
return 1 / (1 + paddle.exp(-x)) def backward(self, x, y):
"""
计算损失函数对模型参数的梯度,并将其存放在 grads 属性中。
:param x: 输入特征
:param y: 真实标签
:return: None
"""
# 计算预测输出
logits = paddle.matmul(x, self.params['w']) + self.params['b']
y_pred = self.sigmoid(logits) # 计算损失函数对权重的偏导数
dw = paddle.matmul(paddle.transpose(x, [1, 0]), (y_pred - y)) / x.shape[0]
db = paddle.sum(y_pred - y) / x.shape[0] # 将梯度存放在 grads 属性中
self.grads = {}
self.grads['w'] = dw
self.grads['b'] = db # 输出计算的梯度
# print("梯度 w:", dw.numpy())
# print("梯度 b:", db.numpy())
def update_params(self, lr=0.01):
"""
使用梯度下降法更新模型的权重和偏置
:param lr: 学习率
:return: None
"""
# 更新权重和偏置
self.params['w'] = self.params['w'] - lr * self.grads['w']
self.params['b'] = self.params['b'] - lr * self.grads['b'] # print("更新后的权重 w:", self.params['w'].numpy())
# print("更新后的偏置 b:", self.params['b'].numpy())通过以下步骤进行模型训练,并应用自定义优化器来更新参数:
1.定义模型并初始化。
2.计算每个小批次的梯度。
3.使用梯度下降法更新模型参数。
4.重复以上步骤,直到损失函数收敛。
# 将数据集转换为 Paddle 张量X_train_tensor = paddle.to_tensor(X_train, dtype='float32')
y_train_tensor = paddle.to_tensor(y_train.reshape(-1, 1), dtype='float32') # 确保标签形状匹配# 构建模型input_dim = X_train.shape[1] # 输入特征的维度model = LogisticRegression(input_dim)# 训练过程epochs = 10000learning_rate = 0.01for epoch in range(epochs): # 前向计算
outputs = model(X_train_tensor)
# 计算梯度
model.backward(X_train_tensor, y_train_tensor)
# 更新参数
model.update_params(lr=learning_rate)
# 每200回合打印一次损失值
if (epoch + 1) % 200 == 0: # 计算预测值与实际标签的损失
logits = paddle.matmul(X_train_tensor, model.params['w']) + model.params['b']
predictions = model.sigmoid(logits)
loss = paddle.mean(-y_train_tensor * paddle.log(predictions) - (1 - y_train_tensor) * paddle.log(1 - predictions)) print(f"Epoch {epoch + 1}/{epochs}, Loss: {loss.numpy()}")Epoch 200/10000, Loss: 0.434282124042511 Epoch 400/10000, Loss: 0.35399749875068665 Epoch 600/10000, Loss: 0.31881678104400635 Epoch 800/10000, Loss: 0.2997851073741913 Epoch 1000/10000, Loss: 0.2880668342113495 Epoch 1200/10000, Loss: 0.2802077829837799 Epoch 1400/10000, Loss: 0.2746124267578125 Epoch 1600/10000, Loss: 0.2704521119594574 Epoch 1800/10000, Loss: 0.26725637912750244 Epoch 2000/10000, Loss: 0.2647395730018616 Epoch 2200/10000, Loss: 0.26271817088127136 Epoch 2400/10000, Loss: 0.2610691785812378 Epoch 2600/10000, Loss: 0.25970688462257385 Epoch 2800/10000, Loss: 0.25856953859329224 Epoch 3000/10000, Loss: 0.25761178135871887 Epoch 3200/10000, Loss: 0.25679925084114075 Epoch 3400/10000, Loss: 0.25610560178756714 Epoch 3600/10000, Loss: 0.2555100917816162 Epoch 3800/10000, Loss: 0.25499647855758667 Epoch 4000/10000, Loss: 0.25455155968666077 Epoch 4200/10000, Loss: 0.2541647255420685 Epoch 4400/10000, Loss: 0.2538272738456726 Epoch 4600/10000, Loss: 0.25353190302848816 Epoch 4800/10000, Loss: 0.2532727122306824 Epoch 5000/10000, Loss: 0.2530447244644165 Epoch 5200/10000, Loss: 0.25284361839294434 Epoch 5400/10000, Loss: 0.2526659667491913 Epoch 5600/10000, Loss: 0.25250861048698425 Epoch 5800/10000, Loss: 0.2523690164089203 Epoch 6000/10000, Loss: 0.2522450387477875 Epoch 6200/10000, Loss: 0.25213468074798584 Epoch 6400/10000, Loss: 0.25203630328178406 Epoch 6600/10000, Loss: 0.2519485056400299 Epoch 6800/10000, Loss: 0.2518700659275055 Epoch 7000/10000, Loss: 0.25179991126060486 Epoch 7200/10000, Loss: 0.25173699855804443 Epoch 7400/10000, Loss: 0.2516807019710541 Epoch 7600/10000, Loss: 0.25163009762763977 Epoch 7800/10000, Loss: 0.25158464908599854 Epoch 8000/10000, Loss: 0.2515437602996826 Epoch 8200/10000, Loss: 0.2515070140361786 Epoch 8400/10000, Loss: 0.25147390365600586 Epoch 8600/10000, Loss: 0.25144410133361816 Epoch 8800/10000, Loss: 0.2514171898365021 Epoch 9000/10000, Loss: 0.2513929307460785 Epoch 9200/10000, Loss: 0.25137099623680115 Epoch 9400/10000, Loss: 0.2513512074947357 Epoch 9600/10000, Loss: 0.2513332962989807 Epoch 9800/10000, Loss: 0.2513171434402466 Epoch 10000/10000, Loss: 0.25130248069763184
在训练完模型之后,我们需要对模型进行评估并在新的数据上进行预测。在这一部分,我们将介绍如何使用训练好的 Logistic 回归模型来进行评估和预测。
模型评估的核心是通过计算损失函数(如交叉熵损失)来度量模型在测试集上的表现。我们可以使用测试集的真实标签和模型预测结果计算损失,以了解模型在训练后是否有较好的泛化能力。
评估公式:
L(y,y^)=−N1i=1∑N[yilog(y^i)+(1−yi)log(1−y^i)]
其中,y 为真实标签,y^ 为预测输出,N 为样本数。
# 模型评估def evaluate_model(model, X_test, y_test):
# 前向计算
logits = paddle.matmul(X_test, model.params['w']) + model.params['b']
predictions = model.sigmoid(logits)
# 计算交叉熵损失
loss = paddle.mean(-y_test * paddle.log(predictions) - (1 - y_test) * paddle.log(1 - predictions)) print(f"测试集损失: {loss.numpy()}")
# 预测标签
predicted_labels = (predictions > 0.5).astype('float32') # 将 predicted_labels 转为 float32
# 计算准确率
accuracy = paddle.mean(paddle.cast(predicted_labels == y_test, dtype='float32'))
print(f"测试集准确率: {accuracy.numpy()}") return predicted_labels.numpy()# 将数据转换为 Paddle 张量X_test_tensor = paddle.to_tensor(X_test, dtype='float32')
y_test_tensor = paddle.to_tensor(y_test, dtype='float32')
predicted_labels = evaluate_model(model, X_test_tensor, y_test_tensor)# print("预测标签:", predicted_labels)测试集损失: 1.567063570022583 测试集准确率: 0.5015000104904175
<br/>
# 将数据转换为 Paddle 张量X_test_tensor = paddle.to_tensor(X_test, dtype='float32')
y_test_tensor = paddle.to_tensor(y_test, dtype='float32')# 测试阶段的预测predictions = model(X_test_tensor) # 概率值predicted_labels = (predictions > 0.5).astype('int32') # 转为二分类标签# # 检查预测结果分布# print("预测概率分布:", predictions.numpy().flatten())# print("预测标签分布:", predicted_labels.numpy().flatten())# 可视化对比plt.figure(figsize=(10, 6))# 实际标签可视化plt.subplot(1, 2, 1)
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_test.flatten(), cmap='bwr', alpha=0.6)
plt.title('Actual Labels')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')# 预测标签可视化plt.subplot(1, 2, 2)
plt.scatter(X_test[:, 0], X_test[:, 1], c=predicted_labels.numpy().flatten(), cmap='bwr', alpha=0.6)
plt.title('Predicted Labels')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.tight_layout()
plt.show()<Figure size 1000x600 with 2 Axes>
以上就是【PaddlePaddle】基础理论教程 - 机器学习理论实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号