模型的对比效果解释

文章目录

  • 模型的对比效果解释
    • 数据集介绍:
      • 首先查看minist数据的构成
    • 第一部分:pytorch中的不同优化模型之间在以10次为单位的对比中的效果分析
      • SGD模型的描述:
      • adam优化模型的描述:
      • AdaGrad 优化模型的描述:
      • 动量(Momentum)
    • 第二部分:tensonflow中的不同优化模型之间在以1000次对比中的效果分析
    • 第三部分:keras中的不同优化模型之间在以10次为Epoch的对比中的效果分析
    • 第四部分:paddle中的不同优化模型之间在以10000的5个epoch的对比中的效果分析
  • 针对分析pytorch模型中的节点数,层数,学习率,优化器,损失函数,激活函数,迭代次数的对比试验
    • 第一部分:损失函数不同导致的minst数据集的模型训练差别
    • 第二部分:激活函数不同导致的minst数据集的模型训练差别
    • 第三部分:模型迭代次数不同导致的minst数据集的模型训练差别
    • 第四部分:模型节点个数不同导致的minst数据集的模型训练差别
    • 第五部分:模型学习率不同导致的minst数据集的模型训练差别
    • 第六部分:模型不同层数不同导致的minst数据集的模型训练差别
  • 总结
    • 学习率过大时侯,模型出现损失为nan缘故

数据集介绍:

MNIST包含70,000张手写数字图像: 60,000张用于培训,10,000张用于测试。图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。

首先查看minist数据的构成

examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
print(example_targets)
print(example_data.shape)

下图可以反映出数据的内容:

这个意味着我们拥有128个例子的28x28像素的灰度(即没有rgb通道)。

通过matplotlib函数绘制其中一些内容:

import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(6):plt.subplot(2,3,i+1)plt.tight_layout()plt.imshow(example_data[i][0], cmap='gray', interpolation='none')plt.title("Ground Truth: {}".format(example_targets[i]))plt.xticks([])plt.yticks([])
plt.show()

可以看到其中的一部分的数据内容如上图所示。

以上为数据演示部分。

第一部分:pytorch中的不同优化模型之间在以10次为单位的对比中的效果分析

pytorch中不同的深度模型优化器主要通过torch.optim.Optimizer参数默认值)来进行是实现:

SGD模型的描述:

SGD是随机梯度下降(stochastic gradient descent)的首字母。

torch.optim.SGD(params,lr=<required parameter>,momentum=0,dampening=0,weight_decay=0,nesterov=False)
  • params (iterable) – 可迭代的参数以优化或定义参数组
  • lr (float) – 学习率
  • momentum (float, optional) – 动量因子 (default: 0)
  • weight_decay (float, optional) – 权重衰减(L2优化) (L2 penalty) (default: 0)
  • dampening (float, optional) – 动量阻尼 (default: 0)
  • nesterov (bool, optional) – enables Nesterov momentum (default: False)
  • maximize (bool, optional) – 根据目标最大化参数,而不是最小化(default: False)

adam优化模型的描述:

Adam 是一种可以替代传统随机梯度下降过程的一阶优化算法,它能基于训练数据迭代地更新神经网络权重。它的名称来源于适应性矩估计(adaptive moment estimation)。在介绍这个算法时,原论文列举了将 Adam 优化算法应用在非凸优化问题中所获得的优势:

  • 直截了当地实现
  • 高效的计算
  • 所需内存少
  • 梯度对角缩放的不变性(第二部分将给予证明)
  • 适合解决含大规模数据和参数的优化问题
  • 适用于非稳态(non-stationary)目标
  • 适用于解决包含很高噪声或稀疏梯度的问题
  • 超参数可以很直观地解释,并且基本上只需极少量的调参

Adam 算法同时获得了 AdaGrad 和 RMSProp 算法的优点。Adam 不仅如 RMSProp 算法那样基于一阶矩均值计算适应性参数学习率,它同时还充分利用了梯度的二阶矩均值(即有偏方差/uncentered variance)。具体来说,算法计算了梯度的指数移动均值(exponential moving average),超参数 beta1 和 beta2 控制了这些移动均值的衰减率。

AdaGrad 优化模型的描述:

AdaGrad算法是通过参数来调整合适的学习率,是能独立自动调整模型参数的学习率,对稀疏参数进行大幅更新和对频繁参数进行小幅更新,因此,AdaGrad方法非常适合处理稀疏数据。AdaGrad算法在某些深度学习模型上效果不错。但还是有些不足,可能是因其累积梯度平方导致学习率过早或过量的减少所致。

更新梯度:g​←1batch_size​∑i=0batch_size​∇θ​L(f(x(i)),y(i))累积平方梯度:r←r+g​⊙g​计算参数:△θ←−δ+r​λ​⊙g​更新参数:θ←θ+△θ更新梯度: g^​ ← \frac{1}{batch\_size}​ \sum_{i=0}^{batch\_size} ​ ∇_θ​ L(f(x_{(i)} ),y_{(i)} )\\ 累积平方梯度: r←r+ g^​ ⊙ g^​ \\计算参数:△θ←− δ+ r​ λ​ ⊙ g^​\\ 更新参数:θ←θ+△θ 更新梯度:g​←batch_size1​​i=0∑batch_size​​∇θ​​L(f(x(i)​),y(i)​)累积平方梯度:r←r+g​⊙g​计算参数:△θ←−δ+r​λ​⊙g​更新参数:θ←θ+△θ

  1. 随着迭代时间越长,累积梯度r rr越大,导致学习速率
    λδ+r\frac{\lambda}{\delta+\sqrt{r}} δ+r​λ​
    随着时间较小,在接近目标值时,不会因为学习率过大而越过极值点。

  2. 不同参数之间的学习速率不同,因此,与之前固定学习率相比,不容易卡在鞍点。

  3. 如果梯度累积参数r 比较小,则速率会比较大,所以参数迭代的步长就会比较大。相反,如果梯度累积参数r 比较大,则速率会比较小,所以参数迭代的步长就会比较小。

动量(Momentum)

