免费、绿色、专业的手机游戏中心下载安装平台-游家吧

当前位置: 首页 > 教程攻略 > 【PaddlePaddle】基础理论教程 - 机器学习理论实践

【PaddlePaddle】基础理论教程 - 机器学习理论实践

更新时间:2026-02-16 10:23:07

爱情和生活模拟rpg手机版
  • 类型:体育竞技
  • 大小:87.5mb
  • 语言:简体中文
  • 评分:
查看详情

【PaddlePaddle】基础理论教程 - 机器学习理论实践

本篇深入解析机器学习的核心理论,聚焦线性回归和逻辑回归。详细介绍数据预处理技巧,模型构建与训练过程,并通过性能评估进行验证。特别强调如何利用PaddlePaddle框架实现深度学习,全面掌握从概念到实践的开发流程,实操经验显著提升。

前言

在前一节中,我们从机器学习的根基开始,并一步步实现了线性回归与分类任务。接下来,我们将通过分析加州房价预测的问题,来深入理解如何应用简单线性模型来进行数值预测。

接下来,利用Logistic回归成功处理了Moon据集,通过优化提升模型准确率。

逐步让大家熟练学习机器学习的理论和实践

一、利用Paddle实现基于线性回归的加州房价预测

1.1 数据集介绍

我们将使用美国加利福尼亚州住房价格数据集(California Housing Dataset)作为本次案例的研究对象。此数据集涵盖了加州普查区的房价和其他相关属性信息,旨在探索地区特性与房价之间的联系。数据包含样本和特征变量,其目标是预测该地区的中位房价。

1.1.1 数据集特征说明

以下是数据集中的特征描述:- MedInc:区域收入中位数,单位为万美元。 - HouseAge:区域房屋年龄中位数,单位为年。 - AveRooms:每户平均房间数,无单位。 - AveBedrms:每户平均卧室数,无单位。 - Population:区域人口数量,单位为人。 - AveOccup:每户平均入住人数,无单位。 - Latitude:区域纬度,单位为度。 - Longitude:区域经度,单位为度。目标变量是房价中位数,单位为万美元。

1.1.2 目标分析

在本研究中,采用线性回归模型对区域内的房价中位数进行预测,并通过特征工程和正则化技巧提升模型表现。

# 导入必要库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登录后复制

1.2 数据清洗与可视化分析

在机器学习任务中,数据清洗是构建模型基础环节的关键,旨在处理缺失值、异常值等问题,并通过数据分析揭示数据分布与特征间的关联性,确保数据质量提升模型表现。

1.2.1 数据清洗

(1) 缺失值分析

缺失值可能会导致模型无法正常训练。我们需要检查数据集是否存在缺失值,并采取适当措施进行填补或移除。 In [2]

