【AI绘图学习笔记】深度前馈网络(二)
创始人
2024-06-03 12:58:09
0

有关深度前馈网络的部分知识,我们已经在吴恩达的机器学习课程中有过了解了,本章主要是对《深度学习》花书中第六章:深度前馈网络的总结笔记。我希望你在看到这一章的时候,能回忆起机器学习课程中的一些环节或者细节,这对理解本文很有帮助。

参考笔记:
前馈神经网络初探——深度学习花书第六章(一)
神经网络损失函数、输出层、隐藏层详解——深度学习第六章(二)
反向传播算法——深度学习第六章(三)


文章目录

  • 基于梯度的学习方法
    • 代价函数(损失函数)
      • 使用最大似然学习条件分布
    • 学习条件统计量
    • 输出单元
      • 基于高斯输出分布的线性单元
      • 用于伯努利Bernoulli输出的sigmoid单元
      • 用于Multinoulli输出的softmax单元
      • 其他输出类型
      • 混合密度网络
  • 隐藏单元
    • ReLU及其拓展
  • 架构设计
    • 万能近似性质和深度


基于梯度的学习方法

在学习“机器学习”算法的时候,我们为了选择一个最优参数,往往会采用梯度下降的方法。在神经网络中的梯度下降训练和其他机器学习的模型并无太大差别,但是我们需要考虑到一个使用情景的问题:由于神经网络的非线性,导致大多数我们感兴趣的代价函数都变得非凸,这意味着使用基于梯度的优化可能并不能达到全局最低点,并且大多数对参数的初始值很敏感,我们不能保证从任意一个初始参数开始下降能够收敛到全局最低点。

针对这种情况,我们将会在以后介绍一种用于训练几乎所有深度模型的迭代的基于梯度的优化算法:随机梯度下降法(SGD),我们现在只需要知道:训练算法几乎总是基于使用梯度来使得代价函数下降的各种方法。计算梯度对于神经网络会稍微复杂一点,但仍然可以高效而精确地实现。

现在,为了使用基于梯度的学习方法,我们必须选择一个代价函数,并且必须选择如何表示模型的输出。

代价函数(损失函数)

选择代价函数(损失函数)是深度神经网络设计的一个重要环节,幸运的是:神经网络的代价函数或多或少是和其他参数模型(例如线性模型的代价函数)相同的。

在大多数情况下,参数模型定义了一个分布p(y∣x;θ)p(y|x;\theta)p(y∣x;θ)并且简单地使用最大似然法,我们在总结数学原理的时候提到了:最大似然估计等价于最小KL散度等价于最小交叉熵(交叉熵就是负对数似然)。这意味着我们可以使用训练数据和模型预测间的交叉熵作为代价函数。

使用最大似然学习条件分布

大多数神经网络使用最大似然来训练,这意味着代价函数就是负的对数似然(最小化交叉熵),这个代价函数表示为:
J(θ)=−Ex,y∼p^datalogpmodel(y∣x)J(\theta)=-\Bbb E_{{x,y \sim \hat p_{data}}}logp_{model}(y|x)J(θ)=−Ex,y∼p^​data​​logpmodel​(y∣x)

代价函数的具体形式会随着模型而改变,取决于logpmodellogp_{model}logpmodel​的具体形式。上式展开后通常我们可以舍去一些与参数θ\thetaθ无关的项。

现在我们举一个例子:当pmodel(y∣x)=N(y;f(x;θ),I)p_{model}(y|x)=N(y;f(x;\theta),I)pmodel​(y∣x)=N(y;f(x;θ),I),也就是模型的概率分布满足该正态分布。当误差服从正态分布时,则样本的最大似然函数等价于最小均方误差。

因此原式=最小负对数似然=最大似然=最小均方误差,因此我们带入原式的最大似然代价函数,就能重新得到一个均方误差代价函数:

J(θ)=12Ex,y∼p^data∣∣y−f(x;θ)∣∣2+constJ(\theta)=\frac{1}{2}\Bbb E_{{x,y \sim \hat p_{data}}}||y-f(x;\theta)||^2+constJ(θ)=21​Ex,y∼p^​data​​∣∣y−f(x;θ)∣∣2+const

