定义:二分类线性分类模型,其输入为实例的特征向量,输出为实例的类别,为1或-1
简单说,就是在平面坐标轴画一条直线,把点分为两类
单层感知器的局限性:因为处理函数是线性的,所以不管中间怎么处理,输出都是线性的,最后的结果能表示的范围也很小,由于它只有一层功能神经元,所以学习能力有限
多层感知器(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+e−x1,其导数为: δ ′ ( 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+e−xex−e−x,其导数为: ( 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=1−tanh2x,,从图像可看出,函数的取值范围为[-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=1nxi⋅wi+b),其中f(x)为激活函数,一个节点的输出是下一个节点的输入
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()
在机器学习算法中,我们经常会遇到分类特征,例如:人的性别有男女,国家有中国,美国,法国…这些特征值不是连续的,而是离散的,无序的,通常我们需要对其进行特征数字化
什么是特征数字呢?例子如下:
独热编码(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]
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编码后,编码后的特征其实每一维度的特征都可以看做是连续的特征,就可以跟对连续型特征的归一化方法一样,对每一维特征进行归一化,比如归一化[-1,1],或归一化均值为0,方差为1
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=...)
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)
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)) # 参数表示丢弃率
...
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工作是指:给定一张图像,让它经历一系列 [卷积层,非线性层(激活层),池化层(下采样(downshampling)) ],和全连接层,得到最终输出,该输出最好的描述了图像内容的一个单独分类或者一组分类的概率
什么是卷积:
[卷积层(conv2D) -> 非线性变换层(激活层 relu/sigmoid/tanh)-> 池化层(pooling2D)]
x n -> 全连接层
如果没有这些层,模型很难与复杂模式匹配,因为网络将有过多的信息填充,其他那些层作用就是突出重要信息,降低噪声
卷积层
池化层
常用的池化层有:
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
数据标准化让机器学习模型看到的样本彼此间更加相似,有助于模型的学习以及对新数据的泛化
常见形式
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的梯度
增加数据维度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是训练轮数
微调:冻结模型库底部的卷积层,共同训练新添加的分类器层和顶部部分的卷积层
这允许我们“微调”基础模型中的高阶特征,使他们与特定任务更相关
只有分类器训练好了,才能微调卷积基的顶部卷积层(如果没有这样做的话,刚开始的训练误差很大,微调之前这些卷积层学到的表示会被破坏掉)
微调步骤:
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)
有时候我们只需要保存模型的状态(其权重值),而对模型的架构不感兴趣,我们可以通过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)
参数:
model.fit(train_image,train_label,epoch=xxx,callbacks=[cp_callback])
model.load_weights(path)
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)