第二十五周周报
- 摘要
- Abstract
- 机器学习复习------神经卷积网络(CNN)
-
-
- 基于Neuron理解CNN
-
- 1.1 receptive field(感受野)
- 1.2 stride(步幅)
- 1.3 padding(填充)
- 1.4 filter(滤波器)
- 1.5 convolution Layer(卷积层)
-
- 基于filter理解CNN
-
- 2.1 Feature Map(特征图)
-
- Pooling(池化层)
-
- 3.1 Max Pooling
-
- 动手深度学习之------线性回归
-
- 1.线性回归
- 2.softmax回归
- 3.多层感知机
- 4.应对过拟合问题
-
- 4.1 权重衰减
- 4.2 丢弃法
- 5.Kaggle实战:房价预测
- 总结
摘要
本周的周报主要对机器学习的核心概念和实践应用进行了复习和学习,本周复习了神经卷积网络(CNN)的基础知识,包括CNN的工作原理、卷积层、池化层以及它们在图像识别中的应用。在动手深度学习中,学习了了线性回归、softmax回归和多层感知机(MLP)的基本概念和实现,并通过代码示例展示了如何使用PyTorch框架来实现这些模型。此外,还讨论了过拟合问题及其解决方案,如权重衰减和丢弃法,并以Kaggle房价预测竞赛为例,展示了如何将这些概念应用于实际问题中。
Abstract
This week's report delves into the intricacies of machine learning with a focus on convolutional neural networks (CNNs) and their application in image recognition. It reviews the fundamental concepts of CNNs, including the understanding of CNN through neurons and filters, the role of convolutional layers, pooling layers, and how they contribute to image classification tasks. The report also covers linear regression, softmax regression, and multilayer perceptrons (MLPs), providing code examples using the PyTorch framework to implement these models. Furthermore, it discusses the issue of overfitting and its solutions such as weight decay and dropout methods, culminating with a practical application in a Kaggle house price prediction competition.
机器学习复习------神经卷积网络(CNN)
1. 基于Neuron理解CNN
我们用CNN最常见的就是影响分析,也就是我们之前常常讲的classification。
即通过输入一张图片,最后判断出这个图片是什么?
注意:输入的图片都是相同尺寸的(即需要进行预处理)
过程如下图所示:
其中Ŷ表示识别出物体各个种类的真实值,有多少维度就是有多少种可以识别出物品
通过Cross entropy算法,得到最终预测结果
复习Cross entropy算法 :
问题是怎么把一张影像当做一个模型的输入呢?
其实我们的图片都是三维的tensor (张量,其实就是是多维(>2维)数组)
1.其中一维代表图片的宽
2.另外一维代表图片的高
3.还有一维代表图片的channel的数目 (3通道)
(一张彩色的图片的每一个pixel都是由RGB三个颜色所组成 的,所以这三个channel就代表了RGB三个颜色)
然后就要把这一个三维的tensor把它拉直 ,拉直就可以丢到一个network里面去了
所以我们的network输入都是一个向量
所以只要能够把一张图片变成一个向量,我们就可以把它当做是network的输入。
但是怎么把这个三维的tensor变成一个向量呢?
在上面图片的例子里面有100×100×3个数字,所以这一张图片是由100×100×3个数字所组成的,把这些数字通通拿出来,排成一排就是一个巨大的向量。
这个向量可以作为的输入,而这个向量里面每一维它里面存的数值其实就是某一个pixel
(一个pixel有RGB三个颜色所组成,每个颜色都有一个数值代表这个颜色的强度。
所以在这个向量里面,每一维它的数值就代表了某一个位置的某一个颜色的强度。)
如果我们把这个向量当做输入,feature vector的长度就是100×100×3。
1、假设现在的第一层neuron(神经元)的数目有1000个
计算一下第一层总共有多少个weight?
我们每一个neuron跟输入的向量的每一个数值都会有一个weight ,所以如果输入的向量长度是100×100×3有1000个neuron。也就是1000 * 100 * 100 * 3 = 3 * 10⁷,是一个非常庞大的数值
2、如果参数越多,会有什么样的问题呢?
虽然随着参数的增加,我们可以增加模型的弹性,也可以增加它的能力,但是我们也增加了overfitting的风险。
那我们怎么减少在做影像识别的时候避免使用这么多的参数呢?
基于影像的特性,我们其实不需要每一个neuron跟input的每一个dimension都有一个weight。
比如需要识别一只鸟,机器如果看到鸟嘴 、 鸟的眼睛、鸟的爪子 就可以判断这张图片是一张鸟了(这些称之为 critical pattern,关键图案)
其实我们人在辨别物体的时候何尝不是通过辨别这些关键特征来进行判断的呢。
所以其实按照这样的思路,机器没有必要去看完一张完整的图片去再给出判断,只需要关注某些特征就可以做出判断,这会大大减少需要计算的参数。
如下图,这一些重要的pattern,比如鸟嘴、眼睛、鸟爪,并不需要看整张完整的图片才能够得到这些信息。所以这些neuron根本就不需要把整张图片当做输入,他们只需要把图片的一小部分当做输入就足以让他们侦测某些特别关键的pattern。
1.1 receptive field(感受野)
那怎么通过这个观察来设计我们的neural network呢?
在CNN里面有这样一个做法。它会设定一个区域,叫做receptive field(感受野)
每一个neuron都只关心自己的receptive feel里面发生的事情即可。
比如
先定义说这一个蓝色的neuron,他的负责范围就是这一个有33 3个数字的立方体的receptive field,那对这个蓝色receptive field来说,它只需要关心这一个小范围,不需要在意整张图片里面有什么东西。
怎么考虑这个蓝色负责的receptive field发生什么样的事情呢?
1、它要做的事情就是把这333个数值拉直 ,变成 一个长度 是3×3×3即27维的向量
2、再把这27维的向量作为这个neuron的输入 ,这个neuron会给27维的向量的每一个dimension给定一个weight,所以这个neuron有33 3 = 27个weight,再加上bias的然后输出
3、这个输出再送给下一层的neuron当做输入 。
那这个receptive filed要怎么决定呢?
这个可以根据自己的情况决定
如下图:
1、不同的receptive field可以负责的不同的范围
蓝色的neuron就负责左上角这个范围,另外又有一个黄色的neuron负责右下角这个3×3×3的范围.
2、receptive filed彼此之间可以是重叠的
比如下图中的receptive filed,在这个地方它是绿色的neuron的范围跟蓝色的跟黄色的都有一些重叠的空间。
3、同个范围可以有多个不同的neuron,多个neuron可以去负责同一个receptive filed。
4、receptive field可以有大有小
因为毕竟pattern有大有小,有的在3×3的范围内就可以被侦测出来,有的pattern也许要11×11的范围才能被侦测出来
5、receptive field可以只考虑某些channel
因为上述的resettive field啊是rgb三个channel都考虑。
因为有些patterns在红色的圈头会出现、有些patterns在蓝色的圈头会出现,但在一般CNN里面,有这样子的做法但不经常这样考虑。
6、上面的receptive field都是正方形的,也可以是长方形的
这完全都是你自己设计的,是你自己定义的,你完全可以根据对问题的理解定义receptive field 与 neuron,所以对receptive filed 与 neuron的定义还可以有各式各样怪怪的想法
虽然你可以任意设计,最经典的的安排方式还是需要学习的
第一个就是会看所有的channel,然后同一个receptive field 会有一组neuron去负责(比如64个neuron为一组,每一个neuron负责发现一个特征)
我们在描述一个receptive field的时候,只要考虑它的高跟宽
这个高跟宽合起来叫做kernel size(卷积核),常见的receptive filed设定方式kernel size 是3×3
1.2 stride(步幅)
各个不同receptive field之间的关系是什么样呢?
下图中最左上角的这个receptive field 往右移一点,然后就制造一个新的范围
这个移动的量叫做stride(步幅)像下图的例子里面,stride = 2(具体由自己决定),但这个stride往往不会设太大,stride往往设1或2就可以了,因为我们希望这些receptive field之间是有重叠
假设receptive field完全没有重叠,会发生什么?
那有一个pattern正好出现在两个receptive field的交界上面,就会变成没有任何neuron发现这个pattern,那可能会忽略掉这个patent。
所以我们希望receptive field彼此之间有高度的重叠。
1.3 padding(填充)
假设我们stride = 2,那第一个receptive field和第二个receptive field如下如所示,当到第三个receptive field就遇到一个问题了,它超出了影像的范围,怎么办呢?
超出范围就做padding(填充,填充的地方补0(不一定非要是0可以是其他值))
除了这个水平方向的移动,也会有垂直方向的移动
垂直的移动同样是按照stride的值来上下移动的
stride = 2,所以一个receptive field在这边垂直方向移动两格 ,就有制造一个新的receptive field你按照这个方式扫过整张图片。
以上是第一个简化fully connected的方式
说第二个方式前,先说一下我们观察到的东西
我们可以观察到同样的pattern会出现在图片的不同区域
比如下图中的鸟嘴,它可能出现在图片的左上角,也可能出现在图片的中间。
按照刚刚的方式一
以同样的出现在图片的不同的位置,似乎也不是太大的问题
因为出现在左上角的瞄准,它一定它落在某一个receptive field里面 ,又因为receptive field是盖满整个图片的 ,所以图片里面任何地方都在某个neuron的负责范围内 。这些地方一定是某一个neuron的receptive field
所以假设在左上角的receptive field里面有一个neuron,它的工作就是侦测鸟嘴的话,那鸟嘴就会被侦测出来。
就算鸟嘴出现在中间也没有关系
因为一定在其中的某一个receptive field的范围里,那个receptive field其中的neurons有一个nuero它可以侦测鸟嘴的话,那出现在图片的中间也会被侦测出来。
1.4 filter(滤波器)
那么问题来了
这些侦测鸟嘴的neuron,他们做的事情其实是一样的,只是他们守备的范围是不一样
那我们真的需要每一个receptive field都去放一个侦测鸟嘴的neuron吗?
那每一个都需要的话,参数量就会过多了
那如果参数能够共享的话是不是就不用在每一个receptive field都设置一个这样的neuron了
所以方式二就是:
让不同receptive field的neuron他们共享参数
那共享参数是什么意思呢?
所谓共享参数就是这两个neuron它们的weight完全是一样的
如下图中:两个neuron负责的receptive field是不一样的,但是它们的参数(w₁、w₂、...)是一模一样的。
那有人可能就会问,它的参数是一模一样那它会不会输出永远都是一样的?
不会,因为他们的输入是不一样的。 这两个neuron的参数一模一样,但是他们负责的范围是不一样的,所以输出也不一样。
如下图所示x₁、x₂未必等于x₁'、x₂'
共享的方式可以由我们自己定义,但是常见的影像识别上面的共享的方法 是怎么设定的还是需要学习的。
那我们刚才已经讲说每一个receptive field,它都有一组neurons(假设一组neurons比如有64个neuron),所以这个receptive field有64个neuron
那它们彼此之间会怎么共享参数呢?
如下图,有一样的颜色的neuron(代表这两个neuron共享一样的参数)
每一个receptive field的neuron都只有一组参数 (就是一个receptive field的第一个neuron跟另外一个receptive field的第一个neuron共用参数 ,它的第二个neuron跟其他的receptive field第二个neuron共用参数,以此类推)
这些共享的参数有一个名字叫做filter(滤波器) ,所以这两个neuron他们共用同一组参数,这组参数就叫filter 1,第二个同颜色的两个neuron它们共用同一组参数,这组参数就叫filter 2以此类推。
1.5 convolution Layer(卷积层)
那我们来整理一下,我们学到了什么?
1、首先是fully connected network,它是弹性最大的。当我们强制一个neuron只能看一张图片里面的一个范围的时候,它的弹性是变小的。如果是fully connected的,他可以决定看整张图片还是只看一个范围就。如果他只想只看一个范围就把很多weight设成零,所以fully connected layer是由它自己决定,就是所谓的弹性大
2、但是因为我们不需要看整张图片,也许只要看图片的一小部分就可以侦测出重要的pattern,所以我们有了receptive field。但加上receptive field的概念以后意思就是就只能看一个小范围,所以加入receptive field以后,你的network的弹性是变小。
3、接下来我们还有参数共享
参数共享这件事又更进一步限制了network的弹性。原来其可以决定说这两个neuron的参数要是什么,每一个neuron可以各自有不同的参数,但是加入参数共享以后就意味着说某一些neuron无论什么参数都要一模一样,所以这又更增加了对neuron的限制。
而receptive field加上 parameters sharing就是convolution Layer(卷积层)
convolution Layer(卷积层) = receptive field(感受野) + parameters sharing(参数共享)
那有convolutional layer的network就叫Convolutional Neural Network就是CNN。
所以下图可以看出其实CNN它的bias比较大
它的model的bias比较大model bias大,不一定是坏事
因为当model bias小model的灵活度很高的时候,它比较容易overfitting,所以fully connected layer它可以做各式各样的事情,它可以有各式各样的变化,但是它可能没有办法在任何特定的任务上做好。
而convolutional layer它是专门为影像识别设计的 ,所以它在影像上仍然可以做得好。虽然它的model bias很大,但这个在影像上不是问题
但是如果它用在影像之外的任务,那你就要很小心了
2. 基于filter理解CNN
第二种方式是比较常见的教学理解方式(基于filter),具体如下:
什么叫做convolution layer?
如下图所示
convolutional layer就是里面有很多的filter ,大小是3×3×channel
(channel就是是彩色图片的话就是rgb,channel = 3;如果是黑白的图片channel = 1 。)
所以在这里convolution里面就是有一排的filter,每一个filter它都是一个3×3×channel这么大的tensor。
那每一个filter的作用就是要去图片里面捕获某一个pattern(这些pattern要在3×3×channel那么小的范围内)
那这些filter怎么去图片里面捕获pattern的呢?
下面用一个例子解释说明:
如下图:
假设channel = 1(黑白图)
假设这些filter的参数是已知的,filter就是一个一个的tensor(即tensor里面的数值已知)
在方式一中:实际上这些tensor里面的数值其实就是model里面的parameter(即w₁、w₂、...),是通过gradient descent找出来的
假设我们有一张6*6的黑白图
那么我们如何通过filter识别图像的pattern呢?
1、首先把这个filter里面的九个值跟左上角范围里面的九个值做相乘再相加
2、接下来这个filter本来在左上角,接下来就往右移一点 ,那这个移动的距离叫做stride(步幅),这里设stride = 1,所以往右移动一格(垂直方向同理)
3、再重复步骤1,算出结果
4、以此类推,得出如下结果
那接下来呢,我们把每一个filter都做重复的计算,结果如下:
filter 2:
2.1 Feature Map(特征图)
所以每一个filter都会给我们一群数字 ,如果我们有64个filter,我们就得到64群的数字。
这一群数字有一个名字叫做feature Map(特征图)
当我们把一张图片通过一个convolutional layer,其里面有一堆filter的时候,我们产生出来了一个Feature Map。
那假设convolutional layer里面,它有64个filter,那产生出来的这个feature map就有64组数字
(每一组在如上例子里面是4×4,第一个filter产生4×4个数字,第二个filter也产生4×4的数字...以此类推到64个filter都产生4×4的数字)
那这个feature map可以看成是另外一张新的图片,只是这个图片的channel啊不是rgb,这个图片的channel有64个
即本来一张图片channel = 3,通过一个convolution变成一张新的图片channel = 64。
convolutional layer是可以叠很多层的
那如果叠第二层要注意什么呢?
第二层的convolution里面也有一堆的filter,这堆filter的大小我们这边也设3×3。
那它的高度呢?
它的高度必须设为64
因为filter的高度就是它要处理的影像的channel数
所以跟刚才第一层的假设输入的影像是黑白的,channel = 1。那我们的filter的高度就是一输入的影像。如果是彩色的channel = 3,那filter的高度就是3。
那在第二层里面,我们也会得到一张图片,对第二个convolutional layer来说它的输入也是一张图片。那第二层图片的channel是多少?
这个图片的channel是64,这个64是前一个convolutional layer的filter数目 。前一个convolutional layer它filter数目64,那输出以后就是64个channel,所以第二层想把这个图片当做输入,那filter的高度也得是64。
如果我们的的大小一直设3×3,会不会让我们的network没有办法看比较大范围的呢?
(顺便回答1.1中的卷积核默认大小3x3的问题)
其实不会
因为如果我们在第二层convolutional layer,我们的filter的大小一样设3×3
当我们看最左上角这个数值的时候,最左上角这个红色框框的数值在图片上其实是对应到红色范围。
当我们看最右下角这个红色框框数值的时候,右下角的数值在图像上其实对应到右下角红色框框范围。
所以当我们看这3×3的范围的时候,**看第一个convolutional layer的输出的这个feature map的3×3的范围的时候,我们在原来的影像上其实是考虑了一个5×5的范围。**所以虽然我们的filter只有3×3,但它在原来图片上考虑的范围是比较大的5×5。
只要你的network叠的越深,同样是3×3的大小的filter,它看的范围就会越来越大
所以network够深,你不用怕你检测不到比较大的pattern,它还是可以探测到比较大的patter的。
总结两种理解方式
我们在第一个方式里面说到了有一些neuron,这些neuron会共用参数,就是第二个理解方式里面的filter。
方式一中的share weight(共享参数)其实就是方式二中filter扫过一张图片 ,把filter扫过一张图片其实就是convolution,所以把filter扫过一张图片其实就是convolution。
总结如下:
3. Pooling(池化层)
那convolutional layer在做图像识别的时候其实还有第三个常用的东西,叫做Pooling(池化)
那Pooling是什么呢?
我们拿一张很大的图片做subsampling(二次采样)。比如,就是把一张图的偶数的color都去除,奇数的color都取出,图片变成为原来的四分之一,但是不会影响里面是什么东西。
如下图:
把一张大的图片缩小,这是一只鸟,这张小图片看起来还是一只鸟。
Pooling的行为都是固定好的,没有要根据data学任何东西(类似一个sigmoid函数)
3.1 Max Pooling
Pooling有很多版本,但是我们常见的版本是Max Pooling
我们刚才说每一个filter都产生一组数字
要做Pooling的时候,我们就把这些数字几个几个一组
如下图中就是2×2个一组
每一组里面选一个代表,在max pooling里面,我们选的代表就是最大的那一个
如下图:
为什么是选最大的那一个呢?
因为这是Max pooling规定的 ,当然也有mean pooling(取平均值)等等
也未必要2×2一组 ,自己可以决定4×4或者3×3一组也可以。
所以我们做完convolution以后,一般后面还会搭配pooling
pooling做的事情,就是把图片变小。
本来64个channel还是64个channel。(channel不变,pooling不改变channel)
但是会把图片变得小一点
在刚才的例子里面,本来4×4的图片经过pooling变成了2×2的一组
在实践上,一般就是convolution跟pooling交替使用
可能做几次convolution,做一次pooling,比如两次convolution,一次pooling。
不过你可以想见说pooling和对于performance还是有不好的影响的,假设要侦测的是非常微小的东西,那使用pooling最终的效果肯定不好。
CNN的常见框架如下:
flatten就是把图像本来排成矩阵的样子的东西,拉直把所有的数值拉直变成一个向量
动手深度学习之------线性回归
1.线性回归
线性回归是一个单层神经网络,公式如下:
y = w i x i + b y=w_ix_i+b y=wixi+b
例如上图所示,输入分别为和,因此输入层的输入个数为2。输入个数也叫特征数或特征向量维度。网络的输出为o,输出层的输出个数为1。
py
import numpy as np
import torch
import torch.utils.data as Data
from torch import nn, optim
from torch.nn import init
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels) # 对应匹配
# 随机读取小批量
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
print(net) # 使用print可以打印出网络的结构
init.normal_(net[0].weight, mean=0, std=0.01) # 将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布
init.constant_(net[0].bias, val=0) # 也可以直接修改bias的data: net[0].bias.data.fill_(0)
loss = nn.MSELoss() #均方误差损失作为模型的损失函数
optimizer = optim.SGD(net.parameters(), lr=0.03)
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
l.backward()
optimizer.step() # 梯度更新
print('epoch %d, loss: %f' % (epoch, l.item()))
dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)
2.softmax回归
第二节介绍的线性回归模型适用于输出为连续值的情景。在另一类情景中,模型输出可以是一个像图像类别这样的离散值。对于这样的离散值预测问题,可以使用诸如softmax回归在内的分类模型。和线性回归不同,softmax回归的输出单元从一个变成了多个,且引入了softmax运算使输出更适合离散值的预测和训练。
softmax回归同线性回归一样,也是一个单层神经网络。
softmax回归如下图所示:
softmax的输出即输出分类中属于每一类的概率,然后将最有可能的类别作为模型的预测。
py
import torch
import sys
from torch import nn
from torch.nn import init
from d2lzh_pytorch import FlattenLayer
sys.path.append("..") # 为了导入上层目录的d2lzh_pytorch
import d2lzh_pytorch as d2l
from collections import OrderedDict
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 定义和初始化模型
num_inputs = 784 # 1x28x28的图片,需要先将图片.view()一下
num_outputs = 10
net = nn.Sequential(
OrderedDict([ # OrderedDict指定每个模块的键(名称)和值(模块本身)
('flatten', FlattenLayer()), # 格式转换
('linear', nn.Linear(num_inputs, num_outputs))
])
)
# 随机初始化参数
init.normal_(net.linear.weight, mean=0, std=0.01)
init.constant_(net.linear.bias, val=0)
# 交叉熵损失函数
loss = nn.CrossEntropyLoss()
# 优化算法
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
# 训练模型
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
结果如下:
3.多层感知机
线性回归和softmax回归是单层神经网络,然而深度学习主要关注多层模型。多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer),隐藏层位于输入层和输出层之间。如下图所示:
py
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 和softmax回归唯一的不同在于,多加了一个全连接层作为隐藏层。它的隐藏单元个数为256,并使用ReLU函数作为激活函数。
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(), # 如果网络中没有激活函数,那么无论网络有多少层,其输出都是输入的线性组合,这限制了模型的能力
nn.Linear(num_hiddens, num_outputs),
)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
结果如下:
4.应对过拟合问题
过拟合,即模型的训练误差远小于它在测试集上的误差。虽然增大训练数据集可能会减轻过拟合,但是获取额外的训练数据往往代价高昂。所以引入权重衰减和丢弃发两种方法。过拟合情况如下图所示:
4.1 权重衰减
权重衰减等价于L2范数正则化,正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。L2范数正则化在模型原损失函数基础上添加L2范数惩罚项,从而得到训练所需要最小化的函数。L2范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。损失函数如下:
J ( w ⃗ , b ) = 1 2 m ∑ i = 1 m ( f w ⃗ , b ( x ⃗ ( i ) ) − y ( i ) ) 2 + λ m ∑ j = 1 m w j 2 J(\vec{w}, b)=\frac{1}{2 m} \sum_{i=1}^{m}\left(f_{\vec{w}, b}\left(\vec{x}^{(i)}\right)-y^{(i)}\right)^{2}+\frac{\lambda}{m} \sum_{j=1}^{m} w_{j}^{2} J(w ,b)=2m1i=1∑m(fw ,b(x (i))−y(i))2+mλj=1∑mwj2
代码如下:
py
import torch
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
n_train, n_test, num_inputs = 20, 100, 200 # 训练数据很少
true_w, true_b = torch.ones(num_inputs, 1) * 0.01, 0.05
# 创建数据
features = torch.randn((n_train + n_test, num_inputs))
labels = torch.matmul(features, true_w) + true_b # matmul()矩阵点乘
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float) # 加入噪声
train_features, test_features = features[:n_train, :], features[n_train:, :]
train_labels, test_labels = labels[:n_train], labels[n_train:]
# 定义随机初始化模型参数的函数,并为每个参数都附上梯度
def init_params():
w = torch.randn((num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
return [w, b]
# 定义L2范数惩罚项,只惩罚模型的权重参数
def l2_penalty(w):
return (w**2).sum() / 2
# 训练网络
batch_size, num_epochs, lr = 1, 100, 0.003
net, loss = d2l.linreg, d2l.squared_loss
dataset = torch.utils.data.TensorDataset(train_features, train_labels)
train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
def fit_and_plot(lambd):
w, b = init_params()
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
# 添加了L2范数惩罚项
l = loss(net(X, w, b), y) + lambd * l2_penalty(w) # 损失函数加上权重
l = l.sum()
if w.grad is not None:
w.grad.data.zero_()
b.grad.data.zero_()
l.backward()
d2l.sgd([w, b], lr, batch_size)
# .item()方法用于将这个标量Tensor转换为一个Python标量(通常是float类型)
# train_ls.append()将计算得到的平均损失值添加到名为train_ls的列表中
train_ls.append(loss(net(train_features, w, b), train_labels).mean().item())
test_ls.append(loss(net(test_features, w, b), test_labels).mean().item())
print("平均损失值:", sum(test_ls)/len(test_ls))
# 画图显示
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss', range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('L2 norm of w:', w.norm().item())
# 当lambd设为0时,没有使用权重衰减
fit_and_plot(lambd=0)
fit_and_plot(lambd=3)
结果如下:
4.2 丢弃法
对某隐藏层使用丢弃法时,该层的隐藏单元将有一定概率被丢弃掉。设丢弃概率为p,那么有p的概率会被清零,有1-p的概率会除以1−p拉伸。丢弃法不改变其输入的期望值,在反向传播时,被丢弃的隐藏单元相关的权重的梯度均为0。由于在训练中隐藏层神经元的丢弃是随机的,即所有都有可能被清零,输出层的计算无法过度依赖隐藏层中的任一个单元,从而在训练模型时起到正则化的作用,并可以用来应对过拟合。丢弃概率是丢弃法的超参数。
随机丢弃了h~2~和h~5~,如下图所示:
代码如下:
py
# 倒置丢弃法,丢弃法不改变其输入的期望值,对该隐藏层使用丢弃法时,该层的隐藏单元将有一定概率被丢弃掉。
# 设丢弃概率为p,那么有p的概率参数hi会被清零。丢弃概率是丢弃法的超参数。
import torch
import torch.nn as nn
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
drop_prob1, drop_prob2 = 0.2, 0.5
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens1),
nn.ReLU(),
nn.Dropout(drop_prob1),
nn.Linear(num_hiddens1, num_hiddens2),
nn.ReLU(),
nn.Dropout(drop_prob2),
nn.Linear(num_hiddens2, 10)
)
for param in net.parameters():
nn.init.normal_(param, mean=0, std=0.01)
num_epochs, batch_size = 5, 256
loss = torch.nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
结果如下图:
5.Kaggle实战:房价预测
py
import torch
import torch.nn as nn
import pandas as pd
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
torch.set_default_tensor_type(torch.FloatTensor)
train_data = pd.read_csv('./data/kaggle/train.csv')
test_data = pd.read_csv('./data/kaggle/test.csv')
# 将所有的训练数据和测试数据的79个特征按样本连结
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
# 数据预处理,对连续数值的特征做标准化
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
all_features[numeric_features] = all_features[numeric_features].apply(lambda x: (x - x.mean()) / (x.std()))
# 标准化后,每个数值特征的均值变为0,所以可以直接用0来替换缺失值
all_features[numeric_features] = all_features[numeric_features].fillna(0)
# dummy_na=True将缺失值也当作合法的特征值并为其创建指示特征
all_features = pd.get_dummies(all_features, dummy_na=True)
n_train = train_data.shape[0] # 训练集数据长度
print("n_train:", n_train)
# train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float)
train_features = torch.tensor(all_features[:n_train].to_numpy(dtype=float), dtype=torch.float)
# test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float)
test_features = torch.tensor(all_features[n_train:].to_numpy(dtype=float), dtype=torch.float)
# train_labels = torch.tensor(train_data.SalePrice.values, dtype=torch.float).view(-1, 1)
train_labels = torch.tensor(train_data.SalePrice.to_numpy(dtype=float), dtype=torch.float).view(-1,1)
loss = torch.nn.MSELoss()
def get_net(feature_num):
net = nn.Linear(feature_num, 1)
for param in net.parameters():
nn.init.normal_(param, mean=0, std=0.01)
return net
# 对数均方根误差
def log_rmse(net, features, labels):
with torch.no_grad():
# 将小于1的值设成1,使得取对数时数值更稳定
clipped_preds = torch.max(net(features), torch.tensor(1.0))
rmse = torch.sqrt(loss(clipped_preds.log(), labels.log()))
return rmse.item()
def train(net, train_features, train_labels, test_features, test_labels, num_epochs, learning_rate, weight_decay, batch_size):
train_ls, test_ls = [], []
dataset = torch.utils.data.TensorDataset(train_features, train_labels)
train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
# 这里使用了Adam优化算法
optimizer = torch.optim.Adam(params=net.parameters(), lr=learning_rate, weight_decay=weight_decay)
net = net.float()
for epoch in range(num_epochs):
for X, y in train_iter:
l = loss(net(X.float()), y.float())
optimizer.zero_grad()
l.backward()
optimizer.step()
train_ls.append(log_rmse(net, train_features, train_labels))
if test_labels is not None:
test_ls.append(log_rmse(net, test_features, test_labels))
return train_ls, test_ls
# K折交叉验证,返回第i折交叉验证时所需要的训练和验证数据
def get_k_fold_data(k, i, X, y):
# 返回第i折交叉验证时所需要的训练和验证数据
assert k > 1
fold_size = X.shape[0] // k
X_train, y_train = None, None
for j in range(k):
idx = slice(j * fold_size, (j + 1) * fold_size)
X_part, y_part = X[idx, :], y[idx]
if j == i:
X_valid, y_valid = X_part, y_part
elif X_train is None:
X_train, y_train = X_part, y_part
else:
X_train = torch.cat((X_train, X_part), dim=0)
y_train = torch.cat((y_train, y_part), dim=0)
return X_train, y_train, X_valid, y_valid
# K折交叉验证中返回训练和验证的平均误差
def k_fold(k, X_train, y_train, num_epochs, learning_rate, weight_decay, batch_size):
train_l_sum, valid_l_sum = 0, 0
for i in range(k):
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net(X_train.shape[1])
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate, weight_decay, batch_size)
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
if i == 0:
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse',
range(1, num_epochs + 1), valid_ls, ['train', 'valid'])
# 训练集均方根误差和测试集均方根误差
print('fold %d, train rmse %f, valid rmse %f' % (i, train_ls[-1], valid_ls[-1]))
return train_l_sum / k, valid_l_sum / k
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr, weight_decay, batch_size)
print('%d-fold validation: avg train rmse %f, avg valid rmse %f' % (k, train_l, valid_l))
结果如下图所示:
总结
在本周的学习中,我深入复习了神经卷积网络(CNN),并在动手机器学习里学习了线性回归、softmax回归以及多层感知机(MLP)。通过对CNN的复习,加深了我对利用卷积层和池化层来提取图像特征的理解。在动手深度学习中,我还学习了如何处理线性回归和分类问题,并通过softmax回归进行了实践。在面对模型过拟合的问题时,学会了权重衰减和丢弃法两种策略,并在Kaggle房价预测案例中应用了这些知识。
基础就用动手深度学习和以往的周报加深印象,在方向PINN的学习上也要开始了,下一周计划精读PINN正反解求PDE的文献,开始对自己的方向进行学习。