因此,使用最大似然来导出代价函数的方法的一个优势就是:它减轻了为每个模型设计代价函数的负担,只需明确一个模型p(y∣x)p(y|x)p(y∣x)就能自动地确定一个代价函数logp(y∣x)logp(y|x)logp(y∣x),我们只需明确每个模型pmodel(y∣x)p_{model}(y|x)pmodel​(y∣x)的概率分布,将其带入到最大似然的代价函数中,就能够自动地确定该代价函数,而不需要我们分别进行设计。

由于神经网络的非线性的特征,我们通常需要用梯度下降的方法逐渐逼近其极值,而且不能保证损失一定会收敛。假如损失函数很容易饱和即在某些区间趋于不变的话,则其梯度很小,梯度下降算法很难更新,所以我们希望损失函数在我们所研究的区间内尽量不饱和。这也是为什么我们通常选取交叉熵的形式,因为很多概率分布函数都会出现指数形式(如sigmoid函数)而在某些区间饱和,交叉熵中的log函数正好可以抵消其饱和,使得模型可以较快更新纠正错误预测。

用于实现最大似然估计的交叉熵代价函数有一个不同寻常的特征:那就是当它被应用于实践中经常遇到的那些模型时,它通常没有最小值。对于实数值的输出变量,极高的输出密度可能导致交叉熵趋向负无穷;对于离散型输出变量,它只能无限接近0或者1而不能等于0或1。


学习条件统计量

有时我们不需要学习一个完整的概率分布p(y∣x;θ)p(y|x;\theta)p(y∣x;θ),而仅仅是想学习在给定x时y的某个条件统计量。如果使用一个足够强大的神经网络,我们可以用其表示一大类函数中的任意一个函数fff,我们可以将代价函数看作一个泛函,泛函是函数到实数的映射,相当于一个受函数的一些特征影响——例如连续性和有界,但不具有特殊的参数形式的函数。因此我们可以将学习过程看作选择一个函数而非选择一组参数。例如我们可以使得代价泛函的最小值处于一个特殊的函数上,这个函数将x映射到给定x时y的期望值,在这个过程中我们需要使用到变分法(本章中暂时无需理解,只需知道可以导出两个结果),例如对最小二乘法使用变分法,可以得到均方误差和平均绝对误差两种结果,而然在基于梯度的优化方法中,即使没必要估计整个p(y∣x)p(y|x)p(y∣x)的分布,效果也不比交叉熵要好,因此我们还是选择交叉熵,所以就不展开讲了,详细可以看书。

输出单元

代价函数的选择与输出单元紧密相关,在这里我们所指的输出单元是整个神经网络的输出层,在后面我们也会介绍当其作为模型内部隐藏层的输出单元时的一些细节。

大多数时候,我们简单地使用数据分布和模型分布的交叉熵,选择如何表示输出决定了交叉熵函数的形式。现在我们假设神经网络提供了一组定义为h=f(x;θ)h=f(x;\theta)h=f(x;θ)的隐藏特征,输出层的作用就是对其进行一些额外的变换,使得隐藏特征能够映射到我们的预测目标。对于不同的输出分布,我们有不同的输出函数和代价函数


基于高斯输出分布的线性单元

一种简单的输出单元是基于仿射变换的输出单元,或者我们常常称为线性单元。

给定特征h,线性输出单元层产生一个向量y^=WTh+b\hat y=W^Th+by^​=WTh+b

线性输出层常常被用来产生条件高斯分布的均值:

p(y∣x)=N(y;y^,I)p(y|x)=N(y;\hat y,I)p(y∣x)=N(y;y^​,I)此时最大化对数似然等价于最小化均方误差

最大似然框架使得学习高斯分布的协方差矩阵更加容易,然而对于所有输出,协方差矩阵都必须被限定为一个正定矩阵,仅仅依靠线性输出层是很难满足这种限定的,所以通常我们使用其他的输出单元来对协方差参数化。

线性单元的一个优点就是不会饱和,因此它们易于采用基于梯度的优化方法,甚至使用其他多种优化方法。


用于伯努利Bernoulli输出的sigmoid单元

