小白的神经网络学习

小白的神经网络学习笔记

一.环境配置
  • Python3.6 + tensorflow2.0 + keras + numpy + matplotlib + pandas + jupyter notebook
二.感知器(Perceptron)
单层感知器
  • 定义:二分类线性分类模型,其输入为实例的特征向量,输出为实例的类别,为1或-1

    简单说,就是在平面坐标轴画一条直线,把点分为两类

  • 单层感知器的局限性:因为处理函数是线性的,所以不管中间怎么处理,输出都是线性的,最后的结果能表示的范围也很小,由于它只有一层功能神经元,所以学习能力有限

    单层感知机不能表示异或逻辑
    在这里插入图片描述

多层感知器(MLP,Multilayer Perceptron)
  • 多层感知器(MLP)也叫人工神经网络(ANN,Artificial Neural Network)前馈神经网络,除了输入输出层,他中间可以有多个隐藏层,最简单的MLP只含有一个隐藏层,即三层结构
    在这里插入图片描述

  • 从上图看出,MLP层与层之间是全连接的,MLP最底层是输入层,中间是隐藏层,最后是输出层

  • 特征(feature):每一个输入的 x i x_i xi都是一个特征(i=1,2,…,n)

  • 权重(weight):和每一个特征相对应的都有一个权重 w i w_i wi(i=1,2,…,n),这也是整个网络需要训练的参数,其实也很好理解(就像判断一个西瓜是否成熟,颜色,声音,花纹等等都是这个西瓜的特征,而与之对应的,颜色,声音,花纹对西瓜是否成熟都有不同的影响权重)

  • 偏置(bias):可以简单的理解为“截距”

  • 激活函数(activation function):从单层感知器可知,如果不使用激活函数,那么每一层的输出都是上一层输入的线性函数,无论有多少层神经网络,输出都是输入的线性组合,使用激活函数,能够给神经元引入非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以利用到更多的非线性模型

    激活函数的特性:

    1.连续可导(允许少数点上不可导)的非线性函数,可导的激活函数可以直接利用数值优化的方法来学习网络参数

    2.激活函数及其导函数要尽可能简单,有利于提高网络计算效率

    3.激活函数的导函数的值域要在一个合适的区间内,不能太大,也不能太小,否则会影响训练的效率和稳定性

    常用激活函数

    1.Sigmoid(Logistic)函数: δ ( x ) = 1 1 + e − x \delta(x)=\frac{1}{1+e^{-x}} δ(x)=1+ex1,其导数为: δ ′ ( x ) = δ ( x ) ( 1 − δ ( x ) ) \delta'(x)=\delta(x)(1-\delta(x)) δ(x)=δ(x)(1δ(x))

    ​ 可以从图上看到,Sigmoid函数的取值范围为 (0,1),所以经常将它用在二分类的问题上,对于任何的输入,都能给出(0,1)上的输出

    缺点:在进行反向传播时,Sigmoid函数容易出现梯度消失的情况(因为在函数的两端,梯度很小,只有在0附近的梯度才很大),所以在使用Sigmoid的函数的时候,通常要将数据经过归一化处理(将数据分布在0周围)
    在这里插入图片描述

    2.Tanh函数(双曲正切函数): t a n h x = s i n h x c o s h x = e x − e − x e x + e − x tanhx=\frac{sinhx}{coshx}=\frac{e^x-e^{-x}}{e^x+e^{-x}} tanhx=coshxsinhx=ex+exexex,其导数为: ( t a n h x ) ′ = s e c h 2 x = 1 − t a n h 2 x (tanhx)'=sech^2x=1-tanh^2x (tanhx)=sech2x=1tanh2x,,从图像可看出,函数的取值范围为[-1,1]

    tanh在特征相差明显时,效果会很好,在循环过程中会不断扩大特征效果

    **缺点:**同样的,也会在反向传播中造成梯度消失
    在这里插入图片描述

    3.relu函数(非线性激活函数): f ( x ) = m a x ( 0 , x ) f(x)=max(0,x) f(x)=max(0,x),最为最常用的激活函数,relu的作用就是增加了神经网络各层之间的非线性关系,并且,函数的表达式简单,计算量小,便于求导
    在这里插入图片描述

  • MLP一层的输出可以表示为 f ( ∑ i = 1 n x i ⋅ w i + b ) f(\sum_{i=1}^nx_i·w_i+b) f(i=1nxiwi+b),其中f(x)为激活函数,一个节点的输出是下一个节点的输入

