• 浙大人工智能算法与系统课程作业指南系列(一)口罩识别的数据处理部分


    浙大人工智能算法与系统课程作业指南系列(一)口罩识别的数据处理部分

    写在前面:

    我原来本科的时候并不是计算机专业的,属于跨考到计算机这个专业的。在本科期间也就接触了点C的基础,然后因为是传统工科,所以Matlab和Fortran也写过一些代码,趁着考完研的暑假继续学习了一下C++,顺便拿B站上能搜到的吴恩达的网课补了补机器学习的网课,然后拿C++简单写了个全连接网络。多多少少有一点基础以后开学了,然后就摊上这一门尴尬的课······你敢信这门课让你在两个月左右的时间学Pytorch并且完成CV、NLP还有RL的全家桶作业么······

    我感觉我已经算是有一点点基础了,但是做作业的时候还是感觉恶心的要死,那更别提其他根本不怎么写代码的专业了(今年这门课就好几个专业一块上,不管以后用不用AI都要做这个AI作业┓( ´∀` )┏),所以在这里打算把做作业过程里面的一些不清楚的东西记录在这里,以供广大跨专业的浙大研一计算机 倒霉蛋 新生们参考。

    这篇博客主要是从代码功能层面上帮各位萌新们做个简单指南,并不会(或者很少)涉及到原理上的内容(比如说我肯定不会分析Adam的优化算法是咋回事),所以如果是想看原理上的内容,还是去其他大佬们的博客里转转吧······

    由于我这也是第一次写博客,肯定会在格式还有内容分配上有很多的问题,还请多多担待。如果里面有什么知识性的错误,还望评论区大佬们不吝赐教,我在看到以后会尽快修改。

    啊,还有还有,这篇博客如果有需要的话可以转载,但是尽量别转CSDN吧,好了就这些

    1. 前提要求:如果是纯粹的小白,就是那种只会C,从没用过python;或者是python会用但是机器学习或深度学习的基础一点都没有的,那么看这个可能会有一点点费劲,所以在看这一篇博客之前,最好先做到以下三点:

      • 要了解Python的基础,高大上的操作不会,但是最起码Python的一些基本的数据结构(list,dict,tuple)是要知道并且大致会用的,并且怎么导入一些模块、包还有库什么的也是要会的呢

      • 要有一些机器学习和神经网络的基础,不要求会手推梯度下降啥的,至少对于一个CNN(卷积神经网络)的基本结构要有一定的了解

      • 该课程在网站平台上提供的notebook文件要从头到尾看过,并且都实际运行过,至少要知道自己要实现什么功能,还要知道自己哪一部分没有看懂,然后好在这里找找该咋办

    2. 推荐材料:这门课的所有的代码推荐是使用Pytorch框架,当然了平台也允许你使用其他的比如Keras和TensorFlow,还有一个华为的AI平台。因为现在好多的新的论文里面代码都用Pytorch写嘛,所以我这里的东西都是基于Pytorch写的,如果是想用tf的,那对不起啦我帮不了忙呢。这里推荐一些Pytorch入门的资料吧~

      • Pytorch入门的书籍:首推《Deep Learning with Pytorch》这本书,虽然是全英文的,但是很好读,这本书会教你快速入门Pytorch,了解Pytorch里面的一些基本语法以及数据结构,并且会教你做一些简单的识别项目。如果只是想先简单入下门的话,就读前八章就好咯~(资源在知乎以及直接在浏览器上搜索都是找得到的哟)

      • Pytorch的入门视频:比较推荐Pytorch官方提供的60分钟的Pytorch教程,总之先看一下具体要怎么建立一个向量(Tensor)以及怎么加载数据什么的

      • Pytorch的官方文档:这个就去官网看就好了,在这篇里面可能查文档还少一点,等到后面的那个NLP的作业,可能官方文档就要经常查了。啊说句题外话,虽然这个课的作业很坑,但是做完作业以后,别的不说,查文档的能力会提高不少23333

    好了下面开始我们的正式介绍吧。啊还有还有,因为写这个文章的时候,课程网站的作业界面已经进不去了,所以我只能凭着印象按顺序介绍了,如果与实际的notebook上的顺序不一样的话,就麻烦大家慢慢找了www

    1. 数据处理部分

    在数据处理部分,我们先抛去前面所有的和什么图片大小修改、图片显示、图片翻转等乱七八糟的东西不谈,我们直接找到下面的这个函数:

    def processing_data(data_path, height = 224, width = 224, batch_size = 32, test_split = 0.1):
     
         transforms = T.Compose([
             T.Resize((height, width)), #图片尺寸的重整
             T.RandomHorizontalFlip(0.1),  # 进行随机水平翻转
             T.RandomVerticalFlip(0.1),  # 进行随机竖直翻转
             T.ToTensor(), #将图片的数据转换为一个tensor
             T.Normalize([0], [1]) #将图片的数值进行标准化处理
         ])
    
         dataset = ImageFolder(data_path, transform = transforms)
    
         train_size = int((1-test_split) * len(dataset))
         test_size = len(dataset) - train_size
    
         train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])
         train_data_loader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)
         test_data_loader = DataLoader(test_dataset, batch_size = batch_size, shuffle = True)
    
         return train_data_loader, test_data_loader
    

    为什么要直接找这一段代码,主要是因为只有这一段代码有用,其他的图片处理的那些代码都是让你知道数据集里面的数据大致上是长什么样子,对于后面你去做作业训练模型是基本没什么用的。像我一样的各位从没接触过Pytorch的小白们一看到这个肯定是一脸懵逼,天啊这里面都是啥鬼玩意,别急别急,咱们一点点来说。

    首先是这个函数的第一个部分:

     transforms = T.Compose([
     	T.Resize((height, width)),
     	T.RandomHorizontalFlip(0.1),
     	T.RandomVertivcalFlip(0.1),
     	T.ToTensor(),
     	T.Normalize([0], [1])
     ])
    

    这段代码主要是定义了图片处理的方式,就是说,如果你的程序读入了一张图片,那么程序应该怎么处理这张图片呢?你可以设置重新设置大小(Resize)、进行图片的随机翻转(Random巴拉巴拉)、图片转化为向量(ToTensor)还有图片的标准化处理(Normalize)。

    重新设置大小的函数其实很容易看懂啊,就是根据你输入的那个想要转化的图片的尺寸,把图片重新进行处理嘛,比如说你的模型训练的时候,想要图片的尺寸都是160x160,但是如果数据集的所有图片都是224x224的,那你有两种解决办法:一个是你自己手动在画图里面图片一个一个转化了(怕不是个憨憨),另一个就是直接调这个Resize方法。

    然后是图片的随机翻转,就是两个Random巴拉巴拉的函数,这两个函数······我觉得看函数名字就知道是要干啥吧······功能就是函数名,然后后面的参数是概率,做随机的翻转主要是为了增强我们的数据,就是万一我们的模型在实际使用的时候读到一些让人头皮发麻的倒置数据的时候能够识别出来(虽然我觉得没啥必要,┓( ´∀` )┏)

    总之这个块块里面的前三个都不用太在意,但是接下来的两个是重头戏,千万不能嫌麻烦不看了,因为这个对于之后自己独立去做一些图片识别的项目是特别重要的吖!

    在介绍后面两个函数之前,我们不可避免地要介绍一下,什么样子的数据才能被Pytorch里面建立的神经网络模型正确地读出来,并且在模型里面正常地进行运算。

    如果各位萌新同伴们接触过opencv的话,那就应该知道我们在读入一张图片的时候,不考虑文件的文件头等东西的话,我们得到的数据应该是矩阵,矩阵的每一个元素记录了图片在某个位置的像素值。对于一个我们常见的.jpg格式的图片,它是一个三通道的RGB图片,也就是说我们用opencv读出来的话每个图片的矩阵维度信息应该是这个样子的:

    (height, width, channels)

    其中呢,height和width不用我多说吧,就是尺寸嘛,然后这个channels就是对应的R、G、B的颜色通道上的像素值(虽然opencv实际上读进来是BGR的鬼顺序,┓( ´∀` )┏)

    但是!!!Pytorch表示我不吃你这一套(好任性),首先你opencv还有PIL读到的图片都是numpy数组,而Pytorch它只处理tensor;而且事实上为了能够尽可能的加快矩阵运算的速度,Pytorch的模型在读取图片的时候,要求你读的图片都要是(channels, height, width)的尺寸,你要是对图片不处理就直接放到模型里面,虽然倒是能跑,但是那个结果emmmmm,算了算了。除此之外,神经网络在进行训练的时候,受到激活函数等一系列的限制,如果能把输入数据的范围调整在[-1, 1]或者[0, 1]之间那是坠吼的,然而,图片里面的像素值的范围都是[0, 255]······为了能够让你的模型能够正常训练然后跑个还算可以的结果,你必须要把图片的像素值都除以255,强制转换为[0, 1]之间。

    萌新们看到这里可能会觉得,哇我才刚入门,咋写这一大堆的处理函数啊Or2(众所周知,Or2比Orz的屁股更翘www),别急别急,这个函数ToTensor把上面的要做的几项处理全都给你搞定了哟!所以你可以认为,当经过ToTensor()这个函数之后,图片就已经变成(channels, height, width)的数值在[0, 1]之间的tensor了,是不是很神奇?当然了,具体的原理还是建议大家看一下源码,篇幅有限我就不贴了。

    接下来我们看一下这一块还剩下来的最后一个函数,Normalize([0], [1])。如果你看过吴恩达的那个在Coursera上的那个很简单的机器学习的课程的话,你应该会了解,当我们的输入数据的尺度差的很大的时候(比如一个在1000量级,一个才0.1量级),模型的训练会很不稳定,因此我们需要尽可能把数据的范围缩放到同一个尺度上。萌新可能就会问啦,这个图片不是已经缩放到[0, 1]之间了嘛,那只能说你太天真,万一除以255之前,数据像素值都是0和1呢?缩放后基本都是0,然后来了一张全都255的图,缩放完全是1,看你咋搞,┓( ´∀` )┏。所以我们需要使用数据的均值和方差来进行缩放。实际上Normalize这个函数的参数写全了应该是这样(只考虑我们要用的部分哈):

    Normalize(
    	mean = [xxx, yyy, zzz],
    	std = [aaa, bbb, ccc],
    )
    

    mean就是均值,std就是标准差,在调用这个函数的时候,实际上做了一个x = (x - mean) / std的处理,就把图片的像素值的尺度搞到一个差不多的范围内了。

    然后呢我们接着看processing_data函数的剩下的部分(写了这么久才刚到这里,我都快要哭了)。接下来的一行代码里面一个很关键的函数是ImageFolder(data_path, transform = transforms)。第二个参数就是我们刚刚定义的那个数据转换方式啦,但是前面这个参数还有这个函数的整体功能是啥吖?好吧这又是一个大坑。我估计这篇文章要写完可能要将近5000字,┓( ´∀` )┏

    先看一下这个函数的功能吧。在看完课程提供的notebook的代码之后(所以还没看完课程提供的notebook的赶紧去看,哼╭(╯^╰)╮),可能各位萌新小伙伴们都会感到疑惑:欸?数据在训练时候用的标签都是什么时候加进去的呀?我咋没看着呀?emmmmm,实际上标签就是在这个函数里面加进去的。这个函数的功能很别致,他会假设你已经把数据进行了分类,并且分别放在了不同的文件夹里面,这样这个函数就会根据数据图片所在的图片的文件夹位置,自动为每一个图片添加标签。可能这里光说还是不太清楚,那我就给各位萌新伙伴们举个栗子吧~

    我们就按照notebook提供的数据集为例吧,在我们的notebook里的datasets的文件夹里面一直往里找,我们会找到image这个文件夹,打开以后里面有mask和nomask两个文件夹,分别存着有口罩和没口罩的图片,然后呢,这个ImageFolder函数,就会按照image这个文件夹里面的文件夹的顺序,为文件夹里的文件设置好标签。也就是说,ImageFolder函数会将所有mask文件夹里面的图片标签设置为0,所有nomask文件夹里的图片标签设置为1。验证的话在notebook下面是有示例的,虽然当时看的时候没看明白。这也解释了为啥notebook的说明上面罗里吧嗦地说了一大堆,它就是想让你在传入data_path的时候,就写到image这一层就行了,别再往下写了。(所以,data_path在notebook里面有过多次改动,唯一要记住的是那个末尾是/image的那个,别问我为啥不直接粘到这里,因为数据集里有坑,之后再说啦)

    接下来我们来看一下这个代码段:

    train_size = int((1-test_split) * len(dataset))
    test_size = len(dataset) - train_size
    
    train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])
    

    这个很简单,就是把之前我们得到的那个加好标签了的整个数据集,按照我们给出的各个集合中的元素个数,随机分配成训练集和验证集。

    然后又是一个劝退萌新的大坑,┓( ´∀` )┏。就是最后的一段代码:

    train_data_loader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)
    
    test_data_loader = DataLoader(test_dataset, batch_size = batch_size, shuffle = True)
    

    在介绍这段代码之前,我们还是要回到之前介绍Pytorch的输入模型的数据的结构上面>_<

    我们之前说过,在Pytorch中的图片要调整成(channels, height, width)的尺寸,可能你觉得这就完事了,但是事实上并没有。Pytorch为了进一步提高你电脑硬件的使用效率,它从不满足于一次读入一个图片,它会要求你传入的数据要能够一次传入多个图片。萌新伙伴们可能就又要懵逼了,这咋传呀(╬◣д◢)。其实很简单,想一想我们的矩阵,如果一个二维矩阵是一个图片,那么我好几个图片叠在一起,不就是好几个图了嘛,好几张图叠在一起就看起来就像一个立方体,也就是说你要在矩阵里再多加一维嘛。同理,对于一个三通道的tensor来说,想要好几张图放一起,在最开头那里加一维就好啦。

    在深度学习里面,这一坨的图片的专门术语叫批(batch)。所以实际上,读入到一个模型中的图片尺寸维度应该是这样的:(batch_size, channels, height, width)
    batch_size是你一批里面有几张图片(可以是1哟,传一张其实也是没事情的,但是这一维必须要有)
    剩下的参数就还和之前介绍的每一张图片的维度是一样的,就不多BB了。

    然后呢,DataLoader这个函数就是替你完成 “将好几张图放在一起组成一个batch” 这件事的,说清楚了图片怎么组,那这个函数的参数就不用我再多说了吧,已经很清楚了吖~

    好了好了,图片处理部分终于结束了,我的手都要废了┓( ´∀` )┏。别看这个函数代码很少,但是对于之前根本没有接触过Pytorch的萌新小伙伴们(比如我),里面坑太多了,真的查资料就查的要吐血了。接下来的一部分,看情况为大家介绍一下神经网络模型和训练模型部分的一些坑,然后如果内容太多,可能还要再多分一节出来,和大家聊聊这个作业里面关于数据集还有代码里面的一些坑,那这篇就到这里,我们下一篇再见咯~

    参考内容:

    1. Pytorch基础:《Deep Learning with Pytorch》
    2. ToTensor相关:https://www.cnblogs.com/ocean1100/p/9494640.html
    3. ImageFolder相关:https://www.cnblogs.com/wanghui-garcia/p/10649364.html
  • 相关阅读:
    云时代架构阅读笔记十一——分布式架构中数据一致性常见的几个问题
    云时代架构阅读笔记十——支付宝架构师眼中的高并发架构
    云时代架构阅读笔记九——Disruptor无锁框架为啥这么快
    云时代架构阅读笔记八——JVM性能调优
    lightoj 1024 (高精度乘单精度)
    lightoj 1023
    lightoj 1022
    codeforces 260 div2 C题
    codeforces 260 div2 B题
    codedorces 260 div2 A题
  • 原文地址:https://www.cnblogs.com/JacobDale-TechLearning/p/14135328.html
Copyright © 2020-2023  润新知