对于二元分布问题我们常常归结为这种形式,我们常常使用sigmoid逻辑函数作为输出层。此时最大似然的方法是定义y在x条件下的Bernoulli分布(两点分布),我们仅需单个参数p来进行定义,现在我们的输出层预测P(y=1∣x)P(y=1|x)P(y=1∣x),由于有效概率必须∈[0,1]\in [0,1]∈[0,1],因此我们需要阈值来约束它:
在这里插入图片描述

如果我们给出下面这个概率分布P(y=1∣x)=max{0,min{1,wTh+b}}P(y=1|x)=max\{{0,min\{{1,w^Th+b}\}\}}P(y=1∣x)=max{0,min{1,wTh+b}},这的确满足了我们的约束条件,但是并不能用梯度下降来高效的训练它,当wTh+bw^Th+bwTh+b处于单位区间外时,由于直接成水平线了,梯度为0。梯度为0通常是有问题的。

其中一种优化方法是使用sigmoid函数y^=σ(wTh+b)=σ(z)\hat y=\sigma(w^Th+b)=\sigma(z)y^​=σ(wTh+b)=σ(z),这样最上方和最下方只会无限趋近1和0,从而避免梯度为0。我们可以将其视为两个部分:一层使用了线形层进行计算,最后使用sigmoid激活函数将zzz转化为概率。

或者我们也可以忽略函数对x的依赖性,转而使用z来表示y,即P(y)=σ((2y−1)z),P(y)=\sigma((2y-1)z),P(y)=σ((2y−1)z),用于定义这种二值型变量分布的变量zzz也被称为分对数。其对应的代价函数为:

J(θ)=−logσ((2y−1)z)=ζ((1−2y)z)J(\theta)=-log\sigma((2y-1)z)=\zeta ((1-2y)z)J(θ)=−logσ((2y−1)z)=ζ((1−2y)z),其中ζ(x)=log(1+exp(x))\zeta(x)=log(1+exp(x))ζ(x)=log(1+exp(x))代表了softplussoftplussoftplus函数

在这里插入图片描述

从上图softplussoftplussoftplus的图像不难看出,只有当(1−2y)z(1-2y)z(1−2y)z的值趋近负无穷时才会出现饱和,而这种情况只出现在我们的模型已经能够做出准确预测的情况,即y=1且z极正,或者y=0而z极负,在这种情况下,代价函数图像请看0的左半轴部分,梯度很小,但由于我们已经得到了足够准确的预测,我们并不需要更新我们的网络。对于对z作出错误预测的情况,即y=1且z为负,或y=0且z为正,则这两种情况下,(1−2y)z=∣z∣(1-2y)z=|z|(1−2y)z=∣z∣成立,此时代价函数图像请看0的右半轴部分,明显梯度较大,因此即使产生了错误预测,我们也能够通过梯度下降很好地纠正错误。


用于Multinoulli输出的softmax单元

在这里插入图片描述
在这里插入图片描述

Multinoulli的译名应该是范畴分布,它是一个多项式分布,其实就是我们之前所讲的多分类问题,也就是二元分布的sigmoid函数拓展到多元的问题,我们常常使用softmaxsoftmaxsoftmax(看清楚是softmax,刚才在二元分布用的是softplus!)函数来解决:

在这里插入图片描述
softmax(z)i=exp(zi)∑jexp(zj)softmax(z)_i=\frac{exp(z_i)}{\sum_j exp(z_j)}softmax(z)i​=∑j​exp(zj​)exp(zi​)​

为了最小化损失函数,我们将这个分布函数带入最大对数似然:

J(θ)=logP(y=i;z)=logsoftmax(z)i=zi−log∑jexp(zj)J(\theta)=logP(y=i;z)=logsoftmax(z)_i=z_i-log\sum _jexp(z_j)J(θ)=logP(y=i;z)=logsoftmax(z)i​=zi​−log∑j​exp(zj​)

想要最大化的话就是使第一项ziz_izi​变大,第二项log∑jexp(zj)log\sum _jexp(z_j)log∑j​exp(zj​)变小,log∑jexp(zj)≈maxjzjlog\sum _jexp(z_j) \thickapprox \underset{j}{max}z_jlog∑j​exp(zj​)≈jmax​zj​,第二项可以理解为我们对最不正确的预测做出的惩罚,如果正确项已经是softmax输入中贡献最大的,此时maxjzj=zi\underset{j}{max}z_j=z_ijmax​zj​=zi​,则两项可近似抵消,说明这个数据对整体的损失贡献较小,因此我们可以集中精力处理其他没有正确分类的数据。而对于其他不正确的预测logPlogPlogP将会是负值,因此近似抵消的时候是最大的。