动量算法每下降一步都是由前面下降方向的一个累积和当前点梯度方向组合而成。含动量的随机梯度下降算法,其更新方式如下:
更新梯度:g​←1batch_size​∑i=0batch_size​∇θ​L(f(x(i)),y(i))计算梯度:v←βv+g更新参数:θ←θ−ηv更新梯度: g^​ ← \frac{1}{batch\_size}​ \sum_{i=0}^{batch\_size} ​ ∇_θ​ L(f(x_{(i)} ),y_{(i)} )\\ 计算梯度:v←βv+g\\ 更新参数:θ←θ−ηv 更新梯度:g​←batch_size1​​i=0∑batch_size​​∇θ​​L(f(x(i)​),y(i)​)计算梯度:v←βv+g更新参数:θ←θ−ηv
其中β 为动量参数,η为学习率。

具体实现方式在下面的代码块中:

    """优化函数的设置,采用SGD模式,实践adam,Momentum,AdaGradparams (iterable) – 可迭代的参数以优化或定义参数组lr (float) – 学习率momentum (float,optional) – 动量因子 (default: 0)weight_decay (float,optiona) – 权重衰减(L2优化) (L2 penalty) (default: 0)dampening (float,optiona) – 动量阻尼 (default: 0)nesterov (bool,optional) – enables Nesterov momentum (default: False)maximize (bool,optional) – 根据目标最大化参数,而不是最小化(default: False)"""if choice=="SGD":optimizer = torch.optim.SGD(params=model.parameters(), lr=0.01)elif choice == "Adam":optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)elif choice == "Adagrad":optimizer = torch.optim.Adagrad(params=model.parameters(), lr=0.01)elif choice == "Momentum":optimizer = torch.optim.SGD(params=model.parameters(),lr = 0.01,momentum=0.9)

通过不同的结果的图形的绘制,可以看出模型之间的区别实际上是很明显的,通过如下的方式将其中对比的关系图绘制出来:

# 可视化预测
import matplotlib.pyplot as pltfrom pytorch_mnist import MLP, train, test
from tqdm import tqdm# pytorch的loss函数
def tenPytorch(num, choice="SGD"):tenLoss = []acy = []for i in tqdm(range(num)):model = MLP()# print(model)# TODO 将模型放到GPU上####################################model = model.cuda()####################################lossElem = train(choice)tenLoss.append(lossElem)accuracy = test()acy.append(accuracy)return tenLoss, acypytorchLoss, accuracy = tenPytorch(10, choice="SGD")
adamLoss, adamAcy = tenPytorch(10, choice="Adam")
adagradLoss, adagradAcy = tenPytorch(10, choice="Adagrad")
momentumLoss, momentumAcy = tenPytorch(10, choice="Momentum")
# plt画出各种不同模型之间的差异图
# 展示出不同优化参数之间的对比关系,在pytorch模型下,我们同时观察同样都是十次不同的计算,结果之间有什么区别
""":cvar
通过展示sgd、adam、adagrad、momentum的模型的区别,我们可以看出准确率之间区别
"""
plt.plot(range(10), accuracy, color="r", label="sgd")
plt.plot(range(10), adamAcy, color="b", label="adam")
plt.plot(range(10), adagradAcy, color="g", label="adagrad")
plt.plot(range(10), momentumAcy, color="k", label="momentum")
plt.legend()
plt.title("不同优化方法,准确率的区别")
plt.show()plt.plot(range(10), adamLoss, color="b", label="adam")
plt.plot(range(10), adagradLoss, color="g", label="adagrad")
plt.plot(range(10), momentumLoss, color="k", label="momentum")
plt.plot(range(10), pytorchLoss, color="r", label="sgd")
plt.legend()
plt.title("不同优化方法,模型损失的区别")
plt.show()

我们可以通过绘图展示出不同的最优化方法在相同的pytorch迭代中的展示出的效果之间的区别:

通过图片我们可以看出在pytorch平台上面,通过这四条线我们可以清晰地看出:

  • SGD的优化方法,相对来讲比较的缓慢,每次将模型放进去训练,模型总是会有不同的结果而且这个结果相对于其他的优化方法而言比较缓慢。
  • adam模型在开始的时候会相对与SGD优化方法会有更好的效果,在最后模型的最后准确率也相对于SGD效果要好上一些
  • 另外两个模型adagrad,参数优化在以第一次的模型循环中就已经取得了较好的效果,可以看到,在后面的几次循环中模型的准确率在缓慢的波动,
  • 另外对于采用了动量的momentum的SGD优化方法,也可以在第一次模型训练的时候将模型的准确率得到较好的水平,可以看到,动量的方式对SGD模型的学习率优化改善的效果是明显的。

另外对于损失效果的分析:

这个图形画的不是很好,但是我们从中依然可以看到到单独的SGD优化策略在手写数字识别的问题上,遇到的问题相对而言较为明显在一开始的数字识别的损失相对较大,而且同样的对于adam的优化策略而言,下过相较于SGD性能较为好一些,但是在手写数字识别的问题上我们可以看到黑色和绿色的线,两条线几近重合,说明这两的模型的损失变化从一开就相对稳定,这个说明模型在另外的两种优化策略下,及adagrad、带有动量momentum的SGD模型能够很快的达到平衡。这个同样可以在上面的表示准确率的关系的图中体现出来

第二部分:tensonflow中的不同优化模型之间在以1000次对比中的效果分析

我们通过绘制在不同优化模型下的准确率以及损失的图片来观察结果的变化与所选择的优化模型之间的联系,其中实现的代码如下所示:

    if choices == "SGD":tf.optimizers.SGD(learning_rate).apply_gradients(zip(gradients, trainable_variables))elif choices == "Adam":tf.optimizers.Adam(learning_rate).apply_gradients(zip(gradients, trainable_variables))elif choices == "Adagrad":tf.optimizers.Adagrad(learning_rate).apply_gradients(zip(gradients, trainable_variables))elif choices == "Momentum":tf.optimizers.SGD(learning_rate, momentum=0.9).apply_gradients(zip(gradients, trainable_variables))

