第3章 过程建模问题 3.1问题描述 3.1.1基准数据集 通过前两章的阅读,读者已经具备使用代码编辑器研究元学习算法的能力。从本章开始,章节内容的安排将在一定程度上进行升级——在问题描述阶段,即开始与代码注释结合。MAML模型的原始代码主要采用了2个数据集,即MiniImageNet数据集和Omniglot_resized数据集。 MiniImageNet数据集是Google DeepMind团队从ImageNet数据集中抽取的一小部分,大小约3GB,共有100个类别,每个类别都有600张图像,共60000张(均是以.jpg结尾的文件),而且图像的大小并不是固定的。 Omniglot_resized数据集是Omniglot 数据集的修正版,包含来自50个不同字母的1623个不同手写字符,构成1623个类别,其每个类别有20个样本,每个样本大小为28×28像素。原始代码采用的这2个数据集也是小样本学习常用的基准数据集。 制作MiniImageNet数据集,对普通研究人员或者开发者是一个友好的选择。ImageNet数据集可以称得上深度学习革命的加速器,这是一个知名度非常高的开源、海量数据集。常见的目标检测、识别等算法,在完成设计后,通常需要在ImageNet 数据集1000个类别的数据上进行训练以及验证。新模型的框架一般也需要先基于ImageNet数据集进行预训练,根据预训练模型做基线(baseline)评估。然而,这个数据集全部下载,大概有100GB,训练过程对硬件要求也非常高,即使采用很多块高端显卡并行训练,也要花费好几天的时间。元学习模型进行了任务构建与联合训练,相比于深度学习,元训练过程对硬件要求要高出很多。因此,在元学习模型编程调试、算法应用改进中,建议采用MiniImageNet数据集代替ImageNet数据集。 要理解样本产生过程,就需要先了解数据集的结构。以MiniImageNet数据集为例,其根目录为miniimagenet,里面有4个子目录。第一个子目录是images,所有的图像都存在该子目录中; 其余3个子目录分别是train.csv、val.csv、test.csv,分别用于保存训练集、验证集、测试集的标签文件。文件格式.CSV的英文全称为Commaseparated Values,即逗号分隔值,主要用于在程序之间转换表格数据。这类文件以纯文本形式存储表格数据,也可以转换为Excel表格。 MiniImageNet数据集每个CSV文件之间的图像以及类别相互独立,共60000张图像、100个类别。作为元学习领域的基准数据集,标签文件并不是从每个类别中采样的。数据集的64%用于训练、16%用于验证、20%用于测试。换言之,train.csv子目录中包含64个类别的38400张图像,val.csv子目录中包含16个类别的9600张图像,test.csv子目录中包含20个类别的12000张图像。 MAML模型的原始代码中提供了该数据集制作的模块代码,在3.1.3节会进行深入解读。理解数据集制作过程细节,再结合第2章的研究,可以完整地理解任务样本的产生过程。 3.1.2图像尺寸调整 与MiniImageNet数据集的处理方式不同,Omniglot_resized是对Omniglot中的图像尺寸调整后得到的数据集。图像尺寸调整也是服务于元学习过程建模的。Python程序读取图像后,会将其转为矩阵向量的形式。在深度学习及元学习研究中,输入向量维数决定了输入层的网络节点数。因此,有必要统一输入向量的维度。通过一个常规的几何变换算法,就可以把Omniglot中的图像尺寸调整到相同的尺寸,以便于在元学习过程中进行标准化处理。在MAML模型的原始代码中,也提供了数据集Omniglot图像尺寸调整的模块,相关文件名为resize_images.py,详细代码如下。 from PIL import Image #从PIL库导入Image类,该类是PIL库中用于图像处理的函数 #在Python中,需要使用PIL(Python Image Library)库处理图像 import glob #导入glob模块,此模块可用于查找符合特定规则的路径名 #glob是global的缩写,表示在Windows系统中进行全局搜索 #常用函数有glob.glob()、glob.iglob()等,后者每次只能获取一个路径名 image_path = '*/*/' #准备接收搜索到的图像路径,*用于打包位置参数 #*和**属于glob模块,是很灵活的符号,用于解包参数、扩展序列以及对字典和集合进行操作等 #不同的是,**侧重于解包关键字参数,并将这些参数打包成一个字典 all_images = glob.glob(image_path + '*') #完成对所有图像的全局搜索,并将其位置参数打包成一个元组all_images #glob.glob()函数可以同时获取所有的匹配路径,并将这些路径返回至一个列表中 i = 0 #分类控制指标i,其初始值为0 for image_file in all_images: #对图像路径列表中的所有文件做循环处理 #all_images是图像路径列表 im = Image.open(image_file) #打开文件image_file,并以矩阵向量形式赋值给im,im是image的简写 #Image.open()函数属于PIL库的Image模块,用于打开图像 im = im.resize((28,28), resample=Image.LANCZOS) #调用im.resize()函数,以调整图像尺寸 #缩放过程中需要重采样 #im.resize()函数的用法是im=im.resize(目标尺寸,重采样滤波器) #resample,顾名思义,是重采样 #重采样过程中消除了锯齿噪声 #Image.LANCZOS是重采样滤波器,可以抗锯齿噪声 im.save(image_file) #调用im.save()函数,保存修改 i += 1 #分类控制指标i的值加1 if i % 200 == 0: #如果i除以200的余数等于0,即i=200、400、600等 #每10个类作为一个小样本单元,每个类有20个样本,故而每个单元有200个样本 print(i) #输出对应的i值 3.1.3知识获取过程 所谓知识,其本质是一种元优化机制。这种知识的成功获取,具体体现在模型拥有了自动调整超参数的能力。在获取已有知识的基础上,模型可以快速学习新的任务。对于深度学习训练时间过长、参数微调难、新任务需要重复训练的问题,元学习模型设计的知识获取过程提供了一个全新的解决方案。知识获取的过程就是构建数据集的过程,即先生成一批服从某种分布模型的学习任务,然后通过对这批任务进行联合训练,可以得到一组较好的超参数,并赋予模型自主调参的先验知识。知识的自主应用过程对新的学习任务不用从头开始训练模型,只需要经过少数几次梯度下降,就可以完成超参数的自主微调,从而快速完成新的学习。元学习网络在现有神经网络结构的输入层、隐含层、输出层之外,新增了meta层,用于实现知识的获取过程。其获取的知识在整个学习过程中被全程应用,如图31所示。 图31元学习模型的知识获取过程 图31中,θ是元学习模型的参数,x和y分别是模型的输入和输出,θL表示任务样本上的损失函数梯度,w、r分别为模型权重系数及其对应的残差值(residual)。元学习的知识获取是模拟人类学会学习的过程,从而让机器学会学习。传统深度学习研究的模式是获取特定任务的海量数据集,并利用该数据集从头开始训练模型。这一训练过程构成深度神经网络的学习过程,与人类的学习过程相去甚远。人类在学习过程中,能够利用过去的经验,快速学习新任务。元学习的算法思想和训练过程主要借鉴类似的建模思路。对于每个不同的学习任务,不再需要重新训练模型。通过多次梯度下降,实现模型参数的自主微调,即可快速适应新任务。 3.2建模思路 3.2.1图像加载模型 知识获取始于对数据的学习,所以知识获取的第一步就是数据集加载与任务样本的输入。元学习模型的输入输出问题将在第4章进行研究,本节重点解释图像数据加载模型的建模思路。 图像加载模型需要考虑计算机的配置,因为图像尺寸决定进行运算处理时要求的配置。尺寸调整的基本目标是图像尺寸小于500×500像素,很多预训练模型甚至要求图像尺寸小于300×300像素。基于3.1.2节的代码注释,可按照如下步骤获取Omniglot_resized数据集。 1. 下载Omniglot数据集 首先,为Conda配置下载工具wget,输入命令pip install wget即可(注意,pip不可省略,否则会报错)。工具wget很小,只有10kB,一般在两分钟内可以完成下载和安装,如图32所示。 图32配置下载工具wget 在Python编程中,wget模块可以导入代码中,并用于下载数据集。顾名思义,wget=web+get,是通过网络下载。纽约大学助理教授Brenden Lake在GitHub网站上分享了专门用于Python的Omniglot数据集的下载链接。 本节的元学习模型代码调试主要使用其中的两个数据集——images_background和images_evaluation,Omniglot数据集下载的前置代码如下所示。 import wget url =\ `https://github.com/brendenlake/omniglot/blob/master/python/images_background.zip’ #请注意,字符串符号的标记不能用引号,虽然Word中显示为引号 #指定一个保存路径,指定路径的下载代码为 path="D:\mywgetdata" #请注意,字符串符号的标记不能用引号,虽然Word中显示为引号 wget.download(url, path) #调用wget模块的wget.download()函数 #下载链接为url,下载后保存路径为path 参考第2章的方法,采用上述命令,编写代码文件metadownload.ipynb,运行结果如图33所示。 图33images_background.zip的下载代码及运行结果 选中一个代码行,可以看到该行结尾有6个按钮,分别用于复制、上移、下移、上方插入行、下方插入行、删除。Python将这种代码行称为cell,这些cell操作为编程提供的很多便利条件,有助于提高编程效率。此处,复制是duplicate操作,即插入一行完全相同的代码。如果需复制代码行,则需选中代码行,单击右上角“小剪刀”后的复制按钮即可。 下载需要时间,请耐心等待,直到mywgetdata目录里出现images_background.zip。不要重复单击运行按钮,否则可能出错,因为是网络下载,涉及网络协议。 将上述代码中的url链接替换为 url = https://github.com/brendenlake/omniglot/blob/master/python/images_evaluation.zip 如图34所示,重新运行代码文件metadownload.ipynb,即可下载images_evaluation.zip,下载完成后,将在目录中出现images_evaluation.zip。当然,读者也可以直接单击链接,手动下载。 图34images_evaluation.zip的下载代码及运行结果 如果希望同时下载images_background.zip和images_evaluation.zip,则需要做一个TXT文档将两个链接都写进去。如果希望分别下载到不同的目录,也可以做一个TXT文档将两个路径写进去,感兴趣的读者可以去尝试。网络下载源是动态的,一段时间后,重新执行代码,可能会发现下载的两个压缩包均无法解压。此时尝试手动下载,会发现该下载链接的数据已经被上传人删除。幸运的是,还可以通过其他途径找到Omniglot数据集。 2. 对其子数据集进行解压 解压后,即可找到两个主要子目录images_background、images_evaluation。感兴趣的读者也可以尝试用代码完成解压,Python并没有提供unzip()方法,但通过二次调用zip()也可实现解压。 Step 3.创建目录data/omniglot,将Step 2得到的两个子目录复制到目录data/omniglot内。另一个数据集MiniImageNet的处理方法类似,下文不再赘述。 3.5.2节会重点介绍如何配置MAML模型代码调试环境Python+Pycharm。图像尺寸调整模型代码是已在3.1.2节注解的resize_images.py,该代码不必执行。如感兴趣,可在命令行窗口执行,这些内容将在3.3.1节演示。 3.2.2尺寸调整模型 3.1.2节对图像尺寸调整代码resize_images.py进行了较为详细的注解,其核心调整代码为 im = im.resize((28,28), resample=Image.LANCZOS) 主要是通过调用im.resize()函数,完成图像尺寸的调整。图像尺寸调整过程中,需要重采样(resample)。因此,im.resize()函数至少需要两个输入,第一个输入决定了经过调整后图像的尺寸,第二个输入决定了重采样时采用的滤波器。resize()函数还可以仅对感兴趣的区域(Region Of Interest,ROI)进行重采样,此时需要第三个输入,用法为resize(size,resample,box)。要进行调整的图像区域参数box,可以用4个坐标元组指定,核心调整代码改为 im = im.resize((300,200), resample=Image.LANCZOS, box=(0, 0, 150, 100)) #把(0,0)开始的150×100像素图像区域放大到300×200像素 resize()函数中的size表示图像的宽度和高度,单位为像素。这里采用的是 Image.LANCZOS重采样滤波器,它可以较好地处理锯齿噪声。Lanczos算法是匈牙利数学家Cornelius Lanczos在20世纪40年代建立的模型,这是一种用于计算矩阵特征值和特征向量的迭代算法,已经被广泛应用于科学和工程领域的大规模矩阵计算中。对于图像尺寸的调整问题,Lanczos算法的主要建模思路是通过正交相似变换,将对称矩阵变成对称三角矩阵,从而在很大程度上降低重采样过程中的计算复杂性。每个图像的Hessian矩阵都是一个对称矩阵。除了Lanczos算法,重采样的常用算法还有双三次插值(bicubic interpolation)、双线性插值(bilinear interpolation)、 最近邻插值(nearest interpolation)等。 调整图像尺寸之前,需要先打开图像,对应的核心代码为 im = Image.open(image_file) 用于打开图像的Image.open()函数的用法为open(filename,mode)。除了文件名filename,另一个输入指定了打开的模式mode,模式可以作为im的属性直接调用,调用格式为im.mode。该模型还定义了im的像素类型、深度信息和色彩空间RGB、HSV等。并可以借助代码行print(im, mode)运行输出。除了size和mode,format 也是图像的重要属性,用于表示JPEG、PNG等图像格式。 3.2.3空间插值模型 在深度学习模型发展的早期阶段,由于网络结构的限制,全连接层的输入维度是固定的,所以必须把输入图像进行归一化变换,统一为固定的尺寸。得到模型输出后,还可以再借助反变换,还原到原来的尺寸。这种早期的修正方法主要是为了适应卷积神经网络,但是随着对模型精度的进一步研究,科学家开始意识到归一化修正所带来的损失,空间插值模型由此应运而生。 事实上,只需要确保任意维度的图像都可以转换成固定的维度,就能够适应全连接层的输入,从而解除网络结构对输入图像尺寸的依赖。此外,还有一个建模思路是舍弃全连接层,直接构建全卷积神经网络。此时,输入图像的尺寸不受限制,因为卷积操作是一种类似滑动窗口扫描的处理方式。在元学习模型代码中引入调整问题,主要是考虑训练阶段的问题。模型假设训练集和测试集的大小一致,而且batch的读取方式也要求保持训练图像尺寸一致。 im.resize()函数可以应用的空间插值模型有很多种,除了3.2.2节中提到的最近邻插补模型、双线性插值模型、双三次插值模型和Lanczos插值模型,还有面积插值模型AREA,即根据像素面积相关性完成重采样。空间插值模型的运用从本质上改变了图像的特征细节,在元学习模型研究过程中要注意灵活运用。当元学习模型精度达不到预期效果时,可以考虑选择不同的空间插值模型,特别是元学习面临未知的新任务及其相关的陌生图像,空间插值模型的选择将直接决定训练结果的好坏。对于质量较低的大规模数据集,空间插值模型导致的差异可能会非常明显。然而,不能简单地通过少数几次实验论证空间插值模型的优劣。一般可以借助公开的数据集做初步的评估,然后结合实际应用效果,为对应的项目选择适合的空间插值模型。 3.3算法思想 3.3.1文件保存算法 在3.1.2节注解的代码中,尺寸调整的结果需要调用im.save()函数,以完成图像文件的保存,即 im.save(image_file) 本节主要关注代码实际应用过程中的文件保存算法。尝试在3.1.2节中注解代码文件resize_images.py的原始用法,如果遇到困难,进一步理解并修改即可。参考该代码公开的原始用法,文件处理和保存算法的完整实现步骤如下。 Step 1. 打开命令行窗口,输入命令“d:”,切换到保存数据集的磁盘空间,如图35所示。注意确认保存数据集的硬盘名称。 图35文件保存算法——Step 1从命令行窗口切换到保存数据集的磁盘空间 Step 2. 从命令行窗口输入保存数据集的完整路径,即可进入保存数据集的目录。 cd D:\Metalearning-从最优化到元优化\code\第3章\data\omniglot #cd命令适用于DOS系统、Linux系统及Windows系统,用于进入指定的目录 注意确认保存数据集的路径修改,否则会有错误提示——系统找不到指定的路径。运行成功后,会显示当前已进入的目录,如图36所示。 图36文件保存算法——Step 2从命令行窗口进入保存数据集的目录 现在可以运行此目录中的代码了。确认目录omniglot是否已经按照3.2.1节的要求进行处理,即是否已经包含了images_background和images_evaluation两个子目录。确认后,复制尺寸调整算法代码文件,即3.1.2节注解的resize_images.py,并备份到目录data/omniglot内。 Step 3.在命令行窗口,输入命令python resize_images.py,按Enter键执行。运行结果如图37所示,该代码涉及尺寸调整。 图37文件保存算法——Step 3resize_images.py运行成功 元学习代码调试中,无须提前单独运行resize_images.py和proc_images.py代码文件对数据做预处理,因此,读者不必尝试运行此代码。此外,必须在执行命令之前检查并确认数据集目录及其子目录里的文件均为非只读状态,否则会被拒绝修改,进而导致运行失败。 3.3.2目录创建算法 为方便在元学习过程中调用数据,完成样本任务的构建与联合训练,需要进行文件目录创建。在Python编程中,可以导入os模块达成这些目标。模块os称为标准模块,可用于实现以下目录的创建。 (1) os.getcwd()函数用于获取当前工作目录; (2) os.mkdir()函数用于创建新的目录; (3) os.makedirs()函数用于递归创建目录; (4) os.remove()函数用于删除指定文件; (5) os.rmdir()函数用于删除非空目录; (6) os.listdir( )函数用于返回指定目录下文件或子目录名字的列表。 特别地,MAML模型代码对于MiniImageNet数据集的处理涉及目录创建,现仍然采用模块切割的办法,将代码分解为5个简单的小模块,以便做通俗易懂的解释。其对应的核心代码及其算法思想详细注解如下。 """ #第一个模块导入目录创建过程中所需的科学计算包 """ from __future__ import print_function #从 __future__ 模块导入print_function输出特性 #该特性将print语句解析为print()函数,以便在当前安装的版本中使用最新版的print()函数 #模块_future_很灵活,且很强大 #该模块的原理是,通过修改Python解释器的行为以适应未来的Python版本。相较于当前Python #版本,未来版本可能增加或修改一些特性 #模块_future_提供的新特性带有预测性质,但被广泛认可。甚至有观点认为,_future_模块中存在 #的特性终将成为Python语言标准的一部分,届时将不再需要使用Python的_future_模块 import csv #导入Python自带的csv模块 #该模块的open()函数可打开CSV文件,并与csv.reader()、csv.writer()函数结合,读取和写入文件 #目录信息源于CSV文件,此文件格式的英文全称为Comma-Separated Values,即逗号分隔值,主要 #用于在程序之间转移表格数据。这类文件以纯文本形式存储表格数据,也可转换为Excel表 import glob #导入glob模块。该模块可用于查找符合特定规则的文件路径名,glob是global的缩写,即在 #Windows下进行全局搜索。常用函数有glob.glob()、glob.iglob()等,后者每次只能获取一个路 #径名 import os #导入os模块,以便完成对文件或目录的操作 from PIL import Image #从PIL库导入Image类。Image类用于图像处理 #在Python中,需要使用PIL库来处理图像。PIL英文全称为Python image library """ #第二个模块获取目录创建过程中所需的文件路径名 """ path_to_images = 'images/' #准备接收搜索到的图像路径 all_images = glob.glob(path_to_images + '*') #完成所有图像的全局搜索,并将其路径位置参数打包成一个元组all_images #glob.glob()函数可以同时获取所有的匹配路径,并将这些路径返回至一个列表中 #*用于打包位置参数,+用于连接字符串,避免路径名的混连 #*和**属于glob模块,是很灵活的符号,用于解包参数、扩展序列以及对字典和集合进行操作等 #不同的是,**侧重于关键字参数的解包,并将这些参数打包成字典 3.3.3文件读取算法 目录创建过程中,应同时进行文件的读取,以确保算法效率。 如前所述,尺寸调整是数据集处理的必要环节。在深度学习及元学习研究中,输入向量维数决定了输入层的网络节点数。3.1节和3.2节解释了Omniglot数据集的尺寸调整方法,MiniImageNet数据集的处理方法也是类似的。不同的是,MiniImageNet数据集还涉及目录创建和文件读取的过程,其目录创建算法已在3.3.1节进行了解释。 在文件读取之前,需要先执行图像尺寸调整,对应的核心代码及其注释如下。 """ #第三个模块完成文件读取之前所需的图像尺寸调整操作 """ for i, image_file in enumerate(all_images): #对图像路径列表中的所有文件做循环处理 #enumerate()函数用于枚举 #单词enumerate有枚举、列举的含义 #与Omniglot的图像尺寸调整方法不同,此处的i与image_file同步进入for循环 #all_images是图像路径列表 im = Image.open(image_file) #打开文件image_file,并以矩阵向量形式赋值给im,im是image的简写 #Image.open()函数属于PIL库的Image模块,用于打开图像 im = im.resize((84, 84), resample=Image.LANCZOS) #调用im.resize()函数,调整图像尺寸,此处该函数的目标尺寸是(84,84) #resample,顾名思义,是重采样,重采样过程中消除了锯齿噪声 #Image.LANCZOS是重采样滤波器,可以抗锯齿噪声 #缩放过程中需要借助重采样滤波器,以便保持图像质量 im.save(image_file) #调用im.save()函数,以保存修改 if i % 500 == 0: #如果i除以500的余数等于0,即i=500、1000、1500等 #每100个类作为一个小样本单元,每个类有5个样本,故而每个单元有500个样本 print(i) #输出对应的i值 """ #第四个模块完成目录创建过程中的文件读取 """ for datatype in ['train', 'val', 'test']: #3轮循环,依次是训练集、验证集和测试集 #datatype有数据类型的含义,此处将数据归并为3类 os.system('mkdir ' + datatype) #在当前系统下,为当前循环的datatype创建目录 #os.system()函数主要用于执行与操作系统相关的命令 #os.mkdir()函数用于创建新的目录 #对应当前循环的datatype创建 #循环结束后将产生train、val、test 3个目录 #此处, +用于连接字符串,避免目录名称的混连 with open(datatype + '.csv', 'r') as f: #在为当前循环的datatype打开对应的CSV文件,完成冒号后的操作之后,自动关闭文件 #在下述代码中,被打开的CSV文件将被简写为f #with open()函数用于打开文件,并在使用完后自动关闭,以免后续编辑权限受到限制 #此处,+用于连接字符串,避免datatype名称的混连 reader = csv.reader(f, delimiter=',') #调用csv.reader()函数,完成with open()函数对应的操作 #操作对象为f,即上一行代码中打开的CSV文件 #该操作类型在调用with open()函数时已经声明 #上一行代码中的r是reader的简写 # 单词delimiter有分隔符的含义 #在文件读取过程中采用逗号分隔符 """ #第五个模块完成文件读取过程中的标签分配,该模块是第四个模块的内嵌模块 """ last_label = '' #定义标签分配的收尾符 #标签分配结束后,以''结尾 for i, row in enumerate(reader): #对reader列表中的所有字符串做循环处理 #enumerate()函数用于枚举 #单词enumerate有枚举、列举的含义 #reader列表中包含了i与row,这两个指标同步进入for循环 #reader列表已在文件读取的外层循环中定义 if i == 0: #冒号前为操作前提 Continue #冒号后为具体操作 #如果i == 0,则跳过本次循环,返回至第一行 label = row[1] #第一行是标签字符串 image_name = row[0] #标签名称的前一行,即第0行,为图像名称 if label != last_label: #如果当前标签不等于收尾符,则执行冒号后的操作 cur_dir = datatype + '/' + label + '/' #具体操作是标签分配,首先是datatype与label的匹配,并打包为cur_dir #此处,+用于连接字符串,避免datatype名称与label字符串的混连 os.system('mkdir ' + cur_dir) #继续完成操作,现在将目录字符串归并到cur_dir #至此,为已创建的目录分配datatype、label已完成,下一步还需要对应到图像文件 last_label = label #标签分配完成 os.system('mv images/' + image_name + ' ' + cur_dir) #将image_name字符串归并到cur_dir #至此,完成目录创建的最后一步 #此处, +用于连接字符串,避免在归并过程中导致字符串的混连 #mv是os.system()函数中的命令,可以实现很灵活的操作 # 用法为os.system('mv 文件名称' + 路径名称) 3.4最优化方法 3.4.1随机抽样过程 元学习过程建模的关键思想是,通过随机抽取元训练数据中的类,划分小样本单元,完成任务构建,以进行联合训练,最终提取跨任务的元知识。因此,随机抽样方法的选择是过程建模的关键一步。在开源的MAML模型代码中,主要通过Python中的random模块实现随机抽样,并在随机抽样的基础上构建一批小样本学习任务,然后借助样本学习、最优化的方式完成每一个学习任务。 现将过程建模中的随机抽样环节、样本学习环节的代码及其对应的最优化方法依次详细注解,并将代码分解为6个简单的小模块,以便读者轻松理解元学习过程建模的细节,如下所示。 """ #第一个模块导入元学习过程建模所需的科学计算包 """ import numpy as np #将NumPy库导入为np #import as句式在导入科学计算包的同时,提供了简写 #后续代码行可以用np表示numpy #该工具包是Python开源的高级科学计算包,可用于元学习模型编程 #能对数组结构数据进行运算,实现对随机数、线性代数、傅里叶变换等的操作 #它不只是一个函数库,更是拥有强大的n维数组对象——numpy数组,也称为ndarray数组 #ndarray数组由两部分构成——真实数据和描述真实数据的元数据,可通过索引或切片来访问和修改 import os #导入os模块,以便对目录中的图像进行抽样 import random #导入random模块,该模块主要用于生成随机数 #任务构建过程中需要从数据集分布D中随机采样 import tensorflow as tf #导入tensorflow模块,并简写为tf #TensorFlow是一个端到端的开源机器学习框架 from tensorflow.contrib.layers.python import layers as tf_layers #从tensorflow.contrib.layers.python模块导入layers工具,并简写为tf_layers #layers工具主要用于访问模型的层次细节,从而在元学习过程中可以轻松完成建模 from tensorflow.python.platform import flags # 从tensorflow.python.platform模块导入flags工具 #flags工具主要用于定义、获取命令行参数,以便完成样本学习 FLAGS = flags.FLAGS #引入全局参数tensorflow.python.platform.flags.FLAGS,并简写为FLAGS #在样本学习环节,将使用FLAGS解析命令行参数 """ #第二个模块实现元学习过程中的随机抽样环节 """ def get_images(paths, labels, nb_samples=None, shuffle=True): #定义抽样函数get_images() #get_images()函数有4个输入,前两个输入分别是路径、标签 #第三个输入是要抽取的样本数,nb_samples是number of samples的简写 #None表示nb_samples被解析为空值,等待输入 #第四个输入是洗牌方法,即打乱次序的方式 #True表示每次都会返回不同的次序 #get_images()函数将采用随机抽样算法 if nb_samples is not None: #当输入的nb_samples值满足条件时,执行此冒号后的操作 sampler = lambda x: random.sample(x, nb_samples) #从路径x无放回随机抽样,抽取nb_samples个样本,保存到小样本单元sampler。基于小样本单 #元的任务构建是通过循环调用此代码模块而完成的 #random.sample()函数用于无放回地随机抽样 #函数lambda是Python的一个很灵活的创新,被称为匿名函数,有时候也称为lambdas #用法为lambda 输入变量:函数表达式 #在任意一行代码中,函数lambda允许随时进行嵌入式定义,只需要一个表达式就可以完成表达式 #的计算结果也很方便得到,直接将输入变量值代入表达式即可 else: #当输入的nb_samples值没有满足条件时,执行此冒号后的操作 sampler = lambda x: x #将None保存到小样本单元sampler,此时x=nb_samples=None # lambda后变量的含义由对应的表达式决定,此时的x不表示路径 images = [(i, os.path.join(path, image)) \ #对每个文件image,进行os.path.join(path, image)操作,并将结果归并到images #此番归并,将配合下一步的for循环进行迭代,以完成每个小样本单元的标签分配 #os.path.join()函数是路径操作函数,可以将多个路径拼接、合并为一个新的路径 #此处,os.path.join()函数实现path与image路径的拼接 #依据下一步for循环代码,可以将此处的path理解为标签labels的路径 #与下一步的循环控制指标i配对,标签和image路径合二为一,从而预设了标签分配格式 for i, path in zip(labels, paths) \ #zip(labels, paths)中的每一对指标i和path进入同步循环 #zip()函数函数能将多个可迭代对象打包成一个元组,而每个元组包含来自所有可迭代对象的相 #同索引位置上的元素。所以zip(classes, labels)将标签和类别的字符串索引合二为一,在索引上 #达成了预设的标签分配格式 for image in sampler(os.listdir(path))] #将小样本单元sampler中的每一个image加入嵌套循环 #os.listdir(path) 返回path目录下所有image的列表 if shuffle: #如果条件满足,执行此冒号后的操作 #笔者认为,if shuffle在这里的含义是if shuffle==True random.shuffle(images) #将images次序打乱,然后随机抽样 return images #至此,已完成get_images()函数的定义,该函数的返回值为抽取的images 3.4.2样本学习过程 如前所述,过程建模主要包括随机抽样、样本学习及其对应的最优化过程。在3.4.1节中,已经详细注解了随机抽样的代码。样本学习主要是通过卷积操作和最大池化操作来实现,在元学习系统完成卷积后,需要先经过归一化过程,之后才进行最大池化操作。 在解释样本学习代码模块之前,需要先介绍一下步长(stride)的概念。在Python中,stride作为一个有趣的概念被引入,并用于提高图像处理算法的性能,主要是基于两方面的原因。一方面,图像矩阵数据排列紧密,如按行操作会频繁读取非对齐内存,从而影响效率。另一方面,CPU的工作原理也要求内存访问对齐,否则会触发硬件非对齐访问错误。 当CPU需要取m个连续字节时,若内存起始位置的地址可以被m整除,则称为对齐访问。若不被整除,则称为非对齐访问。由于图像维度的多样性,非对齐访问几乎是无法避免的。stride的概念限定了图像矩阵中一行元素所占存储空间的长度,实现了强制性的对齐访问。|stride|≥图像宽度(byte)值,有可能会造成部分内存浪费。因此,使用时要确保内存充裕。 在样本学习环节,stride是一个有符号数,可以理解为卷积操作和池化操作的步幅。如从下而上滑动,图像将拥有一个负的stride 值。stride=m,相当于把图像尺寸缩小到原来的1/m,处理速度提升了m倍。代码里的stride是长度为4的一维向量,即stride=[batch, horizontal, vertical, channel]。 步长的4个分量依次代表batch、水平、垂直、channel 4个维度上的滑动步长。其中,batch维度是小样本单元维度,当batch=1时,不会跳过任何一个样本; channel维度是颜色通道维度,当channel=1时,不会跳过任何一个颜色通道。 Tensorflow框架中一般采用stride = [1,1,1,1] 或 stride = [1,2,2,1]。在元学习过程建模代码中,两者皆被采用。但是,令stride =[1,2,2,1],将 [1,1,1,1] 记为no_stride,可突出2种步幅的差异。当最大池化方式为VALID时,采用 [1,2,2,1],否则采用[1,1,1,1]。事实上,4个维度的分量均为1,即没有任何跳过的情况,就是no stride。由此可见,stride主要用于协助完成样本学习过程中的最大池化操作。在TensorFlow框架下,最大池化操作可以借助tf.nn.max_pool()函数实现。 tf.nn.max_pool()函数的具体用法为tf.nn.max_pool(conv_outp, stride1, stride2, max_pool_pad, name)。此处,第一个输入conv_outp,是卷积层的输出结果,一般是先卷积再池化。stride1、stride2分别是池化、卷积操作的步幅,max_pool_pad是最大池化的方式。第五个输入是操作的名称name。其中,pad是padding的简写,有SAME和VALID两种方式。受限于池化窗口大小和步幅,图像部分区块可能缺失。SAME方式是对池化后的图像矩阵进行补零操作,而VALID方式是进行舍弃操作,2种方式得到的输出维度不同。此函数的返回值是特征图feature map。在TensorFlow框架下被解释为一个四维张量Tensor=[batch, height, width, channels]。 现在,将解释过程建模代码的第三个模块。 """ #第三个模块实现过程建模中的样本学习过程 """ def conv_block(inp, cweight, bweight, reuse, scope, activation=tf.nn.relu, max_pool_pad='VALID', residual=False): #定义卷积模块函数conv_block(),conv是convolutional的简写,单词convolutional有卷积的含义 ''' #conv_block()函数有8个输入,分别为inp、cweight、bweight、reuse、scope、activation、max_pool_pad和residual。其中,inp是输入样本input的简写,cweight是卷积层的权重系数,bweight是与偏差bias对应的权重系数,reuse规定了这些参数是否重复使用,scope给出了这些参数共享的范围,activation、max_pool_pad、residual分别代表激活函数、最大池化空间、是否考虑残差 ''' #pad是padding的简写,单词padding有填充的含义,这里定义了学习内容之间的空间属性 stride, no_stride = [1,2,2,1], [1,1,1,1] # 定义两种滑动窗口:有跳跃stride= [1,2,2,1],无跳跃no_stride = [1,1,1,1] #引入no_stride,以减少stride造成的内存浪费 if FLAGS.max_pool: #当条件满足时,执行此冒号后的操作 # 条件if FLAGS.max_pool 是if max_pool_pad=='VALID'的简写 #在第二个模块已经引入全局参数tensorflow.python.platform.flags.FLAGS,简写为FLAGS conv_output = tf.nn.conv2d(inp, cweight, no_stride, 'SAME') + bweight #执行的操作为卷积,输出结果为tf.nn.conv2d(inp, cweight, no_stride, 'SAME') + bweight #在最大池化方式为VALID的前提下,无须跳跃,直接调用tf.nn.conv2d()函数,计算2D卷积结果 #因为no_stride = [1,1,1,1],此时最大池化方式为VALID,所以卷积过程中无须跳跃 #tf.nn是TensorFlow专为神经网络设计的模块化接口,具体包含卷积、池化、线 #性等操作和损失函数loss的计算等,nn是neural network的简写 # tf.nn中的每一个模块都是神经网络层次化结构中的某一层。conv2d是其中的二维卷积层 #'SAME'表示采用SAME卷积计算公式,即在卷积输出时,特征图的尺寸保持不变 else: #当条件没有满足时,执行此冒号后的操作 conv_output = tf.nn.conv2d(inp, cweight, stride, 'SAME') + bweight #在最大池化方式为SAME的前提下,调用tf.nn.conv2d()函数,先跳跃,再计算2D卷积结果 #因为stride = [1,2,2,1],此时最大池化方式为SAME,所以卷积过程中需要分别在水平维度和垂 #直维度进行跳跃,跳跃的步幅为2 #以上代码是归一化之前的卷积操作 normed = normalize(conv_output, activation, reuse, scope) #归一化过程可调用normalize()函数实现,该函数是自定义函数 #normalize()函数有4个输入,分别为conv_output、activation、reuse和scope,是自定义函数,其 #具体定义将在过程建模代码的第四个模块进行展示 #归一化之前的卷积结果conv_output也作为normalize()函数的输入 #以下代码为归一化之后的最大池化操作 if FLAGS.max_pool: #当条件满足时,执行此冒号后的操作 # 条件if FLAGS.max_pool 是if max_pool_pad=='VALID'的简写 normed = tf.nn.max_pool(normed, stride1, stride2, max_pool_pad) #池化过程可调用函数tf.nn.max_pool实现,这是一个自定义函数 # tf.nn.max_pool()函数有4个输入,分别为normed、stride1、stride2和max_pool_pad #最大池化结果仍然记为normed,因为最大池化是在归一化之后进行的 #池化和卷积均采用同一个步幅stride=[1,2,2,1] #此时,最大池化方式为VALID,输出图像矩阵中缺失的元素将被舍弃 return normed #卷积模块函数conv_block()的定义到此结束 #该函数的返回值是归一化之后的最大池化结果 """ #第四个模块定义样本学习环节的归一化过程 """ def normalize(inp, activation, reuse, scope): #normalize()函数有4个输入,分别为inp、activation、reuse和scope。其中,inp是输入样本 #input的简写,activation代表激活函数,reuse规定模型参数是否重复使用,scope给出这些参数 #共享的范围 if FLAGS.norm == 'batch_norm': #当此条件1满足时,执行此冒号后的操作 #这里batch_norm用于计算模型的BN层(英文batch normalization,批归一化处理层) #BN层与激活函数层分别是卷积与池化的前置层 return tf_layers.batch_norm(inp, activation_fn=activation, reuse=reuse, scope=scope) #此时,normalize()函数的返回值为tf_layers.batch_norm()函数的当前计算结果 #tf_layers.batch_norm()函数用于计算BN层,有4个输入,分别为inp、activation_fn、reuse和 #scope。此处,inp是input的简写,activation_fn=activation指定当前激活函数为默认的 #activation,设定reuse=reuse和scope=scope是为跟踪参数。其中,reuse规定模型参数是否 #重复使用,scope给出参数共享的范围。BN层用于反馈中间层数据分布的变化 elif FLAGS.norm == 'layer_norm': #当此条件2满足时,执行此冒号后的操作 #此处的layernorm也是用于计算模型的BN层,有两种归一化维度不同的计算方式,都是先计算 #一个batch中所有通道的参数均值和方差,然后进行归一化。在自然语言处理中只需要使用输入 #张量的第一维度,而在图像处理中对维度的操作则采完全不同 #张量有一维、二维、三维、四维等。图像矩阵就是二维张量,加入深度信息就成为三维张量,一批三 #维张量可以打包成一个四维张量 return tf_layers.layer_norm(inp, activation_fn=activation, reuse=reuse, scope=scope) #此时,normalize()函数的返回值为tf_layers.layer_norm()函数的当前计算结果 ''' #tf_layers.layer_norm()函数用于计算BN层,有4个输入,分别为inp、activation_fn、reuse和 #scope。此处,inp是input的简写,activation_fn=activation指定当前激活函数为默认的 #activation,设定reuse=reuse和scope=scope是为跟踪参数。其中,reuse规定模型参数是否 #重复使用,scope给出参数共享的范围。BN层用于反馈中间层数据分布的变化 ''' #如果条件1和条件2都没有满足,必然满足条件3 elif FLAGS.norm == 'None': #条件3 FLAGS.norm == 'None'表示未明确如何计算模型的BN层 #当条件3满足时,执行此冒号后的操作 #分两种情况: if activation is not None: #条件3-1表示未明确如何计算模型的BN层,但是指定了激活函数 #当不满足条件1和条件2,但满足条件3-1时,执行此冒号后的操作 return activation(inp) #此时normalize()函数的返回值为激活函数的当前计算结果 else: #此时,不仅未明确如何计算模型的BN层,也没有指定激活函数 #当不满足条件1和条件2,且不满足条件3-1时,执行此冒号后的操作 return inp #此时normalize()函数的返回值为当前的输入结果 3.4.3最优化过程 本书以问题为导向,对元学习模型的建模思路、算法思想、最优化方法和元优化机制展开系统化的研究。以元学习的经典模型MAML为例,前两章主要探讨元学习模型中的联合训练问题与任务构建问题,第3章主要分析过程建模问题,最后两章将分别研究元学习模型的输入输出问题和应用拓展问题。通过前面章节的阅读,读者已经获得必要的前期积累。接下来,将以通俗易懂的语言,并结合对应的模型代码,解释元学习模型的最优化方法,即损失函数作为最优化目标的收敛方法。 """ #第五个模块定义过程建模中的第一类最优化目标——均方误差MSE """ def mse(pred, label): #定义mse()函数,顾名思义,是将损失函数定义为均方误差 #均方误差MSE的英文全称为mean-square error,用于反映预测值与真实值之间的差异程度 #在第2章对任务构建问题的分析过程中,已将关注焦点集中在监督学习 #预测值pred是模型输出的标签,真实值label是图像的真实标签 #pred是predicted label的简写,单词predicted有预测的含义 pred = tf.reshape(pred, [-1]) #无法预知pred当前的shape #但目标是明确的,希望重新调整为一列 label = tf.reshape(label, [-1]) #无须关注label当前的shape #目标是明确的,希望重新调整为一列 return tf.reduce_mean(tf.square(pred-label)) #mse()函数的返回值为tf.reduce_mean(tf.square())函数的当前计算结果 # tf.square(pred-label)是调用square模块计算pred-label中每一个元素的平方值 #tf.reduce_mean()函数用于计算张量tf.square(pred-label)沿指定数轴上的平均值,指定的数 #轴是指该张量的第一维度。tf.reduce_mean(tf.square(pred-label))函数构成了均方误差MSE #的计算公式 """ #第六个模块实现过程建模中的第二类最优化目标:交叉熵XENT """ def xent(pred, label): #定义xent()函数,顾名思义,是将损失函数定义为交叉熵 #交叉熵XENT的英文全称为cross entropy,用于反映预测值与真实值概率分布间的差异程度 #交叉熵损失函数xent()作为最优化的目标函数,具有明显优势 #可以根据交叉熵损失函数xent()的二阶导数,判断最优化目标函数的凸性或者凹性 #根据最优化目标函数的凸性或者凹性,可判断元学习模型训练过程中是否会陷入局部最优解 #注意TensorFlow的版本,低版本的TensorFlow可能计算出错误的二阶导数。3.5节将给出解决方案 return tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=label) / FLAGS.update_batch_size #xent()函数的返回值是一个平均损失,FLAGS.update_batch_size代表当前学习的样本数,因此 #tf.nn.softmax_cross_entropy_with_logits/FLAGS.update_batch_size的当前计算结果就是当前 #的平均损失 #tf.nn.softmax_cross_entropy_with_logits()函数有2个输入。其中,logits取当前预测值 #在元学习模型中,tf.nn.softmax_cross_entropy_with_logits()函数是softmax激活函数和交叉 #熵损失函数的结合体,用于计算多任务联合训练过程中的多分类损失,形成有全局指导意义的最 #优化目标 3.5元优化机制 3.5.1元优化过程 元优化过程中调用的科学计算包主要来自tensorflow.python.framework工具包,该工具包包含元学习过程中的最优化机制(简称为元优化机制)。TensorFlow集成了Python的常用开发框架python.framework,这是一组用于简化和加速Python应用程序开发的库和工具。tensorflow.python.framework工具包提供了一系列预定义的功能和结构,以便开发者能够快速构建、测试和维护应用程序。tensorflow.python.framework工具包中的ops(operations)模块是一个非常重要的组件,用于管理和监控系统运行状态。 本节所解释的元优化机制,主要包括元优化过程中科学计算包的3方面的机制,即导入机制、梯度下降机制和最大池化机制。现将代码分解为3个简单的小模块,以便读者轻松理解元优化机制的细节。 首先看元优化科学计算包的导入机制,核心代码如下。 """ #第一个模块导入元优化过程中所需的科学计算包 """ from tensorflow.python.framework import ops #从工具包tensorflow.python.framework导入ops #完成导入后,即可以使用tensorflow.python.ops模块 from tensorflow.python.ops import array_ops #从tensorflow.python.ops模块导入array_ops #科学计算包array_ops中包含优化过程中的常用函数和类 #array是排序的意思,即按照使用频率进行排序 from tensorflow.python.ops import gen_nn_ops #从tensorflow.python.ops模块导入gen_nn_ops #科学计算包gen_nn_ops包含优化过程中的常用神经网络操作 #gen、nn分别是generate、neural network的简写 3.5.2拓展优化环境 元优化科学计算包的使用涉及对优化机制的理解。在解释元优化过程中的梯度下降机制之前,先拓展完成Python+PyCharm的环境配置,此配置将在第5章用于MAML模型源代码的调试。感兴趣的读者,可以尝试在Anaconda下直接调试,那将是一个有趣的探索过程。 Python的安装过程比较简单,打开如图38所示的官网。首先找到与计算机系统对应的安装包。单击Downloads按钮,可以看到All releases(所有版本)、 Source code(源代码)、Windows(Windows系统对应的版本)、macOS(macOS系统对应的版本)、Other Platforms(其他系统和平台对应的版本)、License(许可证,包括一些使用条款)、Alternative Implementations(包含Python安装配置的替代方式,主要是一些传统方式)选项,图39为操作系统是Windows10时的选择。 图38打开Python官网 图39单击Downloads按钮并找到对应系统的选项 不建议下载最新版,因为最新版对应的工具包可能还没有发布。也不建议下载太老的版本,因为部分支持库不适用于旧版本的Python。笔者的计算机操作系统是64位,选择的版本为Python 3.7.4,单击对应的链接Windows x8664 executable installer,即可下载该版本的安装包,预计5分钟左右完成,如图310所示。 图310单击对应的按钮,下载最新版本的Python安装包 下载完成后的安装包文件名为python3.7.4amd64.exe,双击该EXE文件,即可安装该版本的Python,注意勾选Add Python 3.7 to PATH复选框,如图311~图313所示。 图311最新版本的Python安装包已下载完成 图312启动Python 3.7.4的安装 图313安装正式开始之前,勾选Add Python 3.7 to PATH复选框 这里的Customize installation是自定义安装方式,可以选择安装路径和具体的安装内容。选择自定义安装方式,勾选所有Optional Features下的选项,如图314所示。 图314选择自定义安装,勾选所有“Optional Features”下的选项 Install Now按钮是快捷安装方式,在计算机中对应的直接安装路径(即快捷安装的位置)为C:\Users\WWF\AppData\Local\Programs\Python\Python37,这是安装程序自动选择的路径。注意,其中默认包含了IDLE(Python的集成开发环境)、pip(Python的包管理工具)及Documentation(相关文件、文档)。这种安装方式还默认产生Python运行的快捷方式(shortcuts)和文件打开方式的关联性(file associations)。 单击Next按钮,还可以看到其他高级选项,如图315所示。 图315单击Next按钮后看到的其他高级选项 此处保留已勾选的高级选项,然后单击Browse按钮更改安装路径,如图316所示。 图316单击Browse按钮更改安装路径 单击“确定”按钮,安装路径已修改为D:\Programfilesforai\Python,如图317所示。 图317安装路径已修改为D:\Program filesforai\Python 单击Install按钮,弹出是否允许Python 3.7.4对该设备进行修改的确认信息,单击“I Agree”按钮,即可开始安装。由于只勾选了少数高级选项,安装过程不到一分钟即可完成,如图318所示。 图318Python 3.7.4的安装过程 安装完成后,出现安装成功的提示,如图319所示。 图319安装成功后出现的提示 该版本对Python初学者(new to Python)是友好的,提供在线教程(online tutorial)和辅助文档(documentation),读者可以选择对应的选项自行查阅学习。同时,上述安装成功界面也提示了扩展名为.py的文件可以在Python内打开。读者还可以查看whats New In Python 3.7内的相关文档以了解该版本的新特性及如何在Windows系统上使用该版本(using Python on Windows),如图320所示。 图320Python 3.7的新特性 需要注意的是,如果出现Disable path length limit提示信息,则是在提醒和引导去修改计算机配置(machine configuration),直接单击此按钮,弹出是否允许Python 3.7.4对该设备进行修改的对话框,单击“I Agree”按钮,即可完成修改。修改后的安装成功界面就没有 Disable path length limit提示了。单击Close按钮完成安装,接下来可以简单验证Python是否真的安装成功。 图321Python 3.7.4自带的IDLE 单击屏幕左下方的按钮,可以看到IDLE(Python 3.7.4自带的集成开发环境),如图321所示。 单击IDLE(Python 3.7 64 bit)按钮,启动Python,在弹出的Python 3.7.4 Shell中输入一个简单的程序,然后按Enter键运行该程序。如果可以看到运行结果,说明Python 3.7.4安装成功。在Python 3.7.4 Shell中有File(文件创建、打开、保存)、Edit(文件编辑)、Shell(脚本查看和调试)、Debug(调试)、Options(高级选项)、Window(*Shell IDLE 3.7.4)、Help(帮助)选项,如图322所示。 图322通过一个简单程序验证Python是否安装成功 也可以在Python命令行窗口进行验证。单击屏幕左下方“展开”旁的下拉箭头,可以看到Python 3.7(64 bit)选项,单击此选项可以打开Python命令行窗口,如图323~图325所示。 输入相同的简单程序,然后按Enter键运行该程序,可以看到相同的运行结果,如图326所示。 图323找到“展开”旁的下拉箭头并单击按钮 图324看到Python 3.7 (64 bit)选项 图325单击Python 3.7 (64 bit)选项后,出现Python命令行窗口 图326通过一个简单程序验证Python是否安装成功 元学习模型编程所需的科学计算包托管在Anaconda上,也可以直接通过PyCharm调用,所以需要先安装Anaconda和PyCharm,已经在前两章完成Anaconda的安装配置。PyCharm是JetBrains(捷克的软件公司)开发的Python集成开发环境,有Professional(专业版)和Community(社区版)两种,其官网下载页面如图327所示。 此处选择下载免费的社区版(于2022年2月2日发布),如图328和图329所示。 双击EXE文件pycharmcommunity2022.2.2,可以启动安装。在弹出的第一个窗口中单击Next按钮,在新弹出的窗口中单击Browse按钮以修改安装路径,继续单击Next按钮,在弹出的窗口中勾选所有选项,再次单击Next按钮,如图330~图332所示。 图327PyCharm官网下载页面 图328社区版(PyCharm) 正在下载 图329已下载的PyCharm安装包 图330在弹出的第一窗口中单击Next按钮 图331在新弹出的窗口中单击Browse按钮以修改安装路径 图332勾选所有安装选项 单击Next按钮,在弹出的窗口中单击Install按钮,即可完成安装,如图333所示。 安装完成后,需要重启计算机(选择Reboot now选项,并单击Finish按钮),如图334所示。 此时,可以在计算机桌面上看到PyCharm的图标,双击此图标即可打开PyCharm。在弹出窗口中选择Do not import settings选项,并单击OK按钮即可启动PyCharm,如图335所示。 成功启动PyCharm后出现的界面如图336所示。在左侧可以看到4个选项,分别为Projects(项目)、Customize(自定义)、Plugins(插件,包括设置PyCharm操作界面语言为中文)、Learn PyCharm(学习PyCharm)。其中,Projects用于人工智能项目的开发,右侧对应有New Project(创建新项目)、Open(打开已有项目)、Get from VCS(从之前配置的GitHub账号里获取该账号拥有的项目)3个功能。 图333正在安装社区版PyCharm 图334完成社区版PyCharm的安装 图335通过快捷方式启动PyCharm 图336成功启动PyCharm后的界面 在右侧下方还可以看到Take a quick onboarding tour提示信息,意思是“你只需要花费7分钟,就可以熟悉PyCharm界面并学会在智能辅助下进行Python编程”。单击底部的Start Tour按钮,根据提示熟悉PyCharm界面。元学习代码调试所需的PyCharm配置将在第4章进行讲解。 3.5.3最大池化过程 众所周知,TensorFlow提供了比较完备的优化框架,但TensorFlow自带的科学计算包无法完全解决最优化过程中的二阶求导问题。因此,对过程建模而言,更为关键的还是梯度下降机制,其核心代码如下。 """ #第二个模块注册元优化过程中所需的新op """ @ops.RegisterGradient("MaxPoolGrad") #注册一个新op,并命名为MaxPoolGrad,加入ops,用于计算梯度 #顾名思义,主要用于计算最大池化梯度 #作为一个深度学习框架,TensorFlow提供了大量的基本操作 #基本操作可任意组合并设计出比常用神经网络更强大的算法,使用优化技术的开发简单高效 #在元学习模型编程中,还会涉及一些不易实现的操作,此时就有必要注册新的op 注册一个名为MaxPoolGrad的op,主要是为了方便自定义梯度。换言之,MaxPoolGrad作为一个新op,可用于最大池化梯度的定义。该定义将有助于理解元优化过程中的最大池化机制,相关定义的核心代码如下。 """ #第三个模块定义了用op和grad完成最大池化的过程 """ def _MaxPoolGradGrad(op, grad): #定义 _MaxPoolGradGrad()函数,顾名思义,两个grad连写表示连续两次求导 #该函的输入为op和grad #这里将计算出3个梯度值 gradient = gen_nn_ops._max_pool_grad(op.inputs[0], op.outputs[0], grad, op.get_attr("ksize"), op.get_attr("strides"), padding=op.get_attr("padding"), data_format=op.get_attr("data_format")) #反池化梯度gradient可以用gen_nn_ops._max_pool_grad()函数计算 #其本质是计算最大池化的反向传播梯度,因此也称为反池化函数 #该函数有7个输入,依次为op的输入和输出模块、待求导梯度、op自带的两个属性以及空间属 #性、数据格式 gradgrad1 = array_ops.zeros(shape = array_ops.shape(op.inputs[1]), dtype=gradient.dtype) #第一个二阶导数gradgrad1是调用array_ops.zeros()函数计算的 #array_ops.zeros()函数的输入为shape和dtype,dtype直接采用gradient.dtype #在gradgrad1中,shape采用array_ops.shape(op.inputs[1]) gradgrad2 = array_ops.zeros(shape = array_ops.shape(op.inputs[2]), dtype=gradient.dtype) #第二个二阶导数gradgrad2也可以调用array_ops.zeros()函数计算 #array_ops.zeros()函数的输入为shape和dtype,dtype直接采用gradient.dtype #在gradgrad2中,shape采用array_ops.shape(op.inputs[2]) return (gradient, gradgrad1, gradgrad2) #_MaxPoolGradGrad()函数的返回值为3个梯度 #3个梯度值依次为上述计算得到gradient、gradgrad1和gradgrad2