作业:使用机器学习实现MNIST数据集学习。
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
import numpy as np
def load_mnist(path, kind='train'):
import os
import gzip
# 使用的本地加载
"""加载MNIST二进制数据(原始格式)"""
# 拼接标签路径
labels_path = os.path.join(path, f'{kind}-labels-idx1-ubyte.gz')
# 拼接特征路径
images_path = os.path.join(path, f'{kind}-images-idx3-ubyte.gz')
# read,binary以只读模式读取二进制文件
with gzip.open(labels_path, 'rb') as lbpath:
# 从标签路径读取数据,并且使用np.frombuffer转为numpy数组,offset跳过前八个字节
labels = np.frombuffer(lbpath.read(), dtype=np.uint8, offset=8)
with gzip.open(images_path, 'rb') as imgpath:
images = np.frombuffer(imgpath.read(), dtype=np.uint8, offset=16).reshape(len(labels), 784)
return images, labels
# 加载本地数据
X_train, y_train = load_mnist(r'D:\qq文件\深度学习\深度学习\图片资料\MNIST\raw', kind='train')
X_test, y_test = load_mnist(r'D:\qq文件\深度学习\深度学习\图片资料\MNIST\raw', kind='t10k')
# 验证是否为np.array
print(type(X_train), type(y_train), type(X_test), type(y_test))
print(len(y_test))
# 这里降维是为了快点计算,维度太多了。有用的只有那一部分,设置管道是工程化管理
pie = Pipeline([('pca', PCA(n_components=80)), ('random_forest', RandomForestClassifier(n_estimators=100, n_jobs=-1))])
# 超参数搜索参数,设置少是为了电脑跑快点,可以多设置几个
param_dict = {"random_forest__n_estimators": [120, 200, 300], "random_forest__max_depth": [5, 8, 12, 20]}
grid_search = GridSearchCV(pie, param_grid=param_dict, cv=5)
# 数据集太大了,取前1000个进行训练
grid_search.fit(X_train[:1000], y_train[:1000])
print(grid_search.best_params_)
print(grid_search.best_score_)
print(grid_search.best_estimator_)
# 一万个验证集
print(grid_search.score(X_test, y_test))
<class 'numpy.ndarray'> <class 'numpy.ndarray'> <class 'numpy.ndarray'> <class 'numpy.ndarray'>
10000
{'random_forest__max_depth': 20, 'random_forest__n_estimators': 200}
0.867
Pipeline(steps=[('pca', PCA(n_components=80)),
('random_forest',
RandomForestClassifier(max_depth=20, n_estimators=200,
n_jobs=-1))])
0.85
Process finished with exit code 0
树的深度可以再多点,数据数量也可以往上加,增加训练量,准确率还能增加。
笔记
一、参数初始化
1、固定值初始化
# 固定初始化
# 1.全零初始化
# 一般不用于权重初始化,但是可以用来初始化偏置
# 同一层大的参数会得到相同的梯度,影响神经网络多样性的表达能力
def text01():
fc1 = nn.Linear(6, 4)
# 全零初始化
nn.init.zeros_(fc1.weight)
# 全一初始化
nn.init.ones_(fc1.weight)
# 固定值初始化
nn.init.constant_(fc1.weight, 4.0)
print(fc1.weight)
2、平均初始化,正态分布初始化
# 随机初始化,均匀初始化和正态初始化
def text02():
fc1 = nn.Linear(6, 4)
# 均匀分布初始化,a,b默认范围0-1
nn.init.uniform_(fc1.weight, -0.1, 0.1)
print(fc1.weight)
def text03():
fc1 = nn.Linear(6, 4)
# 正态分布初始化,标准正态分布,默认mean,std,0-1
nn.init.normal_(fc1.weight)
print(fc1.weight)
3、Xavier初始化
Xavier 初始化(由 Xavier Glorot 在 2010 年提出)是一种自适应权重初始化方法,专门为解决神经网络训练初期的梯度消失或爆炸问题而设计。Xavier 初始化也叫做Glorot初始化。Xavier 初始化的核心思想是根据输入和输出的维度来初始化权重,使得每一层的输出的方差保持一致。具体来说,权重的初始化范围取决于前一层的神经元数量(输入维度)和当前层的神经元数量(输出维度)。
优点:平衡了输入和输出的方差,适合Sigmoid 和 Tanh 激活函数。
应用场景:常用于浅层网络或使用Sigmoid 、Tanh 激活函数的网络
4、he初始化
也叫kaiming 初始化。He 初始化的核心思想是调整权重的初始化范围,使得每一层的输出的方差保持一致。与 Xavier 初始化不同,He 初始化专门针对 ReLU 激活函数的特性进行了优化。
-
fan_in
模式(默认):优先保证前向传播稳定,方差。
-
fan_out
模式:优先保证反向传播稳定,方差。
-
优点:适用于ReLU 和 Leaky ReLU 激活函数。
应用场景:深度网络,尤其是使用 ReLU 激活函数时
5、总结
在神经网络各个层都有适合的初始化方法,偏置最简单可以直接赋值0.掌握各个层适合的初始化方法,可以在梯度下降时表现出更快的速度,更加稳定。
二、损失函数Loss
1、MAE(Mean Absolute Error,平均绝对误差)通常也被称为 L1-Loss,通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。
特点:
-
鲁棒性:与均方误差(MSE)相比,MAE对异常值(outliers)更为鲁棒,因为它不会像MSE那样对较大误差平方敏感。
-
物理意义直观:MAE以与原始数据相同的单位度量误差,使其易于解释。
-
应用场景: MAE通常用于需要对误差进行线性度量的情况,尤其是当数据中可能存在异常值时,MAE可以避免对异常值的过度惩罚。
2、MSE损失
均方差损失,也叫L2Loss。
MSE(Mean Squared Error,均方误差)通过对预测值和真实值之间的误差平方取平均值,来衡量预测值与真实值之间的差异。
特点:
-
平方惩罚:因为误差平方,MSE 对较大误差施加更大惩罚,所以 MSE 对异常值更为敏感。
-
凸性:MSE 是一个凸函数(国际的叫法,国内叫凹函数),这意味着它具有一个唯一的全局最小值,有助于优化问题的求解。
3、交叉熵损失-CrossEntropyLoss
特点:
-
概率输出:CrossEntropyLoss 通常与 softmax 函数一起使用,使得模型的输出表示为一个概率分布(即所有类别的概率和为 1)。PyTorch 的 nn.CrossEntropyLoss 已经内置了 Softmax 操作。如果我们在输出层显式地添加 Softmax,会导致重复应用 Softmax,从而影响模型的训练效果。
-
惩罚错误分类:该损失函数在真实类别的预测概率较低时,会施加较大的惩罚,这样模型在训练时更注重提升正确类别的预测概率。
-
多分类问题中的标准选择:在大多数多分类问题中,CrossEntropyLoss 是首选的损失函数。
应用场景:
CrossEntropyLoss 广泛应用于各种分类任务,包括图像分类、文本分类等,尤其是在神经网络模型中。
4、二分类交叉熵损失-BCELoss
二分类交叉熵损失函数,使用在输出层使用sigmoid激活函数进行二分类时。
5、总结
1、在线性回归任务时,如果对于数据敏感,并且可能含有异常值时使用L1平均误差损失,对于数据具有更好的鲁棒性。
2、线性回归任务时,如果数据正常没有异常值,使用L2也叫做MSE均方误差损失。
3、对于多分类任务,同时激活函数为softmax,使用交叉熵损失函数CrossEntropyLoss
4、对于二分类任务,输出激活函数为sigmoid,使用二分类交叉损失函数-BCELoss
三、梯度下降算法
1、传统梯度下降方式。
批量梯度下降Batch Gradient Descent BGD ,随机梯度下降SGD,小批量梯度下降Mini-batch Gradient Descent MGBD
# 传统的梯度下降算法
# 1.批量梯度下降,使用所有数据进行训练
# 优点,能按照真实的梯度方向下降
# 缺点:占用内存大,计算速度慢
# 2.随机梯度下降,使用数据集其中几个样本进行梯度计算
# 优点:计算速度块,占用内存小,
# 缺点:容易出现参数震荡,不太容易找到最优解
# 3.小批量梯度下降,使用数据集的小部分进行计算,介于批量和随机之间
# 优点:在计算效率和稳定之间找到平衡点
# 缺点:不容易找到小批量的大小,如果太小接近随机,如果太大,接近批量
# 一般选择:32,64,128,256等小批量
def text01():
x = torch.randn(1000, 10)
y = torch.randn(1000, 1)
dataset = TensorDataset(x,y)
# 批量梯度下降
# dataloader = DataLoader(dataset, batch_size=len(dataset), shuffle=True)
# 随机梯度下降
# dataloader = DataLoader(dataset, batch_size=10, shuffle=True)
# 小批量梯度下降
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
fc1 = nn.Linear(10, 1)
# 损失算法
criterion = nn.MSELoss()
# 定义优化器
optimizer = optim.SGD(fc1.parameters(), lr=0.1)
for epoch in range(10):
for data, label in dataloader:
# 预测值
y_pred = fc1(data)
# 计算损失
loss = criterion(y_pred, label)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 梯度更新
optimizer.step()
print(epoch, loss.item())
-
收敛速度慢:BGD和MBGD使用固定学习率,太大会导致震荡,太小又收敛缓慢。
-
局部最小值和鞍点问题:SGD在遇到局部最小值或鞍点时容易停滞,导致模型难以达到全局最优。
-
训练不稳定:SGD中的噪声容易导致训练过程中不稳定,使得训练陷入震荡或不收敛。
2、动量算法Momentum
动量(Momentum)是对梯度下降的优化方法,可以更好地应对梯度变化和梯度消失问题,从而提高训练模型的效率和稳定性。它通过引入 指数加权平均 来积累历史梯度信息,从而在更新参数时形成“动量”,帮助优化算法更快地越过局部最优或鞍点。
总结:
-
动量项更新:利用当前梯度和历史动量来计算新的动量项。
-
权重参数更新:利用更新后的动量项来调整权重参数。
-
梯度计算:在每个时间步计算当前的梯度,用于更新动量项和权重参数。
Momentum 算法是对梯度值的平滑调整,但是并没有对梯度下降中的学习率进行优化
3、AdaGrad
AdaGrad(Adaptive Gradient Algorithm)为每个参数引入独立的学习率,它根据历史梯度的平方和来调整这些学习率。具体来说,对于频繁更新的参数,其学习率会逐渐减小;而对于更新频率较低的参数,学习率会相对较大。AdaGrad避免了统一学习率的不足,更多用于处理稀疏数据和梯度变化较大的问题。
优点:
-
自适应学习率:由于每个参数的学习率是基于其梯度的累积平方和 G_{t,i} 来动态调整的,这意味着学习率会随着时间步的增加而减少,对梯度较大且变化频繁的方向非常有用,防止了梯度过大导致的震荡。
-
适合稀疏数据:AdaGrad 在处理稀疏数据时表现很好,因为它能够自适应地为那些较少更新的参数保持较大的学习率。
缺点:
-
学习率过度衰减:随着时间的推移,累积的时间步梯度平方值越来越大,导致学习率逐渐接近零,模型会停止学习。
-
不适合非稀疏数据:在非稀疏数据的情况下,学习率过快衰减可能导致优化过程早期停滞。
AdaGrad是一种有效的自适应学习率算法,然而由于学习率衰减问题,我们会使用改 RMSProp 或 Adam 来替代。
4、RMSProp
虽然 AdaGrad 能够自适应地调整学习率,但随着训练进行,累积梯度平方 G_t会不断增大,导致学习率逐渐减小,最终可能变得过小,导致训练停滞。
RMSProp(Root Mean Square Propagation)是一种自适应学习率的优化算法,在时间步中,不是简单地累积所有梯度平方和,而是使用指数加权平均来逐步衰减过时的梯度信息。旨在解决 AdaGrad 学习率单调递减的问题。它通过引入 指数加权平均 来累积历史梯度的平方,从而动态调整学习率。
优点
-
适应性强:RMSProp自适应调整每个参数的学习率,对于梯度变化较大的情况非常有效,使得优化过程更加平稳。
-
适合非稀疏数据:相比于AdaGrad,RMSProp更加适合处理非稀疏数据,因为它不会让学习率减小到几乎为零。
-
解决过度衰减问题:通过引入指数加权平均,RMSProp避免了AdaGrad中学习率过快衰减的问题,保持了学习率的稳定性
缺点
依赖于超参数的选择:RMSProp的效果对衰减率 \gamma 和学习率 \eta 的选择比较敏感,需要一些调参工作。
5、Adam
Adam(Adaptive Moment Estimation)算法将动量法和RMSProp的优点结合在一起:
-
动量法:通过一阶动量(即梯度的指数加权平均)来加速收敛,尤其是在有噪声或梯度稀疏的情况下。
-
RMSProp:通过二阶动量(即梯度平方的指数加权平均)来调整学习率,使得每个参数的学习率适应其梯度的变化。
优点
-
高效稳健:Adam结合了动量法和RMSProp的优势,在处理非静态、稀疏梯度和噪声数据时表现出色,能够快速稳定地收敛。
-
自适应学习率:Adam通过一阶和二阶动量的估计,自适应调整每个参数的学习率,避免了全局学习率设定不合适的问题。
-
适用大多数问题:Adam几乎可以在不调整超参数的情况下应用于各种深度学习模型,表现良好。
缺点
-
超参数敏感:尽管Adam通常能很好地工作,但它对初始超参数(如 \beta_1、 \beta_2 和 \eta)仍然较为敏感,有时需要仔细调参。
-
过拟合风险:由于Adam会在初始阶段快速收敛,可能导致模型陷入局部最优甚至过拟合。因此,有时会结合其他优化算法(如SGD)使用。
2.5 总结
梯度下降算法通过不断更新参数来最小化损失函数,是反向传播算法中计算权重调整的基础。在实际应用中,根据数据的规模和计算资源的情况,选择合适的梯度下降方式(批量、随机、小批量)及其变种(如动量法、Adam等)可以显著提高模型训练的效率和效果。
Adam是目前最为流行的优化算法之一,因其稳定性和高效性,广泛应用于各种深度学习模型的训练中。Adam结合了动量法和RMSProp的优点,能够在不同情况下自适应调整学习率,并提供快速且稳定的收敛表现。