可以看出,如果我们选取的损失函数不是交叉熵的形式,则对于softmax函数来说,当z为负值时,则其梯度接近于零,例如采取如mean square error形式的损失函数,则模型很难更新。而交叉熵保证了梯度不会消失。另一方面,对于z较大的情况,则softmax趋于1饱和,梯度也接近于零,只有采取交叉熵的形式才能很好的抵消饱和的影响。
在这里插入图片描述
现在我们回看这张图像
由于softmaxsoftmaxsoftmax函数对于所有输入都加上一个相同常数时,输出不变:
softmax(z)=softmax(z+c)softmax(z)=softmax(z+c)softmax(z)=softmax(z+c)

因此我们可以得到一个数值方法稳定的softmax函数变体:
softmax(z)=softmax(z−maxizi)softmax(z)=softmax(z-\underset{i}{max}z_i)softmax(z)=softmax(z−imax​zi​)
当其中一个输入是最大(zi=maxizi)z_i=\underset{i}{max}z_i)zi​=imax​zi​),并且ziz_izi​远大于其他输入时,相应的输出softmax(z)isoftmax(z)_isoftmax(z)i​会饱和到1,当ziz_izi​不是最大值且最大值非常大时,相应的输出则饱和到0。因此我们需要损失函数对其进行补偿,避免梯度下降为0产生学习困难。

softmax的变量z可以通过两种方式来产生:

  • 简单地使神经网络较早的层输出z的每个元素,如线形层z=WTh=bz=W^Th=bz=WTh=b,虽然很直观,但是对分布的过度参数化
  • 既然总概率是1,因此n个分布的概率实际上可以由n-1个分布的概率来决定,最后一个可以由1减去其他n-1个概率,因此我们也可以固定一个元素,例如要求zn=0z_n=0zn​=0。sigmoid函数正是这么做的,sigmoid等价于用二维的zzz以及z1=0z_1=0z1​=0来定义的P(y=1∣x)=softmax(z)1P(y=1|x)=softmax(z)_1P(y=1∣x)=softmax(z)1​。

在实践中,第一种实现过度参数化的版本还更为简单一些

此外,softmax存在一个"零和博弈"的性质,因为softmax输出总和为1,所以一方的收益必然导致另一方的损失,当其中某个分类的概率分布过大,十分接近1的时候,就会导致其他分类的概率分布都接近0。


其他输出类型

最大似然原则给如何为几乎任何种类的输出层设计一个好的代价函数提供了指导,假如我们定义了一个条件分布p(y∣x;θ)p(y|x;\theta)p(y∣x;θ),最大似然原则建议我们使用交叉熵−logp(y∣x;θ)-logp(y|x;\theta)−logp(y∣x;θ)来作为代价函数。

上一章我们也说有时我们并不需要一个完整的概率分布,而只需要一个给定x时的预测器f(x;θ)f(x;\theta)f(x;θ),它不是对y的准确值的直接预测,而是对给定x时y的映射。对于这个f(x;θ)=ωf(x;\theta)=\omegaf(x;θ)=ω提供的y分布的参数,我们的损失函数就可以直接表示为−logp(y;ω(x))-logp(y;\omega(x))−logp(y;ω(x))

(书中举例讲到了一个异方差模型的概念,暂时没看懂,按下不表)

混合密度网络

高斯混合

对于输出是高斯分布的情况之前已经总结过,输出层常常是线性的变换y^=WTh+b\hat y=W^Th+by^​=WTh+b,而线性输出层用来得到高斯分布的均值p(y∣x)=N(y;y^,I)p(y|x)=N(y;\hat y,I)p(y∣x)=N(y;y^​,I),最小化交叉熵就转化为最小化均方差的形式。由于线性输出不存在饱和区间,所以能较好的应用梯度下降算法。

