紧随上一篇,我们已经设置好了一些参数,下面我们来定义模型:
代码如下:
import typing
import tensorflow as tf
import theone
def get_vgg19_model(layers):"""初始化并创建vgg19模型:param layers::return:"""#加载imagenet上预训练的vgg19#include_top=False表示不包含模块的顶层也就是全链接层#weights='imagenet'表示使用在Imagenet数据集上的预训练权重加载模型vgg=tf.keras.applications.VGG19(include_top=False,weights='imagenet')#提取需要被用到的vgg层的outputoutputs=[vgg.get_layer(layer).output for layer in layers]#使用outputs创建新的模型model=tf.keras.Model([vgg.input, ],outputs)#锁死参数,不进行训练model.trainable=Falsereturn model
class NeuralStyleTransferModel(tf.keras.Model):def __init__(self, content_layers:typing.Dict[str,float]= theone.CONTENT_LAYERS,style_layers:typing.Dict[str,float]= theone.STYLE_LAYERS):super(NeuralStyleTransferModel,self).__init__()#内容特征层字典DICT[层名,加权系数]self.content_layers=content_layers#风格特征层self.style_layers=style_layers#提取需要用到的所有vgg层layers=list(self.content_layers.keys())+list(self.style_layers.keys())#创建layer_name到output索引的映射self.outputs_index_map=dict(zip(layers,range(len(layers))))#创建并初始化vgg网络self.vgg=get_vgg19_model(layers)def call(self,inputs,training=None,mask=None):"""前向传播::returntyping.Dict[str,typing.List[outputs,加权系数]"""outputs=self.vgg(inputs)#分离内容特征层和风格特征层的输出,方便后续计算typing.List[outputs,加权系数]content_outputs=[]for layer,factor in self.content_layers.items():content_outputs.append((outputs[self.outputs_index_map[layer]][0],factor))style_outputs=[]for layer,factor in self.style_layers.items():style_outputs.append((outputs[self.outputs_index_map[layer]][0],factor))#以字典形式返回输出return {'content':content_outputs,'style':style_outputs}
下面看分析:
先来看这段代码:
def get_vgg19_model(layers):"""初始化并创建vgg19模型:param layers::return:"""#加载imagenet上预训练的vgg19#include_top=False表示不包含模块的顶层也就是全链接层#weights='imagenet'表示使用在Imagenet数据集上的预训练权重加载模型vgg=tf.keras.applications.VGG19(include_top=False,weights='imagenet')#提取需要被用到的vgg层的outputoutputs=[vgg.get_layer(layer).output for layer in layers]#使用outputs创建新的模型model=tf.keras.Model([vgg.input, ],outputs)#锁死参数,不进行训练model.trainable=Falsereturn model
vgg=tf.keras.applications.VGG19(include_top=False,weights='imagenet')
调用VGG19模型,其中include_top=False表示不包含模块的顶层,也就是全连接层。只包含卷积层。weight='imagenet'表示使用在imagenet数据集上的训练权重加载模型。
具体来说,当我们使用VGG19,ResNet50,InceptionV3等经典的神经网络模型时,这些模型通常在大规模图像数据集ImageNet上进行了预训练,在预训练过程中,模型通过学习大量的图像样本来提取特征,并对这些样本进行分类。当我们自己在任务中使用这些预训练模型时,我们可以选择是否加载预训练权重。如果我们将weights='imagenet'设置为true。在实例化模型的时候,TensorFlow会自动下载并加载在ImageNet数据集上预训练的权重。这样做的好处是,模型会具有较好的初始特征提取能力,通常能够提高模型的性能和收敛速度。
outputs=[vgg.get_layer(layer).output for layer in layers]
这段代码创建了一个列表'outputs',其中包含了VGG模型中指定层的输出。
这段代码使用列表推导式,遍历layers中每个层名layer,并为每个层名获取其对应的输出张量,然后将这些输出张量存储在列表‘outputs’中。
总体而言就是根据传入的层列表layers提取需要的VGG层输出(加载预训练模型的权重)。
model=tf.keras.Model([vgg.input, ],outputs)
tf.keras.Model是Keras中用于创建模型的类。
[vgg.input, ]这个列表包含了模型的输入张量,其中vgg.input表示了VGG的输入张量。
在Keras中,模型的输入层可以通过模型的.input属性来访问。
为什么要将其打包成一个列表呢?
tf.keras.Model类构造函数需要接受输入参数的列表。当然,只有一个元素,将其写成[vgg.input]其实也可以。
outputs是一个列表,包含了VGG模型中指定层的输出张量,这些张量将作为新模型的输出。
总的而言就是使用tf.keras.Model类创建了一个新的模型,指定了模型的输入和输出。
model.trainable=False是指定模型的参数不可训练,即在模型进行反向传播算法优化参数时,这些参数不会被更新,意味着模型的参数将不会参与训练过程,他们的值将会保持不变,这通常用于在迁移学习或特定场景下,固定住某些层的参数,只训练模型的部分参数,或者在指定的任务中只使用模型的特征提取能力而不更新模型的参数。(显然使用的参数是预训练模型上的,具有很高的可靠性,训练反而会使可靠性降低)。
接下来我们看:
class NeuralStyleTransferModel(tf.keras.Model):def __init__(self, content_layers:typing.Dict[str,float]= theone.CONTENT_LAYERS,style_layers:typing.Dict[str,float]= theone.STYLE_LAYERS):super(NeuralStyleTransferModel,self).__init__()#内容特征层字典DICT[层名,加权系数]self.content_layers=content_layers#风格特征层self.style_layers=style_layers#提取需要用到的所有vgg层layers=list(self.content_layers.keys())+list(self.style_layers.keys())#创建layer_name到output索引的映射self.outputs_index_map=dict(zip(layers,range(len(layers))))#创建并初始化vgg网络self.vgg=get_vgg19_model(layers)
我们定义了一个名为NeuralStyleTransferModel的自定义神经风格迁移模型类,继承自tf.keras.Model类。表示这个类是一个Keras模型。
def __init__(self, content_layers:typing.Dict[str,float]= theone.CONTENT_LAYERS, style_layers:typing.Dict[str,float]= theone.STYLE_LAYERS): super(NeuralStyleTransferModel,self).__init__()
这是一个构造函数,在创建类的实例时会自动调用。
content_layers和style_layers是构造函数的参数,它们指定了内容特征层和风格特征层以及它们的加权系数。格式就是字典类型,使用content_layers:typing.Dict[str,float]无非就是一个注释功能。
当然我们可以选择:(个人认为这种方法更简洁,源码更能凸显参数类型)。
def __init__(self, content_layers=theone.CONTENT_LAYERS, style_layers=theone.STYLE_LAYERS):
super(NeuralStyleTransferModel, self).__init__()
self.content_layers=content_layers,为什么在构造函数中已经初始化content之后还要再初始化?
在构造函数中通过参数传入的content_layers和style_layers变量仅在构造函数内部可见,并且只在构造函数执行过程中有效,为了在整个类中都能访问这些变量,我们需要将它们保存为类的实例变量,以便在类的其他方法中也能使用它们。
layers = list(self.content_layers.keys()) + list(self.style_layers.keys())
这段代码将内容特征层和风格特征层的层名合并为一个列表layers,得到一个包含所有层名的列表。
self.outputs_index_map = dict(zip(layers, range(len(layers))))
创建一个字典outputs_index_map,用于将层名与它们在VGG模型输出列表中的索引位置相对应。
range(len(layers))生成一个从0到len(layers)-1的整数序列,其长度与列表layers的长度相同。
zip(layers,range(len(layers))):将两个列表中的元素逐一配对,形成一个可迭代的元组序列,每个元组中包含了layers中的一个元素和对应的range(len(layers))中的元素。
self.vgg = get_vgg19_model(layers)
这段代码调用一个函数get_vgg19_model()并传入层列表layers作为参数。
这个函数的作用是创建并初始化一个VGG19的网络模型,其中包含了指定层名的部分,用于提取图像的特征。
用于后面提取图像的特征。
最后一个模块,写完收工:
def call(self,inputs,training=None,mask=None):"""前向传播::returntyping.Dict[str,typing.List[outputs,加权系数]"""outputs=self.vgg(inputs)#分离内容特征层和风格特征层的输出,方便后续计算typing.List[outputs,加权系数]content_outputs=[]for layer,factor in self.content_layers.items():content_outputs.append((outputs[self.outputs_index_map[layer]][0],factor))style_outputs=[]for layer,factor in self.style_layers.items():style_outputs.append((outputs[self.outputs_index_map[layer]][0],factor))#以字典形式返回输出return {'content':content_outputs,'style':style_outputs}
这个模型时前向传播模型:
def call(self,inputs,training=None,mask=None):
input是模型输入数据。training表示模型是否处于训练模式,如果为True表示模型正在训练中,在训练模式下,模型通常会执行一些特定的操作。training=False表示处于推断模式(测试模式或者评估模式),在推断模式下,模型可能会有一些与训练不同的行为。training=None的时候,表示模型的训练模式将由框架自动确定,通常情况下,在调用模型的时候,如果没有显式指定训练模式,框架会自动将模型置于推断模式下。
mask=None
如果mask被指定为一个张量(tensor),那么模型的计算将会受到这个掩码的影响,掩码可以指示哪些位置的输入需要被忽略或者被加权。
当mask=None时,表示没有提供掩码,模型在计算时将不会考虑额外的掩码信息。
outputs=self.vgg(inputs)
由前面的可知,vgg是一个模型,通过向get_vgg19_model中传入层(layers)得到。将inputs传入得到outputs输出。
for layer,factor in self.content_layers.items(): content_outputs.append((outputs[self.outputs_index_map[layer]][0],factor))
重点就是看outputs[self.outputs_index_map[layer]]
self.outputs_index_map是一个将层名称映射到输出索引的字典,[self.outputs_index_map[layer]]得到的是一个数值(索引),在outputs中的索引,后面的[0]表示取该元素outputs的该索引处对应的元素的第一个元素。通常是因为特征表示是一个张量的数组,而我们只需要其中的一个特征。
而factor表示的是与特征表示相关联的因子或权重,在风格迁移中,这个因子常用于调整特征表示的权重。
整体而言,这个元组表示了一个特征表示(通过outputs和self.outputs_index_map获取)和与之相关联的权重或因子的组合。
return {'content':content_outputs,'style':style_outputs}
最后将得到的字典以字典的形式返回。