通过设置选择选择的分支语句将模型中的优化函数部分进行替换,将其替换为较为简单明了的形式。

我们利用如下的方式来将其中的对比关系体现出来,通过如下的代码实现对比的内容,看到模型之间的差异:

"""
通过可视化预测的方式我们可以看出模型在不同优化方式条件下,显著的不同,较为清晰只管,可以说明
至少在手写数字这个问题上我们模型的的优化方式选择对模型的影响较为明显。
"""
import matplotlib.pyplot as pltsgdVal, sgdArr, sgdLoos = train("SGD")
adamVal, adamArr, adamLoos = train("Adam")
adaGradVal, adaGradArr, adaGradLoos = train("Adagrad")
momVal, momArr, momLoos = train("Momentum")
""":cvar
开始进行画图,从图中展现出各个优化方式之间的不同之处
"""
plt.plot(sgdArr, label="SGD", color="r")
plt.plot(adamArr, label="Adam", color="g")
plt.plot(adaGradArr, label="Adagrad", color="b")
plt.plot(momArr, label="Momentum", color="k")
plt.ylabel("Accuracy")
plt.grid(axis='y')
plt.legend()
plt.title("different accuracy")
plt.show()
plt.plot(sgdLoos, label="SGD", color="r")
plt.plot(adamLoos, label="Adam", color="g")
plt.plot( adaGradLoos, label="Adagrad", color="b")
plt.plot(momLoos, label="Momentum", color="k")
plt.ylabel("Loss")
plt.grid(axis='y')
plt.legend()
plt.title("different Loss")
plt.show()
plt.plot([1, 2, 3, 4], [sgdVal, adamVal, adaGradVal, momVal], linestyle='', marker='d', markersize=10)
# 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("测试集准确率情况")
plt.show()

下图是通过上述代码生成的关于,不同的优化方式之间模型在相同的一千次迭代的过程中,模型之间关系的对比显示,可以看出模型之间的对比差异。

上面的不同准确率的图反映了没100的条件下,模型的准确率在验证集上面的变化趋势,同时我们也可以看出在不同的模型的变化过程中,准确率实际上是从100开始的,图中的描述存在一些的不准确,x轴的起点是100。

  • SGD优化方式,也就是随机梯度的优化方式,对模型的提高很容易陷入局部极值,并且模型的优化结果相对其他的集中优化方式而言,是最慢的一种优化模型。
  • Adam优化方式是图中的绿色曲线我们可以看出绿色曲线在一开始的变化呈上升趋势,相较于另外两个优化的方式,优化的所需要的时间也相对较长
  • Adagrad以及Momentum,两种方式都是能够在较少的迭代次数的情况下将模型快速达到极值点,并且准确率也相较于另外两种的程度在前期会高一些,但是后期和adam的优化情况类似,动量的方式,我设置的值为0.9就会使的SGD模型很快的跳出局部极值并加速其模型的快速收敛。

下图是绘制出来的几种不同的优化方式中的损失变得变化情况,同样的模型每100次做一次记录

通过这个损失函数的图像我们呢可以看出在不同的模型的条件下,SGD模型的变化最为明显,adam模型的变化相对较弱,不是很明显,另外两个模型在进行100次迭代的时候模型就已经基本上收敛了。

  • SGD模型的损失变化是最明显的,说明其收敛的速度是其他几个优化方式中最慢的
  • adam模型的损失变化明显由于SGD,但是可以看出在前600的条件下模型的损失变化依然相较于另外两种优化变化慢
  • adagrad模型的损失变化同momentum的变化较为类似,在手写数字识别的神经网络中,他们两个效果是较好的。

下面的图展示的在测试集上四种优化方式在同时进行1000次的情况下模型的准确率的情况:

可以看出SGD优化方式的模型的变化结果是最不好的低于0.9,但是对于其他几个模型来讲其准确率呈现出逐步上升的趋势。

第三部分:keras中的不同优化模型之间在以10次为Epoch的对比中的效果分析

通过修改keras-minist.py文件中的部分代码,统计出在不同的优化策略下的准确率以及误差的差别,可以如图所示:

sgdLoss, sgdAcc = train("SGD")
adamLoss, adamAcc = train("Adam")
adaGradLoss, adaGradAcc = train("Adagrad")
momLoss, momAcc = train("Momentum")
plt.plot(1, sgdAcc, linestyle='', marker="d", markersize=10, color="r", label="SGD")
plt.plot(2, adamAcc, linestyle='', marker='d', markersize=10, color="g", label="Adam")
plt.plot(3, adaGradAcc, linestyle='', marker='d', markersize=10, color="b", label="Adagrad")
plt.plot(4, momAcc, linestyle='', marker='d', markersize=10, color="k", label="momentum")
plt.legend()
# 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("测试集准确率情况")
plt.show()
plt.plot(1, sgdLoss, linestyle='', marker="d", markersize=10, color="r", label="SGD")
plt.plot(2, adamLoss, linestyle='', marker='d', markersize=10, color="g", label="Adam")
plt.plot(3, adaGradLoss, linestyle='', marker='d', markersize=10, color="b", label="Adagrad")
plt.plot(4, momLoss, linestyle='', marker='d', markersize=10, color="k", label="momentum")
plt.legend()
# 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("测试集损失情况")
plt.show()

下图所展示的就是利用keras来绘制出的不同和优化策略下,在相同的60000次数*10轮下的模型在测试集上面的准确率的效果:

我们可以通过这个图清晰的看出SGD单独的优化模型的优化效果是 很一般的虽然训练的数量很大,但是相比于其他模型,SGD显然没有跳出局部收敛的区域,获得图形从整体上来看略显“不行”,其他三个模型在测试集上面的准确率都相对较高,达到0.97以上,说明他们的模型基本上都能够从局部最优解跳出来,使得网络的训练得到更好的表示。

下图是通过上面的代码绘制出的损失的情况的图像,可以清晰地看到,模型在:

尤其是对于SGD优化策略的方法,模型的损失明显还没有没有其他几个损失小,反而是其他几个模型损失的3倍左右,这个也同样说明SGD的收敛速度也同样远低于其他几个模型。

第四部分:paddle中的不同优化模型之间在以10000的5个epoch的对比中的效果分析

我们可以看出利用padlle的如下代码:

import paddle
import paddle.nn.functional as F
from paddle.vision.transforms import ToTensor
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
# 导入数据
train_dataset = paddle.vision.datasets.MNIST(mode="train", transform=ToTensor())
val_dataset = paddle.vision.datasets.MNIST(mode="test", transform=ToTensor())# 定义模型
class MLPModel(paddle.nn.Layer):def __init__(self):super(MLPModel, self).__init__()self.flatten = paddle.nn.Flatten()self.hidden = paddle.nn.Linear(in_features=784, out_features=128)self.output = paddle.nn.Linear(in_features=128, out_features=10)def forward(self, x):x = self.flatten(x)x = self.hidden(x)  # 经过隐藏层x = F.relu(x)  # 经过激活层x = self.output(x)return xdef train(choices):model = paddle.Model(MLPModel())if choices == "SGD":model.prepare(paddle.optimizer.SGD(parameters=model.parameters()),paddle.nn.CrossEntropyLoss(),paddle.metric.Accuracy())elif choices == "Adam":model.prepare(paddle.optimizer.Adam(parameters=model.parameters()),paddle.nn.CrossEntropyLoss(),paddle.metric.Accuracy())elif choices == "Adagrad":model.prepare(paddle.optimizer.Adagrad(parameters=model.parameters(), learning_rate=0.01),paddle.nn.CrossEntropyLoss(),paddle.metric.Accuracy())elif choices == "Momentum":model.prepare(paddle.optimizer.Momentum(parameters=model.parameters()),paddle.nn.CrossEntropyLoss(),paddle.metric.Accuracy())model.fit(train_dataset,epochs=5,batch_size=64,verbose=1)eval_result = model.evaluate(val_dataset, verbose=1)return eval_result['loss'][0], eval_result['acc']sgdLoss, sgdAcc = train("SGD")
adamLoss, adamAcc = train("Adam")
adaGradLoss, adaGradAcc = train("Adagrad")
momLoss, momAcc = train("Momentum")
plt.plot(1, sgdAcc, linestyle='', marker="d", markersize=10, color="r", label="SGD")
plt.plot(2, adamAcc, linestyle='', marker='d', markersize=10, color="g", label="Adam")
plt.plot(3, adaGradAcc, linestyle='', marker='d', markersize=10, color="b", label="Adagrad")
plt.plot(4, momAcc, linestyle='', marker='d', markersize=10, color="k", label="momentum")
plt.legend()
# 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("测试集准确率情况")
plt.show()
plt.plot(1, sgdLoss, linestyle='', marker="d", markersize=10, color="r", label="SGD")
plt.plot(2, adamLoss, linestyle='', marker='d', markersize=10, color="g", label="Adam")
plt.plot(3, adaGradLoss, linestyle='', marker='d', markersize=10, color="b", label="Adagrad")
plt.plot(4, momLoss, linestyle='', marker='d', markersize=10, color="k", label="momentum")
plt.legend()
# 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("测试集损失情况")
plt.show()

首先采用如下的方式观察四种不同的优化策略之间导致的不同的神经网络模型在测试集上面的准确率,如下图所示:

通过模型对比可以看出在优化模型的对比上,可以看出SGD模型的在测试及上面的 准确率从效果来看是比较差的,相较于其他的模型,但是对于paddle而言利用动量法优化的模型也没有效果特编号,准确率在92%多一些,明显差于利用tensorflow和pytorch以及keras的训练出来的模型的效果。

  • 从整体上来看,在优化方式上面SGD的方式,相比于其他模型依然是效果较差容易陷入局部极值
  • 从局部上来看,momentum的动量方法在其他几种框架的运用中模型的训练出来的效果明显较好,但是在paddle的训练过程中效果却比较差
  • 可以看出paddle的训练较为针对于adam方法反而会得到最好的训练效果其准确率在98%左右,相比于其他框架的效果来看paddle明显更好一些。

下图是关于模型损失的结果图片:

通过上图我们可以清晰的看出SGD的模型效果他的损失是最高的还没有完全的收敛,可以明显看出,从这个角度同样也说明SGD模型的训练时间相较于其他的模型时间较长。

同样我们可以看到,其他的几个模型的loss基本上都接近于0,说明模型已经很训练到收敛的效果了。同样momentum的方法针对于相较于前几个框架中较好的效果在paddle 中表现得并不是很明显,但是同样可以证明的是,通过动量的方式可以将SGD的优化方式大大改善,可以得到更好的训练效果。

针对分析pytorch模型中的节点数,层数,学习率,优化器,损失函数,激活函数,迭代次数的对比试验

本过程全部采用控制变量的思想,完成对比实验的内容,初始参数设计:

节点数 [28*28,512,256,10]
层数 3
学习率 0.01
优化器 SGD
损失函数 CrossEntropyLoss(交叉熵)
激活函数 relu
迭代次数 5

第一部分:损失函数不同导致的minst数据集的模型训练差别

损失函数 名称 适用场景
torch.nn.MSELoss() 均方误差损失 回归
torch.nn.L1Loss() 平均绝对值误差损失 回归
torch.nn.CrossEntropyLoss() 交叉熵损失 多分类
torch.nn.NLLLoss() 负对数似然函数损失 多分类
torch.nn.NLLLoss2d() 图片负对数似然函数损失 图像分割
torch.nn.KLDivLoss() KL散度损失 回归
torch.nn.BCELoss() 二分类交叉熵损失 二分类
torch.nn.MarginRankingLoss() 评价相似度的损失
torch.nn.MultiLabelMarginLoss() 多标签分类的损失 多标签分类
torch.nn.SmoothL1Loss() 平滑的L1损失 回归
torch.nn.SoftMarginLoss() 多标签二分类问题的损失 多标签二分类

从上图中,我们可以分类确定,在手写数字识别的任务中,损失函数的类别主要针对于0-9这十个数字,问题属于分类问题,多分类问题可选择的损失函数的内容主要有:

  • torch.nn.CrossEntropyLoss() # 交叉熵损失
  • torch.nn.NLLLoss() # 图片负对数似然函数损失
  • torch.nn.MultiLabelMarginLoss() # 多标签分类的损失

这三种可以使用,从上面的实验中我们可以看到然后我们对模型的损失函数进行修改并整理:

通过编写分支语句实现相关功能:如下

    # 加上.cuda()代表着采用了gpu的形式来设置交叉熵函数if lossLabel=="CrossEntropyLoss":#交叉熵函数lossFunc = torch.nn.CrossEntropyLoss().cuda()elif lossLabel=="NLLLoss":# # 图片负对数似然函数损失lossFunc = torch.nn.NLLLoss().cuda()elif lossLabel=="MultiLabelMarginLoss": #多标签分类的损失lossFunc = torch.nn.MultiLabelMarginLoss().cuda()

之后至此其他参数的设定内容大致不变,如下表格表格所示:

节点数 [28*28,512,256,10]
层数 3
学习率 0.01
优化器 SGD
损失函数 变量
激活函数 relu
迭代次数(epoch) 8

其中损失函数作为变量,来进行实验对比,我们会绘制出模型的在测试集上的准确率以及损失函数的图像的代码如下所示:

# 加上.cuda()代表着采用了gpu的形式来设置交叉熵函数
if lossLabel == "CrossEntropyLoss":  # 交叉熵函数lossFunc = torch.nn.CrossEntropyLoss().cuda()
elif lossLabel == "NLLLoss":  # # 图片负对数似然函数损失lossFunc = torch.nn.NLLLoss().cuda()
elif lossLabel == "MultiLabelMarginLoss":  # 多标签分类的损失lossFunc = torch.nn.MultiLabelMarginLoss().cuda()# lossFunc = torch.nn.CrossEntropyLoss()
#####################################################################
"""
绘制图像的方法,总计三种不同的对比之间的区别,同时采用SGD网络模型
"""model = MLP()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossElem, accuracy = train("SGD", "CrossEntropyLoss")
nullLoss, nullAccuracy = train("SGD", "NLLLoss")
# muLoss, muAccuracy = train("SGD", "MultiLabelMarginLoss")
# accuracy = test()
plt.plot(range(len(accuracy)), accuracy, color="r", label="CrossEntropyLoss")
plt.plot(range(len(nullAccuracy)), nullAccuracy, color="b", label="NLLLoss")
# plt.plot(range(len(muAccuracy)), muAccuracy, color="g", label="MultiLabelMarginLoss")
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("不同的损失函数,准确率的区别")
plt.show()plt.plot(range(len(lossElem)), lossElem, color="r", label="CrossEntropyLoss")
plt.plot(range(len(nullLoss)), nullLoss, color="b", label="NLLLoss")
# plt.plot(range(len(muLoss)), muLoss, color="g", label="MultiLabelMarginLoss")
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("不同的损失函数,误差的区别")
plt.show()

如下图所示,准确率之间的关系:

从此处可以看出nullloss损失函数对其模型的训练很有帮助。相比于交叉熵函数而言。

如下图所示,误差函数的区别:

从误差函数也可以看出模型的损失变化情况,nullloss远远优于交叉熵函数,在进行手写数字识别问题的时候。

第二部分:激活函数不同导致的minst数据集的模型训练差别

不同的激活函数:

名称 公式 特性
Relu r(x)=max(0,x) 可以使随机梯度下降收敛的更快;计算速度非常快;
Sigmoid σ(z)=1/(1+e^(-x)) Sigmoid激活函数输入实数,输出结果为0到1之间;
Tanh tanh(x)=(ex-e-x)/(ex+e-x) 非线性函数tanh输出结果为-1到1之间;
Softplus ζ(x)=log(1+e**x) SoftPlus平滑版的ReLU;

设计不同激活函数之间对比的代码内容以及格式:

# 创建以RELU为激活函数的模型
model = MLP()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossElem, accuracy = train("SGD", "CrossEntropyLoss")# 创建以sigmoid为激活函数的模型
model = MLP2()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossSigmoid, accuracySigmoid = train("SGD", "CrossEntropyLoss")# 创建以tanh为激活函数的模型
model = MLP3()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossTanh, accuracyTanh = train("SGD", "CrossEntropyLoss")# 创建以softplus为激活函数的模型
model = MLP4()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossSoftplus, accuracySoftplus = train("SGD", "CrossEntropyLoss")plt.plot(range(len(accuracy)), accuracy, color="r", label="RELU")
plt.plot(range(len(accuracySigmoid)), accuracySigmoid, color="b", label="Sigmoid")
plt.plot(range(len(accuracyTanh)), accuracyTanh, color="g", label="Tanh")
plt.plot(range(len(accuracySoftplus)), accuracySoftplus, color="g", label="Softplus")
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("不同的激活函数,准确率的区别")
plt.show()plt.plot(range(len(lossElem)), lossElem, color="r", label="RELU")
plt.plot(range(len(lossSigmoid)), lossSigmoid, color="b", label="Sigmoid")
plt.plot(range(len(lossTanh)), lossTanh, color="g", label="Tanh")
plt.plot(range(len(lossSoftplus)), lossSoftplus, color="g", label="Softplus")
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("不同的激活函数,损失之间的区别")
plt.show()

通过设置不同的模型之间的激活函数,来生成不同的模型,并观察不同的模型之间的在激活函数之后不同的情况下的模型的准确率和损失的差异之处。

对于激活函数的实验控制变量的内容如下:

节点数 [28*28,512,256,10]
层数 3
学习率 0.01
优化器 SGD
损失函数 CrossEntropyLoss
激活函数 变量
迭代次数(epoch) 8

通过绘制在不同激活函数下,模型的准确率以及loss的图像,我们可以通过下图进行观察:

