#lab

lab4 pytorch

developer

lab4 pytorch基础实验

  • pytorch入门

  • task1-2 线性回归

    • 闭式解、梯度下降
  • task3-4 sklearn入门

    • SVC、KMeans、DBScan
    • PCA、tSNE

pytorch入门

conda

环境管理

# 创建一个新环境
conda create -n myenv python=3.10

# 激活环境
conda activate myenv

# 退出当前环境
conda deactivate

# 列出已有的环境
conda env list

# 删除环境
conda remove -n myenv --all

# 查看目录
conda info --envs

# 指定安装目录
conda create --prefix ~/projects/ml-lab/env python=3.10
conda activate ~/projects/ml-lab/env

包管理

# 安装包(推荐带上频道)
conda install numpy -c conda-forge

# 移除包
conda remove numpy

# 更新包
conda update numpy

# 查看当前环境已安装的包
conda list

环境导出复制

# 导出环境配置为 YAML 文件
conda env export > environment.yml

# 从 YAML 文件创建环境
conda env create -n new-env -f environment.yml

# 克隆环境
conda create --name new-env --clone old-env

配置文件

.condarc .zshrc -> conda initialize

数据集操作

# 加载数据
iris = datasets.load_iris()
# 拆分数据
# 如果已经二维,则可以省略
X = iris.data[:, :2]
y = iris.target

lab4 task1-2 pytorch线性回归

线性回归闭式解的推导

线性回归模型

$$ \hat{y} = X \omega + b $$ 其中:

  • $X \in \mathbb{R}^{n \times d}$:样本矩阵,$n$个样本,一个样本$d$个特征
  • $\vec\omega \in \mathbb{R}^{d}$:权重向量
  • $b \in \mathbb{R}$:偏置标量
  • $\vec y \in \mathbb{R}^{n}$:目标值向量

目标:求参数 $\vec\omega$ 和 $b$ ,最小化均方误差损失:

$$ J(\vec\omega, b) = \frac{1}{2n} | X \vec\omega + b \vec 1 - \vec y |^2 $$ 其中$ \vec{1} \in \mathbb{R}^n $是全 1 的列向量,$| \vec x |^2$ = $\sum x_i^2$。

总结

显式偏置法 $$ \boxed{ \begin{aligned} \omega &= (X^\top X)^{-1} X^\top (y - b \mathbf{1}) \ b &= \frac{1}{n} \mathbf{1}^\top (y - X\omega) \end{aligned} } $$ 增广向量法 $$ \boxed{ \theta = \begin{bmatrix} b \ \omega \end{bmatrix} = \left( X’^\top X’ \right)^{-1} X’^\top y } \ X’ = [\mathbf{1} \quad X] $$

第一种推导方式更符合最小二乘法的推广和求无条件极值的思路,第二种不够直观但计算更简单,也更符合线性代数的思维方式,具体实现中我们也采用第二种方式。

梯度下降解法的推导

伪代码

def GradientDescent $(X,\ y,\ 学习率\eta,\ 迭代次数n )$ $初始化 \omega 为d维随机向量$ $初始化 b = 0$ for iter in range($n):$ $y_{预测} = X \omega + b$ $e_{误差} = y_{预测} - y$ $\$ $\nabla \omega = \frac{1}{n} X^T e_{误差}$ $\nabla b = \frac{1}{n} \sum(e_i) $ $\$ $\omega = \omega - \eta \nabla \omega$ $b = b - \eta \nabla b$ return $\omega, b$

实现

二维版本

闭式解

def lr_cf(X_list, Y_list):
    w = 2
    b = 4

    X = torch.tensor(X_list, dtype=torch.float32).view(-1, 1)  # [n, 1]
    Y = torch.tensor(Y_list, dtype=torch.float32).view(-1, 1)  # [n, 1]
    n = X.shape[0]

    # Step 1: 添加一列偏置项
    ones = torch.ones(n, 1)
    X_aug = torch.cat([ones, X], dim=1)  # shape: [n, d+1]

    # Step 2: 显式计算闭式解
    XT_X = X_aug.T @ X_aug             # (d+1, d+1)
    XT_Y = X_aug.T @ Y                 # (d+1, 1)
    theta = torch.inverse(XT_X) @ XT_Y  # 闭式解

    b = theta[0].item()
    w = theta[1:].view(-1)
    return w, b

梯度下降

def lr_gradient_descent(X_list, Y_list):
    w = 0.1
    b = 3

    lr=0.01
    num_iters=100
    X = torch.tensor(X_list, dtype=torch.float32).view(-1, 1)  # [n, 1]
    Y = torch.tensor(Y_list, dtype=torch.float32).view(-1)     # [n]
    n = X.shape[0]

    w = torch.randn(1, dtype=X.dtype)
    b = torch.tensor(0.0, dtype=X.dtype)

    for _ in range(num_iters):
        y_pred = X @ w + b    # shape: [n]
        error = y_pred - Y    # shape: [n]

        grad_w = (1 / n) * (X.T @ error)  # shape: [1]
        grad_b = (1 / n) * error.sum()    # scalar

        w = w - lr * grad_w
        b = b - lr * grad_b

    return w.item(), b.item()

效果

闭式解运行效果

不同学习率

我们用学习率控制梯度下降时每次迭代的步长。如果步长过大,则会发生震荡甚至溢出,表现为画不出图象;如果步长过小,则收敛极慢,无法快速收敛到闭式解。在notebook中调整不同学习率并和闭式解的图象比较,验证发现确实如此。

学习率适中,lr=0.02

image-20250715145026043

学习率过小,lr=0.00000001

image-20250715144241083

学习率过大,lr=10

image-20250715145121089

lab4 task3-4 sklearn入门

SVM分类器

导包

from sklearn import svm

调用