Keras实现
import tensorflow as tf
from tensorflow import keras 
from tensorflow.keras import layers
import pandas as pd

data=pd.read_csv(path)   # 获取数据

model=keras.Sequential()  # 初始化序列模型
# MLP模型的第一层
# Dense:表示该层是一个全连接层
# 10:表示该层有10个输出
# input_shape:第一层需要给定输入的维度(也就是特征个数),需要注意,输入的是一个元组
# activation:激活函数
model.add(layers.Dense(10,input_shape=(data.shape),activation='relu'))
# 输出层,输出1个维度
model.add(layers.Dense(1))

# 模型编译,选择优化器和损失函数
model.compile(optimizer='adam',loss='mse')

# 模型训练,输入对应的训练数据,并将训练记录保存在history中,还可以添加validation_data参数来添加验证数据
history=model.fit(x,y,epochs=10)

# 模型预测
model.predict(x)
三.逻辑回归与交叉熵
  • 逻辑回归:输出的结果只有两种情况(“是”或“否”),所以逻辑回归不是回归,他是一个分类任务

  • 一般对于逻辑回归,我们通常选用Sigmoid激活函数,原因第二章已经说了

  • 交叉熵:

    • 交叉熵刻画的是是技术处(概率)与期望输出(概率)间的距离,它实际上描述的是概率与概率间的距离,也就是说,交叉熵值越小,两个概率分布越接近

    • 假设概率分布p为期望输出,概率分布q为实际输出H(p,q)为交叉熵

      H ( p , q ) = − ∑ x p ( x ) l o g q ( x ) H(p,q)=-\sum_xp(x)logq(x) H(p,q)=xp(x)logq(x)

      在Keras中使用binary_crossentropy损失函数来计算二元交叉熵

import pandas as pd
import numpy as np
import tensorflow as tf
from tensorflow import keras 
from tensorflow.keras import layers

data=pd.read_csv(path)  # 读取数据
x=data[[....]]  # 取出数据和对应的标签
y=data[....]