检查缺失值```python missing_values = data.isnull.sum print(各列缺失值情况:\n, missing_values) ```如果存在缺失值,可以选择删除或填充: - 删除缺失值样本:`cleaned_data = data.dropna` - 填充均值:`filled_data = data.fillna(data.mean)` 清洗后的数据集基本信息:```python 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登录后复制

(2) 异常值处理

异常值是数据中的极端值,可通过统计分析和图表识别,例如使用四分位法界限或箱线图进行检测。

IQR方法:利用四分位数间距(IQR)检测异常值。

异常值范围:小于(Q11.5×IQR)(Q11.5×IQR)或大于(Q3+1.5×IQR)(Q3+1.5×IQR)的值被视为异常值。 In [3]

# 统计每列的上下四分位数和异常值范围for col in data.columns[:-: Q= data[col].quantile( Q= data[col].quantile( IQR = Q- Q1 lower_bound = Q- * IQR upper_bound = Q+ * IQR outliers = data[(data[col] <= lower_bound) | (data[col] >= upper_bound)] print(f{col} 列异常值数量:{len(outliers)})

MedInc列的异常值有;HouseAge列没有异常值;AveRooms列异常值为;AveBedrms列异常值为;Population列的异常值为;AveOccup列的异常值有;Latitude和Longitude列中均无异常值。

1.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登录后复制 In [5]

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>登录后复制登录后复制登录后复制

1.4 数据特征归一化

在机器学习任务中,特征归一化是特征工程不可或缺的一部分。其核心目标是将不同量纲的特征值调整到同一尺度范围内,这有助于提升模型训练的速度和优化效果,尤其适用于使用梯度下降算法及依赖于距离计算的模型,如线性回归、逻辑回归和支持向量机等。通过这个步骤,可以确保所有特征在后续处理中具有相对公平的竞争机会,从而有效避免不同量纲导致的偏差。

1.4.1 为什么需要特征归一化?

现实数据中,不同特征的取值范围可能存在巨大差异。例如:- 收入中位数(MedInc):范围为 [ 。 - 人口数量(Population):范围为 [ 。 - 经纬度(Latitude 和 Longitude):范围分别为 [ 和 [- -。

这种差异会导致以下问题:- 梯度更新不均衡:梯度下降时,不同特征的梯度大小会不一致,可能导致模型难以收敛或收敛速度过慢。 - 模型偏向问题:未归一化的特征可能对模型决策产生不均衡的影响,如范围较大的特征可能主导模型输出。 - 优化器敏感性:大多数优化算法(如SGD)对特征的尺度变化较为敏感。

1.4.2 常见的特征归一化方法

(1) 最小-最大归一化(Min-Max Scaling)

将特征值线性缩放到指定范围(通常为 [0, 1] 或 [-1, 1])。

公式:

[x′=xmin(x)max(x)min(x)][x′=max(x)min(x)xmin(x)]

优点: 保持数据分布形状不变。 对特征值范围的约束性强。

适用场景: 特征范围已知,且分布无显著异常值。

(2) 标准化(Standardization)

将特征值转化为标准正态分布(均值为0,标准差为1)。

公式:[x′=xμσ][x′=σxμ]其中,(μ)(μ)为均值,(σ(σ) 为标准差。

优点: 适合处理有偏分布数据。 对异常值较为鲁棒。

适用场景: 特征范围未知,且数据分布可能存在不同量级。

(3) 对数缩放(Log Scaling)

对数变换将特征值压缩到较小范围。

公式:[x′=log(x+1)][x′=log(x+1)]

适用场景: 特征值存在长尾分布,且非负。

(4) 归一化的对比

数据标准化的影响方法 数据分布影响 是否受异常值影响 适用范围 Min-Max归一化 保持原始分布不变 不敏感于极端值 特征范围已知 标准化 转换为均值和标准差的分布 鲁棒性强,但依赖特征的具体范围 特征范围未知,适用于大多数场景对数缩放 处理极端负值表现良好 易受正负异常值的影响 适合具有长尾分布的数据集

使用最大最小归一化进行特征和目标变量的预处理,确保数据在训练过程中更加平滑。通过`sklearn`库中的`train_test_split`函数将数据集划分为训练集和测试集,进一步增强了模型对新数据的适应能力。归一化方法能有效减少特征间的强相关性,并为后续的机器学习任务提供更好的基础数据格式。

# 标准化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)登录后复制 In [12]

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.登录后复制

(5)可视化归一化效果

In [13]

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>登录后复制登录后复制登录后复制 In [15]

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>登录后复制登录后复制登录后复制

1.5 模型构建

完成数据清洗和预处理之后,我们将进入到模型构建阶段。本文利用PaddlePaddle框架定制了一个线性回归模型。我们采取了最小二乘法的解析方法,直接求得模型参数,以此来替代传统的需要进行大量迭代训练的过程。这种方法不仅节省了大量的时间与计算资源,而且保证了结果的一致性和准确性。

1.5.1 自定义Linear算子

在本节中,我们将探索如何使用 PaddlePaddle 构建并实现自己的算子。首先,创建一个简单的线性算子实例。代码示例:```python class LinearOp(paddle.nn.Layer): def __init__(self, in_features, out_features): super(LinearOp, self).__init__ self.weight = paddle.nn.Parameter(paddle.empty([out_features, in_features])) def forward(self, input_tensor): return paddle.matmul(input_tensor, self.weight)linear_op = LinearOp(in_features= out_features= result = linear_op(paddle.randn([)) print(result.shape) # 输出: [ ```

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.bias登录后复制

1.5.2 定义 Runner 类

Runner类是实现模型训练的关键组件,用于设定参数、计算损失、选择优化器,并执行整个训练、评估及预测流程。

# 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()登录后复制

1.5.3 使用 Runner 类来构建线性回归模型并完成训练、评价和预测。

In [18]

# 加载数据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[)# 使用最小二乘法解析解求解模型参数runner.prepare_model(X_train, y_train)# 评价模型predictions = runner.evaluate_model(X_test, y_test)# 对新数据进行预测X_new = X_test[: # 假设我们使用测试集的前数据进行预测predicted_prices = runner.predict(X_new)print(预测结果(前):, predicted_prices)

解析得到的模型参数: 权重: [[ ] [ [- [ ] [ [- [-] [-]] 偏置: [ 模型评价 - 测试集均方误差: 预测结果(前): [[ [ [ [ []]

二、线性分类

在前边的学习过程中,我们通过对线性回归的学习,为了解决实际问题提供了直观且基础的方法。通过将输入特征与目标变量之间的关系表示成一个线性方程,线性回归帮助我们理解了如何在多维空间中找到最优的拟合直线。然而,除了回归问题之外,机器学习中的分类问题同样重要。许多应用场景下,我们需要根据输入数据的特征对样本进行分类。这时,线性分类模型成为了一种非常有效的选择。

在与线性回归模型异曲同工之妙的是线性分类模型,它通过线性方程来确定样本所属类别的归属。与线性回归不同,线性分类的目标是将数据点精准地分隔到不同的类别中,而不是预测连续的数值。接下来,我们将介绍线性分类的定义、核心思想,并展示如何利用机器学习方法进行分类操作。

线性分类是一种运用线性模型的算法,目标是在给定的数据上,通过创建一个线性的分隔平面,将不同类别的样本区分开。这种方法的核心在于使用线性函数来构建数据的分割边界,使得每个样本都被准确地分配到相应的类别标签中。

给定一个输入数据点 \( x \in R^n \),线性分类模型尝试使用一个线性方程来分类。

[y=sign(w1x1+w2x2++wnxn+b)][y=sign(w1x1+w2x2++wnxn+b)]

其中:权重(ww…,wn)(ww…,wn)、特征(xx…,xn)(xx…,xn)和偏置(b)(b)是模型的关键组成部分,共同作用以生成决策边界的位置(y)(y),预测类别通常为±

在训练过程中,线性分类器的目标是找出最优的权重(w)和偏置(b),使决策边界能最好地区分不同类别的数据点。

在机器学习中,回归和分类是两种常见的模型类型。与回归问题不同,线性分类模型的输出通常是一个类别标签,而不是一个连续的数值。根据训练数据的不同,线性分类器会调整其参数,使得分类边界最大程度地正确地划分样本。这有助于提高模型对新数据的预测准确性。

常见的线性分类模型

常见的线性分类模型包括: 感知机(Perceptron):这是一种最简单的线性分类模型,基于梯度下降方法。它通过不断调整权重来找到分割不同类别的超平面。 支持向量机(SVM):SVM通过最大化类别之间的间隔来寻找最优超平面,从而提高分类精度。 逻辑回归(Logistic Regression):尽管名字中包含“回归”,但它是一种广泛使用的线性分类模型。输出的是概率值,经过Sigmoid激活函数后用于二分类问题。逻辑回归通常与决策树或其他集成学习方法结合使用。 Softmax回归:解决了二分类问题中逻辑回归的局限,能够处理多个类别的分类问题。在Softmax回归中,我们通过一个线性模型计算每个类别的得分,然后通过Softmax函数将这些得分转换为概率,最终输出每个类别的预测概率。这些模型各有特点和应用场景,可以有效地解决不同的分类问题。

2.1 介绍 Logistic 回归

Logistic 回归(Logistic Regression),一种广泛应用的统计工具,虽然名字中含有“回归”,但实质上是一种用于解决二元分类任务的技术。其关键在于构建一个线性模型,用以预测数据点归属于某类的概率。

2.1 Logistic 回归模型

在二分类任务中,我们利用线性方程来生成一个实数结果,随后通过Sigmoid函数将此实数转换为介于间的概率值,代表该数据归属于特定类别的机会。

Logistic回归模型的预测概率公式为:\( P(y=x) = \frac{{+ e^{-(\theta_+ \sum_{i=^n \theta_i x_i)}} \),其中 \(y\) 的取值范围是 { 。

[P(y=1x)=σ(wTx+b)][P(y=1x)=σ(wTx+b)]

其中 $( w ) $ 是权值,$( x ) $ 是输入特征,$( b ) $ 是偏差参数,$( \sigma ) $ 是 Sigmoid 函数,将其输出转化为概率。

2.2 相关的激活函数

Logistic 回归中使用的核心激活函数是Sigmoid 函数,它的数学表达式为:

[σ(z)=11+ez][σ(z)=1+ez1]

Sigmoid 函数的作用是将输入值 ( z ) 映射到区间 ( (0, 1) ),这使得它非常适合用来表示概率。对于输入值 ( z ) 较大时,Sigmoid 输出接近 1,而当输入值较小时,输出接近 0。

Sigmoid函数特性如下:当输入趋向于正无穷时,输出接近当输入趋向于负无穷时,输出趋向于此函数形成一个S形曲线,其导数平滑,非常适合用在梯度下降法进行优化。

2.3. Logistic 函数实现代码

使用PaddlePaddle框架,实现一个简单的 Logistic 回归模型的实现代码

Logistic 函数实现了标准的 Sigmoid 激活函数(σ(z)=11+ez)(σ(z)=1+ez1)

使用 paddle.linspace 生成从 -10 到 10 的等间距数值,并将这些值输入到 Logistic 函数中,计算其对应的输出值。 In [19]

使用PaddlePaddle库定义并可视化Logistic函数,展示其在[- 区间内的变化。

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>登录后复制

函数解析X轴:表示线性模型的输出(z=wTx+b)(z=wTx+b) Y轴:表示通过 Sigmoid 激活函数得到的概率值(σ(z))(σ(z))。 当 ( z ) 较大时,Sigmoid 输出接近 表示类别概率很高。 当 ( z ) 较小时,Sigmoid 输出接近 表示类别概率很高。 在 ( z = ) 时,Sigmoid 输出为 表示类别类别概率相等。

2.4 基于Logistic实现Moon1000数据集回归测试

我们设计了一个基于Moon据集的简单二分类数据集,这个集合包含由两个半月形构成的数据点,适用于评估二分类模型的表现。

make_moons:我们生成了 1000 条带有噪声(noise=0.1)的二分类数据,形成了两个半月形状的数据集。

StandardScaler:对数据进行标准化处理,使其均值为 0,标准差为 1,这有助于提高模型训练的效果。

plt.scatter:通过不同的颜色和标记(Class 0 用蓝色,Class 1 用红色)绘制两类数据点。 In [20]

生成一个二分类数据集,使用`sklearn.datasets.make_moons`。随机抽取样本,采用标准化处理:import numpy as npimport matplotlib.pyplot as pltfrom sklearn.datasets import make_moonsfrom sklearn.preprocessing import StandardScaler# 生成Moon数据集X, y = make_moons(n_samples= noise= random_state=# 数据标准化scaler = StandardScaler X_scaled = scaler.fit_transform(X)# 可视化数据分布plt.figure(figsize=( ) plt.scatter(X_scaled[y == [:, , X_scaled[y == [:, , color='b', label='Class , s= plt.scatter(X_scaled[y == [:, , X_scaled[y == [:, , color='r', label='Class , s= plt.title('Moon Data Visualization') plt.xlabel('Feature ) plt.ylabel('Feature ) plt.legend plt.grid(True) plt.show

<Figure size 800x600 with 1 Axes>登录后复制 In [21]

# 拆分数据集(训练集、验证集、测试集)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 的形状为 ( ;验证集特征 X_val 的形状为 ( ;测试集特征 X_test 的形状为 ( 。训练集标签 y_train 的形状为 ,验证集标签 y_val 的形状为 ,测试集标签 y_test 的形状为 ( )。

2.5 构建 Logistic 回归模型

In [22]

```python import paddle import paddle.nn as nn import paddle.optimizer as optim# 自定义 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, ) self.params['b'] = paddle.zeros(shape=[) self.fc = nn.Linear(input_dim, def forward(self, x): # 使用 paddle.matmul 计算加权和 logits = paddle.matmul(x, self.params['w']) + self.params['b'] outputs = Logistic(logits) return outputs# 登录后复制登录 ```

paddle.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]])登录后复制

2.6 损失函数介绍

在机器学习领域,损失函数(Loss Function)或称为代价函数(Cost Function),是一个衡量模型预测结果与真实结果之间差异的数学函数。它的关键作用是通过量化预测错误,驱动优化算法,从而使模型能够精确定位并调整参数,最终实现更准确的预测结果。

1. 损失函数的定义与作用

损失函数是一个数学公式,评估模型预测值与实际标签间的偏差,主要功能是:- 量化预测错误 - 调整目标函数导向 - 指引模型参数优化

当损失函数的数值越低时,表明模型的预测结果与其实际值之间的误差越小,从而成为优化算法的主要追求目标。

2. 损失函数的公式

在不同的任务中,损失函数有所不同。常见的损失函数包括:

均方误差(Mean Squared Error,MSE):用于回归问题,公式如下:

L(y,y^)=1N∑i=1N(yiy^i)2L(y,y^)=N1i=1∑N(yiy^i)2

其中,yiyi为真实值,y^iy^i为预测值,NN为样本数量。

交叉熵(Cross-Entropy):用于分类问题,特别是二分类和多分类任务,公式如下:

对于二分类,交叉熵损失函数可以写为:

L(y,y^)=1N∑i=1N[yilog(y^i)+(1yi)log(1y^i)]L(y,y^)=N1i=1∑N[yilog(y^i)+(1yi)log(1y^i)]

对于多分类,交叉熵损失函数可以写为:

L(y,y^)=∑i=1Cyilog(y^i)L(y,y^)=i=1∑Cyilog(y^i)

在这个分类任务中,yiyi代表真实类别标签(一般为one-hot编码),y^iy^i是预测类别的概率,而CC则表示类别数量。

3. 交叉熵损失函数

在机器学习中,交叉熵损失函数常应用于分类问题,特别是在进行二分类及多分类任务时发挥重要作用。其核心概念在于评估模型预测结果与真实标签之间的差异,具体而言是对概率分布的量化比较。通过这种方法,可以有效地评价模型性能并改进决策过程,从而提升整体算法效果。

二分类交叉熵:当标签是二分类时,交叉熵损失函数用于度量二进制分类模型输出的概率和真实标签之间的差异。

在多分类场景下,交叉熵损失评估每个类的概率差异,并决定模型预测的最大可能性类别。

4. 构建交叉熵损失函数类代码

以下是一个简单的交叉熵损失函数实现,代码使用了 PaddlePaddle 框架: In [24]

import paddleclass 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([[ -, [ -], dtype='float) labels = paddle.to_tensor([ , dtype='int)loss_fn = CrossEntropyLoss loss = loss_fn(logits, labels) print(交叉熵损失:, loss.numpy)

交叉熵损失: 0.46265036登录后复制

2.7 回归模型优化 - 梯度优化

梯度优化是机器学习中广泛使用的优化技术之一,它利用了梯度下降算法来调整模型参数以最小化损失函数,从而提升预测精度。在训练阶段,参数通过计算梯度并在最优路径上移动,逐步减少误差。

1. 梯度计算

梯度计算是优化算法的关键步骤,它用于求解损失函数关于模型参数的导数值。梯度展示了损失函数在特定点处的变化率,并指导我们通过调整参数来最小化损失。损失函数:在逻辑回归中,常用的损失函数为交叉熵损失函数,其目的是衡量模型预测与实际标签之间的差距。 偏导数:偏导数揭示了每个参数对损失函数变化的敏感度。它表示当一个参数改变时,导致整个损失函数变化量的程度。通过计算每个参数的偏导数,我们可以确定调整哪个参数以降低损失的最佳方法。

2. 偏导数计算

对于逻辑回归的交叉熵损失函数,损失函数可以表示为:

L(y,y^)=(ylog(y^)+(1y)log(1y^))L(y,y^)=(ylog(y^)+(1y)log(1y^))

在此模型中,y_hat = σ(Wx + b)是预测结果,而 y 是真实标签,W 代表权重,b 表示偏置,σ 是 Sigmoid 激活函数。

为了优化权重和偏置,我们需计算损失函数对参数(权重WW、偏差bb)的偏导数。利用链式法则,可得每个参数的梯度。

对权重WW的梯度:

LW=1N∑i=1N(y^iyi)xiWL=N1i=1∑N(y^iyi)xi

对偏置bb的梯度:

Lb=1N∑i=1N(y^iyi)bL=N1i=1∑N(y^iyi)

3. Backward函数的实现

为了在逻辑回归模型中准确地进行梯度计算,我们需要编写一个`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())登录后复制 In [26]

使用backward函数进行梯度计算是一种常用的技术,通过向模型输入数据并根据目标标签生成输出,我们可以逐步调整参数以最小化预测误差。在这种方式下,我们首先创建一个具有特定维度的模型,并生成随机输入和对应的目标标签。然后,我们将这些数据传递给模型进行前向传播;接着,使用backward方法计算梯度,并将它们存储在相应的变量中。在此示例中,模型是一个简单的逻辑回归实例(通过其input_dim参数设置)。我们展示了如何通过随机生成的数据训练一个分类模型,并使用backward函数来更新权重和偏置。这些步骤是机器学习中的基本操作之一,对于理解神经网络的训练过程至关重要。

模型输出为[[],梯度w为[[]],[-,b为-存储在grads中的权重梯度为[[], [-],偏置梯度为-

2.8 模型优化 - 设计优化器

在训练模型时,优化器通过利用计算得到的梯度来调整参数,以最小化损失函数,从而提升模型的预测性能。主流优化策略包括传统的梯度下降算法及其变体,例如随机梯度下降(SGD)和Adam等方法。

1. 梯度下降法(Gradient Descent)

梯度下降法是基础的优化策略,其核心在于利用损失函数的梯度方向调整参数,从而减少损失,实现模型优化,直到达到收敛点。

梯度下降的更新公式

对于一个模型的参数θθ,在每一次迭代中,梯度下降法通过以下公式更新参数:

θ:=θηLθθ:=θηθL

θθ表示模型的参数,如权重、偏置等;ηη为学习率,决定每次更新步长;LθθL代表在损失函数LL对θθ求导得到的梯度值。

计算梯度并进行更新

如果我们有一个模型 \( f(x, \theta) \) 的损失函数是 \( L(y, f(x, \theta)) \),我们首先通过反向传播计算出模型的梯度。接着,利用梯度下降法,按照以下步骤更新参数: 初始化参数:选择一个合适的初值或者从随机分布中产生初始权重和偏置。 反向传播损失:计算目标 \( y \) 实际值与预测值 \( f(x, \theta) \) 之间的差距,即通过反向传递方式更新梯度。 计算梯度:使用链式法则计算模型的各个参数 \( \theta \) 的导数(梯度)。 确定下降步长:根据学习率设定一个合适的步长或学习速率,用于调整参数以减小损失函数的值。 更新参数:通过上述步骤计算出的梯度信息,应用梯度下降法则来逐步调整每个参数 \( \theta \) 的大小和方向。

计算损失函数LL对于模型参数θθ的梯度LθθL。

根据梯度更新模型参数:

θnew=θηLθθnew=θηθL

在每次迭代中,参数会沿着梯度的反方向进行调整。

2. 优化器设计 - 自定义梯度下降

在设计优化器时,我们通常利用梯度下降法则来调整模型的参数。假设我们的逻辑回归模型已通过后向传播(backpropagation)计算了其梯度,接下来,我们将使用优化器更新模型的权重和偏置。

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())登录后复制

2.9 模型训练与优化

通过以下步骤进行模型训练,并应用自定义优化器来更新参数:

- 定义模型并初始化。

- 计算每个小批次的梯度。

- 使用梯度下降法更新模型参数。

- 重复以上步骤,直到损失函数收敛。 In [71]

# 将数据集转换为 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登录后复制

3.0 模型评估与预测

在完成模型训练后,我们需对模型进行评估,并在新数据上进行预测。接下来,本文将详细介绍如何利用已训练的Logistic回归模型进行评估与预测。

1. 模型评估

模型评估的关键在于利用损失函数,例如交叉熵损失,来衡量模型在测试数据集上的表现。通过对比真实标签与模型的预测结果,我们可以计算损失值,以此来判断模型是否具有良好的泛化能力。

评估公式:

L(y,y^)=1N∑i=1N[yilog(y^i)+(1yi)log(1y^i)]L(y,y^)=N1i=1∑N[yilog(y^i)+(1yi)log(1y^i)]

其中,yy为真实标签,y^y^为预测输出,NN为样本数。 In [72]

```python # 模型评估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) - (- y_test) * paddle.log(- predictions)) print(f测试集损失: {loss.numpy}) # 预测标签 predicted_labels = (predictions > .astype('float) # 计算准确率 accuracy = paddle.mean(paddle.cast(predicted_labels == y_test, dtype='float)) print(f测试集准确率: {accuracy.numpy}) return predicted_labels.numpy# 将数据转换为 Paddle 张量X_test_tensor = paddle.to_tensor(X_test, dtype='float) y_test_tensor = paddle.to_tensor(y_test, dtype='float)predicted_labels = evaluate_model(model, X_test_tensor, y_test_tensor) ```

测试集损失: 1.567063570022583 测试集准确率: 0.5015000104904175登录后复制 In []

<br/>登录后复制 In [73]

将数据转换为Paddle张量:X_test_tensor = paddle.to_tensor(X_test, dtype='float) y_test_tensor = paddle.to_tensor(y_test, dtype='float)在测试阶段,执行预测并将其结果存储在predictions中。之后,通过判断条件(>将概率值转换为二分类标签:predicted_labels = (predictions > .astype('int)。为了检查预测结果的分布情况,请打印出相应的概率和标签分布列表:print(预测概率分布:, predictions.numpy.flatten) print(预测标签分布:, predicted_labels.numpy.flatten)最后,可视化对比实际标签和预测标签。使用Paddle绘制了两个子图,分别展示了原始标签和预测标签在二维特征空间中的分布。

<Figure size 1000x600 with 2 Axes>登录后复制

以上就是【PaddlePaddle】基础理论教程 - 机器学习理论实践的详细内容,更多请关注其它相关文章!

精品推荐

相关文章

最新资讯

热门文章

更多

最新推荐

更多

最新更新

更多