Tensorflow 2.3学习笔记
安装
我先用pip安装了tensorflow,整个装过程很顺利,只需要在bash上输入
pip install tensorflow 2.3.0rc0
需要注意的是我使用的是macOS 10.15.6,使用时系统会默认使用自带的 python2.7,pip也是对应为老版的,这时候就需要我们手动升级,并在~/.bash_profile
中添加
alias python = "/usr/local/bin/python3.8"
若仍然失败那说明系统目录下没有~/.zshrc
文件,就需要touch一个~/.zshrc
并写入
source ~/.bash_profile
重启bash即可将自己下载的 python 作为默认的 python了。我使用的是清华大学的镜像,上面只支持2.1、2.2、2.3,既然下不了2.0了,索性来个最高版本吧,于是就用了2.3.0rc0
Hello World
按照惯例学什么东西第一个要输出就是hello world
的了,而tensorflow又不是语言怎么入手呢?
于是我去官网上查看了tensorflow的介绍及实例代码
示例代码与解析
import tensorflow as tf
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)
这个代码乍一看很复杂,但是实际上看不懂的部分都是我不熟悉的函数之类。感觉到这里和之前学习爬虫很相似,只需要了解相应的函数,对应总体编写可以总结为简单的几个步骤,于是我又去挨个看了函数的解释,以方便理解。
第2行
minist = tf.keras.datasets.mnist
这实际上是获取MNIST数据集的方法,这个方法会自动从https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz的链接下载mnist.npz并存放于~/.keras/datasets/下,因此提前下载好放在这里就可以避免无法下载的问题。
tips: MNIST是一个非常常见的数据集,数据量小,方便读入内存,而且直观可见,在实现各种机器学习算法的时候,经常可以用来当小白鼠实验。
第4行
(x_train, y_train),(x_test, y_test) = mnist.load_data()
plain English,keras加载MNIST数据的方法
第5行
x_train, x_test = x_train / 255.0, x_test / 255.0
这是将RGB图像三色归为0,1矩阵的处理
第7-12行
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
Sequential()方法是一个容器,描述了神经网络的网络结构,在Sequential()的输入参数中描述从输入层到输出层的网络结构
model = tf.keras.models.Sequential([网络结构]) #描述各层网络
网络结构举例:
- 拉直层:
tf.keras.layers.Flatten() #拉直层可以变换张量的尺寸,把输入特征拉直为一维数组,是不含计算参数的层
- 全连接层:
tf.keras.layers.Dense(神经元个数,
activation = "激活函数“,
kernel_regularizer = "正则化方式")
其中:activation可选 relu 、softmax、 sigmoid、 tanh等
kernel_regularizer可选 tf.keras.regularizers.l1() 、tf.keras.regularizers.l2()
- 卷积层:
tf.keras.layers.Conv2D(filter = 卷积核个数,
kernel_size = 卷积核尺寸,
strides = 卷积步长,
padding = ”valid“ or "same")
- LSTM层:
tf.keras.layers.LSTM()
其中的dropout是一种防止神经网络过拟合的手段。随机的拿掉网络中的部分神经元,从而减小对W权重的依赖,以达到减小过拟合的效果。
注意:dropout只能用在训练中,测试的时候不能dropout,要用完整的网络测试哦。
第13行
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
Plain English still,这是编译模型的代码,其中包含几个必须的参数:
- optimizer 优化器
https://blog.csdn.net/weixin_41417982/article/details/81561210
https://blog.csdn.net/qq_34649170/article/details/93209481?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
具体移步这两篇博客 - loss 损失函数
https://blog.csdn.net/wzyaiwl/article/details/107823769
这篇博客详细介绍了内置函数的使用方法 - metrics
Metrics是衡量一个神经网络性能的重要指标
https://cnblogs.com/xiximayou/p/12689915.html
https://stackoverflow.com/questions/45947351/how-to-use-tensorflow-metrics-in-keras/50527423#50527423
这篇博客有比较全的解释
(没看懂,先码住)
第15行
model.fit(x_train, y_train, epochs=5)
首先Keras中的fit()函数传入的x_train和y_train是被完整的加载进内存的,当然用起来很方便,但是如果我们数据量很大,那么是不可能将所有数据载入内存的,必将导致内存泄漏,这时候我们可以用fit_generator函数来进行训练。
其中fit中有几种参数:
-
x: 训练数据的 Numpy 数组(如果模型只有一个输入), 或者是 Numpy 数组的列表(如果模型有多个输入)。 如果模型中的输入层被命名,你也可以传递一个字典,将输入层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例如 TensorFlow 数据张量)数据,x 可以是 None(默认)。
-
y: 目标(标签)数据的 Numpy 数组(如果模型只有一个输出), 或者是 Numpy 数组的列表(如果模型有多个输出)。 如果模型中的输出层被命名,你也可以传递一个字典,将输出层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例如 TensorFlow 数据张量)数据,y 可以是 None(默认)。
-
batch_size: 整数或 None。每次梯度更新的样本数。如果未指定,默认为 32。
-
epochs: 整数。训练模型迭代轮次。一个轮次是在整个 x 和 y 上的一轮迭代。 请注意,与 initial_epoch 一起,epochs 被理解为 「最终轮次」。模型并不是训练了 epochs 轮,而是到第 epochs 轮停止训练。
verbose: 0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行。
callbacks: 一系列的 keras.callbacks.Callback 实例。一系列可以在训练时使用的回调函数。 详见 callbacks。
validation_split: 0 和 1 之间的浮点数。用作验证集的训练数据的比例。 模型将分出一部分不会被训练的验证数据,并将在每一轮结束时评估这些验证数据的误差和任何其他模型指标。 验证数据是混洗之前 x 和y 数据的最后一部分样本中。 -
validation_data: 元组 (x_val,y_val) 或元组 (x_val,y_val,val_sample_weights), 用来评估损失,以及在每轮结束时的任何模型度量指标。 模型将不会在这个数据上进行训练。这个参数会覆盖 validation_split。
-
shuffle: 布尔值(是否在每轮迭代之前混洗数据)或者 字符串 (batch)。 batch 是处理 HDF5 数据限制的特殊选项,它对一个 batch 内部的数据进行混洗。 当 steps_per_epoch 非 None 时,这个参数无效。
-
class_weight: 可选的字典,用来映射类索引(整数)到权重(浮点)值,用于加权损失函数(仅在训练期间)。 这可能有助于告诉模型 「更多关注」来自代表性不足的类的样本。
-
sample_weight: 训练样本的可选 Numpy 权重数组,用于对损失函数进行加权(仅在训练期间)。 您可以传递与输入样本长度相同的平坦(1D)Numpy 数组(权重和样本之间的 1:1 映射), 或者在时序数据的情况下,可以传递尺寸为 (samples, sequence_length) 的 2D 数组,以对每个样本的每个时间步施加不同的权重。 在这种情况下,你应该确保在 compile() 中指定 sample_weight_mode=“temporal”。
-
initial_epoch: 整数。开始训练的轮次(有助于恢复之前的训练)。
-
steps_per_epoch: 整数或 None。 在声明一个轮次完成并开始下一个轮次之前的总步数(样品批次)。 使用 TensorFlow 数据张量等输入张量进行训练时,默认值 None 等于数据集中样本的数量除以 batch 的大小,如果无法确定,则为 1。
-
validation_steps: 只有在指定了 steps_per_epoch 时才有用。停止前要验证的总步数(批次样本)。
其返回值为:
一个 History 对象。其 History.history 属性是连续 epoch 训练损失和评估值,以及验证集损失和评估值的记录(如果适用)。
第16行
输入数据和标签,输出损失和精确度
model.evaluate(x_test, y_test)
总结
搭建一个程序共有五步:
-
创建Sequential模型;
-
添加所需要的神经层;在示例程序中,创建模型和添加神经层同步完成了,我们还可以用model.add()来添加神经层,并设置其功能,形状,激活函数,留存率等属性;
-
使用.compile方法确定模型训练结构;有三个重要参数:
- optimizer:此对象会指定训练过程。从 tf.train 模块向其传递优化器实例;
- loss:要在优化期间最小化的函数。常见选择包括均方误差 (mse)、categorical_crossentropy 和 binary_crossentropy。损失函数由名称或通过从 tf.keras.losses 模块传递可调用对象来指定。
- metrics:用于监控训练。它们是 tf.keras.metrics 模块中的字符串名称或可调用对象。
- 使用.fit方法使模型与训练数据“拟合”;
- predict或evaluate方法进行预测。
应用
这次的任务是猫狗大战。这是是kaggle的一个著名比赛项目,即编写一个算法使机器能够区分猫和狗。
这是其的介绍:https://www.kaggle.com/c/dogs-vs-cats/overview
所遇到的新的问题及解决
简单来说,就是要用tensorflow写一个可以分辨猫狗的脚本,现在我的手上有25,000张猫和狗的照片。除了上面总结出的步骤外,还需要:
- 找到图片所在的位置
- 导入图片并处理图片
对于1,用glob库就可以实现,具体看这篇博客https://www.sohu.com/a/334008637_120291025根据这篇博客,我导入了图像并用0和1表示猫和狗:
path = '/Users/luominxing/Downloads/cats_and_dogs_filtered' #path为你数据集所在的位置
train_image_path = glob.glob(path + '/train/*/*.jpg')
test_image_path = glob.glob(path+'/validation/*/*.jpg')
train_image_label = [int(p.split('/')[-2] == 'cats') for p in train_image_path]
test_image_label = [int(p.split('/')[-2] == 'cats')for p in test_image_path]
BTW,我一开始用glob库练手就是把train图片里的猫和狗分成了两组,下附代码:
import os
from shutil import copy
path_source = "/Users/luominxing/Downloads/train"
path_destination = '/Users/luominxing/Downloads/cats_and_dogs_filtered/train'
pathDir = os.listdir(path_source)
for i in pathDir:
if i.split('.')[0] == 'cat':
to_path = path_destination + '/cats'
elif i.split('.')[0] == 'dog':
to_path = path_destination + '/dogs'
from_path = os.path.join(path_source, i)
copy(from_path, to_path)
对于2,这篇博客详细讲解了tensorflow里内附的各种处理图像的函数https://blog.csdn.net/qq_30638831/article/details/81046455?ops_request_misc=%7B%22request%5Fid%22%3A%22159817412919725247612054%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=159817412919725247612054&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-81046455.first_rank_ecpm_v3_pc_rank_v2&utm_term=tensorflow处理图像&spm=1018.2118.3001.4187
在这里我认为把图像剪裁到统一大小更有利于训练,然后对比度和亮度调高,让不清晰和光线较暗的图像也能把特征显现出来,对此写了一个函数,来处理图像:
def load_preprocess_image(path, label):
image = tf.io.read_file(path)
image = tf.image.decode_jpeg(image, channels=3)
#读取jpg格式的文件,tf同时还支持多种格式图像文件的读取
image = tf.image.resize(image, [360, 360])
#将图像进行缩放
image = tf.image.random_crop(image, [256, 256, 3])
#将图像进行裁剪
image = tf.image.random_flip_left_right(image)
#将图像上下颠倒
image = tf.image.random_flip_up_down(image)
#将图像左右颠倒
image = tf.image.random_brightness(image, 0.5)
#调整图像的亮度
image = tf.image.random_contrast(image, 0, 1)
#调整图像的对比度
image = tf.cast(image, tf.float32)
#将图像的类型转换为tf.float32类型
image = image / 255
#将图像进行归一化处理,使得图像的范围处于0和1之间
label = tf.reshape(label, [1])
return image, label
完整代码
由于对tensorflow不是很熟悉,接下来的操作都是参考这篇博客https://blog.csdn.net/lys_828/article/details/101322246照葫芦画瓢写出来的,其中Sequential函数里的层,都是究极缝合+玄学,下附完整代码
#版本信息:macOS 10.15.6 python3.8.5 tensorflow 2.3.0 rc0
#作者:敏行讷言
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import os
import matplotlib.pyplot as plt
import glob
def load_preprocess_image(path, label):
image = tf.io.read_file(path)
image = tf.image.decode_jpeg(image, channels=3)
# 读取jpg格式的文件,tf同时还支持多种格式图像文件的读取
image = tf.image.resize(image, [360, 360])
# 将读取到的图像进行缩放
image = tf.image.random_crop(image, [256, 256, 3])
# 将读取到的图像进行裁剪
image = tf.image.random_flip_left_right(image)
# 将图像上下颠倒
image = tf.image.random_flip_up_down(image)
# 将图像左右颠倒
image = tf.image.random_brightness(image, 0.5)
# 调整图像的亮度
image = tf.image.random_contrast(image, 0, 1)
# 调整图像的对比度
image = tf.cast(image, tf.float32)
# 将图像的类型转换为tf.float32类型
image = image / 255.0
# 将图像进行归一化处理,使得图像的范围处于0和1之间
# 同样可以使用tf.image.convert_image_dtype进行处理
label = tf.reshape(label, [1])
return image, label
tf.compat.v1.disable_eager_execution()
BATCH_SIZE = 64
num_epochs = 10
train_count = 800
test_count = 400
steps_per_epoch = int(train_count / BATCH_SIZE)
validation_steps = int(test_count / BATCH_SIZE)
print(tf.__version__, tf.config.list_physical_devices('GPU')) # tensorflow版本,gpu加速
path = '/Users/luominxing/Downloads/cats_and_dogs_filtered' # path为你数据集所在的位置
train_image_path = glob.glob(path + '/train/*/*.jpg')
test_image_path = glob.glob(path+'/validation/*/*.jpg')
train_image_label = [int(p.split('/')[-2] == 'cats') for p in train_image_path]
test_image_label = [int(p.split('/')[-2] == 'cats')for p in test_image_path]
train_image_ds = tf.data.Dataset.from_tensor_slices(
(train_image_path, train_image_label))
test_image_ds = tf.data.Dataset.from_tensor_slices(
(test_image_path, test_image_label))
AUTOTUNE = tf.data.experimental.AUTOTUNE
# 根据CPU的状况自动处理,可以不使用
train_image_ds = train_image_ds.map(
load_preprocess_image, num_parallel_calls=AUTOTUNE)
test_image_ds = test_image_ds.map(
load_preprocess_image, num_parallel_calls=AUTOTUNE)
train_image_ds = train_image_ds.shuffle(train_count)
test_image_ds = test_image_ds.shuffle(test_count)
# .shuffle是随机打乱数据集的顺序
train_image_ds = train_image_ds.batch(BATCH_SIZE)
test_image_ds = test_image_ds.batch(BATCH_SIZE)
# .batch是取batch大小,这里我的BATCH_SIZE取得是64
train_image_ds = train_image_ds.prefetch(AUTOTUNE)
test_image_ds = test_image_ds.prefetch(AUTOTUNE)
# 预取数据
model = tf.keras.Sequential([
layers.Conv2D(64, (3, 3), input_shape=(256, 256, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(64, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(),
layers.Conv2D(128, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(128, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(),
layers.Conv2D(256, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(256, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(256, (1, 1)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(),
layers.Conv2D(512, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(512, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(512, (1, 1)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(),
layers.Conv2D(512, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(512, (3, 3)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Conv2D(512, (1, 1)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(),
layers.GlobalAveragePooling2D(),
layers.Dense(4096, activation='relu'),
layers.BatchNormalization(),
layers.Dense(4096, activation='relu'),
layers.BatchNormalization(),
layers.Dense(1000, activation='relu'),
layers.BatchNormalization(),
layers.Dense(1)
])
model.compile(loss='binary_crossentropy',
optimizer=tf.keras.optimizers.Adam(lr=0.0005),
metrics=['acc'])
history = model.fit(train_image_ds, epochs=num_epochs,
# 我设置的轮数是30
steps_per_epoch=steps_per_epoch,
# step_per_epoch是每轮训练需要训练多少次,一般我们用数据的数量除batch_size得到
validation_data=test_image_ds,
validation_steps=validation_steps)
# 和step_per_epoch是同样的道理
model.save('c_v_d_back_up.h5')
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc)) # Get number of epochs
plt.figure()
plt.plot(epochs, acc, 'r')
plt.plot(epochs, val_acc, 'b')
plt.title('Training and validation accuracy')
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.legend(["Accuracy", "Validation Accuracy"])
plt.savefig('train_results_acc.eps', dpi=600, format='eps')
plt.show()
plt.clf() # 添加上这一行,画完第一个图后,重置一下
# 画loss曲线
plt.plot(epochs, loss, 'r')
plt.plot(epochs, val_loss, 'b')
plt.title('Training and validation loss')
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend(["Loss", "Validation Loss"])
plt.savefig('train_results_loss.eps', dpi=600, format='eps')
plt.show()
结果