model=keras.Sequential()
model.add(layers.Dense(10,input_shape=(x.shape),activation='relu'))
model.add(layers.Dense(10,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

model.compile(optimizer='adam',loss='binary_crossentropy')
model.fit()
关于sparse_categorical_crossentropy & categorical_crossentropy
  • softmax分类
    • 神经网络的原始输出不是一个概率值,本质上只是输入了的数值做了复杂的加权和非线性处理之后的一个值而已
    • softmax函数可以将这个输出变成概率分布 f ( z ) j = e z j ∑ k = 1 K e z k , f o r j = 1 , 2 , . . . , K f(z)_j=\frac{e^{z_j}}{\sum_{k=1}^Ke^{z_k}},for j=1,2,...,K f(z)j=k=1Kezkezj,forj=1,2,...,K
    • softmax所有样本概率分量之和为1
  • 在Keras里,对于多分类问题使用categorical_crossentropy & sparse_categorical_crossentropy 来计算交叉熵
    • 当使用普通向量时采用sparse_categorical_crossentropy
    • 当使用独热编码时采用categorical_crossentropy
关于独热编码(one-hot key)
  • 在机器学习算法中,我们经常会遇到分类特征,例如:人的性别有男女,国家有中国,美国,法国…这些特征值不是连续的,而是离散的,无序的,通常我们需要对其进行特征数字化

  • 什么是特征数字呢?例子如下:

    • 性别特征:[‘男’,‘女’]
    • 祖国特征:[‘中国’,‘美国’,‘法国’]
    • 运动特征:[‘足球’,‘篮球’,‘羽毛球’,‘乒乓球’]
    • 假如某个样本,他的特征是[‘男’,‘中国’,‘乒乓球’],我们可以用[0,0,4]来表示,但是这样的特征处理并不能直接放入机器学习算法中,因为类别间是无序的(运动数据就是任意排序的)
  • 独热编码(one-hot key)

    • one-hot码又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都有他独立的寄存器位,并且在任意时候只有一位有效

    • one-hot码是分类变量作为二进制向量的表示,这首先要将分类值映射到整个数值,然后每个整数值被表示为二进制向量,除了整数的索引之外,其他都是0,它被标记为1

    • 以上面的例子为例,性别特征[‘男’,‘女’]按照N位状态寄存器来对N个状态进行编码的原理,处理后的结果是:

      ​ 男 -> 10

      ​ 女 -> 01

      祖国特征:[‘中国’,‘美国’,‘法国’]

      ​ 中国 -> 100

      ​ 美国 -> 010

      ​ 法国 -> 001

      运动特征:[‘足球’,‘篮球’,‘羽毛球’,‘乒乓球’]

      ​ 足球 -> 1000

      ​ 篮球 -> 0100

      ​ 羽毛球 -> 0010

      ​ 乒乓球 -> 0001

      所以,当一个样本为[‘男’,‘中国’,‘乒乓球’]的时候,完整的特征数字化结果为:

      ​ [1,0,1,0,0,0,0,0,1]

在Python中的应用
from sklearn import preprocessing

enc=preprocessing.OneHotEncoder()
enc.fit([[0,0,3],[1,1,0],[0,2,1],[1,0,2]])  # 这里一共4个数据,3种特征

array=enc.transform([[0,1,3]]).toarray()  # 这里使用一个新数据测试

output:[[1 0 | 0 1 0 | 0 0 0 1]]
  • 为什么使用one-hot编码来处理离散特征
    • 在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算非常重要,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,就是基于欧式空间
    • 而使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就是对应欧式空间的某个点
    • 将离散特征使用one-hot编码,会让特征之间的距离计算更加合理
  • 不需要使用one-hot编码的情况
    • 将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并不用one-hot编码就可以很合理的计算出距离,那么久没必要进行one-hot编码

    • 离散特征进行one-hot编码后,编码后的特征其实每一维度的特征都可以看做是连续的特征,就可以跟对连续型特征的归一化方法一样,对每一维特征进行归一化,比如归一化[-1,1],或归一化均值为0,方差为1

Keras实现
from tensorflow import keras
import pandas as pd

data=pd.read_csv(path)  # 拿到数据

# 转化为独热编码
train_label_onehot=keras.utils.to_categorical(tain_label)
test_label_onehot=keras.utils.to_categorical(test_label)

# 可以看到,这里用的损失函数是categorical_crossentropy
model.compile(optimizer='adam',loss='categorical_crossentropy')

#训练的时候需要使用独热编码标签
model.fit(train_data,train_label_onehot,eopchs=...)
四.tf.data模块
  • tf.data.Dataset表示一系列元素,其中每个元素包含一个或多个Tensor对象(例如:在图片管道中,一个元素可能是单个训练样本,具有一对表示图片的数据和标签的张量
  • tf.data.Dataset对象是可迭代的(可for,可next(iter(dataset)))
  • 创建方法:
    • 直接从Tensor创建Dataset e.g:Dataset.from_tensor_slices(xxxx)
      • numpy也可以,tensorflow会自动将其转换为Tensor
    • 通过一个或多个tf.data.Dataset对象来使用变化(例如Dataset.zip)来创建Dataset
    • 一个Dataset对象包含多个元素,每个元素的结构都相同,每个元素包含一个或多个tf.Tensor对象,这些对象被称为组件
    • Dataset的属性由构成该Dataset的元素的属性映射得到,元素可以是单个张量,张量元组,也可以是张量嵌套的元组
  • dataset可以使用shuffle方法,map方法
Python实例
import tensorflow as tf
import numpy as np

dataset=tf.data.Dataset.from_tensor_slices([1,2,3,4,5])
for ele in dataset:
    print(ele.numpy())

# 用take(n)方法取前n个    
for ele in dataset.take(4):  # 这里取前4个
	print(ele.numpy())
dataset_array=tf.data.Dataset.from_tensor_slices([[1,2],[3,4],[5,6]])

dataset_array
output:shape(2,)  # 表示其中每个组件的shape

for ele in dataset_array:
    print(ele.numpy())
output:[1,2]
       [3,4]
       [5,6]    
# 字典创建
dataset_dict=tf.data.Dataset.from_tensor_slices({'a':[1,2,3,4],'b':[6,7,8,9],'c':[12,13,14,15]})

dataset_dict
output:<TensorSliceDataset shapes: {a: (), b: (), c: ()}, types: {a: tf.int32, b: tf.int32, c: tf.int32}>
            
for ele in dataset_dict:
    for k,v in ele.items():
        print(k,v)
output:
a tf.Tensor(1, shape=(), dtype=int32)
b tf.Tensor(6, shape=(), dtype=int32)
c tf.Tensor(12, shape=(), dtype=int32)
a tf.Tensor(2, shape=(), dtype=int32)
b tf.Tensor(7, shape=(), dtype=int32)
c tf.Tensor(13, shape=(), dtype=int32)
a tf.Tensor(3, shape=(), dtype=int32)
b tf.Tensor(8, shape=(), dtype=int32)
c tf.Tensor(14, shape=(), dtype=int32)
a tf.Tensor(4, shape=(), dtype=int32)
b tf.Tensor(9, shape=(), dtype=int32)
c tf.Tensor(15, shape=(), dtype=int32)    
dataset=dataset.shuffle(len(train_data)).repeat().batch(BATCH_SIZE)

dataset=dataset.map(tf.square)
五.Dropout层
  • 为解决过拟合(也就是说,模型在训练数据上表现良好,但是在测试数据上表现差)问题,最好的方法是增加训练数据,但是在训练数据一定的情况下,为防止模型过拟合,一般使用dropout方法
  • **原理:**通过随机抛弃某层的某些神经元,达到降低过拟合的目的
import tensorflow as tf
from tensorflow import keras

model=keras.Sequential()
model.add(keras.layers.Flatten(input_shape=xxx))
model.add(keras.layers.Dense(128,activation='relu'))
model.add(keras.layers.Dropout(0.5))  # 参数表示丢弃率
...
六.函数式API
  • 灵活的编程方式
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

(train_image,train_label),(test_image,test_label)=keras.datasets.fashion_mnist.load_data()

train_image=train_image/255  # 归一化
test_image=test_image/255

input=keras.Input(shape=train_image.shape[1:])
x=layes.Flatten()(input)   # 将输入图像展平,输入数据就是input
x=layers.Dense(32,activation='relu')(x)
x=layers.Dropout(0.5)(x)
x=layers.Dense(64,activation='relu')(x)
output=layers.Dense(10,activation='softmax')(x)

model=keras.Model(inputs=input,outputs=output)

model.compile(xxxxxxxxx)
model.fit(xxxxxx)
七.卷积神经网络(CNN)
原理
  • CNN工作是指:给定一张图像,让它经历一系列 [卷积层,非线性层(激活层),池化层(下采样(downshampling)) ],和全连接层,得到最终输出,该输出最好的描述了图像内容的一个单独分类或者一组分类的概率

  • 什么是卷积:

    • 卷积是指将卷积核应用到某个张量的所有点上,通过将卷积核在输入的张量上滑动而生成经过滤波处理的张量
    • 卷积完成的是对图像特征的提取,或者说是信息的匹配,当一个包含某些特征的图像经过一个卷积核的时候,一些卷积核被激活,输出特定的信号
    • 卷积核会被训练,训练的结果就是:卷积核会对不同种类的图像的不同特征敏感,输出不同的结果,从而达到了图像识别的目的
架构
  • [卷积层(conv2D) -> 非线性变换层(激活层 relu/sigmoid/tanh)-> 池化层(pooling2D)]

    x n -> 全连接层

  • 如果没有这些层,模型很难与复杂模式匹配,因为网络将有过多的信息填充,其他那些层作用就是突出重要信息,降低噪声

  • 卷积层

    • 三个参数
      • ksize(kernel size):卷积核的大小
      • strides:卷积核移动的跨度
      • padding:边缘填充
  • 池化层

    常用的池化层有:

    • MaxPooling2D:最大池化,选取kernel中最大的元素,用以代表整个kernel中的元素,达到下采样(比方说用2x2的kernel,可以使一张图片缩小一半)
    • GlobalAveragePooling2D:全局平均池化,选取kernel中所有元素的均值,用以代表kernel中的元素,该池化方法也可以某种程度上代替Flatten()层,且效果较好
图像的预处理
  • 1.通过路径读取图像
  • 2.解码图像
  • 3.将所有图像变成统一大小
  • 4.转换数据类型
  • 5.归一化操作
def load_preprocess_image(path,label):
    image=tf.io.read_file(path)
    image=tf.image.decode_jpeg(image,channels=3)
    image=tf.image.resize(xxx,xxx)
    image=tf.cast(image,tf.float32)
    image=image/255
    label=tf.reshape(label,[1])
    return image,label
八.标准化
  • 数据标准化让机器学习模型看到的样本彼此间更加相似,有助于模型的学习以及对新数据的泛化

  • 常见形式

    • 标准化和归一化
    • 将数据减去其平均值使其中心为0,然后将数据除以其标准差使其标准差为1
批标准化(Batch Normalization)
  • 批标准化和普通的数据标准化类似,是将分撒的数据统一的一种做法,也是优化神经网络的一种方法
  • 批标准化不仅将数据输入模型之前对数据做标准化,在网络的每一次变换之后都应考虑数据的标准化
  • 即使在训练过程中均值和房产随时间发生变化,它也可以适应性的将数据标准化
  • 批标准化解决的问题是梯度消失与梯度爆炸
  • 数据预处理做标准化可以加速收敛,同理,在神经网络中使用标准化也可以加速收敛,而且还有很多好处(具有正则化效果,提高泛化能力,允许更高的学习率从而加快收敛
  • 批标准化有助于梯度传播,因此允许更深的网络,对于有些特别深的网络,只有包含多个Batch Normalization层时才能进行训练
  • tf.keras.layers.Batchnormalization()层通常在卷积层或密集层连接层后使用
批标准化实现过程
  • 求每一个训练批次数据的均值
  • 求每一个训练批次数据的方差
  • 将数据进行标准化
  • 训练参数 γ , β \gamma,\beta γ,β
  • 输出y,通过 γ \gamma γ β \beta β的线性变换得到原来的数值,在训练的正向传播中,不会改变当前输出,只记录下 γ \gamma γ β \beta β,在反向传播的时候,根据求得的 γ \gamma γ β \beta β通过链式求导方式,求出学习速率至改变权值
批标准化的预测过程
  • 对于预测阶段时所使用的均值和方差其实也是来源于训练集(比如在模型训练时记录每个batch下的均值和方差,待训练完毕后,我们求整个训练样本的均值和方差的期望值,作为我们进行预测时进行BN的均值和方差)
  • training=True:该层将使用当前批输入的均值和方差对其输入进行标准化
  • training=False:该层将使用在训练期间学习的移动统计数据的均值和方差来标准化其输入
  • BN层放在activation层之后效果会更好
九.TF的变量与自动微分运算
梯度带tf.GradientTape
  • tf.GradientTape(persistent=False,watch_accessed_variables=True)
    • persistent:布尔值,用来指定新创建的gradient tape是否是可持续的,默认是False,意味着只能够调用一次gradient()函数
    • watch_accessed_variable:布尔值,标明这个gradient tape是不是会自动追踪任何能被训练(trainable)变量,默认是True,如果为False,表示需要手动指定想追踪的那些变量
import tensorflow as tf

w=tf.Variable([[1.0]])  # w是一个二维变量
with tf.GradientTape() as t:
    func=w*w

dw=t.gradient(func,w)
print(dw)

output:<tf.Tensor: id=39, shape=(1, 1), dtype=float32, numpy=array([[2.]], dtype=float32)>
        
# 上述代码的意思是:求w*w在w=1.0处的倒数        
  • 注意:

    1.求在某点(w)处的导数,w必须为浮点类型

    2.GradientTape占用的资源默认情况下dw=t.gradient(func,w)计算完毕就会立即释放,如不需要释放,则需要设置persistent=True

  • t.gradient(target,sources,output_gradients=None,unconnected_gradients=tf.UnconnectedGradients.NONE)

    作用:根据tape上面的上下文来计算某个或者某些tensor的梯度

    • target:被微分的Tensor或者Tensor列表,可以理解为经过某个函数之后的值
    • sources:Tensors或者Variables列表(当然可以只有一个值),可以理解为函数的某个变量
    • output_gradients:a list of gradients,one for each element of target.Defaults to None.(每一个元素的一个梯度列表,默认为空)
    • unconnected_graeidnts:a value which can either hold ‘none’ or ‘zero’ and alters the value which will be returned if the target and sources are unconnected. the possible values and effects are detailed in ‘UnconnectedGradients’ and it defaults to ‘none’
    • 返回值:一个列表,表示各个变量的梯度值,和source中的变量列表一一对应,表明这个变量的梯度
自定义梯度下降
  • 增加数据维度tf.expand_dims(data,loc)

    参数:

    data:需要增加维度的数据

    loc:在哪个位置增加(e.g,-1:表示在末尾增加)

(train_image,train_label),_=tf.keras.datasets.mnist.load_data()

# 增加数据的维度!!重要!!
train_image=tf.expand_dims(train_image,-1)
train_image=tf.cast(train_image/255,tf.float32)
train_label=tf.cast(train_label,tf.int64)

dataset=tf.data.Dataset.from_tensor_slices((train_image,train_label))
dataset=dataset.shuffle(xxxx).batch(BATCH_SIZE)

----------------------------------------------------------------------
# 普通的模型建立
model=tf.keras.Sequential()
model.add(layers.Conv2D(16,(3,3),inputshape=(xx,xx,xx),activation='relu'))
model.add(layers.Conv2D(32,(3,3),activation='relu'))
model.add(layers.GlobalMaxPool2D())
model.add(layers.Dense(10))

optimizer=keras.optimizer.Adam()
loss_func=keras.losses.SparseCategoricalCrossentropy(from_logits=True)
----------------------------------------------------------------------
# 自定义学习
def loss(model,x,y):
    # 这里直接调用model(x)就相当于model.predict(x)
    y_=model(x)
    return loss_func(y,y_)

def train_step(model,image,label):
    with tf.GradientTape() as t:
        loss_step=loss(model,image,label)
    grads=t.gradient(loss_step,model.trainable_variables)
    optimizer.apply_gradients(zip(grads,model.trainable_variables))
    
def train(epochs):
    for epoch in range(epochs):
        for (batch,(image,label)) in enumerate(dataset):
            train_step(model,image,label)
        print('epoch{} is finished'.format(eopch+1))
        
train(n)  # n是训练轮数        
十.预训练网络
概念
  • 预训练网络是一个保存好的,之前已经在大型数据集(大规模图像分类任务)上训练好的卷积神经网络
  • 如果这个原始数据集足够大且足够通用,那么预训练网络学到的特征的空间层次结构可以作为有效的提取视觉世界特征的模型
  • 即使新问题和新任务与原始任务完全不同,学习到的特征在不同问题之间是可移植的,这也是深度学习与浅层学习相比的一个优势,他使得深度学习对于小数据问题十分有效
  • 关于预训练网络代码,见kaggle
Keras内置预训练网络
  • tf.keras模块包含了很多预训练网络(VGG16,VGG19,ResNet50,Inceptionv3,Xception)等
  • ImageNet是一个手动标注好类别的图片数据库,目前已有22000个类别
微调
  • 微调:冻结模型库底部的卷积层,共同训练新添加的分类器层和顶部部分的卷积层

    • 底部的卷积层提取的特征一般是通用特征(比如说纹理,细小规则等),顶部的卷积层会逐渐随着窗口视野的扩大(包括MaxPooling层的使用)会形成一些抽象的图形,所以,顶部卷积层会更加与特定的抽象任务相关,所以(比如特定的分类,猫狗分类等)
  • 这允许我们“微调”基础模型中的高阶特征,使他们与特定任务更相关

  • 只有分类器训练好了,才能微调卷积基的顶部卷积层(如果没有这样做的话,刚开始的训练误差很大,微调之前这些卷积层学到的表示会被破坏掉)

  • 微调步骤:

    • 1.在预训练卷积基上添加自定义层
    • 2.冻结卷积基所有层
    • 3.训练添加的分类层
    • 4.解冻卷积基的一部分层
    • 5.联合训练解冻的卷积层和添加的自定义层
Python实现
import tensorflow as tf
from tensorflow import keras 
import numpy as np
from tensorflow.keras import layers

# 加载keras自带的预训练网络VGG16
# weights=imagenet:表示使用VGG16网络对imagenet训练好的权重
# include_top=False:表示不使用VGG16后面的全连接层,仅使用前面的卷积基
conv_base=keras.applications.VGG16(weights='imagenet',include_top=False)
# 不训练已经带有权重的VGG网络
conv_base.trainable=False

model=keras.Sequential()
model.add(conv_base)  # 将VGG16网络添加到我们自定义的网络中
# 由于VGG16网络最后一层是MaxPooling2D层,输出维度为(None,None,None,512),为了和后面的全连接层连接,需要进行Flatten()操作,前面提到过,用GlobalAveragePooling2D效果更好
model.add(layers.GlobalAveragePooling2D())
model.add(laers.Dense(512,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

# 编译整个模型,并对整个模型进行训练(但是这个训练只训练了分类器层,原来的卷积基并没有被训练)
model.compile(optimizer=keras.optimizers.Adam(lr=0.0005),loss='binary_crossentropy',metrics=['xxx'])
history=model.fit(train_image_ds,steps_per_epoch=train_count//BATCH_SIZE,
                 epoch=xx,validation_data=test_image_ds,
                 validation_steps=test_count//BATCH_SIZE)

# 当模型训练出现过拟合时,解冻卷积层,和分类器一起训练
conv_base.trainable=True
# 这里需要根据过拟合出现的地方进行设置,一般可以通过画图,或者观察训练输出的数据判断是否出现过拟合
fine_tune_at=-x
# 前面的层仍然设置为不可训练,仅训练最后几层
for layer in conv_base.layers[:fine_tune_at]:
    layer.trainable=False
    
# 重新编译模型,这里要格外注意学习率,用极小的学习率下探    
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0005/10),loss='binary_crossentropy',metrics=['xxx'])  

initial_epochs=xx
fine_tune_epochs=xx
total_epochs=initial_epochs+fine_tune_epochs

history=model.fit(train_image_ds,steps_per_epoch=train_count//BATCH_SIZE,
                 epochs=total_epochs,initial_epochs=initial_epochs,
                 validation_data=test_image_ds,
                 validation_steps=test_count//BATCH_SIZE)
十一.模型的保存
保存整个模型
  • 整个模型可以保存到一个文件中,其中包含权重值,模型配置乃至优化器配置,这样,可以为模型设置检查点,并稍后从完全相同的状态继续训练,而不用访问原始代码

  • 在Keras中保存完全可以正常使用的模型是很有用的,可以在Tensorflow.js中加载他们,在网络浏览器中训练和运用他们

  • Keras只用HDF5标准提供基本的保存格式

  • model.save(path),这种方法保存的是1.权重值,2.模型配置(框架),3.优化器配置

  • 使用保存的模型:

    keras.models.load_model(path)

如何仅保存架构
  • 有时候我们只对模型的架构感兴趣,而无需保存权重值或者优化器,在这种情况下,我们可以仅保存模型的“配置”
  • json_config=model.to_json()是以json格式保存模型
  • reinitialized_model=keras.model.model_from_json(json_config)重新加载模型
  • 这种保存方法仅保存了模型的架构,没有经过编译,权重都是随机初始化的
仅保存权重
  • 有时候我们只需要保存模型的状态(其权重值),而对模型的架构不感兴趣,我们可以通过get_weights()获得其权重值,并通过set_weights()设置权重值

  • weights=model.get_weights(),可以将模型的权重赋值给weights变量

  • model.save_weights()可以保存模型的权重,保存到电脑的磁盘上

  • reinitialized_model.load_weights(path)可以用保存的模型架构,加载磁盘上的模型权重

  • 需要注意

    看似模型的架构+模型的权重=整个完整的模型,但其实不是,保存整个完整的模型还包括了模型的优化器配置,但是模型的架构,和权重都没有保存优化器配置,所以二者并不是相等关系

在训练期间保存检查点
  • 在训练期间或训练结束时自动保存检查点,这样一来,可以使用经过训练的模型,而无需重新训练模型,或从上次暂停的地方继续训练,以防训练过程中断

  • 回调函数:tf.keras.callback.ModelCheckpoint(path,monitor,verbose,save_best_only,save_weight_only,mode,period)

    参数:

    • path:保存模型文件的路径
    • monitor:监控,需要监控的数量
    • verbose:详细模式,0或1
    • save_best_only:如果为True,则不会覆盖根据监控数量的最新最佳模型
    • save_weights_only:如果为True,则仅保存模型权重
    • mode:{auto,min,max}之一,如果save_best_only=True,则根据监控数量的最大化或最小化来决定覆盖当前保存文件,对于val_acc,这应该是max,对于val_loss,这应该是min,在自动模式下,从监控量的名称自动推断方向
    • period:检查点之间的间隔
  • model.fit(train_image,train_label,epoch=xxx,callbacks=[cp_callback])

  • model.load_weights(path)

自定义训练中保存检查点
  • 直接看代码
Python代码实现
import tensorflow as tf
from tensorflow import keras 
from tensorflow.keras import layers
import pandas as pd
import numpy as np
import os

# ------------------------------------------------------------------
# 创建模型
(train_image,train_label),(test_image,test_label)=keras.datasets.fashion_mnist.load_data()
train_image=train_image/255
test_image=test_image/255

# 创建数据集
dataset=tf.data.Dataset.from_tensor_slices((train_image,train_label))
dataset=dataset.shuffle(xxxxx).batch(32)

model=keras.Sequential()
model.add(layers.Flatten(input_shape=train_image.shape[1:]))
model.add(layers.Dense(128,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))

model.compile(optimizer='adam',loss='sparse_categorical_crossentropy')
model.fit(train_image,train_label,epochs=5)
  • 保存整个模型
# 保存整个模型
path=r'xxxxx'
model.save(path)
# 加载模型,这里的new_model就和原模型一样,具有相同的结构,权重,优化器配置
new_model=keras.models.load_model(path)
# 用加载的模型评价,verbose:是否显示提示,0:不显示
new_model.evaluate(test_image,test_label,verbose=0)
  • 仅保存架构
# 这里的json_config就是当前模型的架构,是以json的格式保存
json_config=model.to_json()

# 加载架构
reinitialized_model=keras.models.model_from_json(json_config)
reinitialized_model.summary()  # 可以看到和原模型的架构一样
# 如果用现在的模型进行评估,得到的结果将不确定
reinitialized_model.evaluate(test_image,test_label,verbose=0)

# 所以需要重新编译模型
reinitialized_model.compile(optimizer='adam',loss='sparse_categorical_crossentropy')

# 重新训练
reinitialized_model.fit(train_image,train_label,epochs=xxx)
  • 仅保存权重
weights=model.get_weights()

# 用上面的架构加载保存的权重
reinitialized_model.set_weights(weights)

# 然后再次进行评价
reinitialized_model.evaluate(test_image,test_label,verbose=0)
# ps.可以看到,评价结果正常
  • 训练期间保存检查点
path='xxxx'

# 这里仅保存模型的权重
cp_callback=keras.callbacks.ModelCheckpoint(path,save_weights_only=True)

# 创建模型
model=keras.Sequential()
model.add(keras.layers.Flatten(input_shape=train_image.shape[1:]))
model.add(keras.layers.Dense(128,activation='relu'))
model.add(keras.layers.Dense(10,activation='softmax'))

# 编译模型
model.compile(optimizer='adam',loss='sparse_categorical_crossentropy')

# 训练模型
# 需要注意,要填入回调函数的参数,且是以列表的形式
model.fit(train_image,train_label,epoch=xxx,callbacks=[cp_callback])

# 重新构建一个模型model2
model2=keras.Sequential()
model2.add(keras.layers.Flatten(input_shape=train_image.shape[1:]))
model2.add(keras.layers.Dense(128,activation='relu'))
model2.add(keras.layers.Dense(10,activation='softmax'))

# 加载刚才保存的权重
model2.load_weight(path)

# 用新模型评价,可以看到,评价结果正常
model2.evaluate(test_image,test_label,verbose=0)
  • 自定义训练中保存检查点
# 创建模型
model=keras.Sequential()
model.add(layers.Flatten(input_shape=train_image.shape[1:]))
model.add(layers.Dense(128,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))

optimizer=keras.optimizers.Adam()
# 这里的from_logits参数,如果为True就表示输出层经过了激活函数
loss_func=keras.losses.SparseCategoricalCrossentropy(from_logits=True)

def loss(model,x,y):
    y_=model(x)
    return loss_func(y,y_)

train_loss=keras.metrics.Mean('train_loss',dtype=tf.float32)
train_accuracy=keras.metrics.SparseCategoricalAccuracy('train_accuracy')
test_loss=keras.metrics.Mean('test_lss',tf.float32)
test_accuracy=keras.metrics.SparseCategoricalAccuracy('test_accuracy')

def train_step(model,image,label):
    with tf.GradientTape() as t:
        pred=model(image)
        loss_step=loss_func(label,pred)
    grads=t.gradient(loss_step,model.trainable_variables)
    optimizer.apply_gradients(zip(grads,model.trainable_variables))
    train_loss(loss_step)
    train_accuracy(label,pred)
    
cp_dir='xxx'
cp_prefix=os.path.join(cp_dir,'自己指定的文件名!!!!!')

checkpoint=tf.train.Checkpoint(optimizer=optimizer,model=model)

def train():
    for epoch in range(xxx):
        for (batch,(image,label)) in enumerate(dataset):
            train_step(model,image,label)
        print('epoch{},loss is {}'.format(epoch+1,train_loss.result()))
        print('epoch{},accuracy is {}'.format(epoch+1,train_accuracy.result()))
        train_loss.reset_state()
        train_accuracy.reset_state()
        if (eopch +1) %2 ==0:  # 这里的意思是每2个训练epoch保存一次
            checkpoint.save(file_prefix=cp_prefix)
            
# 查看最新的检查点
tf.train.latest_checkpoint(cp_dir)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值