上述的激活函数之间的不同,我们可以看出在使用几种最常见的激活函数作为模型的变量的时候,我们可以看出从准确率上来看,激活函数作为tanh可以最快的达到较高的准确率,同样的relu的速度相较于tanh收敛的较慢,但是模型的准确率同样可以很快的进行收敛。

下面观察一下损失函数对模型的影响,在不同的激活函数之下的模型的损失变化内容如下图所示:

我们可以看到,采用sigmoid作为激活函数的 时候模型的损失在持续,但是下降的趋势并不明显,说明采用sigmoid作为激活函数也就是取值在(0-1)之间的时候,训练手写数字的模型的效果是比较差的,然后,相对来说,RELU以及Tanh的两个激活函数来讲,模型的损失变化就相对而言更加明显。说明,采用这两个中激活函数模型的训练效果更好。

第三部分:模型迭代次数不同导致的minst数据集的模型训练差别

在本次实验控制变量的过程中 我们控制模型的变量的差别在epoch上面,这个内容与上文中绘制其他的准确率以及损失函数的图片是一样的。过程我们可以只选择其中的一个比较明显的作分析,这次将其中的一个内容设置的整体的epoch次数设定为12我们观察模型的变化。

其中变量的设计结构如下:

节点数 [28*28,512,256,10]
层数 3
学习率 0.01
优化器 SGD
损失函数 CrossEntropyLoss
激活函数 变量
迭代次数(epoch) 12

测试不同的迭代次数的代码源码如下:

model = MLP()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossElem, accuracy = train("SGD", "CrossEntropyLoss")
#####################################################
"""":cvar
对两个结果都进行归一化处理,之后在画图对比,从而增强其图像的感觉
"""
maxNum = max(lossElem)
lossElem = [elem/maxNum for elem in lossElem]
maxAcc = max(accuracy)
accuracy = [elem/maxAcc for elem in accuracy]
#####################################################
# 绘制出不同的激活函数之间的准确率的差异图
plt.plot(range(len(accuracy)), accuracy, color="r", label="accuracy")
# 绘制出不同激活函数之间模型的损失之间的图片
plt.plot(range(len(lossElem)), lossElem, color="b", label="loss")
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("迭代次数变化过程中模型的损失以及准确率")
plt.show()

效果图示如下所示(对模型的预测结果进行归一化处理,使得结果的图像更加立体突出):

从上图,可以看出在不同的epoch下,模型的准确率的变化从最低点到 最高点在不断地上升,同样的损失的函数同样也在不断的从最高点,向最低点前进。说明在迭代次数较少的时候模型处于欠拟合状态,而循环次数增加的时候,模型渐渐达到稳定。

第四部分:模型节点个数不同导致的minst数据集的模型训练差别

通过模型的预先定义可以知道模型的初始节点 的个数是28*28是模型的输出的 图片的像素尺寸,是没有办法更改的因此,本次实验的目的是修改,训练网络中的 第一个隐藏层以及第二个隐藏层的节点个数,设计是从256-1024每次增加64个节点。同时模型的具体参数如下所示:

节点数 [28*28,256-1204,128-512,10]
层数 3层全连接
学习率 0.01
优化器 SGD
损失函数 CrossEntropyLoss
激活函数 relu
迭代次数(epoch) 8

我们通过修改模型的类来实现对模型的整体数据的修改该并绘制模型的图像,通过如下代码实现,模型在不同条件下模型的准确率以及损失之间的变化关系。