我们经常想要执行多峰回归,即预测条件分布p(y∣x)p(y|x)p(y∣x)的实值,该条件分布对于相同的x值在y空间中有多个不同的峰值。做法常常是将高斯分布推广到对于同一个x可能有几个y的峰值的混合高斯分布情况,它的输出层也被称为混合密度层(Mixture of Density),将高斯混合作为其输出的神经网络通常被称为混合密度函数(Mixture density network)。具有n个分量的高斯混合输出由下面的条件所定义:

p(y∣x)=∑i=1np(c=i∣x)N(y;μ(i)(x),Σ(i)(x))p(y|x)=\displaystyle \sum^n_{i=1}p(c=i|x)N(y;\mu^{(i)}(x),\Sigma^{(i)}(x))p(y∣x)=i=1∑n​p(c=i∣x)N(y;μ(i)(x),Σ(i)(x))

其中p(c=i∣x)p(c=i|x)p(c=i∣x)是对于n个组分的multinoulli分布。我们同样的采取交叉熵作为其损失函数。

Ian用一张表很好的总结了以上的常见概率分布,输出层与损失函数的关系:
在这里插入图片描述
对照上表我们发现代价函数大多都是以交叉熵形式定义的,看来交叉熵确实很通用啊。


隐藏单元

我们刚才所讨论的基于梯度的优化方法来训练大多数参数化的机器学习的模型都是适用的,现在我们来讨论一个前馈神经网络的独有问题:如何选择隐藏层中隐藏单元的类型?

ReLU是一个极好的默认选择,实际上选择何种隐藏单元的过程是很困难的,我们只能通过直觉来进行尝试。设计过程充满了试验和错误,我们无法预先得知那种隐藏单元工作的最好,只能用直觉来进行选择,然后组成神经网络进行训练,最后用验证集来评估它的性能。

其他更复杂的隐藏层函数模型效果和ReLU类似,并没有明显优势,而鉴于ReLU简单的形式和较好的结果,大部分前馈神经网络中仍选择ReLU作为隐藏层函数。

大多数的隐藏单元都可以描述为:接受输入向量xxx,计算仿射变换z=WTx+bz=W^Tx+bz=WTx+b,然后适用一个逐元素的非线性函数g(z)g(z)g(z)。大多数隐藏单元的区别也仅仅在于激活函数g(z)g(z)g(z)的形式。

ReLU及其拓展

ReLU易于优化,因为它和线性单元非常类似。线性单元和ReLU的唯一区别在于ReLU在左半的定义域上输出为0,这使得只要ReLU处于激活状态,那么其梯度始终保持较大(y=x),不但大而且始终不变。只要处于激活状态,它的一阶导数处处为1,二阶导数处处为0,相比于引入二阶效应的激活函数,它的梯度方向对于学习更加有用。

ReLU的一个缺点在于它们不能通过基于梯度的方法学习那些使得它们激活为0的样本,因此出现了一些ReLU的拓展使得它们能在各个位置都能接收到梯度。接下来要说的三个拓展都是基于当zi<0z_i<0zi​<0时(即ReLU左半轴恒为0的部分)使用的一个非零的斜率αi:hi=g(z,α)i=max(0,zi)+αimin(0,zi)\alpha_i:h_i=g(z,\alpha)_i=max(0,z_i)+\alpha_imin(0,z_i)αi​:hi​=g(z,α)i​=max(0,zi​)+αi​min(0,zi​)(就是左半轴换了个部分)

  • 绝对值整流(absolute value rectification,AVR) 固定αi=−1\alpha_i=-1αi​=−1来得到g(z)=∣z∣g(z)=|z|g(z)=∣z∣,通常用于图像中的对象识别,其中寻找在输入照明极性反转下不变的特征是有意义的。下面介绍的两种应用会更广泛。
  • 渗漏整流线性单元(Leaky ReLU) 将αi\alpha_iαi​固定成一个类似0.01的小值
  • 参数化整流线性单元(parametric ReLU,PReLU) 将αi\alpha_iαi​作为学习参数
  • maxout单元进一步拓展了ReLU,maxout单元将zzz划分为每组具有kkk个值的组,而不是使用作用于每个元素的函数g(z)g(z)g(z)。每个maxout单元则输出每组中的最大元素:g(z)i=maxj∈G(i)zjg(z)_i=\underset{j \in \Bbb G^{(i)}}{max}z_jg(z)i​=j∈G(i)max​zj​