clf = svm.SVC(
    kernel="linear", 
    C=1.0
)
  • 超参数C
    • SVM正则化参数
    • C越大,越过拟合
    • C>0
  • kernel
    • 选择svm内核算法
    • 'linear': 只会画线性边界
    • 'rbf': 高斯径向基,更灵活

拟合与输出

model = clf.fit(X, y)

此时的model就是svc对象,是一个分类器。

可视化

from sklearn.inspection import DecisionBoundaryDisplay 

# sklearn提供的可视化决策边界
ax = plt.subplots(1, 1)
disp = DecisionBoundaryDisplay.from_estimator( 
    clf, # 分类器
    X, # 输入数据
    response_method="predict", # 使用预测数据
    cmap=plt.cm.coolwarm, # 颜色
    alpha=0.8, # 透明度
    ax=ax, # 指定坐标轴
    xlabel=iris.feature_names[0], # 横轴标签
    ylabel=iris.feature_names[1], # 纵轴标签
)

调参

linear时的图象

image-20250715143727514

rbf时的图象,比较了三种C对拟合效果的影响

聚类-KMeans

导包

from sklearn.cluster import KMeans

调用

kms = KMeans(
    n_clusters=3, # 聚类数量,默认8
    n_init='auto', # 不同种子运行kmeans的轮数
    random_state= 1 # 种子的大致位置
)
  • n_clusters:聚类数
    • 指定聚类的数量
    • 默认8
  • n_init:轮数
    • 默认'auto'
    • 随机出n_init个不同种子运行kmeans的轮数
    • 这是为了尽量避免局部最优
  • random_state:种子
    • 生成地图用的种子

拟合与输出

kms.fit(X)
Y=kms.labels_

kms.labels_是一个标签集合,展示了聚类结果。

可视化

plt.scatter(X[:, 0], X[:, 1], c=Y)
plt.show()

指定c=Y即用kmeans的结果给散点染色。

调参

kmeans的可操纵性一般,主要就是选择不同的种子会让聚类形态有所改变,不好人为干预算法。

聚类-DBScan

导包

from sklearn.cluster import DBSCAN

调用

dbs=DBSCAN(
    eps=0.05, 
    min_samples=5 
)
  • eps:最大邻居距离
    • 认定为同一聚类的两点间的最大距离
    • 通过调整这个参数,可以人为控制两个聚类间的距离,实现精确聚类
  • min_samples:最小人数
    • 认定为一个聚类的最小样本数
    • 可以筛选掉一些噪音

拟合与输出

dbs.fit(X)
Y=dbs.labels_

和kmeans一样,输出一个标签组。

可视化

plt.scatter(X[:, 0], X[:, 1], c=Y)
plt.show()

调参

通过调整eps,可以人为决定聚类间的距离,从而实现将两个同心圆分成不同的聚类,十分强大。在lab的例子中,两个环状点集的大致距离是0.05左右,所以设置eps=0.05时算法表现出了很好的性能

另外,dbscan自带噪音过滤,图中的黑点即未被聚类的噪音。

eps=0.05

eps=0.02

降维-PCA

导包

from sklearn.decomposition import PCA

调用

pca = PCA(
    n_components=2, 
    copy=True, 
    svd_solver='auto' 
)
  • n_components:维度
    • 希望压缩到的维度,2或3用于可视化
  • copy
    • 是否复制数据集以避免重写
  • svd_solver
    • 希望采取的svd分解算法,auto即可

拟合与输出

return pca.fit_transform(data)

pca.fit_transform()返回降维后的数组(fortran-style),可以直接用来画图。 如果使用pca.fit(),会返回实例本身,处理麻烦。

可视化

plt.plot(reduced_data[:, 0], reduced_data[:, 1], "g.", markersize=2)
plt.xticks(())
plt.yticks(())
plt.show()

调参

pca的参数几乎没有操作空间,也不带分类效果(即在高维中相近的点在低维中也尽量相近)。尽管如此,它明显比tsne快非常多,所以有其存在价值。

降维-tSNE

导包

from sklearn.manifold import TSNE

调用

tsne = TSNE(
    n_components=2, # 希望压缩到的维度
    perplexity=42, # 数据集小5~30,数据集大30~50
    random_state=27 # 设置生成地图的种子
)
  • n_components:维度
    • 希望压缩到的维度,2或3用于可视化
  • perplexity:神奇数
    • 每个点的邻居数量估计,越大越考虑全局
    • 小数据集5~30,大数据集30~50,大小大致以1000为界
  • random_state:种子
    • 生成地图的种子

拟合与输出

return tsne.fit_transform(data)

和pca类似,用.fit_transform()更方便。

可视化

plt.scatter(
    reduced_data[:, 0], reduced_data[:, 1], 
    c=labels, cmap='tab10', s=10
)
plt.title("t-SNE colored")
plt.colorbar()
plt.xticks(())
plt.yticks(())
plt.show()

由于t-SNE自带分类的效果,所以采用彩色更合适。

调参

t-SNE相比PCA,有两个可调节的参数。perplexity是一个“魔数”,大概类似于DBScan中的eps,决定聚类的效果。random_state是生成种子,会让图象小幅度变化。

perplexity=40, random_state=20

perplexity=20, random_state=40

3D

还可以降维到3维空间中。

# 3D t-SNE
tsne = TSNE(n_components=3, perplexity=30, random_state=42, init='pca')
reduced_data = tsne.fit_transform(data)

# 可视化
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

scatter = ax.scatter(
    reduced_data[:, 0], reduced_data[:, 1], reduced_data[:, 2],
    c=labels, cmap='tab10', s=10
)
ax.set_title("t-SNE in 3D")
ax.view_init(elev=30, azim=30)
plt.colorbar(scatter)
plt.show()

效果