fig = plt.figure()
for each in range(256, 1025, 64):model = MLP5(each, each // 2)# print(model)plt.subplot(1, 2, 1)# # TODO 将模型放到GPU上# ####################################model = model.cuda()# ####################################lossElem, accuracy = train("SGD", "CrossEntropyLoss")#####################################################plt.plot(range(1, len(accuracy) + 1), accuracy, label="first{0}:second{1}".format(each, each // 2))plt.subplot(1, 2, 2)plt.plot(range(1, len(lossElem) + 1), lossElem, label="first{0}:second{1}".format(each, each // 2))
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.subplot(1, 2, 1)
plt.title("不同节点数之间模型的准确率")
plt.subplot(1, 2, 2)
plt.title("不同节点数之间模型的损失率")
plt.show()

所绘制出的模型的准确率以及损失变化的图像如下图所示:

通过这个图像我们可以清晰看到,模型的准确率在开始阶段也就是模型的节点数的变化模型的内容也有较为明显的改变可以明显的看出模型的的准确率以及对应的损失也有所变化,但是相对比来看,损失的变化较为不明显

但是可以看到,不同节点数模型训练的准确率变化较为明显,模型可以明显的看到在一开始的时候,节点数为384以及第二个全连接层节点数为192的时候模型的训练效果相较于其他的效果是比较差的,但是这个系欸但拿书提升同样较快,可以看出的是模型节点数在256和第二个全连接层为128的时候,模型的训练效果也是相差不大的,可以说在最后的结果来看,模型在第六个epoch之后就没有准确率上较大的区别了。

但是同样可以看到模型节点数512的时候一开始模型的训练效果也是比较好的,所以上图说明了一个情况就是节点数并不是越大越好,越小越差,我们可以清晰的看出节点数小的时候也会有较好的训练结果,所以模型的训练要看具体的训练效果来确定。

第五部分:模型学习率不同导致的minst数据集的模型训练差别

我们通过修改模型的学习率可以看到不同的模型训练效果,在设置优化器的时候进行设定模型的学习率的大小,在本次实验中我们设定学习的大小从0.01变化到1,步幅大小为0.1

模型的 其他参数设定如下:

节点数 [28*28,512,256,10]
层数 3层全连接
学习率 0.01-0.99
优化器 SGD
损失函数 CrossEntropyLoss
激活函数 relu
迭代次数(epoch) 8

通过我们的修改该train()函数中的优化器,可以很轻易地得到模型的准确率以及损失变化的曲线内容:我们可以看到如下所示:

通过令人意外的上图我们可以看到 学习率对模型的影响是很大的在学习率较小的时候模型很明显在8个epoch的情况下并没有达到饱和状态,但是随着学习率的提升模型的训练效果得到了明显的提升,但是同时,我们可以看到红色和紫色的两个也就是对应的学习率为0.61以及0.81的两个学习率的模型训练的曲线在最后的准确性上有很大的变化,对0.81的学习率模型的准确率反而在最后突然降低到0.09的准确率,而损对于红色的 曲线我们可以看到在第七个epoch的时候,模型的准确率有明显的下降,同时损失明显的提高,并且紫色曲线的损失在最后降低为nan。

第六部分:模型不同层数不同导致的minst数据集的模型训练差别

通过修改模型的层数得到不同的结果,修改model对应的函数内容添加几层的内容:

通过修改三四五层的全连接层数我们可以得到 不同的模型之间的差别:如下表所示:

节点数 [28*28,512,256,10]、[28*28,512,256,128,10]、[28*28,512,256,128,64,10]
层数 3-5层全连接
学习率 0.01
优化器 SGD
损失函数 CrossEntropyLoss
激活函数 relu
迭代次数(epoch) 8

通过修改该模型我们得到不同的模型的训练的效果,如下代码:

model = MLP()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossElem3, accuracy3 = train("SGD", "CrossEntropyLoss")
model = MLP6()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossElem4, accuracy4 = train("SGD", "CrossEntropyLoss")
#####################################################
model = MLP7()
# print(model)
# # TODO 将模型放到GPU上
# ####################################
model = model.cuda()
# ####################################
lossElem5, accuracy5 = train("SGD", "CrossEntropyLoss")plt.plot(range(1, len(accuracy3) + 1), accuracy3, label="层数:{}".format(3))
plt.plot(range(1, len(accuracy4) + 1), accuracy4, label="层数:{}".format(4))
plt.plot(range(1, len(accuracy5) + 1), accuracy5, label="层数:{}".format(5))
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("不同全连接层数之间模型的准确率")
plt.show()plt.plot(range(1, len(lossElem3) + 1), lossElem3, label="层数:{}".format(3))
plt.plot(range(1, len(lossElem4) + 1), lossElem4, label="层数:{}".format(4))
plt.plot(range(1, len(lossElem5) + 1), lossElem5, label="层数:{}".format(5))
plt.legend()
plt.rcParams['axes.unicode_minus'] = False  # 不使用中文减号
plt.rcParams['font.sans-serif'] = 'FangSong'  # 设置字体为仿宋(FangSong)
plt.title("不同全连接层数之间模型的损失率")
plt.show()

每个模型修改的内容如下:

class MLP7(nn.Module):def __init__(self):# 继承自父类super(MLP7, self).__init__()# 创建一个三层的网络# 输入的28*28为图片大小,输出的10为数字的类别数# 512hidden_first = 512# 输入隐藏层的大小# 256hidden_second = 256hidden_third = 128hidden_fourth = 64""":cvar其中,in_features为输入样本的大小,out_features为输出样本的大小,bias默认为true。如果设置bias = false那么该层将不会学习一个加性偏差。"""# 设置驶入层的输入图片大小,并设置 self.first = nn.Linear(in_features=28 * 28, out_features=hidden_first)# 作为一个线性变换函数显示输入样本的v大小为28*28输出样本的大小为512,我们可以得知上层的图片的输入神经元个数# 就是28*28,而下一层的神经元的输出个数就是512个self.first = nn.Linear(in_features=28 * 28, out_features=hidden_first)# 设置输入层到隐藏层的第一层网络的内容可以看到,网络的输入神经元个数为512而输出的的神经元个数为256self.second = nn.Linear(in_features=hidden_first, out_features=hidden_second)# 设置最后一层到输出层的神经元的参数,输入的神经元的个数为256而最后输出的神经元的信号个数为10,代表是个数字的结果self.third = nn.Linear(in_features=hidden_second, out_features=hidden_third)# 中间添加一层神经元,将其修改改为4层全连接层self.fourth = nn.Linear(in_features=hidden_third, out_features=hidden_fourth)self.fifth = nn.Linear(in_features=hidden_fourth, out_features=10)def forward(self, data):# 先将图片数据转化为1*784的张量data = data.view(-1, 28 * 28)# 设置各个层级之间的激活函数,可以看出都采用的是relu的激活函数data = F.relu(self.first(data))data = F.relu((self.second(data)))# 说明最后输出的时候采用的是分类的神经元网络softmaxdata = F.relu(self.third(data))data = F.relu(self.fourth(data))data = F.log_softmax(self.fifth(data), dim=1)return data

我们可以得到模型准确率以及其损失的改变内容:

通过上图,可以看出模型在在层数分别为3,4,5的全连接层的时候模型的,训练速度在不断地减慢,并且模型的准确率也存在一定的不稳定性,可以看出对于手写数字识别的神经网络但模型的全连接层数为3的时候模型具有较好的训练效果。

通过损失的变化我们可以看到模型的改变,层数的增加模型的损失变化下降的趋势渐渐变慢,在层数为五的时候前4次epoch并没有使得模型的损失快速降低,反而模型的损失依然很大,可以看出全连接网络的深度增加并没有使得模型取得更好的效果。此图正好与上面的准确率的图像相对应反映出,各个不同层数的网络之间的差异。

总结

通过上面的几组实验,我观察大部分的框架对于不同和优化方式的影响,并可以可以看出模型在优化的过程中效果的不同之处,通过修改原始的方法中的代码将其进行改变,更好的掌握了每个框架的模型训练方法,也感受到了各种优化方式在模型之间不同之处。

第二部分:针对pytorch模型的修改与对比实验,可以看出的是,pytorch模型的准确率随着各个参数以及优化器,激活函数,损失函数的不同选择模型会有不同的训练效果,具体的就反应在模型的训练时间,以及模型的训练的准确率上面。

学习率过大时侯,模型出现损失为nan缘故

如果训练中途出现loss为nan的情况,一般就要针对learning rate做文章了。

在分类问题中,如果learning rate太大会认为某些数据属于错误的类概率为1,而正确类的概率为0(实际上出现了浮点数下溢)。此时交叉熵loss
−yi​logy​−(1−yi​)log(1−y​)−y_i​ log y^​ −(1−y_i​)log(1− y^​ ) −yi​​logy​−(1−yi​​)log(1−y​)
也会变成无穷大。一旦出现该种情形,无穷大对参数求导会变成nan,整个网络就成了nan。

简单的测试:

def loss_test():num = np.nannum = np.log(num)print(num)loss_test()""":输出nan
某个数为nan,此时对其求对数,也为nan。
要解决上面的问题,方法是减小学习率,甚至将学习率变为0,看看问题是否仍然存在。若问题消失,那说明确实是学习率的问题。若问题仍存在,那说明刚刚初始化的网络就已经挂掉了,很可能是实现有错误。
"""

不同框架模型的对比试验相关推荐

  1. 斯坦福CS231n 2017最新课程:李飞飞详解深度学习的框架实现与对比

     斯坦福CS231n 2017最新课程:李飞飞详解深度学习的框架实现与对比 By ZhuZhiboSmith2017年6月19日 13:37 斯坦福大学的课程 CS231n (Convolutio ...

  2. 2017深度学习最新报告及8大主流深度学习框架超详细对比(内含PPT)

    2017深度学习最新报告(PPT) ​ 深度学习领军人物 Yoshua Bengio 主导的蒙特利尔大学深度学习暑期学校目前"深度学习"部分的报告已经全部结束. 本年度作报告的学术 ...

  3. Android Tangram模型:连淘宝、天猫都在用的UI框架模型你一定要懂

    前言 Tangram 是阿里出品.用于快速实现组合布局的框架模型,在手机天猫 Android & iOS版 内广泛使用 今天我将对Tangram 模型 进行全面介绍,希望你们会喜欢. Cars ...

  4. Python实现逻辑回归对比试验(四)

    数据说明 ​ 我们将建立一个逻辑回归模型来预测一个学生是否被大学录取.假设你是一个大学系的管理员,你想根据两次考试的结果来决定每个申请人的录取机会.你有以前的申请人的历史数据,你可以用它作为逻辑回归的 ...

  5. (各种均衡算法在MIMO中的应用对比试验)最小均方误差(MMSE)原理推导以及在MIMO系统中对性能的改善。

    文档和程序地址:下载地址 各种均衡算法在MIMO中的应用对比试验,内附原理推导,对比实验说明和结果等.包括MMSE,ZF,ZF-SIC等.代码附有原理推导小论文.仅供参考

  6. 计算机动画制作 实验要求,有关计算机动画设计课程教学的对比试验

    有关计算机动画设计课程教学的对比试验 "教室授课"与"机房授课"这两种教学形式的选择问题 仅是计算机动画设计面临的一个问题,也是许多对操作技能要求较高的设计类课 ...

  7. MAT之GRNN/PNN:基于GRNN、PNN两神经网络实现并比较鸢尾花(iris数据集)种类识别正确率、各个模型运行时间对比

    MAT之GRNN/PNN:基于GRNN.PNN两神经网络实现并比较鸢尾花(iris数据集)种类识别正确率.各个模型运行时间对比 目录 输出结果 实现代码 输出结果 实现代码 load iris_dat ...

  8. OpenCV加载Caffe框架模型

    OpenCV加载Caffe框架模型 加载Caffe框架模型 简介 源代码 解释 加载Caffe框架模型 简介 在本教程中,您将学习如何使用Caffe模型将opencv_dnn模块用于图像分类. 我们将 ...

  9. python django框架分析_Django框架模型简单介绍与使用分析

    本文实例讲述了Django框架模型简单介绍与使用.分享给大家供大家参考,具体如下: ORM介绍 ORM Object relational mapping 对象关系映射 把面向对象中的类和数据库表一一 ...

  10. Django 07. django框架模型之增删改查基本操作

    简介 django框架模型之数据库表增删改查基本操作 1. 生成数据库表结构         models.py #!/usr/bin/env python # -*- coding: utf-8 - ...

最新文章

  1. 海量日志数据分析与应用》场景介绍及技术点分析
  2. 概率统计笔记:高斯威沙特分布
  3. wampserver 403forbidden问题
  4. JVM 性能调优监控工具
  5. c语言中空格字符怎么表示_C语言中常用的字符串操作函数
  6. 滴滴回应未删道歉微博;阿里 P7 员工租自如病亡;苹果无人车出事故 | 极客头条...
  7. 使用数据库引擎优化顾问添加建议索引
  8. 多层感知机从零开始实现
  9. MYSQL查看操作日志
  10. Windows7旗舰版安装 Anaconda3 详细图文教程
  11. java计算机毕业设计景区门票系统源码+数据库+系统+lw文档+mybatis+运行部署
  12. 你知道bat是什么意思吗?
  13. 中国城市电话区号对照表中国移动短信中心号查询及命名规则
  14. lpop 原子_深圳大学张文静教授团队ACS Nano:单原子铂修饰的二硫化钒催化剂提高电催化析氢性能...
  15. 封装、权限修饰符、javaBean规范、继承中封装、多态、关于上溯造型的解释。
  16. pr怎样进行素材嵌套
  17. 6. Java并发编程-并发包-Lock和Condition
  18. VScode latex 写作小结
  19. 高通Android手机软件开发培训
  20. grpc-gateway 返回值中默认值为什么不显示?

热门文章

  1. Go命令行调用Python运行ParlAI模型,同步输入输出并调用百度翻译API翻译
  2. hp proliant DL360p Gen8风扇故障排除
  3. 页脚html模板,怎样用Photoshop设计漂亮的网页页脚模板实例教程
  4. win10更新后任务栏卡死,桌面正常解决办法
  5. 【Pygame小游戏】超好玩的——Python版“愤怒的小鸟”,我能玩上一整天(附源码)
  6. 基于matlab分析的商业保险案例
  7. 人民日报海外版总编辑詹国枢谈新闻写作技巧:五句话妙手著文章
  8. edgex-ui中文版下载
  9. 科研入门必备知识之论文种类--Journal、magazine、transactions、proceedings
  10. C语言解决找零钱问题