其中G(i)\Bbb G^{(i)}G(i)是组iii的输入索引集{(i−1)k+1,...,ik}\{{(i-1)k+1,...,ik\}}{(i−1)k+1,...,ik}。这提供了一种方法来学习对输入xxx空间中多个方向相应的分段线性函数。

makeout单元可以在分组区间内多段线性输出,因此可以学习具有多达k段的分段线性的凸函数,可以视为学习激活函数本身。如果我们分段分的足够多,k足够大,makeout单元就可以以足够的精确度来近似任何凸函数。一些优点诸如:每个makeout单元现在由k个权重向量来参数化,并且通常比ReLU需要更多的正则化。由于每个单元由多个过滤器驱动,因此makeout拥有一些冗余可以防止出现"灾难遗忘"的现象等等…

其他的激活函数隐藏单元由于泛用性的问题就不介绍了,例如logistic sigmoid,双曲正切,径向基函数等等,感兴趣可以翻阅原本。


架构设计

神经网络的另一个设计关键就是确定它的架构,架构(architecture) 是指神经网络的整体结构:应该有多少单元,这些单元如何连接?

大多数神经网络架构将层次布置成链式结构,其中每一层都是前一层的函数,例如
h(1)=g(1)(W(1)Tx+b(1))h^{(1)}=g^{(1)}(W^{(1)T}x+b^{(1)})h(1)=g(1)(W(1)Tx+b(1))
h(2)=g(2)(W(2)Th(1)+b(2))h^{(2)}=g^{(2)}(W^{(2)T}h^{(1)}+b^{(2)})h(2)=g(2)(W(2)Th(1)+b(2)),以此类推

在链式结构中,我们主要考虑的是选择网络的深度和每一层的宽度。更深层的网络往往对每一层使用更少的单元数和参数,并且经常容易泛化到测试集,但通常也更难以优化。理想的神经网络架构必须通过实验以观测在验证集上的误差来找到。

万能近似性质和深度

线性函数通常拥有一些良好的性质,就比如损失函数会导出凸优化问题。然而实际上大多数时候我们需要学习的都是非线性的问题。幸运的是,具有隐藏层的前馈网络提供了一种万能近似定理(universal approximation theorem)如果一个前馈神经网络具有线性输出层和至少一层具有任何一种“挤压”性质(类似sigmoid的有界域内两端饱和的函数)的激活函数的隐藏层,那么只要基于网络足够数量的隐藏单元,它就可以以任意的精度来近似任何从一个有限维空间到另一个有限维空间的Borel可测函数。

实在是有点难懂啊,说人话就是:一个前馈神经网络就足够表示任何函数的近似到任意的精度,哪怕它只有一层。

那为什么我们还经常要构建更深层次的网络呢?尽管理论上是可行的,但是我们不能保证训练算法能够学得这个函数,即使神经网络能够表示该函数,学习也可能因为“训练的优化算法找不到用于期望函数的参数值”或是“训练算法由于过拟合选择了错误的参数”而失败。因此并不存在一个万能的过程,既能验证训练集上的特殊样本,又能选择一个函数来拓展泛化到训练集上没有的那些点。
尽管我们说单层也可以做到万能近似,但哪怕一个单层的前馈网络可以用于表示这个函数,但是这一个单层的网络层可能会大到无法实现。

因此想要神经网络达到好的近似效果,最根本的在于结点数,如果深度较浅就需要每层的结点数较多,每层就需要更大的宽度。较深的网络则没那么大的宽度要求。另外一方面,浅的网络也更易过拟合,泛化误差较大。因此我们总是希望建立一个更深层次的神经网络来解决更复杂的问题。例如Ian研究了对于图片地址数字识别的问题,测试准确度会随着层数的增大而提高:

在这里插入图片描述

目前为止的神经网络都是成层的简单链式结构,在实践中将会有相当的多样性。

总结一下,这部分主要讲了神经网络的损失函数通常用交叉熵,隐藏层通常用ReLU,而更深的模型能有效减少总节点数且减少泛化误差。

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...