深度学习框架之Tensorflow2
Tensorflow2版本的介绍
Tensorflow(简称tf)是深度学习框架,大大简化了建模的方法和步骤,把Keras Api当作核心,使用非常简单,跨平台,集成各种现成模型,eager mode使得调试起来不那么难受。
TensorFlow2的官方文档
Tensorflow2版本的安装
1.首先得提前安装好Anaconda环境,可以访问官方网站下载最新版。
先确定号conda中的python环境是否是Python 3.6–3.9直接的版本。
可以使用依次执行以下命令:
conda activate base # 激活conda中的虚拟环境
python --version # 查看虚拟环境中的python版本
执行结果:
C:\Users\24307>conda activate base
(base) C:\Users\24307>python --version
Python 3.9.12
(base) C:\Users\24307>
2.其次是安装Tensorflow的第三方库。
CPU版本:pip install tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple/
GPU版本:pip install tensorflow-gpu
# 安装GPU版本要注意,需要配置号CUDA10版本
Tensorflow2的初体验
导包
导入Tensorflow2,并且结合numpy包
# 导包
import tensorflow as tf
import numpy as np
查看版本
print(tf.__version__)
# 本篇使用的是2.18.0版本
# Eager Execution 模式
x = [[1,]]
m = tf.matmul(x,x) # 使用 TensorFlow 的 matmul 函数对 x 和自身进行矩阵乘法运算
print(m)
'''
<tf.Tensor: shape=(1, 1), dtype=int32, numpy=array([[1]], dtype=int32)>
'''
构建矩阵
x = tf.constant([[1,9],[3,6]])
print(x)
'''
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[1, 9],
[3, 6]], dtype=int32)>
'''
扩展:Tensor格式,里面可以是一个值,可以是一个向量,可以是一个矩阵,也可以是个多维数据。可以进行GPU加速计算的矩阵
与numpy进行交互
# 将Tensor转成numpy类的数据
x = tf.constant([[1,9],[3,6]])
x.numpy()
'''
array([[1, 9],
[3, 6]], dtype=int32)
'''
数据类型转换
# 将数据转成float32类型
x = tf.constant([[1,9],[3,6]])
x = tf.cast(x,tf.float32)
'''
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[1., 9.],
[3., 6.]], dtype=float32)>
'''
numpy与Tensor的计算
# numpy 与 Tensor计算
x1 = np.ones([2,2])
x2 = tf.multiply(x1,2)
x2
'''
<tf.Tensor: shape=(2, 2), dtype=float64, numpy=
array([[2., 2.],
[2., 2.]])>
'''
深度学习概述
深度学习解决的问题
首先来谈谈机器学习的流程:
- 数据获取
- 特征工程
- 建立模型
- 评估与应用
其中,特征工程是四个步骤中,最难的一步。
机器学习绝大多数情况下是偏人工的,要人工的选择数据,要人工的提取特征,要人工的选择算法,再人工的得出结果。并不具备真正意义上的智能。
而深度学习一定程度上解决了机器学习中的人工的问题,可以让网络可以真正的学习, 什么样的特征是比较合适的,该怎么组合比较合适 的,这是深度学习能达到的境界。
特征工程的作用:
- 数据特征决定了模型的上限
- 预处理和特征提取是最核心的
- 算法与参数选择决定了如何逼近这个上限
深度学习的核心是神经网络,而神经网络是一个巨大的黑盒子,把原始数据拿到手之后,会做很多很多的操作,能够自动的提取的各种的特性。深度学习是真正的能够进行学习的。
深度学习缺陷,速度比不过机器学习。
深度学习的应用领域
深度学习的应用领域越来越广泛,比如无人驾驶,计算机视觉,自然语言处理,人脸识别,医学,变脸,分辨率重构……
而IMGENET提供了非常庞大的数据集,供人使用。
深度学习要应用到上百万的数据集以上,不适合用少量数据集。
CIFAR-10 提供了10类标签,50万个训练数据,1万个测试数据,但是数据体检大小均为32*32 ,比较小。比较适合个人笔记本测试学习使用。
计算机视觉任务
图像分类任务:
假设我们有一系列的标签:狗,猫,汽车,飞机等,让计算机识别出哪个是猫类。
图像在计算机眼中,就是一个矩阵,而矩阵内就是一些值。
一张图像被表示成三维数组的形式,每个像素的值从0到255
例如:300*100*3,其中3表示颜色通道。
计算机视觉面临的挑战:照射角度,形状改变,部分遮蔽,背景混入……
深度学习常规套路:
- 收集数据并给定标签
- 训练一个分类器
- 测试,评估
机器学习的套路亦是如此。
k近邻计算流程
- 计算已知类别数据集中的点与当前点的距离
- 按照距离依次排序
- 选取与当前点距离最小的k个点
- 确定前k个点所在类别的出现概率
- 返回前k个点出现频率最高的类别作为当前点预测分类
k近邻不适合用来图像分类
因为背景主导是一个最大的问题,k近邻算法关注的却是主体(主要成分)。
k近邻算法
将背景相同的数据集放在一起,它不知道哪个是主体,哪个是背景,没有一个学习的过程。
搭建神经网络进行气温预测
这里提前准备了temp.csv
文件,里面存放着本次要案例测试的数据。
temp.csv中的字段解释:
year:年
month : 月
day : 日
week : 星期
temp_2 : 前天的温度
temp_1 : 昨天的温度
average : 当日在历史上的平均温度
actual : 实际当前温度(实际预测指标)
friend :
其中 year
, month
, day
, week
, temp_2
, temp_1
,average
均可以看作x,actual
看作y。
具体代码
# 导包
import numpy as np
import pandas as pd
import matplotlib.pylot as plt
import tensorflow as tf
from tensorflow.keras import layers # 当构建模型的时候,用keras的api构建模型
import tensorflow.keras
import warnings
warngings.filterwarnings("ignore")
%matplotlib inline
# 读取数据
features = pd.read_csv('temps.csv')
print(features.head())
print("数据维度:",features.shape)
# 处理时间数据(这个可以自己定义时间的格式)
import datetime
years = features['year']
months = features['month']
days = features['day']
# datetime格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]
# 绘制图像
# 准备画图
# 指定默认风格
plt.style.use('fivethirtyeight')
# 设置布局
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize = (10,10))
fig.autofmt_xdate(rotation = 45)
# 标签值
ax1.plot(dates, features['actual'])
ax1.set_xlabel(''); ax1.set_ylabel('Temperature'); ax1.set_title('Max Temp')
# 昨天
ax2.plot(dates, features['temp_1'])
ax2.set_xlabel(''); ax2.set_ylabel('Temperature'); ax2.set_title('Previous Max Temp')
# 前天
ax3.plot(dates, features['temp_2'])
ax3.set_xlabel('Date'); ax3.set_ylabel('Temperature'); ax3.set_title('Two Days Prior Max Temp')
# 我的逗逼朋友
ax4.plot(dates, features['friend'])
ax4.set_xlabel('Date'); ax4.set_ylabel('Temperature'); ax4.set_title('Friend Estimate')
plt.tight_layout(pad=2)
好了,以上步骤都是准备阶段。
数据预处理
数据归一化
temp.csv
文件中有一个特殊的字段week,其他字段都是数值,只有week字段是字符的形式。
所以要week字符串 转成 数值的形式,转成数值的形式方式有很多种,比如用数值映射,dummies(是谁,谁标1)
# 做dummies,独热编码
features = pd.get_dummies(features)
features.head(5)
指定好train_data 和train_labels值
# 标签
labels = np.array(features['actual'])
# 在特征中去掉标签,将actual列去除
features = features.drop('actual',axis=1) # x
# 单独保存名字,以备后患
features_list = list(features.columns)
# 转换成合适的格式
features = np.array(features)
print(features.shape) # 查看数据形状
标准化操作
# 标准化操作
from sklearn import preprocessing
input_features = preprocessing.StandardScaler().fit_transform(features)
print(input_features[0])
基于Keras构建网络模型
按顺序构造网络模型
# y = wx + b 模型
model = tf.keras.Sequential()
# layers模块中有许多层的实现,Dense是连接层
mode.add(layers.Dense(16)) # 第一个隐藏层
mode.add(layers.Dense(32)) # 第二个隐藏层
mode.add(layers.Dense(1)) # 输出结果个数
'''
像上面的16,32是自己指定的,但是没有什么依据,也可以尝试其他数值
'''
网络模型训练
常用参数:
activation
: 激活函数的选择,一般常用relukernel_initializer
,bias_initializer
: 权重与偏置参数的初始化方法,有时候不收敛换种初始化就突然不好使了,就很神奇kernel_regularizer
,bias_regularizer
: 要不要加入正则化inputs
: 输入,可以自己指定,也可以让网络自动选units
: 神经元个数
指定好优化器和损失函数等
一般用迭代的思想优化网络模型
# compile相当于对网络进行配置
model.compile(optimizer=tf.keras.optimizers.SGD(0.001),loss='mean_squared_error')
# optimizer 指定优化器 SGD梯度下降
# loss 指定损失函数,要指定合适的损失函数
# 不同的损失函数 对 最终的结果 影响会很大
开始训练
model.fit(input_features,labels,validation_split=0.25,epochs=10,batch_size=64)
'''
x: input_features 上面的标准化操作后的数据
y: labels 来自于上面的np.array(features['actual'])
验证级 : validation_split=0.25 也就是25%
迭代次数(遍历数据集的轮数):epochs
每次迭代的个数:batch_size
'''
训练出来的数据,loss 表示训练上的损失值,val_loss 表示验证级损失值,当两者的数据相差很大的时候,说明当前网络模型过拟合了,当然要避免过拟合的情况。
# 将网络模型从上到下 展示出来
model.summary()
input_features.shape # (348,14) 14个特征
'''
dense 第一层
dense_1 第二层
dense_2 第三层
parms 各层的值
w1+b1 : 14*16+16 # 16表示隐层神经元 【240】
w2+b2 : 32*16+32 # 【544】
w3+b3 : 32*1+1 # 【33】
'''
以下是上述代码的输出结果:
Model : ”sequential“
Layer(type) Output Shape(输出形状) Param#(训练参数) dense(Dense) multiple 240 dense_1 (Dense) multiple 544 dense_2 (Dense) multiple 33 Total params:817
Trainable params:817
Non-trainable params:0
更新初始化方法
model = tf.keras.Sequential()
model.add(layers.Dense(16,kernel_initializer="random_normal"))
model.add(layers.Dense(32,kernel_initializer="random_normal"))
model.add(layers.Dense(1,kernel_initializer="random_normal"))
'''
kernel_initializer=random_normal
指定权重为 随机高斯分布
'''
# 指定优化器和损失函数
model.compile(optimizer=tf.keras.optimizers.SGD(0.001),loss='mean_squared_error')
# 开始训练
model.fit(input_features,labels,validation_split=0.25,epochs=100,batch_size=64)
此时,loss 与 val_loss 相差就减少了。
加入正则化惩罚项
# kernel_regularizer=tf.keras.regularizers.l2(0.03) 加入正则化惩罚项
model = tf.keras.Sequential()
model.add(layers.Dense(16,kernel_initializer='random_normal',kernel_regularizer=tf.keras.regularizers.l2(0.03)))
model,add(layers.Dense(32,kernel_initializer='random_normal',kernel_regularizer=tf.keras.regularizers.l2(0.03)))
model.add(layers.Dense(1,kernel_initializer='random_normal',kernel_regularizer=tf.keras.regularizers.l2(0.03)))
model.compile(optimizer=tf.keras.optimizers.SGD(0.001),loss='mean_squared_error')
model.fit(input_features,labels,validation_split=0.25,epochs=100,batch_size=64)
可以发现fit出来的结果,loss 与 val_loss 相差比之前更小了
预测模型结果
当得到训练好的权重参数,用权重参数 把三层网络模型 走一遍
# input_features 其实就是 x 的值
predict = model.predict(input_features)
predict.shape # (348,1)
测试结果并进行展示
这步骤 可以忽略
# 数据处理
# 转换日期格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]
# 创建一个表格来存日期和其对应的标签数值
true_data = pd.DataFrame(data = {'date': dates, 'actual': labels})
# 同理,再创建一个来存日期和其对应的模型预测值
months = features[:, features_list.index('month')]
days = features[:, features_list.index('day')]
years = features[:, features_list.index('year')]
test_dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
test_dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in test_dates]
predictions_data = pd.DataFrame(data = {'date': test_dates, 'prediction': predict.reshape(-1)})
# 真实值
plt.plot(true_data['date'], true_data['actual'], 'b-', label = 'actual')
# 预测值
plt.plot(predictions_data['date'], predictions_data['prediction'], 'ro', label = 'prediction')
plt.xticks(rotation = '60');
plt.legend()
# 图名
plt.xlabel('Date'); plt.ylabel('Maximum Temperature (F)'); plt.title('Actual and Predicted Values');
分类任务
准备数据集
这里是从网络上下载mnist的数据集
%matplotlib inline
from pathlib import Path
import requests
DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"
'''
parents=True : 递归创建所有必要的父级目录
exist_ok=True :当目标目录已存在时,不会抛出异常
'''
PATH.mkdir(parents=True, exist_ok=True)
URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"
if not (PATH / FILENAME).exists():
content = requests.get(URL + FILENAME).content
(PATH / FILENAME).open("wb").write(content)
但是下载的速度比较慢,如果不愿意等待的话,那可以直接用已经下载好的压缩包文件mnist.pkl.gz
读取压缩包中的数据
import pickle # 用于 序列化 和 反序列化 Python对象
import gzip # 用于读写gzip压缩的文件
#as_posix(),将路径转换成 POSIX 格式的字符串
with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
'''
x_train:训练集的特征数据。
y_train:训练集的标签数据。
x_valid:验证集的特征数据。
y_valid:验证集的标签数据。
_:表示忽略加载数据中的第三个部分(通常是测试集,但在这里被忽略)。
'''
展示数据集
# 展示数据集
from matplotlib import pyplot
import numpy as np
pyplot.imshow(x_train[0].reshape((28,28)),cmap="gray")
print(x_train.shape)
'''
(50000,784)
784是mnis数据集每个样本的像素点个数
'''
构建网络模型
# 导包
import tensorflow as tf
from tensorflow.keras import layers
# 创建Sequential模型,通过简单的堆叠来构建模型
model = tf.keras.Sequential()
# Dense() 是全连接层(密集层)
model.add(layers.Dense(32,activation='relu')) # 第一层
model.add(layers.Dense(32,activation='relu')) # 第二层
model.add(layers.Dense(10,activation='softmax')) # 输出层 使用Softmax激活函数
选择损失函数和优化器
# Adam优化器,基于梯度下降的优化算法,0.005是学习率
# 当目标执行多类分类,并且标签是以整型形式给出,就可以使用 SparseCategoricalCrossentropy (稀疏分类交叉熵)损失函数
# metrics 设置评估指标,在训练过程中跟踪稀疏分类(SparseCategoricalAccuracy)准确率作为性能评估指标
model.compile(optimizer=tf.keras.optimizers.Adam(0.005),
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])
开始训练
model.fit(
x_train, # x
y_train, # y
epochs=5, # 训练周期
batch_size=64, # 批量大小
validation_data=(x_valid, y_valid) # 验证数据
)
'''
validation_data=(x_valid, y_valid):
validation_data 提供了一组用于验证的数据。在每个epoch结束时,模型将在这些验证数据上进行评估,以监控模型在未见过的数据上的表现。
x_valid 是验证数据的输入特征。
y_valid 是验证数据的真实标签。
'''
Tensor.data模块常用函数
转成Tensor格式数据
在使用Tensorflow框架的时候,建议使用Tensor格式的数据,但是有些数据不是该格式,那就可以用tf.data模块中的函数来转成Tensor格式的数据。
# 随便构建一组非Tensor的数据
import numpy as np
input_data = np.arange(16)
print(input_data)
# 将input_data 转成Tensor个数的数据
dataset = tf.data.Dataset.from_tensor_slices(input_data)
# 循环遍历Tensort数据
for data in dataset:
print(data)
repeat操作
格式:.repeat(次数)
重复构建数据
# 重复构建input_data数据
dataset = tf.data.Dataset.from_tensor_slices(input_data)
dataset = dataset.repeat(2) # 重复构建两次
# 循环遍历
for data in dataset:
print(data)
batch操作
设置每个Tensor中的数据数量
dataset = tf.data.Dataset.from_tensor_slices(input_data)
dataset = dataset.repeat(2).batch(4) # 每个Tensor中有4个数据
for data in dataset:
print(data)
shuffle 操作
shuffle翻译是洗牌的意思,将Tensor数据中的数据顺序打乱
# buffer_size(缓存区)必传参数
dataset = tf.data.Dataset.from_tensor_slices(input_data).shuffle(buffer_size=10).batch(4)
for data in dataset:
print(data)
重新训练
train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train = train.batch(32)
train = train.repeat()
valid = tf.data.Dataset.from_tensor_slices((x_valid, y_valid))
valid = valid.batch(32)
valid = valid.repeat() # 使用 .repeat() 会使数据集无限重复
'''
train : 训练集
validation_data : 验证集
epochs : 训练周期
steps_per_epoch : 每个周期训练的步数,(每个步处理一个批次)
validation_data : 每个周期验证的步数,(每个步处理一个批次)
'''
model.fit(train, epochs=5,steps_per_epoch=100, validation_data=valid,validation_steps=100)
练手——fashion数据集
fashion数据集简介
Fashion MNIST 数据集由 Zalando (一家德国的时尚技术平台) 提供,它旨在作为手写数字识别任务(MNIST 数据集)的一个更复杂的替代品。Fashion MNIST 包含了 70,000 张 28x28 像素的灰度图像,分为 10 个类别,每个类别有 7,000 张图片。这些图像是各种服装和配饰的图片,如T恤、裤子、鞋子等。
以下是 Fashion MNIST 数据集的一些关键特点:
- 图像大小:每张图像都是 28x28 像素。
- 图像格式:灰度图像,即每个像素值范围从 0 到 255,其中 0 表示黑色,255 表示白色。
- 类别数量:10 个类别,每个类别对应一种特定的服装或配饰。
- 数据划分:数据集被划分为训练集和测试集,训练集包含 60,000 张图像,测试集包含 10,000 张图像。
- 类别分布:每个类别在训练集和测试集中都有均匀的分布。
类别说明
0
- T-shirt/top 上面的衣服1
- Trouser 下面的裤子2
- Pullover 冷天穿的上衣3
- Dress 连衣裙4
- Coat 外套5
- Sandal 凉鞋6
- Shirt 衬衫7
- Sneaker 运动鞋8
- Bag 包9
- Ankle boot 踝靴
Fashion MNIST 实际数据集中的图像没有背景色,是纯灰度的。这些图像通常以 numpy 数组的形式存储,每个数组的形状为 (28, 28),数组中的每个元素表示一个像素的灰度值。
准备 fashion 数据集
通过tensorflow 下载 fashion 数据集
# 通过tensorflow 来下载 fashion 数据集
from tensorflow import keras
# 下载
fashion_mnist = keras.datasets.fashion_mnist
(train_images,train_labels),(test_images,test_labels) = fashion_mnist.load_data()
'''
train_images: 训练集的图像数据,形状为 (60000, 28, 28),表示 60,000 张 28x28 像素的灰度图像。
train_labels: 训练集的标签数据,形状为 (60000,),表示 60,000 个标签,每个标签是一个从 0 到 9 的整数。
test_images: 测试集的图像数据,形状为 (10000, 28, 28),表示 10,000 张 28x28 像素的灰度图像。
test_labels: 测试集的标签数据,形状为 (10000,),表示 10,000 个标签,每个标签是一个从 0 到 9 的整数。
'''
fashion 数据集下载到了 C:\Users\24307.keras\datasets 目录下
图像数据 是指 全部的图像
标签数据 是指 全部图像的分类,每个分类对应0-9之间的整数
数据预处理
将数据集中的数字标签映射到人类可读的类别名称
在处理Fashion MNIST数据集时,通常会定义这样一个class_names
列表来帮助解释模型的预测结果。例如,当模型对一张图片的预测结果为3时,可以通过class_names[3]
获取到对应的类别名"Dress",从而知道模型预测这张图片属于"Dress"类别。
# 定义class_name字段 图像分类
class_names = [
'T-shirt/top','Trouser','Pullover','Dress','Coat',
'Sandal','Shirt','Sneaker','Bag','Ankle boot'
]
# 定义class_names列表是为了将数据集中的数字标签映射到人类可读的类别名称。
当然,可以尝试的查看以下 训练集中的图片数据
# 尝试 查看fashion 数据集中的 数据形状
train_images.shape # (60000, 28, 28)
此时train_images[]
中的每个下标都代表着不同的服装,比如说train_images[0]
就是一个鞋子,train_images[1]
就是一件上衣
绘制train_images中的图像
# 绘制 fashion数据集中的 图像
import matplotlib.pyplot as plt
plt.figure(figsize=(2,2)) # 准备画布
plt.imshow(train_images[0]) # 在画布绘制图像
plt.colorbar() # 给图像添加热度条
# plt.grid(False) # 取消图像中的网格线,默认是Ture
plt.show() # 展示
数据归一化
就是将 数据 转换成 0到1之间的数
# 数据归一化
train_images = train_images / 255.0
test_images = test_images / 255.0
绘制 train_labels中的 0-25之间的图像
# 绘制 train_labels 中的 25张图
# 绘制画布
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1) # 绘制子画布
plt.xticks([]) # x轴的刻度
plt.yticks([]) # y轴的刻度
plt.xlabel(class_names[train_labels[i]]) # 设置x轴标签
plt.grid(False) # 取消刻度
plt.imshow(train_images[i],cmap=plt.cm.binary) # 展示子图片
plt.show() # 展示所有图片
构建网络模型
'''
keras.Sequential() 用于线性堆叠网络层(顺序模型)
Flatten层 : 将输入张量的所有非批量维度展平成一维的维度,
input_shape参数:指定输入张量的形状,不包括批量维度。
'''
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28,28)),
keras.layers.Dense(128,activation="relu"),
keras.layers.Dense(10,activation="softmax")
])
选择模型构建三要素
选择优化器和损失函数 和 评估指标
# 选择Adam优化器 和 sparse_categorical_crossentropy损失函数 和 accuracy评估指标
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=['accuracy']
)
开始训练
'''
x : 输入数据
y : 目标数据(标签)
batch_size : 每个批次的样本数
epochs : 训练的轮数
validation_data :验证数据, 用于验证的输入数据和目标数据。在每个 epoch 结束时,模型会使用这些数据评估性能
'''
model.fit(train_images,train_labels,epochs=10)
以下是训练出来的结果:
Epoch 1/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.1027 - loss: 2.3028
Epoch 2/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.1005 - loss: 2.3028
Epoch 3/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0989 - loss: 2.3027
Epoch 4/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0984 - loss: 2.3027
Epoch 5/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.1013 - loss: 2.3026
Epoch 6/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0980 - loss: 2.3028
Epoch 7/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0998 - loss: 2.3027
Epoch 8/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0981 - loss: 2.3027
Epoch 9/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0962 - loss: 2.3028
Epoch 10/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0995 - loss: 2.3027
[100]:
<keras.src.callbacks.history.History at 0x2145e1e67d0>
以上的训练参数是:
x/1875
表示 一共训练1875个批次,当前是第x个批次
4s
表示每个epoch的训练时间大约是4秒
2ms/step
表示每个批次的平均处理时间大约是2毫秒
accuracy
表示准确率
loss
表示损失
总体来看:
loss损失值始终保持在较高的水平(2.3027 到 2.3028),这进一步证实了模型没有有效地学习到数据的模式。
accuracy准确率始终徘徊在10% 左右,这几乎是随机猜测的水平(10 个类别,随机猜测的准确率约为 10%)。
这表明模型没有学会从输入数据中提取有用的特征。
评估操作
评估模型在测试集上的性能
# evaluate() 方法会计算模型在数据上的损失值 和 准确率
test_loss,test_acc = model.evaluate(test_images,test_labels,verbose=2)
print('\nTest accurary:',test_acc)
'''
test_images: 测试集的输入数据。
test_labels: 测试集的目标数据(标签)
verbose : 控制评估过程的输出详情度,0表示不输出任何信息,1表示输出进度条,2表示每个epoch输出一行日志
exaluate() 方法会返回一个包含损失值 和 准确率 的列表。通常,损失值是第一个元素,准确率是第二个元素。
'''
以下是本次案例的输出结果
313/313 - 0s - 1ms/step - accuracy: 0.8799 - loss: 0.3474
# 处理了313次批次,模型的准确率为87.99%,意味着模型在测试集上正确分类了约87.99%的样本,损失值为0.3474
# 损失值是模型预测值与实际值之间差异的度量,通常越低越好。
Test accurary: 0.8798999786376953
# Test accurary是打印输出时的 提示信息,测试集的准确率是 0.8798999786376953
总结
- 评估过程:
- 模型在测试集上进行了评估,共处理了 313 个批次。
- 每个批次的平均处理时间为 1 毫秒。
- 模型在测试集上的准确率为 87.99%,损失值为 0.3474。
- 打印的测试准确率:
- 打印出的测试准确率为
0.8798999786376953
,与评估过程中的0.8799
一致。
模型预测
'''
predict() 方法用于模型预测,对新的图像数据进行预测,通过传入的数据是 测试集 数据
该方法会返回 num_samples(测试集中的样本数量) 和 num_classes(类别的数量,对于fashion数据集,就是10)
'''
predictions = model.predict(test_images)
print(predictions[0]) # predictions[i] 是一个长度为 10 的一维数组,表示模型对第 i 个样本属于每个类别的概率分布。
'''
model.predict(test_images) 会将 test_images 作为输入,通过模型的各个层进行前向传播,最终输出每个样本属于每个类别的概率。
'''
找出模型认为可能性最大的数据
np.argmax
是 NumPy 库中的一个函数,用于找到数组中最大值的索引。
# 找出 模型认为该样本最有可能属于的类别
np.argmax(predictions[0]) # np.int64(9)
对于fashion数据集来说,predictions[0]
中 的图像 属于 编号为9的类型,而编号为9对应 Ankle boot
也就是踝靴
。
预测模型的效果展示
其中蓝色表示正确,红色表示错误
定义函数,用来绘制 预测模型 的 结果
def plot_image(i, predictions_array, true_label, img):
predictions_array, true_label, img = predictions_array, true_label[i], img[i]
plt.grid(False)
plt.xticks([])
plt.yticks([])
plt.imshow(img, cmap=plt.cm.binary)
predicted_label = np.argmax(predictions_array)
if predicted_label == true_label:
color = 'blue'
else:
color = 'red'
plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
100*np.max(predictions_array),
class_names[true_label]),
color=color)
def plot_value_array(i, predictions_array, true_label):
predictions_array, true_label = predictions_array, true_label[i]
plt.grid(False)
plt.xticks(range(10))
plt.yticks([])
thisplot = plt.bar(range(10), predictions_array, color="#777777")
plt.ylim([0, 1])
predicted_label = np.argmax(predictions_array)
thisplot[predicted_label].set_color('red')
thisplot[true_label].set_color('blue')
预测案例一:
i = 0
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i], test_labels)
plt.show()
可见上述的预测,是正确的,图像是 踝靴,预测出来的也是 踝靴。
预测案例二:
i = 12
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i], test_labels)
plt.show()
但是上述的预测,就有点问题,其实图像是 运动鞋,但是预测出来的是 凉鞋
保存训练好的网络模型
保存整个模型
保存的内容
- 模型架构
- 保存模型的结构,包括所有层的配置和连接方式。
- 模型权重
- 保存模型在训练过程中学到的权重参数。
- 优化器状态
- 保存优化器的状态,包括学习率、动量等,这样在重新加载模型时可以从上次训练的状态继续训练。
整个模型包括架构、权重和优化器状态
# 保存训练好的模型
# model.save('fashion_model.h5') # 本地会多出一个fashion_model.h5文件,
model.save('fashion_model.keras') # h5格式的文件已经过时,现在建议用keras格式的文件
'''
model: 你已经训练好的 Keras 模型对象。
.h5 是 HDF5 文件格式,常用于保存 Keras 模型。
'''
网路架构转JSON字符串
to_json()
将模型的配置(即模型的结构、层信息、层的配置等)序列化为一个 JSON 格式的字符串。
要注意的是,这个 JSON 字符串仅包含模型的配置信息,不包含模型的权重。如果你想要保存和加载包含权重的完整模型,你应该使用 model.save()
方法(推荐使用 .keras
格式或 tf.saved_model
格式)而不是 to_json()
。
将网络架构 序列化成 JSON格式的字符串
# 只保存网络架构
config = model.to_json()
print(config)
将序列化后的JSON字符串保存到 json 文件中
with open('config.json','w') as json:
json.write(config)
读取网络架构
# 读取保存的网路架构
'''
models.model_from_json() 是 Keras 中用于从 JSON 格式的字符串重建模型架构的方法
其中config参数 是 上面保存的网络架构
'''
model = keras.models.model_from_json(config)
model.summary()
以下是输出结果:
Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ flatten_1 (Flatten) │ (None, 784) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_5 (Dense) │ (None, 128) │ 100,480 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_6 (Dense) │ (None, 10) │ 1,290 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 203,542 (795.09 KB)
Trainable params: 101,770 (397.54 KB)
Non-trainable params: 0 (0.00 B)
Optimizer params: 101,772 (397.55 KB)
权重参数的操作
权重参数(weight) 决定了模型如何对输入数据进行变换以生成输出。
**权重参数 **在训练过程中通过优化算法(如梯度下降)不断调整,以使模型能够更好地拟合训练数据并进行准确的预测。
权重参数定义 是神经网络中连接不同神经元之间的数值。每个神经元的输出是其输入的加权和,再经过激活函数处理。
权重参数的作用:特征提取、决策边界、非线性变换。
权重参数的HDF5文件是以 .weights.h5 结尾
获取权重参数
weights = model.get_weights()
print(weights)
保存权重参数
model.save_weights('weights.weights.h5')
加载权重参数
# 加载 保存的权重参数
model.load_weights('weights.weights.h5')
读取网络模型
读取已经训练好了的网络模型 fashion_model.keras,然后拿来进行 模型预测
准备数据
导包
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow import keras
准备已经训练好的网络模型文件 和 fashion数据集
# 加载已经训练好的 网络模型 文件
model = keras.models.load_model("fashion_model.keras")
# 加载fashion数据集
fashion_mnist = keras.datasets.fashion_mnist # 通过 tensorflow 下载 fashion_mnsit 数据,下载到本地磁盘的 C:\Users\24307.keras\datasets 目录下
(tarin_images,tarin_labels),(test_images,test_labels) = fashion_mnist.load_data() # 加载数据
数据预处理
将 测试集 和 训练集的 数据进行归一化 到 0-1区间的数
# 数据归一化
tarin_images = tarin_images / 255.0
test_images = test_images / 255.0
预测模型
使用已经构建好的网络模型 来 预测 fashion中的数据集
# 模型预测
predictions = model.predict(test_images)
# 查看模型预测后的部分结果
predictions[0]
'''
输出结果:
array([2.0889627e-06, 1.0242358e-06, 4.6287091e-08, 1.1241977e-11,
6.7687038e-06, 2.1740841e-03, 2.5636136e-06, 1.0373446e-02,
8.4099341e-08, 9.8743975e-01], dtype=float32)
'''
展示预测结果
1.定义一个方法,用来绘制模型的图像
import matplotlib.pyplot as plt
import numpy as np
def plot_image(i, predictions_array, true_label, img):
predictions_array, true_label, img = predictions_array, true_label[i], img[i]
plt.grid(False)
plt.xticks([])
plt.yticks([])
plt.imshow(img, cmap=plt.cm.binary)
predicted_label = np.argmax(predictions_array)
if predicted_label == true_label:
color = 'blue'
else:
color = 'red'
plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
100*np.max(predictions_array),
class_names[true_label]),
color=color)
def plot_value_array(i, predictions_array, true_label):
predictions_array, true_label = predictions_array, true_label[i]
plt.grid(False)
plt.xticks(range(10))
plt.yticks([])
thisplot = plt.bar(range(10), predictions_array, color="#777777")
plt.ylim([0, 1])
predicted_label = np.argmax(predictions_array)
thisplot[predicted_label].set_color('red')
thisplot[true_label].set_color('blue')
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
2.用定义的方法,来绘制模型预测的图像
num_rows = 5
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):
plt.subplot(num_rows, 2*num_cols, 2*i+1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(num_rows, 2*num_cols, 2*i+2)
plot_value_array(i, predictions[i], test_labels)
plt.tight_layout()
plt.show()
CNN卷积神经网络
卷积神经网络的概述
卷积神经网络(CNN) 可以用于检测任务,分类和检索(比如淘宝的拍照搜索),超分辨率重构,医学任务,无人驾驶,人脸识别
卷积神经网络与传统的神经网络相比,减少了矩阵的大小,解决了训练速度慢以及过拟合的情况
与传统的神经网络相比,卷积神经网络输入的数据是个三维的数据,也就是(h*w*c)。
也就是说传统的神经网络的输入层是一个一位数据,即使是一张28*28大小的图,也会被输入成784,但是卷积神经网络输入的依然还是28*28*1的多维数据。
卷积神经网络整体架构
输入层 : 输入的数据,例如 28*28
卷积层 : 提取特征
池化层 : 压缩特性
全连接层 :通过权重参数,例如 784*24
卷积层
卷积的作用
将输入的图像分割成一小块,然后再在这小块的部分中,找出权重参数,计算出最佳的值。
卷积特征值计算方法
RGB图像颜色通道,将分割出来的一小块中的R通道、G通道、B通道分别做计算,最终将每个通道卷积完的结果加在一起。最终结果要+1
卷积层涉及参数
滑动窗口步长 :每次移动的单元格个数,图像一般用步长1
卷积核尺寸 : 选择区域的大小,一般情况情况3*3
边缘填充 : 在区域的最外层加一圈0(zero padding)
卷积核个数 : 最终要得到特征图的个数,一但确定,不能改变
卷积结果计算公式
长度:
H
2
=
H
1
−
F
H
+
2
P
S
+
1
H2 = \dfrac{H_1-F_H+2P}{S} +1
H2=SH1−FH+2P+1
宽度:
W
2
=
W
1
−
F
w
+
2
P
S
+
1
W2=\dfrac{W_1-F_w+2P}{S}+1
W2=SW1−Fw+2P+1
其中W1、H1表示输入的宽度、长度;
W2、H2表示输出特征图的宽度、长度;
F表示卷积核长和宽的 大小;
S表示滑动窗口的步长;
P表示边界填充(加几圈0)
**提问:**如果输入数据是32*32*3的图像,用10个5*5*3的filter来进行卷积操作,指定步长为1,边界填充为2,最终输入的规模为?
答案:(32-5+2*2)/1+1=32,所以输出规模为32*32*10,
经过卷积操作后也可以保持特征图长度、宽度不变。
卷积参数共享
**提问:**数据依旧是32*32*3的图像,继续用10个5*\5*3的filter来进行卷积操作,所需的权重参数有多少个呢?
**回答:**5*5*3=75,表示每一个卷积核只需要75个参数,此时有10个不同的卷积核,就需要10*75=750个卷积核参数,不要忘记还有b参数,每个卷积核都有一个对应的偏置参数,最终只需要750+10=760个权重参数。
池化层
通过卷积层提取的特征数据,过于多,所以就需要池化层来压缩,也就是选择重要的特征留下,不重要的特征去除。
池化层没有涉及到任何计算,只是做了筛选
最大池化(MAX POOLING)
把原始的输入数据,划分区域,在每个区域取出最大值。
整体网络架构
网络架构的流程
一次卷积(CONV),一次激活函数(RELU);
两次卷积和RELU,一次池化(POOL);
经过多次上诉两步骤,最终得到FC(全连接层),分类结果[一维特征向量,分类个数],例如,得到的输入结果是32*32*3,那么一维特征向量就是10240。
带参数计算的才能算一层,所以CONV(卷积)是一层,FC(全连接层)是一层,但是RELU和POOL就不能算是一层。
经典网络架构特点
Alexnet 经典网络架构
提取特征太多(11*11),步长太大(4),不加外层(padding)
VGG网络架构
所有的卷积大小都是3*3
Resnet残差网络(推荐)
使用同等映射,当其中的卷积层不好,就直接跳过不好的卷积层,前一层和 不好的卷积层 连接,前一层再和后面的卷积层连接。
感受野
某一层的卷积层 能够感受到 前一层的卷积层。
感受野越大越好。
如果堆叠3个3*3的卷积层,并且保持滑动窗口步长为1,其感受野就是7*7的了。
假设输入大小都是h*w*c
,并且都使用c
个卷积核(得到c个特征图),可以来计算以下其各自所需参数:
一个7*7卷积核所需参数:
C x (7 x 7 x C) = 49*C的平方
3个3*3卷积核所需参数:
3 x C x (3 x 3 x C) = 27 * C的平方
很明显,堆叠小的卷积核所需的参数更少一些,并且卷积过程越多,特征提取也会越细致,加入的非线性变换也随着增多,还不会增大权重数个数,这就是VGG网络的基本出发点,用小的卷积来完成特征提取操作。
猫狗识别任务
猫狗识别任务大致流程:
- 数据预处理:图像数据处理,准备训练核验证数据集
- 卷积网络模型:构建网络架构
- 过拟合问题:观察训练核验证效果,针对过拟合问题提出解决办法
- 数据增强:图像数据增强方法与效果
- 迁移学习:深度学习必备训练策略
准备数据
在data/cats_and_dogs目录下,分别有两个文件,训练集(train) 和 验证集(validation),而训练集和验证集文件中分别对应dogs和cats文件夹
导包工具包
import os
import warnings
warnings.filterwarnings("ignore")
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.imgae import ImageDataGenerator
指定好数据路径(训练和验证)
# 数据所在文件夹
base_dir = './data/cats_and_dogs'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
# 训练集
train_cats_dir = os.path.join(train_dir, 'cats')
train_dogs_dir = os.path.join(train_dir, 'dogs')
# 验证集
validation_cats_dir = os.path.join(validation_dir, 'cats')
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
构建卷积神经网络模型
构建几层都可以,随便玩。
如果用CPU训练,可以把输入设置的更小一些,一般输入的大小决定了训练速度。
一般的规格是 224*224*3,这种适合于GPU;
如果是CPU跑,建议构建 64*64*3的规格
Conv2D()
方法 卷积是针对于图像数据,Conv3D 是针对于视频数据。
以下是Conv2D()的参数列表:
filters : 特征图的个数
kernel_size : 卷积核大小
strides : 步长,每次移动单元格的数量
padding : 边界填充
model = tf.keras.models.Sequential([
'''
32:表示得到的特征图
(3,3) : 表示filter的h*w 是 3*3
activation : 激活层,一般就指定relu就好
input_shape : 如果不指定,会自动判断,指定的话,强制改成指定矩阵的大小
'''
tf.keras.layers.Conv2D(32,(3,3),activation='relu',input_shape=(64,64,3)),
tf.keras.layers.MaxPooling2D(2,2), # 池化层
tf.keras.layers.Conv2D(64,(3,3),activation='relu'),
tf.keras.layers.MaxPooling2D(2,2)
tf.keras.layers.Conv2D(128,(3,3),activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
# 为全连接层准备 将数据拉长成一维向量
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512,activation='relu'),
# 二分类sigmoid 就够了
tf.keras.layers.Dense(1,activation='sigmoid')
])
展示卷积后的网络模型的数据
model.summary()
配置训练器
'''
learning_rate=1e-4 学习率
'''
model.compile(
loss='binary_crossentropy', # 指定损失函数
optimizer=Adam(learning_rate=1e-4), # 指定优化器
metrics=['acc'] # 评估
)
数据预处理
读进来的数据会被自动转换成tensor(float32)格式,分别准备训练和验证。
图像数据归一化到0-1区间。
#ImageDataGenerator数据生成器
# 把数据全部压缩到0-1之间的数据
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
# 读取数据 generator(生成器)
'''
使用了 flow_from_directory 方法从指定的训练集文件夹中加载数据
target_size=(64,64) 指定了图像将被调整到的大小
batch_size=20 表示每次迭代时将从磁盘读取20个样本
class_mode='binary' 表明这是一个二分类任务
'''
# 训练生成器
train_generator = train_datagen.flow_from_directory(
train_dir, #文件夹路径
target_size = (64,64) # 指定resize成的大小
batch_size=20, # 每次迭代拿出的数据量
# 如果是one-hot就是categorical,二分类用binary就可以
class_mode='binary'
)
# 验证生成器
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(64,64),
batch_size=20,
class_mode='binary'
)
训练网络模型
直接使用fit
方法也可以,但是通常咱们不能把所有数据全部放入内存,fit_generator
相当于一个生成器,动态产生所需的batch数据。
steps_per_epoch
相当于给定一个停止条件,因为生成器会不断产生batch数据,说白了就是它不知道一个epoch里需要执行多少个step。
生成器只要不让其停止,就不会不断的生成数据,fit_generator()
用什么就取什么
但是fit_generator()
在新的版本以及被废弃了。
history = model.fit(
tarin_generator, # 训练生成器
steps_per_epoch=100, # 迭代次数 # step_per_epoch = 数据量 / epochs
epochs=20, # 轮次
validation_data=validation_generator, # 验证生成器
validation_steps=50,
verbose=2
)
以上的训练结果并不好,虽然训练集的准确率达到90%多,但是验证集的准确率却是74%左右,这也就是过拟合现象。
效果展示
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
解决过拟合现象
通过 数据增强 来改变数据,解决过拟合现象。
修改上面的 数据归一化 的步骤 代码如下:
加入数据增强步骤
数据增强
# 对图像进行各种变换
train_dategen = image.ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
test_datagen = ImageDataGenerator(rescale=1./255) # 测试集数据不做数据增强
# ======================以下步骤是重复上面的后续步骤=============
# 训练生成器
train_generator = train_datagen.flow_from_directory(
train_dir, #文件夹路径
target_size = (64,64) # 指定resize成的大小
batch_size=20, # 每次迭代拿出的数据量
# 如果是one-hot就是categorical,二分类用binary就可以
class_mode='binary'
)
# 验证生成器
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(64,64),
batch_size=20,
class_mode='binary'
)
history = model.fit(
tarin_generator, # 训练生成器
steps_per_epoch=100, # 迭代次数 # step_per_epoch = 数据量 / epochs
epochs=20, # 轮次
validation_data=validation_generator, # 验证生成器
validation_steps=50,
verbose=2
)
再次展示数据
展示 加入 数据增强后的 图像效果
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
可以发现,过拟合现象有了一定的改进,虽然不是很多
数据增强
数据增强(Data Augmentation),对于输入的图像数据,进行翻转,旋转,变换,就会实现一张图像拥有翻倍的数据量。
准备数据
导包
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
from keras.preprocessing import image
import keras.backend as K
import os
import glob
import numpy as np
展示输入的数据
# 定义一个方法,用于绘图
def print_result(path):
name_list = glob.glob(path)
fig = plt.figure(figsize=(12,16)) # 定义图像大小
for i in range(3):
img = Image.open(name_list[i])
sub_img = fig.add_subplot(131+i) # 添加子图
sub_img.imshow(img) # 显示图像
# 拿到图片所在的路径
img_path = './img/superman/*'
in_path = './img/'
out_path = './output/'
name_list = glob.glob(img_path)
print(name_list)
图像数据的读取和压缩
'''
flow_from_directory() 从指定文件夹中加载数据
指定`target_size`后所有图像都变为相同大小
'''
datagen = image.ImageDataGenerator() # 实例化ImageDataGenerator对象,对图像进行后续操作
# 告诉generator,要准备做的操作
gen_data = datagen.flow_from_directory(
in_path, # 将./img/ 目录下的 文件夹们 的数据分别读取
batch_size=1,
shuffle=False,
sava_to_dir=out_path+'resize', # 变换后 保存的路径
sava_prefix='gen',
target_size=(224,224) # 指定变换后图像的大小
)
# 开始操作
for i in range(3):
gen_data.next() # 一次next()执行一张图像,这里有三张图像就执行三次
gen_data
的输出结果:
Found 3 images belonging to 1 classes.
一个类别是因为只有一个文件夹
展示缩小后的图像
# 展示变换后的图像
print_result(out_path+'resize/*') # print_result方法是上面自定义的绘图方法
图像数据变换
角度旋转
首先在./out_path 目录下,手动新建一个 rotation_range文件夹
dategen = image.ImageDataGenerator(rotation_range=45) # 指定旋转角度 45°
gen = image.ImageDataGenerator()
data = gen.flow_from_directory( # 读取数据
in_path,
batch_size=1,
class_mode=None,
shuffle=True, # 打乱图像数据
target_size=(224,224)
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_directory(
in_path,
batch_size=1,
shuffle=False,
save_to_dir=out_path+'rotation_range', # 自己在./out_path文件夹下手动创建一个rotation_range文件夹
save_prefix='gen',
target_size=(224,224)
)
for i in range(3):
gen_data.next() # 执行操作
执行上述代码后,就会将旋转之后的图片保存到 ./out_path/rotation_range
目录下。
展示旋转后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/rotation_range 目录下的所有图像
print_result(out_path + 'rotation_range/*')
平移变换
首先在./out_path 目录下,手动新建一个 shift2文件夹
datagen = image.ImageDataGenerator(
width_shift_range=0.3, # 宽度比例值,可以是负数
height_shift_range=0.3 # 长度比例值
)
gen = image.ImageDataGenerator()
data = gen.flow_from_directory( # 读取数据
in_path,
batch_size=1,
class_mode=None,
shuffle=True, # 打乱数据
target_size=(224,224) # 变换后的图像大小
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(
in_path,
batch_size=1,
shullfe=False,
save_to_dir=out_path+'shift2',
save_prefix='gen',
target_size=(224,224)
)
# 对图像进行操作
for i in range(3):
gen_data.next()
展示平移后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/shift2 目录下的所有图像
print_result(out_path + 'shift2/*')
缩放
首先在./out_path 目录下,手动新建一个 zoom文件夹
datagen = image.ImageDataGenerator(zoom_range=0.5) # 缩放比例 <1 缩小,>1 放大
gen = image.ImageDataGenerator()
data = gen.flow_from_directory( # 读取数据
in_path,
batch_size=1,
class_mode=None,
shuffle=True, # 打乱数据
target_size=(224,224) # 变换后的图像大小
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(
in_path,
batch_size=1,
shullfe=False,
save_to_dir=out_path+'zoom',
save_prefix='gen',
target_size=(224,224)
)
# 对图像进行操作
for i in range(3):
gen_data.next()
展示缩放后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/zoom 目录下的所有图像
print_result(out_path + 'zoom/*')
channel_shift颜色通道
首先在./out_path 目录下,手动新建一个 channel文件夹
改变后的图像,肉眼是看不出效果的
datagen = image.ImageDataGenerator(channel_shift_range=15) # 改变颜色通道
gen = image.ImageDataGenerator()
data = gen.flow_from_directory(
in_path,
batch_size=1,
class_Mode=None,
shuffle=True,
target_size=(224,224)
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np.data)
gen_data = gen,flow_from_directory(
in_path,
batch_size=1,
shuffle=False,
save_to_dir=out_path+'channel',
save_prefix='gen',
target_size=(224,224)
)
# 执行操作
for i in range(3):
gen_data.next()
展示 channel_shift 后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/channel 目录下的所有图像
print_result(out_path + 'channel/*')
翻转
水平翻转 和 垂直翻转
首先在./out_path 目录下,手动新建一个 horizontal文件夹
datagen = image.ImageDataGenerator(horizontal_flip=True) # 翻转
gen = image.ImageDataGenerator()
data = gen.flow_from_directory(
in_path,
batch_size=1,
class_mode=None,
shuffle=True,
target_size(224,224)
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(
in_path,
batch_size=1,
shuffle=False,
save_to_dir=out_path + 'horizontal',
save_prefit='gen',
target_size=(224,224)
)
# 开始执行操作
for in range(3):
gen_data.next() # 每次next() 拿到的其实是矩阵数组
展示 翻转后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/horizontal 目录下的所有图像
print_result(out_path + 'horizontal/*')
rescale归一化变换
首先在./out_path 目录下,手动新建一个 rescale文件夹
图像数据的归一化,将数据压缩到0-1区间
变换后的数据,肉眼也是看不到效果的,但是可以查看 gen_data.next()
的数据会不一样
datagen = image.ImageDataGenerator(rescale=1/255) # 归一化
gen = image.ImageDataGenerator()
data = gen.flow_from_directory(
in_path,
batch_size=1,
class_mode=None,
shuffle=True,
target_size(224,224)
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(
in_path,
batch_size=1,
shuffle=False,
save_to_dir=out_path + 'rescale',
save_prefit='gen',
target_size=(224,224)
)
# 开始执行操作
for in range(3):
gen_data.next() # 每次next() 拿到的其实是矩阵数组
展示rescale后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/rescale 目录下的所有图像
print_result(out_path + 'rescale/*')
填充数据
首先在./out_path 目录下,手动新建一个 fill_mode文件夹
在变换图像后,会发现,有部分会空出来,这时候就需要填充数据了。
填充数据方法
- ‘constant’ : kkkkkkkk|abcd|kkkkkkkk (cval=k)
- 'nearest : aaaaaaaa|abcd|dddddddd
- ‘reflect’ : abcddcba|abcd|dcbaabcd
- ‘wrap’ : abcdabcd|abcd|abcdabcd
# 图像缺失的的部分 拿 图像本身 来填充
datagen = image.ImageDataGenerator(fill_mode='wrap',zoom_range[4,4])
gen = image.ImageDataGenerator()
data = gen.flow_from_directory(
in_path,
batch_size=1,
class_mode=None,
shuffle=True,
target_size(224,224)
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(
in_path,
batch_size=1,
shuffle=False,
save_to_dir=out_path + 'fill_mode',
save_prefit='gen',
target_size=(224,224)
)
# 开始执行操作
for in range(3):
gen_data.next() # 每次next() 拿到的其实是矩阵数组
展示填充数据后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/fill_mode 目录下的所有图像
print_result(out_path + 'fill_mode/*')
首先在./out_path 目录下,手动新建一个 nearest文件夹
# 图像缺失的的部分 拿 最近都 来填充
datagen = image.ImageDataGenerator(fill_mode='nearest',zoom_range[4,4])
gen = image.ImageDataGenerator()
data = gen.flow_from_directory(
in_path,
batch_size=1,
class_mode=None,
shuffle=True,
target_size(224,224)
)
np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(
in_path,
batch_size=1,
shuffle=False,
save_to_dir=out_path + 'nearest',
save_prefit='gen',
target_size=(224,224)
)
# 开始执行操作
for in range(3):
gen_data.next() # 每次next() 拿到的其实是矩阵数组
展示填充数据后的图像
'''
print_result(要展示的图片所在路径),上面自定义的绘图方法
'''
# 展示 ./out_path/nearest 目录下的所有图像
print_result(out_path + 'nearest/*')
迁移学习
迁移学习概述
在训练网络的过程中,会遇到一种情况,手里的数据不是很多,数据少,意外着训练出来的模型会过拟合。
数据量少,在训练网络的过程中,就要调节各种各样的参数,做起实验的花费大量时间,这也增加了成本。
为了让训练模型更简单,所以就引入了迁移学习的概念。
所谓迁移学习,就是拿别人训练好的网络模型来使用。也就是拿别人的模型来做初始化。
要去找与本次任务 相类似的模型来用,既然用别人的模型,就要符合别人模型的训练的参数,比如说图像的大小,权重参数,输入输出格式要与人家模型一样的。
迁移学习策略
声明:以下提及 三模 均指:别人训练好的模型。
-
A方案:在拿
三模
训练自己模型的时候,通常会拿三模
中的卷积层拿过来,当作自己的权重参数初始化,在这基础上,继续训练。拿
三模
中的部分卷积层,称之为冻住,就是不修改该部分的卷积层 -
**B方案:**直接把
三模
当作自己权重参数的结果来使用,不进行训练。
对于全连接层来说,需要自己训练,对于前面的特征提取的操作进行参数微调操作。
当数据量小的时候,需要冻住的层数越多
当数据量中等的时候,就可以冻住前面部分的卷积层,最后的卷积层自己进行修改和训练
当数据量大的时候,需要冻住的层数越小。
至于冻住多少层,没有固定答案,基本上按照自己的数据量来,数据量较少,想要避免过拟合,最好直接拿三模
作为结果。
数据量大的时候,尽可能的而不冻住,而是仅仅拿来做初始化。
通俗的来说,有一场考试,分别由A和B两个考生,其中A考生是自己一点一点做的,而B考生直接抄答案,并且稍微修改了点。
也就是说A考生就相当于自己从头到尾来训练模型,并没有做迁移学习,而B考生是再别人的基础上,进行稍微修改,就使用到了迁移学习。
由此可见,迁移学习的速度就很快
Resnet 残差网络原理
该网络就是在训练模型的时候,分两个分支,一条分支按正常网络模型走,另一条分支,就直接跳过正常的网络模型架构,直接到全连接层。
迁移学习的案例
使用别人训练好的模型 来 做猫狗识别的案例。
用人家训练好的模型的权重参数来当作咱们的初始化;
一般全连接层需要自己训练,可以选择是否训练别人训练好的特征提取层。
数据准备
导包
import os
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pylot as plt
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras import layers
from tensorflow.keras import Model
获取数据路径
base_dir = './data/cats_and_dogs'
train_dir = os.path.join(base_dir,'train')
validation_dir = os.path.join(base_dir,'validation')
train_cats_dir = os.path.join(train_dir,'cats')
train_dogs_dir = os.path.join(train_dir,'dogs')
validation_cats_dir = os.path.join(validation,'cats')
validation_dogs_dir = os.path.join(validation,'dogs')
使用内置训练好的模型
tensorflow.keras.applications
模块里面有很多现成训练好的模型可以直接利用
比如说使用ResNet
# 导入模型
from tf.keras.applications.resnet import ResNet50
from tensorflow.keras.applications.resnet import ResNet101
from tensorflow.keras.applications.inception_v3 import InceptionV3s
上述导入模型的操作,仅仅是导入模型,并没有拿到权重参数
# 下载模型的参数
pre_trained_model = ResNet101(
input_shape=(75,75,3), # 输入大小
include_top=False, # 不要模型训练好了的最后的全连接层
weights='imagenet' # 权重参数,固定写就好了
)
执行上面的代码,在notebook当中下载,会很慢,然而,执行上面的代码,下面会出现一段网址,将网址放在第三方下载工具下载,也许会快些,比如迅雷之类的。下载完之后,建议放在
C:\Users\24307\.keras\models
路径下,文件名不要修改。
可以选择训练哪些层
# 选择要训练的哪些层
for layer in pre_trained_model.layers:
layer.trainable = False
定义回调策略
# 自定义方法
class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self,epoch,logs={}):
if(logs.get('acc')>0.95):
print("\nReached 95% accuracy so cancelling training!")
self.model.stop_training = True
开始训练
from tensorflow.keras.optimizers import Adam
# 为全连接层准备
# pre_trained_model.output 作为参数传递给Flatten层
# pre_trained_model.output是别人训练好的模型的输出层
x = layers.Flatten()(pre_trained_model.output)
# 加入全连接层,这个需要从头训练的
x = layers.Dense(1024,activation='relu')(x)
x = layers.Dropout(0.2)(x)
# 输出层
x = layers.Dense(1,activation='sigmoid')(x)
# 构建模型序列
# pre_trained_model.input是别人训练好的模型的输入层
# Model是上面导入的
model = Model(pre_trained_model.input,x)
model.compile(
optimizer=Adam(lr=0.001),
loss='binary_crossentropt',
metrics=['acc']
)
加入数据增强
# 对图像进行各种变换
train_dategen = image.ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
)
test_datagen = ImageDataGenerator(rescale=1.0/255.) # 测试集数据不做数据增强
# 训练生成器
train_generator = train_datagen.flow_from_directory(
train_dir, #文件夹路径
batch_size=20, # 每次迭代拿出的数据量
# 如果是one-hot就是categorical,二分类用binary就可以
class_mode='binary',
target_size = (75,75) # 指定resize成的大小
)
# 验证生成器
validation_generator = test_datagen.flow_from_directory(
validation_dir,
batch_size=20,
class_mode='binary'
target_size=(75,75)
)
加入callback模块训练
# 调用上面自定义好的回调模块myCallback
callbacks = myCallback()
history = model.fit(
tarin_generator, # 训练生成器
validation_data=validation_generator, # 验证生成器
steps_per_epoch=100, # 迭代次数 # step_per_epoch = 数据量 / epochs
epochs=100, # 轮次
validation_steps=50,
verbose=2,
callbacks=[callbacks] # 加入callback模块
)
绘图展示
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'b', label='Training accuracy')
plt.plot(epochs, val_acc, 'r', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'b', label='Training Loss')
plt.plot(epochs, val_loss, 'r', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
callback回调模块
callback的作用
相当于一个监视器,在训练过程中可以设置一些自定义项,比如提前停止,改变学习率等。
如果连续两个epoch还没降低就停止
callbacks = [
tf.keras.callbacks.EarlyStopping(patience=2,monitor='val_loss'), # patience 容忍次数,monitor监听指标
tf.keras.callbacks.LearningRateScheduler,# 可以改变学习率
tf.keras.callbacks.ModelCheckpoint,# 保存模型
tf.keras.callbacks.Callback # 自定义方法
]
TFRecords
TFRecores概述
为了高效的读取数据,可以将数据进行序列化存储,这样也便于网络流式读取数据。
TFRecords是一种比较常用的存储二进制序列数据的方法。TFRecores可以加速网络模型训练。
tf.Example
类是一种将数据表示为{"string":value}
形式的message类型,Tensorflow经常使用tf.Example
来写入、读取TFRecords数据。
通常情况下,tf.Example中可以使用以下几种格式:
- tf.train…BytesList:可以使用的类型包括string和byte
- tf.train.FloatList:可以使用的类型包括float和double
- tf.train.lnt64List:可以使用的类型包括enum,bool,int32,uint32,int64
转化实例
def _bytes_feature(value):
"""Returns a bytes_list from a string/byte."""
if isinstance(value, type(tf.constant(0))):
value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def _float_feature(value):
"""Return a float_list form a float/double."""
return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
def _int64_feature(value):
"""Return a int64_list from a bool/enum/int/uint."""
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
# tf.train.BytesList
print(_bytes_feature(b'test_string'))
print(_bytes_feature('test_string'.encode('utf8')))
# tf.train.FloatList
print(_float_feature(np.exp(1)))
# tf.train.Int64List
print(_int64_feature(True))
print(_int64_feature(1))
TFRecord制作方法
创建tf.Example
def serialize_example(feature0, feature1, feature2, feature3):
"""
创建tf.Example
"""
# 转换成相应类型
feature = {
'feature0': _int64_feature(feature0),
'feature1': _int64_feature(feature1),
'feature2': _bytes_feature(feature2),
'feature3': _float_feature(feature3),
}
#使用tf.train.Example来创建
example_proto = tf.train.Example(features=tf.train.Features(feature=feature))
#SerializeToString方法转换为二进制字符串
return example_proto.SerializeToString()
举个例子
# 数据量
n_observations = int(1e4)
# 随机创建数据
# Boolean feature
feature0 = np.random.choice([False, True], n_observations)
# Integer feature
feature1 = np.random.randint(0, 5, n_observations)
# String feature
strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
feature2 = strings[feature1]
# Float feature
feature3 = np.random.randn(n_observations)
保存到本地
filename = 'tfrecord-1' # 保存的文件名
with tf.io.TFRecordWriter(filename) as writer:
for i in range(n_observations):
example = serialize_example(feature0[i], feature1[i], feature2[i], feature3[i])
writer.write(example)
执行上述代码,本地就会产生一个新的文件
加载TFRecored文件
filenames = [filename]
# 读取
raw_dataset = tf.data.TFRecordDataset(filenames)
print(raw_dataset)
图像数据处理实例
导包
import os
import glob
from datetime import datetime
import cv2 # opencv的库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mping
%matplotlib inline
导入图像数据
image_path = '../img/'
images = glob.glob(image_path + '*.jpg')
for fname in images:
image = mpimg.imread(fname)
f, (ax1) = plt.subplots(1, 1, figsize=(8,8))
f.subplots_adjust(hspace = .2, wspace = .05)
ax1.imshow(image)
ax1.set_title('Image', fontsize=20)
image_labels = {
'dog': 0,
'kangaroo': 1,
}
制作TFRecord
# 读数据,binary格式
image_string = open('./img/dog.jpg', 'rb').read()
label = image_labels['dog']
def _bytes_feature(value):
"""Returns a bytes_list from a string/byte."""
if isinstance(value, type(tf.constant(0))):
value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def _float_feature(value):
"""Return a float_list form a float/double."""
return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
def _int64_feature(value):
"""Return a int64_list from a bool/enum/int/uint."""
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
# 创建图像数据的Example
def image_example(image_string, label):
image_shape = tf.image.decode_jpeg(image_string).shape
feature = {
'height': _int64_feature(image_shape[0]),
'width': _int64_feature(image_shape[1]),
'depth': _int64_feature(image_shape[2]),
'label': _int64_feature(label),
'image_raw': _bytes_feature(image_string),
}
return tf.train.Example(features=tf.train.Features(feature=feature))
#打印部分信息
image_example_proto = image_example(image_string, label)
for line in str(image_example_proto).split('\n')[:15]:
print(line)
print('...')
# 制作 `images.tfrecords`.
image_path = './img/'
images = glob.glob(image_path + '*.jpg')
record_file = 'images.tfrecord'
counter = 0
with tf.io.TFRecordWriter(record_file) as writer:
for fname in images:
with open(fname, 'rb') as f:
image_string = f.read()
label = image_labels[os.path.basename(fname).replace('.jpg', '')]
# `tf.Example`
tf_example = image_example(image_string, label)
# 将`tf.example` 写入 TFRecord
writer.write(tf_example.SerializeToString())
counter += 1
print('Processed {:d} of {:d} images.'.format(
counter, len(images)))
print(' Wrote {} images to {}'.format(counter, record_file))
加载制作好的TFRecord
raw_train_dataset = tf.data.TFRecordDataset('images.tfrecord')
raw_train_dataset
example数据都进行了序列化,还需要解析以下之前写入的序列化string
- tf.io.parse_single_example(example_proto, feature_description)函数可以解析单条example
# 解析的格式需要跟之前创建example时一致
image_feature_description = {
'height': tf.io.FixedLenFeature([], tf.int64),
'width': tf.io.FixedLenFeature([], tf.int64),
'depth': tf.io.FixedLenFeature([], tf.int64),
'label': tf.io.FixedLenFeature([], tf.int64),
'image_raw': tf.io.FixedLenFeature([], tf.string),
}
现在看起来仅仅完成了一个样本的解析,实际数据不可能一个个来写吧,可以定义一个映射规则map函数
def parse_tf_example(example_proto):
# 解析出来
parsed_example = tf.io.parse_single_example(example_proto, image_feature_description)
# 预处理
x_train = tf.image.decode_jpeg(parsed_example['image_raw'], channels=3)
x_train = tf.image.resize(x_train, (416, 416))
x_train /= 255.
lebel = parsed_example['label']
y_train = lebel
return x_train, y_train
train_dataset = raw_train_dataset.map(parse_tf_example)
train_dataset
制作训练集
num_epochs = 10
train_ds = train_dataset.shuffle(buffer_size=10000).batch(2).repeat(num_epochs)
train_ds
for batch, (x, y) in enumerate(train_ds):
print(batch, x.shape, y)
model = tf.keras.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(2, activation='softmax')
])
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=['accuracy'])
model.fit(train_ds, epochs=num_epochs)
递归神经网络RNN
在传统的神经网络基础上,将数据经过 隐层后 的得到的数据保留起来,与下一次输入数据,共同经过隐层,通俗的讲就是,数据会重复两次经过隐层。
RNN网络适用于自然语言文本处理。
RNN网络一般取最后一层数据。
LSTM网络
RNN网络会把之前的结果全部记录下,但是不是全部的记录都有用,大部分的记录到最后都会无效,当记录多了,就会产生误差,所以就引入了LSTM网络,LSTM网络做的事情,就是将部分数据进行遗忘。
通过一个控制参数©决定什么样的信息会被保留,什么样的信息会被遗忘。
词向量模型Word2Vec
Word2Vec就是将词转化成向量化,并且包含顺序、位置信息。
词向量维度 在50至300,也就是50个数至300的个数。
通常,数据的维度越高,能提供的信息也就越多,从而计算结果的可靠性就更值得信赖。
词向量的输入输出
将输入的文本,在词库(语料库)中查找对应的向量(Look up embeddings)。
语料库大表一开始是随机初始化来的,随机构建初始化策略,随机构建词的向量,神经网络在计算过程中,前向传播计算损失函数,反向传播通过损失函数来更新权重参数,此时在word2vec中,不光会更新整个神经网络的权重矩阵,会连输入的进行更新,相当于一开始词库大表是随机初始化,随着训练的进行,每一次都会把输入的词进行更新,一点次数多了,每一个词都会进行更新,神经网络就学的越好。
训练数据来源
数据可以跨文本,只要是正常逻辑,一切可以利用的文本,比如新闻,小说等都可以作为数据来源。
只要合乎说话逻辑,都可以作为数据来源。
比如说,“今天”这个词,在不同的文章当中,它的意思都是一致的。
构建训练数据
对于一段英文文本:
第一次训练数据,可以选中前三个词,第一个和第二个词可以用作输入数据,第三词就用作输出数据。
第二次训练数据,就要选中第二个开始往后数三个词,(对于整个文本来说)第二个词和第三个词就作为输入数据,第四词就作为输出数据。
依次类推,第五个,第六个,第七个……
CBOW架构
cbow可以理解为构建出一段连续的文本。
比如,当前,拿到一段文本,要构建输入数据,输入是上下文,输出(预测)是中间某个缺省的词。
Skipgram结构
输入是 文本中间缺省的词,输出(预测)是上下文。
如果语料库稍微大一些,可能的结果简直太多了,最后一层相当于softmax,计算起来十分耗时。
解决方案
本来要通过A来预测B,但是B的可能性太多了,所以干脆,将A和B都一起作为输入参数,来判断 B这个词 在A后面的可能性有多大,取接近于1的词,即target=1,
然后在此基础上加入一些负样本(Negative Sampling),也就是出现概率为0的词(即target=0),负样本是人为构建的。
词向量训练过程
初始化词向量矩阵
通过神经网路反向传播来计算更新,此时不光更新权重参数矩阵W,也会更新输入数据。
基于Tensorflow实现word2vec
数据预处理
import collections
import os
import random
import urllib
import zipfile
import numpy as np
import tensorflow as tf
准备好训练参数
# 训练参数
learning_rate = 0.1 # 学习率
batch_size = 128
num_steps = 3000000 # 训练总次数
display_step = 10000 # 损失值
eval_step = 200000 # 实验效果
# 测试样例
eval_words = ['nine', 'of', 'going', 'hardware', 'american', 'britain']
# Word2Vec 参数
embedding_size = 200 # 词向量维度
max_vocabulary_size = 50000 # 语料库词语数
min_occurrence = 10 # 最小词频
skip_window = 3 # 左右各窗口大小
num_skips = 2 # 一次制作多少个输入输出对
num_sampled = 64 # 负采样
加载训练数据
# 加载训练数据,其实什么数据都行
# 这是维基百科的数据
data_path = 'text8.zip'
with zipfile.ZipFile(data_path) as f:
text_words = f.read(f.namelist()[0]).lower().split()
# 查看数据量大小
print(len(text_words))
# 创建一个计数器,计算每个词出现了多少次
count = [('UNK', -1)] # 不在预料表中的词,看作UNK
# 基于词频返回max_vocabulary_size个常用词
# Counter内置库计数器,默认做排序
count.extend(collections.Counter(text_words).most_common(max_vocabulary_size - 1)) # 将最常用的词拿出来
# 查看统计好的数据
count[0:10]
设置min_occurrence参数
# 剔除掉出现次数少于'min_occurrence'的词
for i in range(len(count) - 1, -1, -1):# 从start到end每次step多少
if count[i][1] < min_occurrence:
count.pop(i)
else:
# 判断时,从小到大排序的,所以跳出时候剩下的都是满足条件的
break
词-ID参数
# 计算语料库大小
vocabulary_size = len(count)
# 每个词都分配一个ID
word2id = dict()
for i, (word, _)in enumerate(count):
word2id[word] = i
所有词转换成ID
data = list()
unk_count = 0
for word in text_words:
# 全部转换成id
index = word2id.get(word, 0)
if index == 0:
unk_count += 1
data.append(index)
count[0] = ('UNK', unk_count)
id2word = dict(zip(word2id.values(), word2id.keys()))
print("Words count:", len(text_words))
print("Unique words:", len(set(text_words)))
print("Vocabulary size:", vocabulary_size)
print("Most common words:", count[:10])
构建所需训练数据
data_index = 0
# 定义一个函数,用来不断的生成输入输出的标签
def next_batch(batch_size, num_skips, skip_window):
global data_index
assert batch_size % num_skips == 0
assert num_skips <= 2 * skip_window
batch = np.ndarray(shape=(batch_size), dtype=np.int32) # 输入数据
labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32) #标签数据
# get window size (words left and right + current one).
span = 2 * skip_window + 1 #7为窗口,左3右3中间1 (拿到当前窗口)
buffer = collections.deque(maxlen=span)#创建一个长度为7的队列
if data_index + span > len(data):#如果数据被滑完一遍了
data_index = 0
buffer.extend(data[data_index:data_index + span])#队列里存的是当前窗口,例如deque([5234, 3081, 12, 6, 195, 2, 3134], maxlen=7)
data_index += span
for i in range(batch_size // num_skips):#num_skips表示取多少组不同的词作为输出,此例为2
context_words = [w for w in range(span) if w != skip_window]#上下文就是[0, 1, 2, 4, 5, 6]
words_to_use = random.sample(context_words, num_skips)#在上下文里随机选2个候选词
for j, context_word in enumerate(words_to_use):#遍历每一个候选词,用其当做输出也就是标签
batch[i * num_skips + j] = buffer[skip_window]#输入都为当前窗口的中间词,即3
labels[i * num_skips + j, 0] = buffer[context_word]#用当前候选词当做标签
if data_index == len(data):
buffer.extend(data[0:span])
data_index = span
else:
buffer.append(data[data_index])#之前已经传入7个词了,窗口要右移了,例如原来为[5234, 3081, 12, 6, 195, 2, 3134],现在为[3081, 12, 6, 195, 2, 3134, 46]
data_index += 1
data_index = (data_index + len(data) - span) % len(data)
return batch, labels
with tf.device('/cpu:0'):
# 随机初始化语料库,语料库大小47135,维度是200
embedding = tf.Variable(tf.random.normal([vocabulary_size, embedding_size])) #维度:47135, 200
nce_weights = tf.Variable(tf.random.normal([vocabulary_size, embedding_size])) # 负采样
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
将索引转成词向量
# 通过`tf.nn.embedding_lookup` 函数将所以转成词向量
def get_embedding(x):
with tf.device('/cpu:0'):
# x是输入的值,在embeddings语料库中查找x的词向量
x_embed = tf.nn.embedding_lookup(embedding, x)
return x_embed
损失函数定义
先分别计算出正样本和采样出的负样本对应的output 和 label
在通过 sigmoid
cross
enrtopy
来计算output和label的损失值
def nce_loss(x_embed, y):
with tf.device('/cpu:0'):
y = tf.cast(y, tf.int64)
loss = tf.reduce_mean(
tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
labels=y,
inputs=x_embed,
num_sampled=num_sampled,#采样出多少个负样本
num_classes=vocabulary_size))
return loss
评估模块
测试观察模块
想看下,训练好之后的模型,某个词的向量 距离哪个 词向量 比较近。
# Evaluation.
def evaluate(x_embed):
with tf.device('/cpu:0'):
# Compute the cosine similarity between input data embedding and every embedding vectors
x_embed = tf.cast(x_embed, tf.float32)
x_embed_norm = x_embed / tf.sqrt(tf.reduce_sum(tf.square(x_embed)))#归一化
embedding_norm = embedding / tf.sqrt(tf.reduce_sum(tf.square(embedding), 1, keepdims=True), tf.float32)#全部向量的
cosine_sim_op = tf.matmul(x_embed_norm, embedding_norm, transpose_b=True)#计算余弦相似度
return cosine_sim_op
# SGD
optimizer = tf.optimizers.SGD(learning_rate)
# 迭代优化
def run_optimization(x, y):
with tf.device('/cpu:0'):
with tf.GradientTape() as g:
emb = get_embedding(x)
loss = nce_loss(emb, y)
# 计算梯度
gradients = g.gradient(loss, [embedding, nce_weights, nce_biases])
# 更新
optimizer.apply_gradients(zip(gradients, [embedding, nce_weights, nce_biases]))
# 待测试的几个词
x_test = np.array([word2id[w.encode('utf-8')] for w in eval_words])
# 训练
for step in range(1, num_steps + 1):
batch_x, batch_y = next_batch(batch_size, num_skips, skip_window)
run_optimization(batch_x, batch_y)
if step % display_step == 0 or step == 1:
loss = nce_loss(get_embedding(batch_x), batch_y)
print("step: %i, loss: %f" % (step, loss))
# Evaluation.
if step % eval_step == 0 or step == 1:
print("Evaluation...")
sim = evaluate(get_embedding(x_test)).numpy()
for i in range(len(eval_words)):
top_k = 8 # 返回前8个最相似的
nearest = (-sim[i, :]).argsort()[1:top_k + 1]
log_str = '"%s" nearest neighbors:' % eval_words[i]
for k in range(top_k):
log_str = '%s %s,' % (log_str, id2word[nearest[k]])
print(log_str)
RNN文本分析任务
任务介绍
- 数据集构建:影评数据集进行情感分析
- 词向量模型:加载训练好的词向量或者自己训练都可以
- 序列网络模型:训练RNN模型进行识别
准备数据
在data
文件夹下有三个文件:
- test.txt : 测试集
- train.txt : 训练集
- glove.6B.50d.txt : 语料库
准备的数据是英文的,所以不用分词,但是如果是中文的话,那就需要进行分词。
数据预处理
训练模型前,要先将文本信息转换成计算机能够识别的数值。
RNN模型所需的数据格式
- 第一维度:batch(每次要处理多少数据)
- 第二维度:最大长度(maxLength-1),超出部分截断,少于部分用0填充
- 第三维度:每个词 的 词向量 的维度
导包
import os
import warnings
warnings.filterwarnings("ignore")
import tensorflow as tf
import numpy as np
import pprint
import logging
import time
from collections import Counter
from pathlib import Path
from tqdm import tqdm
数据映射表制作
读取数据集
上面提前准备了关于 影评的数据(测试集和训练集),这里就可以直接拿来用。
当然也可以让tensorflow下载,执行以下代码:
(x_train,y_train),(x_test,y_test) = tf.keras.datasets.imdb.load_data()
默认会下载到 C:\Users\24307\.keras\datasets
路径下,下载的文件名是:imdb.npz
。
# 查看下训练数据量
print(x_train.shape) # (25000,)
可以读取训练集中的部分数据
x_train[0]
此时读进来的数据已经被tensorflow自动转成ID映射的了。但是一般情况下,读进来的数据都需要手动转成ID映射。
自己制作ID映射
构建一个语料表,给每个词对应一个索引,即可。
_word2idx = tf.keras.datasets.imdb.get_word_index() # 拿到原始的索引表
word2idx = {w:i+3 for w,i in _word2idx.items()} # i+3 在每个词向量后面留三个空位
word2idx['<pad>'] = 0 # 填充 当小于最大长度时,缺省部分填充0
word2idx['<start>'] = 1 # 开始词 的索引
word2idx['<unk>'] = 2 # 样本当中,特殊词没有对上索引,就用unk来表示
idx2word = {i: w for w,i in word2idx.items()}
按文本长度大小进行排序
# 自定义方法
def sort_by_len(x,y):
x,y = np.asarray(x),np.asarray(y)
idx = sorted(range(len(x)),key=lambda i : len(x[i]))
将中间结果保存到本地,万一程序崩了,还得重来,这里保存的是文本数据,不是ID
也就是后期再读取训练模型,就直接从本地读取好了,不用再下载一遍了
x_train, y_train = sort_by_len(x_train, y_train)
x_test, y_test = sort_by_len(x_test, y_test)
def write_file(f_path, xs, ys):
with open(f_path, 'w',encoding='utf-8') as f:
for x, y in zip(xs, ys):
f.write(str(y)+'\t'+' '.join([idx2word[i] for i in x][1:])+'\n')
write_file('./data/train.txt', x_train, y_train)
write_file('./data/test.txt', x_test, y_test)
构建语料表
基于词频来进行统计。
比如说,拿到好多篇文章,将这些文章中的所有不重复的词提取处理,并且统计出现次数。
counter = Counter()
with open('./data/train.txt',encoding='utf-8') as f:
for line in f:
line = line.rstrip()
label, words = line.split('\t')
words = words.split(' ')
counter.update(words)
# freq >= 10 小于10说明词频太低,就不要了,保留词频高的,也就是常用的词
words = ['<pad>'] + [w for w, freq in counter.most_common() if freq >= 10]
print('Vocab Size:', len(words))
Path('./vocab').mkdir(exist_ok=True)
# 将语料表保存到本地 word.txt文件中
with open('./vocab/word.txt', 'w',encoding='utf-8') as f:
for w in words:
f.write(w+'\n')
得到新的word2id 映射表
word2idx = {}
with open('./vocab/word.txt',encoding='utf-8') as f:
for i, line in enumerate(f):
line = line.rstrip()
word2idx[line] = i # 将索引 与 词 对应
构建embedding层
可以基于网络自己来训练, 也可以直接加载别人训练好的,一般都是加载预训练模型。
推荐使用别人训练好的,自己的训练的模型可能不太好。
常用的语料库数据集
加载已经下载好的语料库模型
#做了一个大表,里面有20598个不同的词,【20599*50】
embedding = np.zeros((len(word2idx)+1, 50)) # + 1 表示如果不在语料表中,就都是unknow
with open('./data/glove.6B.50d.txt',encoding='utf-8') as f: #下载好的
count = 0
for i, line in enumerate(f):
if i % 100000 == 0:
print('- At line {}'.format(i)) #打印处理了多少数据
line = line.rstrip()
sp = line.split(' ')
word, vec = sp[0], sp[1:]
if word in word2idx:
count += 1
embedding[word2idx[word]] = np.asarray(vec, dtype='float32') #将词转换成对应的向量
现在已经已经得到了每个词索引所对应的向量,这里就保存在本地了。
print("[%d / %d] words have found pre-trained values"%(count, len(word2idx)))
np.save('./vocab/word.npy', embedding)
print('Saved ./vocab/word.npy')
数据生成器构造
数据生成器
- tf.data.Dataset.from_tensor_slices(tensor):将tensor沿其第一个维度切片,返回一个含有N个样本的数据集,这样做的问题就是需要将整个数据集整体传入,然后切片建立数据集类对象,比较占内存。(不推荐)
- tf.data.Dataset.from_generator(data_generator,output_data_type,output_data_shape):从一个生成器中不断读取样本
# 定义生成器函数
def data_generator(f_path, params):
with open(f_path,encoding='utf-8') as f:
print('Reading', f_path)
for line in f:
line = line.rstrip()
label, text = line.split('\t')
text = text.split(' ')
x = [params['word2idx'].get(w, len(word2idx)) for w in text]#得到当前词所对应的ID
if len(x) >= params['max_len']:#截断操作
x = x[:params['max_len']]
else:
x += [0] * (params['max_len'] - len(x))#补齐操作
y = int(label)
yield x, y
def dataset(is_training, params):
'''
is_training:是否训练
'''
_shapes = ([params['max_len']], ())
_types = (tf.int32, tf.int32)
if is_training:
ds = tf.data.Dataset.from_generator(
lambda: data_generator(params['train_path'], params),
output_shapes = _shapes,
output_types = _types,)
ds = ds.shuffle(params['num_samples'])
ds = ds.batch(params['batch_size'])
ds = ds.prefetch(tf.data.experimental.AUTOTUNE)#设置缓存序列,根据可用的CPU动态设置并行调用的数量,说白了就是加速
else:
ds = tf.data.Dataset.from_generator(
lambda: data_generator(params['test_path'], params),
output_shapes = _shapes,
output_types = _types,)
ds = ds.batch(params['batch_size'])
ds = ds.prefetch(tf.data.experimental.AUTOTUNE)
return ds
自定义网络模型
在tensorflow中,我们可以自定义模型,自定义网络层,自定义模型等。
tf.nn.embedding_lookup
:将当前的输入数据转成对应的向量维度。
就是:将输入的数据,转换成id,然后再语料库中去寻找对应的向量。
BiLSTM 双向RNN模型
就是在单向的基础上,封装了一层api,仅此而已。
相当于从前到后和从后到前都有了,然后将得到的向量拼接起来。
首先自定义网络模型Model
# 做定义模型,要继承tf.keras.Model
class Model(tf.keras.Model):
# 在自定义模型当中,会用到哪些层
def __init__(self, params):
super().__init__()
self.embedding = tf.Variable(np.load('./vocab/word.npy'),
dtype=tf.float32,
name='pretrained_embedding',
trainable=False,)
self.drop1 = tf.keras.layers.Dropout(params['dropout_rate'])
self.drop2 = tf.keras.layers.Dropout(params['dropout_rate'])
self.drop3 = tf.keras.layers.Dropout(params['dropout_rate'])
# rnn层可以自己看着加,加太多了,速度会变慢
# Bidirectional 双向
self.rnn1 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))
self.rnn2 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))
self.rnn3 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=False))
self.drop_fc = tf.keras.layers.Dropout(params['dropout_rate'])
self.fc = tf.keras.layers.Dense(2*params['rnn_units'], tf.nn.elu)
self.out_linear = tf.keras.layers.Dense(2)
# 定义 各层是如何组合在一起
def call(self, inputs, training=False):
if inputs.dtype != tf.int32:
inputs = tf.cast(inputs, tf.int32)
batch_sz = tf.shape(inputs)[0]
rnn_units = 2*params['rnn_units']
# self.embedding:词向量表
# inputs: 输入值
x = tf.nn.embedding_lookup(self.embedding, inputs)
x = self.drop1(x, training=training)
x = self.rnn1(x)
x = self.drop2(x, training=training)
x = self.rnn2(x)
x = self.drop3(x, training=training)
x = self.rnn3(x)
x = self.drop_fc(x, training=training)
x = self.fc(x)
x = self.out_linear(x)
return x
另外一个自定义模型,速度比较快,比较适合没有用GPU的电脑
class Model(tf.keras.Model):
def __init__(self, params):
super().__init__()
self.embedding = tf.Variable(np.load('./vocab/word.npy'),
dtype=tf.float32,
name='pretrained_embedding',
trainable=False,)
self.drop1 = tf.keras.layers.Dropout(params['dropout_rate'])
self.drop2 = tf.keras.layers.Dropout(params['dropout_rate'])
self.drop3 = tf.keras.layers.Dropout(params['dropout_rate'])
self.rnn1 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))
self.rnn2 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))
self.rnn3 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))
self.drop_fc = tf.keras.layers.Dropout(params['dropout_rate'])
self.fc = tf.keras.layers.Dense(2*params['rnn_units'], tf.nn.elu)
self.out_linear = tf.keras.layers.Dense(2)
def call(self, inputs, training=False):
if inputs.dtype != tf.int32:
inputs = tf.cast(inputs, tf.int32)
batch_sz = tf.shape(inputs)[0]
rnn_units = 2*params['rnn_units']
x = tf.nn.embedding_lookup(self.embedding, inputs)
x = tf.reshape(x, (batch_sz*10*10, 10, 50))
x = self.drop1(x, training=training)
x = self.rnn1(x)
x = tf.reduce_max(x, 1)
x = tf.reshape(x, (batch_sz*10, 10, rnn_units))
x = self.drop2(x, training=training)
x = self.rnn2(x)
x = tf.reduce_max(x, 1)
x = tf.reshape(x, (batch_sz, 10, rnn_units))
x = self.drop3(x, training=training)
x = self.rnn3(x)
x = tf.reduce_max(x, 1)
x = self.drop_fc(x, training=training)
x = self.fc(x)
x = self.out_linear(x)
return x
设置参数
params = {
'vocab_path': './vocab/word.txt',
'train_path': './data/train.txt',
'test_path': './data/test.txt',
'num_samples': 25000,
'num_labels': 2,
'batch_size': 32,
'max_len': 1000, # 文本最大长度
'rnn_units': 200,
'dropout_rate': 0.2,
'clip_norm': 10., # 梯度截断
'num_patience': 3,
'lr': 3e-4, # 学习率
}
定义方法,用来判断进行提前停止
如果当前的模型 连续三次都没有进步,那就停下来
def is_descending(history: list):
history = history[-(params['num_patience']+1):]
for i in range(1, len(history)):
if history[i-1] <= history[i]:
return False
return True
word2idx = {}
with open(params['vocab_path'],encoding='utf-8') as f:
for i, line in enumerate(f):
line = line.rstrip()
word2idx[line] = i
params['word2idx'] = word2idx
params['vocab_size'] = len(word2idx) + 1
model = Model(params)
model.build(input_shape=(None, None))#设置输入的大小,或者fit时候也能自动找到
#pprint.pprint([(v.name, v.shape) for v in model.trainable_variables])
#链接:https://tensorflow.google.cn/api_docs/python/tf/keras/optimizers/schedules/ExponentialDecay?version=stable
#return initial_learning_rate * decay_rate ^ (step / decay_steps)
# ExponentialDecay(学习率,每个多少次迭代,衰减值)
decay_lr = tf.optimizers.schedules.ExponentialDecay(params['lr'], 1000, 0.95)#相当于加了一个指数衰减函数
optim = tf.optimizers.Adam(params['lr'])
global_step = 0
history_acc = []
best_acc = .0 # 最好的准确率
t0 = time.time()
logger = logging.getLogger('tensorflow')
logger.setLevel(logging.INFO)
开始训练文本分类模型
while True:
# 训练模型
for texts, labels in dataset(is_training=True, params=params):
with tf.GradientTape() as tape:#梯度带,记录所有在上下文中的操作,并且通过调用.gradient()获得任何上下文中计算得出的张量的梯度
logits = model(texts, training=True) # 预测结果
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits)# 计算损失
loss = tf.reduce_mean(loss) # 计算损失的平均值
optim.lr.assign(decay_lr(global_step)) # 更新学习率
grads = tape.gradient(loss, model.trainable_variables) # 计算梯度值
grads, _ = tf.clip_by_global_norm(grads, params['clip_norm']) #将梯度限制一下,有的时候回更新太猛,防止过拟合
optim.apply_gradients(zip(grads, model.trainable_variables))#更新梯度
if global_step % 50 == 0:
logger.info("Step {} | Loss: {:.4f} | Spent: {:.1f} secs | LR: {:.6f}".format(
global_step, loss.numpy().item(), time.time()-t0, optim.lr.numpy().item()))
t0 = time.time()
global_step += 1
# 验证集效果
m = tf.keras.metrics.Accuracy() # 衡量标准
for texts, labels in dataset(is_training=False, params=params):
logits = model(texts, training=False) # 预测结果
y_pred = tf.argmax(logits, axis=-1) # 预测出的类别
m.update_state(y_true=labels, y_pred=y_pred) # 更新当前的准确率
acc = m.result().numpy()
logger.info("Evaluation: Testing Accuracy: {:.3f}".format(acc))
history_acc.append(acc)
if acc > best_acc:
best_acc = acc
logger.info("Best Accuracy: {:.3f}".format(best_acc))
if len(history_acc) > params['num_patience'] and is_descending(history_acc):
logger.info("Testing Accuracy not improved over {} epochs, Early Stop".format(params['num_patience']))
break
CNN实现文本分类任务
这里使用卷积神经网络来实现文本份分类任务。
这里得将文本转换成多维的向量。
CNN文本分类
- 文本数据预处理,必须都是相同长度,相同向量维度
- 构建卷积模型,注意卷积核大小的设计
- 将卷积后的特征图池化成一个特征
- 将多种特征拼接在一起,准备完成分类任务
准备数据集 和 导包
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.sequence import pad_sequences # 填充操作
num_features = 3000
sequence_length = 300 # 文章的长度
embedding_dimension = 100 # 把每个词转化成映射向量
(x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=num_features)
print(x_train.shape)
print(x_test.shape)
print(y_train.shape)
print(y_test.shape)
# 填充操作
x_train = pad_sequences(x_train, maxlen=sequence_length)
x_test = pad_sequences(x_test, maxlen=sequence_length)
print(x_train.shape)
print(x_test.shape)
print(y_train.shape)
print(y_test.shape)
# 多种卷积核,相当于单词数
filter_sizes=[3,4,5] # 构造不同的卷积核大小
def convolution():
inn = layers.Input(shape=(sequence_length, embedding_dimension, 1))#3维的
cnns = []
for size in filter_sizes:
# filters:得到特征图的个数
conv = layers.Conv2D(filters=64, kernel_size=(size, embedding_dimension),
strides=1, padding='valid', activation='relu')(inn)
#需要将多种卷积后的特征图池化成一个特征
# sequence_length-size+1 : 特征图长度
pool = layers.MaxPool2D(pool_size=(sequence_length-size+1, 1), padding='valid')(conv)
cnns.append(pool)
# 将得到的特征拼接在一起
outt = layers.concatenate(cnns)
model = keras.Model(inputs=inn, outputs=outt)
return model
def cnn_mulfilter():
model = keras.Sequential([
layers.Embedding(input_dim=num_features, output_dim=embedding_dimension,
input_length=sequence_length),
layers.Reshape((sequence_length, embedding_dimension, 1)),
convolution(),
layers.Flatten(),
layers.Dense(10, activation='relu'),
layers.Dropout(0.2),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer=keras.optimizers.Adam(),
loss=keras.losses.BinaryCrossentropy(),
metrics=['accuracy'])
return model
model = cnn_mulfilter()
model.summary()
训练模型
history = model.fit(x_train, y_train, batch_size=64, epochs=5, validation_split=0.1)
展示数据
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['training', 'valiation'], loc='upper left')
plt.show()