在这一周,我主要是通过观看了吴恩达教授的机器学习,以及B站up主小土堆讲解的PyTorch,以下是我在这一周的学习笔记,继续努力,继续学习,继续进步!
目录
机器学习
神经网络中的误差分析
在上一周,主要是学习了神经网络的一些基本概念,那么我们如何去提升学习算法呢?提升学习算法,首要是解决偏差和方差,其次才是误差分析,通过手动查看,试图深入了解出错的地方。
如果交叉验证集合很大,算法错误分类了可能也很多,会浪费了许多时间,一般会抽取少量的子集,可以在一定时间内查看的子集。
在神经网络中添加更多的数据
那么我们在机器学习中,如何正确且高效的添加更多的数据呢?
如果盲目的获取所有类型的数据,可能会很慢且代价很高,我们可以添加更多分析表明可能有帮助的类型数据。
我们可以采用数据增强的方式,当我们已经有了一个大的训练集合数据增强是一个好的方式。
数据增强的技巧是,对数据所做的更改或者扭曲应该代表测试集合中噪声或者扭曲的类型,比如识别字母A,我们可以增加一些扭曲的字母A,又或者是语音识别,我们可以增加一些在嘈杂地方的声音或者是在车里的声音进行识别。
如果对数据的纯随机的无意义噪声,通常是没有太大帮助的。
对于传统的机器学习,是以代码和算法为中心,对于现在来说,是以数据为中心,一个质量高的数据,对于训练一个最优模型有很大的帮助。
神经网络中的迁移学习
迁移学习是从完全不同的几乎不相关的任务中获取数据,假设去识别数字0到9,我们没有很多的标记数据去来训练这个模型,我们可以利用训练识别汽车,狗的自动驾驶模型,利用它的前几层来进行迁移学习。
它是复制训练其他数据的神经网络,对于最后一层,消除输出层并用更小的输出层替换它。可以做的是使用前几层的参数,实际上是除最后输出层之外的所有层。作为参数起点,运行优化算法,比如梯度下降算法或者Adam算法,使用来自该神经网络的值在顶部初始化的参数。
上述的例子的本质是通过学习识别猫、狗的自动驾驶模型为处理图像输入的早期层,学习了一些合理的参数值,将这些参数转移到新的神经网络,新的神经网络从更好的地方开始。
因为识别猫、狗的自动驾驶模型和识别数字0到9的模型,本质都是图像分类,所以在隐藏层的大部分工作都是相似甚至是一样的,直接把第一个网络的隐藏层拿过来用,对输出层重新训练就能实现新的功能,再举一个简单易懂的例子,相当于螺丝刀的刀柄都是一样的,但把十字刀刀头换成一字刀作用就不一样了。
如果两者的本质并不是同一种,比如一个是图像识别,一个是语音识别,就不能进行迁移学习。
如下图所示:
其中如上图所示,选择1,只训练输出层的参数使用于小训练集,选择2,训练所有的参数适用于大训练集。
在大数据集训练,在小数据集进行调整参数称为监督预训练。
我们之所以可以进行迁移学习的本质是两者使用相同的输入层,如下图所示:
以下是迁移学习的总结:
1、如果有相同输入层时,下载神经网络参数在大训练集上进行预训练。
2、更进一步的训练自己神经网络的数据,在自己的神经网络中进行微调。
机器学习项目的完整周期
以下是机器学习项目的周期步骤,首先去决定做什么,接下来去进行收集数据,这也是很关键的一个步骤,一个优秀的数据集,可以训练出最优的模型,紧接着进行模型的训练,最后部署项目,当然在模型训练的时候可能会遇到各种错误,比如高偏差,高方差等问题,我们需要回到第二步继续去收集数据,如下图所示机器学习项目的周期:
对于部署项目,通常在我们的应用程序中,用户进行输入,将输入部分传递给服务器,服务器根据我们已经训练好的机器学习模型对该输入进行输出,最后返回到我们的应用程序,比如我们常见的应用程序语音识别,图像文字识别等等,项目运行如下图所示:
倾斜数据集的误差指标
我们需要去了解精确率和召回率这两个概念,以便选择最适合我们的算法。
精确率就是在预测中的预测正确率,召回率就是在实际中的预测正确率,具体公式如下图所示:
所谓精确率就是预测的和真实的数据都为1的情况下的数据去除以在预测中为1的数据,就是预测中的预测正确率。
所谓召回率就是预测的和真实的数据都为1的情况下的数据去除以在实际中为1的数据,就是实际中的预测正确率。
对于上图中的例子,就是说模型预测你患病是真的话,有75%概率是真的,然后这个模型能够预测出来患病的人占实际总理的60%。
精确率针对预测,预测有多少是对的,召回率针对数据,一类数据有多少可以被认出。
如果我们需要预测y=1更精确时,我们可以定义更高的阀值,那么这样会有更高的精确率和较低的召回率;如果我们需要避免y=1的人错误预测时,我们可以更低的阀值,那么这样会有更高的召回率和较低的精确率,如下图所示:
关于我们如何选择最适合的算法,我么可以利用F1 score,去计算排序平均值的方法,更关注较低的那个,最后算出的F1 score 更大,我么优先考虑,如下图所示:
算法2和算法3有很高的精确率和召回率但是又有极低的召回率和精确率,这样的话相差太大,并不是是一个适合我们的算法,通过F1 score 我们可以很容易从几个算法中选择出最适合我们的算法。
决策树模型概念
决策树(Decision Tree),它是一种以树形数据结构来展示决策规则和分类结果的模型,作为一种归纳学习算法,其重点是将看似无序、杂乱的已知数据,通过某种技术手段将它们转化成可以预测未知数据的树状模型,每一条从根结点(对最终分类结果贡献最大的属性)到叶子结点(最终分类结果)的路径都代表一条决策的规则。
决策树可以用于尝试一个种类和非这个种类进行分类,做出分类决策,从最顶点的根结点开始,像多个if语句。
比如下图所示的例子,判断是否是猫,可以判断它的耳朵的形状,面部形状等进行判断。
决策树的学习过程
决策树学习的第一步:必须决定在根结点使用什么特征,这是决策树最顶端的一个节点,将其拆分为左,右子树,进行判断;第二步是,只关注决策树的左(右)侧的分支,决定将哪些节点放在那里,再次决定使用什么特征进行区分;第三步是,判断完左(右)侧分支后,继续判断右(左)侧分支,继续选择一些特征来进行拆分。必须要在算法的各个步骤中做出关键决定。
那么决策树学习的难点之一是我们如何去选择在每个节点上使用哪些特征进行拆分呢?
如果我们的这个特征选择的好,那么可能一次分类就可以预测成功,如果特征选择的很差,我们可能会使得决策树的深度过大,在这里涉及到了纯度的问题,若数据左右两个子集都是完全纯的,只有一个类别就可以区分,比如说DNA,如果可以得到一个高度纯净的例子子集,就可以预测且大部分是对的,纯度问题在后面有介绍。
决策树学习的难点之二是我们什么时候停止分裂呢?
我们应该在什么时候才不会继续向下分裂,认为是预测成功呢?第一种情况是当我们的决策树的节点为100%是一个类别的时候;第二种情况是当分裂节点,导致树超过我们设置的最大高度时;第三种情况是优先级分数的改进,当该分数低于某个阀值时;第四种情况是当一个节点的示例的数量低于某个阀值时。
下面介绍纯度这个概念,如何通过纯度来选择每个节点上应该用哪个特征进行拆分。
纯度
如果都是单一的类别,那么会说这个是很纯的,介于两者之间如何去量化这组例子有多么纯呢?
我们引入熵的概念,熵就是衡量一组数据不纯程度的指标,如果这个例子全是活着全不是这个类别,则熵为0,熵为1是最不纯的。我们可以这样理解,熵越大,混乱程度越大,纯度越低。
一般H代表熵。
假如说p1代表猫的这个类别的比例,那么这个类别的熵为
熵值越接近1,代表越不纯,熵值越接近0,代表越纯。
选择拆分信息增益
在构建决策树时,我们需要决定在节点上拆分哪个特征的方式选择最能减少熵的或者说是选择可以最大化纯度的。
我们把熵的减少称为信息增益,意思是信息增益越大,代表熵的减少越多。
我们选择几个特征查看熵值进行比较,不如我们可以对其进行加权平均值,就是以数量占比为权重的加权平均。
最后我们计算信息增益为根结点的熵减去子结点的熵,也就是分之前的熵减去分之后的熵。
具体的实现过程如下:
其中算出来的0.28,0.03,0.12为信息增益,是衡量由于分裂而导致树中熵减少的,其中H(0.5)是指根结点的熵,所以在其中我们应该去选择第一个特征进行拆分,因为第一个特征的信息增益最大,混乱程度减少多少也就是说信息的纯度提高多少,就是信息增益。
熵小的是要选择拆分特征,信息增益如果太小就没有必要继续拆分,避免树太肥胖,防止过拟合,所以需要两个指标。
以下是我们的信息增益的公式:
整合
如上述信息增益,信息增益标准可以决定如何选择一个特征进行分割一个节点。
而我们决策树的学习过程变为:
第一步,计算所有可能特征的信息增益,选择拆分的特征,提供最高信息增益。
第二步,拆分为两个子集,并创建树的左右分支,并将训练示例发送到左侧或者右侧分支,具有取决于该功能的值。
第三步,继续在树的左右分支去重复拆分过程。
停止的条件为:当一个节点100%是单一类或者熵为0时;或者拆分节点导致超过设置的最大深度;或者额外的信息增益小于阀值;或者示例的数量低于阀值。其中的阀值是分类的界限。
信息增益太低,则放弃细分。
决策树的学习过程也可以看作是一个递归算法,构建决策树的方式是构建其他较小的决策树在左右的子系,如下图所示:
PyTorch
PyTorch是一个开源的深度学习框架,用于构建神经网络。它是基于Torch的Python版本,支持GPU加速和自动求导。它由Facebook的人工智能研究团队开发,使用Python编写,易用、灵活、功能强大。它常用于图像识别、自然语言处理等机器学习应用程序
pytorch中的tensorboard基本使用
图片的一些信息存储到本地的logs文件夹中
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")
其中writer这个类有add_scalar这个方法,里面可以传递scalar_value,global_step还有tag等参数,其中scalar_value是指我们平时遇到坐标轴的y轴,而global_step是指我们平时遇到坐标轴的x轴
如输出一个y=x的图像,可以这样:
for i in range(100):
writer.add_scalar("y=x", i, i)
writer.close()
在终端输入:
tensorboard --logdir=logs
会生成一个页面链接:
如果更改端口,可以这样做:
tensorboard --logdir=logs --port=6007 更改端口
如果想输出y=2x的图像,则代码变为
for i in range(100):
writer.add_scalar("y=2x", 2*i, i) #第二个参数为y轴,第三个为x轴
writer.close()
pytorch的transforms的基本使用
我们可以向tensorboard中去生成图像,比如一些函数图像,同样我们也可以向tensorboard中去添加我们的图片,具体有一下几种,我们向tensorboard中去添加图片的时候,需要将我们的图片转化为tensor类型。
具体代码如下:
# 1 转为totensor
img = Image.open('images/pytorch.jpg') #pil
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor.__call__(img) # totensor
writer = SummaryWriter('logs')
writer.add_image('img_tensor', img_tensor,global_step=0)
调用transforms中的Totensor方法,将这个方法实例化,并且调用其中的__call__方法,将图片传递过去,转化为totensor类型,最后将此图片传入tensorboard。
正则化代码如下:
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 均值 标准差
img_norm = trans_norm.forward(img_tensor)
# print(img_norm[0][0][0])
writer.add_image('Normalize', img_norm, 0)
输入均值和标准差,调用forward方法,将正则化的数据输入到tensorboard中。
我们还可以去改变图片的大小,我们可以resize图片的大小,有两种实现的方式,第一种实现的代码如下:
trans_resize = transforms.Resize((512, 512)) # 创建这个类实例化,变为512*512
img_resize = trans_resize.__call__(img) # 调用方法,将PIL图片传入,变为512*512 img PIL ->resize ->img_resize PIL
# print(img_resize)
# img_resize PIL -> totensor -> img_resize ToTensor
img_reszie = trans_totensor.__call__(img_resize) # 需要变为totensor类型,进行转化
writer.add_image('Resize', img_reszie, 0) # 输出到transboard
创建Resize实例化,将要改变图片的大小输入上去,接下来调用其中的__call__方法,将图片传入,最后需要将改变的图片转为tensor类型,传入tensorboard即可。
第二种方式,我们可以利用transforms的Compose来简化代码,代码如下所示:
trans_resize2 = transforms.Resize(512) # 输入一个参数,代表最短的那个边输出的像素点数量
# 上一个的输出是下一个的输入 PIL -> PIL ->tensor,第一个输入的是PIL,输出也是PIL,只是size变了,接下来将这个输出
# 作为totensor的输入,变为tensor类型,最后输出到transboard
trans_compose = transforms.Compose([
trans_resize2,
trans_totensor
])
img_reszie2 = trans_compose(img)
writer.add_image('Resize', img_reszie2, 1)
我们首先实例化,接下来调用transforms中的Compose方法,上一个的输出就是下一个的输入,我们可以在Compose中去先将图片改变大小,在去转变为tensor类型,可以简化我们的代码。
我们还可以去对我们的图片进行随机裁剪,调用RandomCrop方法,具体代码如下:
trans_random = transforms.RandomCrop([500,1000]) # 进行随机裁剪大小
trans_compose2 = transforms.Compose([
trans_random,
trans_totensor
])
for i in range(10):
img_crop = trans_compose2(img)
writer.add_image('RandomCrop', img_crop, i)
将随机裁剪的大小输入,之后进行随机裁剪,并进行转化为tensor类型,最后我们在代码中,可以让图片随机裁剪10次,进行一个for循环,将每一次的裁剪输入到tensorboard中。
DataLoader的基本使用
之前学习过Dataset,我们可以继承和重写这个类已实现自己的数据类,可以去定义__getitem__和__len__这两个函数,DataLoader时Pytorch处理模型输入数据的一个工具类,在数据集上提供单线程和多线程的可迭代的对象,我们可以利用DataLoader进行神经网络的训练。
引入相关模块:
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
准备测试数据集,并转化为tensor类型:
test_data = torchvision.datasets.CIFAR10(root='./dataset',train=False, transform=torchvision.transforms.ToTensor())
创建一个用于测试的数据加载器,将test_data中的数据按照batch_size进行批量的加载,在每个epoch开始时对数据进行打乱,在加载数据时不使用多进程,并且在处理不足一个batch的情况下丢弃最后一个不完整的batch。
test_loader =DataLoader(dataset=test_data,batch_size=64,shuffle=True,num_workers=0,drop_last=True)
其中DataLoader中的各个参数的意思为:
dataset为加载时数据集对象
batch-size为每批中包含的样本数量,在这里为64,就是每批中包含64张图片,为8*8的形状
shuffle指每一轮开始前是否对数据进行打乱,默认为True,就是进行打乱,若为False,则不进行打乱
num_workers是数据加载的子进程数目,在这里为0,指不使用多进程
drop_last指如果数据的样本不能被batch_size整除时,是否丢弃最后一个不完整的batch,在本例中为True,就是保证每一步都是64张图片,若最后没有被整除,则舍弃,若drop-last设置为False,则为不舍弃,在最后一步会显示不足batch_size的图片。
之后,我们将图片写入tensorboard中,代码如下;
writer=SummaryWriter('logs')
for epoch in range(2):
step = 0
for data in test_loader:
imgs,targets = data
writer.add_images(f'Epoch:{epoch}',imgs,global_step=step)
step += 1
writer.close()
这里的epoch指,输入两次,step记录每一步。
结果如下:
卷积操作
了解了通过卷积输入和卷积核,如何算出卷积输出。
引入相关的模块:
import torch
import torch.nn.functional as F
输入卷积输入和卷积核:
input = torch.tensor([[1, 2, 0, 3, 1],
[0, 1, 2, 3, 1, ],
[1, 2, 1, 0, 0],
[5, 2, 3, 1, 1],
[2, 1, 0, 1, 1]]) # 输入
kernel = torch.tensor([[1, 2, 1],
[0, 1, 0],
[2, 1, 0]]) # 卷积核
如果只是这样操作的话,大小就是二维的:
print(input.shape) # torch.Size([5, 5])
print(kernel.shape) # torch.Size([3, 3])
我们需要计算卷积输出,需要对张量input进行reshape重塑操作,变为四维张量,代码如下:
input = torch.reshape(input, (1, 1, 5, 5))
kernel = torch.reshape(kernel, (1, 1, 3, 3))
其中重塑后的第一个数字是指通道数,第二个数字是指批次大小,第三个数字是指图像高度,第四个数字代表图像宽度,通道是指图像的特定信息的组成部分,分别是红色通道R,绿色通道G,蓝色通道B,每个通道对应着图像中该颜色的信息,为RGB彩色模型。
我们可以对该输入进行输出,代码如下:
output = F.conv2d(input=input, weight=kernel, stride=1, padding=0) # stride为步长,output为卷积后的输出
print(output)
output2 = F.conv2d(input=input, weight=kernel, stride=2, padding=0) # 步长为2
print(output2)
output3 = F.conv2d(input=input, weight=kernel, stride=1, padding=1)
print(output3)
我们可以调用卷积神经网络函数中的conv2d,是二维卷积操作的类,输入卷积输入和卷积核,还有stride代表步数,padding代表填充上下左右几个宽度,为数字或者元组,在输入的边界周围添加零值填充的层数,以控制输出特征图的大小,默认为0。
输出为:
神经网络卷积层
我们可以利用pytorch框架去搭建一个简单的卷积神经网络,并数据集中的图片输入到神经网络后进行处理,结果在tensorboard中显示。
代码如下:
首先进行导入相关的模块
import torch
import torchvision
from torch.utils.data import DataLoader
from torch import nn
from torch.utils.tensorboard import SummaryWriter
我们需要用到的DataLoader和卷积神经网络,tensorboard
接下来,导入我们下载好的CIFAR10数据集,其中,我们这里是测试数据集,将其转化为tensor类型。
dataset = torchvision.datasets.CIFAR10(root='./dataset', train=False, transform=torchvision.transforms.ToTensor(),
download=True)
在pytroch中创建一个数据加载器,传入数据集,并设置每批包含64个样本数据,方便去遍历数据集,从中获取每一小批量的样本用于模型的训练或者评估。在这里没有设置数据是否随机打乱,多进程数据加载等。
dataloader = DataLoader(dataset=dataset, batch_size=64)
之后去搭建一个卷积神经网络,其中首先进行初始化,集成父类的属性,并且调用卷积神经网络的二维卷积层的类,将卷积输入通道设为3,卷积输出的通道为6个,卷积核大小为3*3,步长为1,填充为0,并且定义forward前向传播过程,返回x。在这一个简单的例子中,只包含了一个卷积层,输入x经过卷积层self.conv1进行处理后,输入特征图x,然后返回x,可以用于图像分类等任务。
class NetWork(nn.Module):
def __init__(self): # 初始化方法
super(NetWork, self).__init__() # super继承父类的属性
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1,
padding=0) # 通道为3,卷积输出通道为6个,卷积核大小为3*3,步长为1,填充为0
# 这样神经网络中有了卷积层
def forward(self, x): # 前向传播
x = self.conv1(x)
return x
紧接着创建NetWork类的一个实例,实例化NetWork,调用NetWork(),赋值给变量network。
network = NetWork()
最后进行一个简单的训练循环,去遍历数据加载器,每次去迭代一个batch的数据,将图像数据输入到神经网络network中进行前向传播,得到输出结果output,并将图像输入到tensorboard中,在前面我们将卷积的输出通道设为6个,在这里对结果output进行重塑,将通道调整为3,最后将卷积输出后的图像输入到tensorboard中,代码如下:
writer = SummaryWriter('logs')
step = 0
for data in dataloader:
imgs, targets = data
output = network.forward(imgs)
# print(imgs.shape) # torch.Size([64, 3, 32, 32]) 卷积神经网络前,第一个是batchSize大小,第二个是通道数,第三个是矩阵的高,第四个是矩阵的宽
# print(output.shape) # torch.Size([64, 6, 30, 30]) 卷积神经网络之后
writer.add_images('input', imgs, global_step=step) # 卷积神经网络输入前
output = torch.reshape(output, (-1, 3, 30, 30)) # 重塑,因为卷积神经网络输入后为6个通道,要改变
writer.add_images('output', output, global_step=step) # 卷积神经网络输入后
step += 1
writer.close()
在tensorboard中的显示如下: