TensorFlow 2.0 使用指南 (2020.5.4完结)

文章目录
  1. 1. 数据预处理
    1. 1.1. tf框架自带的数据集
    2. 1.2. sklearn自带的数据集
  2. 2. 数据加载和预处理
    1. 2.1. csv数据处理
      1. 2.1.1. x,label处理方式一:tf.data.experimental.make_csv_dataset
      2. 2.1.2. x,label处理方式二:pandas
      3. 2.1.3. 离散型特征
      4. 2.1.4. 连续型数据处理
      5. 2.1.5. 创建预处理层
    2. 2.2. 模型构建,训练与评估
      1. 2.2.1. tf处理数据版泰坦尼克生存预测模型完整代码
      2. 2.2.2. pandas处理数据版泰坦尼克生存预测模型完整代码
      3. 2.2.3. 总结
    3. 2.3. numpy数据预处理
    4. 2.4. 图像数据处理
    5. 2.5. 文本数据处理
  3. 3. Estimator
    1. 3.1. 预创建的Estimator
    2. 3.2. Keras模型转Estimator
  4. 4. 自定义
    1. 4.1. 张量和操作
      1. 4.1.1. 张量的基本计算
      2. 4.1.2. Numpy和Tensor的相互转换
      3. 4.1.3. 设置运行设备
    2. 4.2. 自定义层
  5. 5. 卷积神经网络实现
    1. 5.1. CNN实现图像分类
    2. 5.2. CNN实现文本分类
  6. 6. 循环神经网络实现
    1. 6.1. 普通RNN实现
    2. 6.2. Padding_Pooling
    3. 6.3. 双向RNN
    4. 6.4. LSTM
  7. 7. 模型保存

前后大概花了一周半的时间把TensorFlow2.0的框架学完,这个版本相较于1.0在代码上要更加易于理解和操作。
所以这里用这篇博客来记录 TensorFlow 2.0 中的各种操作细节,算是个人备忘录版的使用指南吧。

本篇博客会按照数据处理,Estimator,模型保存,CNN及其实践,RNN及其实践,注意力机制等几个部分来撰写。
p.s. 以前开发做点Python相关的东西还是习惯用Pycharm,不过学习这个框架的过程中由于需要清晰的看到每个步骤的结果,所以用了一下Jupyter,害,用户体验嘛,那当然是极好的!以后做神经网络模型这类型的,我可能会继续沿用Jupyter,不过如果项目规模偏大,那还是回到Pycharm。btw,VScode编写ipynb体验还不错。

注:本篇博客参考内容主要来自于TF2.1.0官网的教程,部分内容有参考相关学习过的内容,结合这二者写完这篇笔记。

最后,全文实现的代码都是在Jupyter上写的。

数据预处理

以我个人的看法,数据预处理这部分内容在神经网络的构建中可以说是极其重要的。如果没有处理好数据,并生成用以训练的向量,那这个模型基本白搭,所以这块的内容我会尽量去详细的记录。

tf框架自带的数据集

tf框架最常见的自带数据集包括:mnist,fashion_minist,imbd等,这类数据集最大的好处就是自带切分功能。
引用方式通常比较简单,并且已经是向量化的数据,以这里取出来的数据来说,就是一张大小为28x28的图像各个像素点的值。

1
2
3
4
5
6
7
8
9
10
import tensorflow as tf
from tensorflow import keras

# 直接获取数据
mnist=keras.datasets.mnist
(x_train_all,y_train_all),(x_test,y_test)=mnist.load_data()

# 切分训练集和验证集
x_train,y_train=x_train_all[:5000],y_train_all[:5000]
x_valid,y_valid=x_train_all[5000:],y_train_all[5000:]

sklearn自带的数据集

sklearn下的数据集类似于tf下面的,不过需要额外引入另外的两个包,一个负责取出数据集,一个负责对训练集和测试集进行分割。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sklearn
from sklearn.datasets import fetch_california_housing # 取出数据集
from sklearn.model_selection import train_test_split # 对数据集进行分割

# 取出相应的数据集
housing=fetch_california_housing()

# 打印出相关信息
print(housing.DESCR) # 数据描述
print('----------------')
print(housing.data.shape) # 数据大小
print('----------------')
print(housing.target.shape) # label

x_train_all, x_test, y_train_all, y_test = train_test_split(
housing.data, housing.target, random_state = 7)
x_train, x_valid, y_train, y_valid = train_test_split(
x_train_all, y_train_all, random_state = 11)
print(x_train.shape, y_train.shape)
print(x_valid.shape, y_valid.shape)
print(x_test.shape, y_test.shape)

数据加载和预处理

以我过去从事NLP文本分类的经验,单从上面两个包中提供的数据集不满足实验需求,不同的文章中使用到的数据集不一样,所以还需要具备处理其他数据集的能力。

csv数据处理

x,label处理方式一:tf.data.experimental.make_csv_dataset

这里直接使用这俩数据集(泰坦尼克号生存数据集),下载地址如下:

1
2
https://storage.googleapis.com/tf-datasets/titanic/train.csv
https://storage.googleapis.com/tf-datasets/titanic/eval.csv

下载下来以后单独保存在一个 dataset 文件夹下面,下载完成以后,有两种方法可以查看csv中数据集包含的数据:
1. 使用pandas
关于pandas部分库的操作,之前我写过一篇笔记,可以作为参考:Python 读写csv文件相关操作

这里直接取出列表名称,和相关数据

1
2
3
4
5
import pandas as pd

file=pd.read_csv('./dataset/train.csv')
print(list(file.head()))#列表名称
print(file.head())#列表数据

2. 直接在Jupyter的环境下查看

1
!head {文件路径}

这些取出来的列名参数需要传参给tf.data.experimental.make_csv_dataset下面的column_names参数。
使用这个包和取出的列名来构造可以用于基于TensorFlow搭建模型的训练和测试数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
import pandas as pd

file_path='./dataset/train.csv'
file=pd.read_csv(file_path)
csv_columns=list(file.head())
print(csv_columns)#列表名称
print(file.head())#列表数据

# 设置 label
label_culomn='survived'
labels=[0,1]

def get_dataset(file_path):
dataset=tf.data.experimental.make_csv_dataset(
file_path,
batch_size=12,
label_name=label_culomn,# 取出某列的数据作为label
na_value='?',# 识别NA/NaN的附加字符串,设置为?
num_epochs=1,# 遍历数据集的次数
ignore_errors=True)
return dataset

# 生成的值类型为:
# <class 'tensorflow.python.data.ops.dataset_ops.PrefetchDataset'>
# 如果想查看其中的内容,可以使用iter进行迭代,通过next嵌套iter可以查看第一轮运行的数据
# 第一轮运行数据的大小取决于设置的batch_size
raw_train_data=get_dataset(file_path)
raw_test_data=get_dataset(file_path)

# 查看生成的数据集中的样本和标签
example,label=next(iter(raw_train_data))
print('Example:',example) # sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone
print('Label:',label) # label:survived

输出值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Example:OrderedDict([('sex', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'male', b'female', b'male', b'male', b'male', b'female', b'male',
b'male', b'female', b'male', b'male', b'female'], dtype=object)>), ('age', <tf.Tensor: shape=(12,), dtype=float32, numpy=
array([28. , 28. , 21. , 26. , 45. , 31. , 28. , 18. , 28. , 43. , 32.5,
58. ], dtype=float32)>), ('n_siblings_spouses', <tf.Tensor: shape=(12,), dtype=int32, numpy=array([0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0])>), ('parch', <tf.Tensor: shape=(12,), dtype=int32, numpy=array([0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 1])>), ('fare', <tf.Tensor: shape=(12,), dtype=float32, numpy=
array([ 35.5 , 133.65 , 73.5 , 8.05 , 8.05 , 164.8667,
56.4958, 7.7958, 14.4542, 26.25 , 30.0708, 153.4625],
dtype=float32)>), ('class', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'First', b'First', b'Second', b'Third', b'Third', b'First',
b'Third', b'Third', b'Third', b'Second', b'Second', b'First'],
dtype=object)>), ('deck', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'A', b'unknown', b'unknown', b'unknown', b'unknown', b'C',
b'unknown', b'unknown', b'unknown', b'unknown', b'unknown', b'C'],
dtype=object)>), ('embark_town', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'Southampton', b'Southampton', b'Southampton', b'Southampton',
b'Southampton', b'Southampton', b'Southampton', b'Southampton',
b'Cherbourg', b'Southampton', b'Cherbourg', b'Southampton'],
dtype=object)>), ('alone', <tf.Tensor: shape=(12,), dtype=string, numpy=
array([b'y', b'n', b'y', b'y', b'y', b'n', b'y', b'y', b'n', b'n', b'n',
b'n'], dtype=object)>)])
Label:tf.Tensor([1 1 0 0 1 1 1 0 0 0 0 1], shape=(12,), dtype=int32)

这里有个问题,如何单独查看example或者label中的值,这个很简单,这两个值类似于字典类型,以example举例子,取出age这一栏,并通过numpy形式展示:

1
print(example['age'].numpy())

x,label处理方式二:pandas

基于pandas读取出来的csv文件可以直接使用pop函数来取出某个单独列的值。

1
2
3
4
5
6
7
8
9
10
import pandas as pd

train_df=pd.read_csv('./dataset/train.csv')
eval_df=pd.read_csv('./dataset/eval.csv')

y_train=train_df.pop('survived')
y_eval=eval_df.pop('survived')

print('train_df.shape:',train_df.shape)
print('eval_df.shape:',eval_df.shape)

pandas在划分x和y的时候相对比之前tf.data.experimental.make_csv_dataset要简便不少,同时,取出的pandas Series数据可以在外嵌套一层numpy.array()就可以转换为numpy类型的array,然后通过reshape转换成模型需要的任意大小:

1
2
3
import numpy as np

new_train_df=np.array(train_df).reshape([-1,])

离散型特征

上面介绍了两种对数据进行划分的方法,下面继续对数据进行分类。
这里直接引用官方文档的话:
csv数据中的有些列是分类的列,也就是说,这些列只能在有限的集合中取值。(这些列也就相当于是已经提取好的特征值)

使用 tf.feature_column API创建一个 tf.feature_column.indicator_column 集合,每个 tf.feature_column.indicator_column对应一个分类的列。

核心api:

tf.feature_column.categorical_column_with_vocabulary_list
tf.feature_column.indicator_column

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 这里分类仅使用几个特征列
categories = {
'sex': ['male', 'female'],
'class' : ['First', 'Second', 'Third'],
'deck' : ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
'embark_town' : ['Cherbourg', 'Southhampton', 'Queenstown'],
'alone' : ['y', 'n']
}

categorical_columns=[]
for feature,vocab in catecategories.items():
cat_col=tf.feature_column.categorical_column_with_vocabulary_list(
key=feature,
vocabulary_list=vocab)
categorical_columns.append(tf.feature_column.indicator_column(cat_col))

print(categorical_columns)

连续型数据处理

处理完分类数据之后需要处理连续数据.主要需要进行归一化处理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def process_continuous_data(mean,data):
# 标准化数据
# tf.cast:转换数据格式
data=tf.cast(data,tf.float32)*1/(2*mean)
return tf.reshape(data,[-1,1])

MEANS = {
'age' : 29.631308,
'n_siblings_spouses' : 0.545455,
'parch' : 0.379585,
'fare' : 34.385399
}

numerical_columns=[]

for feature in MEANS.keys():
num_col=tf.feature_column.numeric_column(
feature,
normalizer_fn=functools.partial(process_continuous_data,MEANS[feature]))
numerical_columns.append(num_col)

print(numerical_columns)

输出的结果就是各个连续值特征归一化的值:

创建预处理层

上面分别介绍了 离散型特征 和 连续型特征 的处理方法,现在需要对两类型的特征进行合并,从而创建一个能进行预处理的输入层。

1
preprocessing_layer = tf.keras.layers.DenseFeatures(categorical_columns+numerical_columns)

模型构建,训练与评估

这里暂时先插一句,在tf2.0的版本中,调用构建神经网络中各种层都是使用 tf.keras.layers 来进行构建。
同时,由于这是一个Sequential模型,所以要使用 tf.keras.Sequential 方法来创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
model=tf.keras.Sequential([
preprocessing_layer,
tf.keras.layers.Dense(128,activation='relu'),# 基于特征层的基础上构建全连接层
tf.keras.layers.Dense(128,activation='relu'),
tf.keras.layers.Dense(1,activation='sigmoid')# 最后只需要预测一个结果
])

model.compile(
loss='binary_crossentropy',# 定义损失函数
optimizer='adam',# 定义优化器
metrics=['accuracy'])# 定义衡量结果的标准

train_data=raw_train_data.shuffle(500) # 打乱数据
test_data=raw_test_data

model.fit(train_data,epochs=20) # 训练模型

test_loss, test_accuracy = model.evaluate(test_data) # 测试模型
print(test_loss)
print(test_accuracy)

tf处理数据版泰坦尼克生存预测模型完整代码

上面为了分步描述步骤,写得比较简略,这里贴上一个完整版以供参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
import pandas as pd
import functools

file_path='./dataset/train.csv'
file=pd.read_csv(file_path)
csv_columns=list(file.head())
print(csv_columns)#列表名称
print(file.head())#列表数据

# 设置 label
label_culomn='survived'
labels=[0,1]

def get_dataset(file_path):
dataset=tf.data.experimental.make_csv_dataset(
file_path,
batch_size=12,
label_name=label_culomn,# 取出某列的数据作为label
na_value='?',# 识别NA/NaN的附加字符串,设置为?
num_epochs=1,# 遍历数据集的次数
ignore_errors=True)
return dataset

# 生成的值类型为:
# <class 'tensorflow.python.data.ops.dataset_ops.PrefetchDataset'>
# 如果想查看其中的内容,可以使用iter进行迭代,通过next嵌套iter可以查看第一轮运行的数据
# 第一轮运行数据的大小取决于设置的batch_size
raw_train_data=get_dataset(file_path)
raw_test_data=get_dataset(file_path)

# 查看生成的数据集中的样本和标签
example,label=next(iter(raw_train_data))
print('Example:',example) # sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone
print('Label:',label) # label:survived

# 处理离散型特征
categories = {
'sex': ['male', 'female'],
'class' : ['First', 'Second', 'Third'],
'deck' : ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
'embark_town' : ['Cherbourg', 'Southhampton', 'Queenstown'],
'alone' : ['y', 'n']
}

categorical_columns=[]
for feature,vocab in catecategories.items():
cat_col=tf.feature_column.categorical_column_with_vocabulary_list(
key=feature,
vocabulary_list=vocab)
categorical_columns.append(tf.feature_column.indicator_column(cat_col))

print(categorical_columns)

def process_continuous_data(mean,data):
# 标准化数据
# tf.cast:转换数据格式
data=tf.cast(data,tf.float32)*1/(2*mean)
return tf.reshape(data,[-1,1])

MEANS = {
'age' : 29.631308,
'n_siblings_spouses' : 0.545455,
'parch' : 0.379585,
'fare' : 34.385399
}

numerical_columns=[]

for feature in MEANS.keys():
num_col=tf.feature_column.numeric_column(
feature,
normalizer_fn=functools.partial(process_continuous_data,MEANS[feature]))
numerical_columns.append(num_col)

print(numerical_columns)

preprocessing_layer = tf.keras.layers.DenseFeatures(categorical_columns+numerical_columns)

model=tf.keras.Sequential([
preprocessing_layer,
tf.keras.layers.Dense(128,activation='relu'),# 基于特征层的基础上构建全连接层
tf.keras.layers.Dense(128,activation='relu'),
tf.keras.layers.Dense(1,activation='sigmoid')# 最后只需要预测一个结果
])

model.compile(
loss='binary_crossentropy',# 定义损失函数
optimizer='adam',# 定义优化器
metrics=['accuracy'])# 定义衡量结果的标准

train_data=raw_train_data.shuffle(500) # 打乱数据
test_data=raw_test_data

model.fit(train_data,epochs=20)

test_loss, test_accuracy = model.evaluate(test_data) # 测试模型
print(test_loss)
print(test_accuracy)

pandas处理数据版泰坦尼克生存预测模型完整代码

私以为这个版本比上面那个版本更容易理解一些,不过在处理特征上的区别不是很大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import sklearn
import pandas as pd
import os
import sys
import time
import tensorflow as tf
from tensorflow import keras

train_file='./dataset/train.csv'
eval_file='./dataset/eval.csv'

train_df=pd.read_csv(train_file)
eval_df=pd.read_csv(eval_file)

print(train_df.head())
print(eval_df.head())

# 取出survived字段作为label
y_train=train_df.pop('survived')
y_eval=eval_df.pop('survived')

print(y_train.head())
print(y_eval.head())

print(train_df.shape)
print(eval_df.shape)

feature_columns=[]

# 处理离散型特征
categorical_columns=['sex','n_siblings_spouses','parch',
'class','deck','embark_town','alone']

for categorical_column in categorical_columns:
# 取出离散型特征对应的值并去重
vocab=train_df[categorical_column].unique()
# 使用 tensorflow 来定义离散特征 categorical_column_with_vocabulary_list
# tf 生成离散型特征需要两个参数,特征名和特征值
# tf 生成 one-hot 编码:indicator_column
print(categorical_column,vocab)
feature_columns.append(tf.feature_column.indicator_column(
tf.feature_column.categorical_column_with_vocabulary_list(
categorical_column,vocab)))

# 处理连续型特征
numeric_columns=['age','fare']
for numeric_column in numeric_columns:
print(numeric_column)
feature_columns.append(tf.feature_column.numeric_column(numeric_column,dtype=tf.float32))

print('feature_columns:\n',feature_columns)

# 制作数据集
def make_dataset(data_df,label,epochs=10,shuffle=True,batch_size=32):
dataset=tf.data.Dataset.from_tensor_slices(
(dict(data_df),label))
if shuffle:# 是否打乱数据集
dataset=dataset.shuffle(10000)
# 设置数据集需要迭代的次数
dataset=dataset.repeat(epochs).batch(batch_size)
return dataset

model=keras.models.Sequential([
keras.layers.DenseFeatures(feature_columns),
keras.layers.Dense(100,activation='relu'),
keras.layers.Dense(100,activation='relu'),
keras.layers.Dense(2,activation='softmax')
])
# tf2.1.0的版本里如果不单独设置,则默认sdg=0.01,这样收敛太快容易找不到对应的值,所以手动调一下
sgd=keras.optimizers.SGD(0.001)
model.compile(
loss='sparse_categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy'])

# 训练模型

train_dataset=make_dataset(train_df,y_train,epochs=100)
eval_dataset=make_dataset(eval_df,y_eval,epochs=1,shuffle=False)

# steps_per_epoch:训练集中样本数/batch_size
# validation_steps:验证集中样本数/batch_size
model.fit(
train_dataset,
steps_per_epoch=15,
epochs=100)

# 验证模型
loss,acc=model.evaluate(eval_dataset)
print('loss:',loss)
print('acc:',acc)

总结

构建可以在模型中进行训练的数据集主要有两种方式:

  1. 使用 tf.data.experimental.make_csv_dataset 来设置;
  2. 使用 tf.data.Dataset.from_tensor_slices,将数据和标签合并为一个元组来创建数据集。

numpy数据预处理

这类数据的类型.npz,需要通过 np,load()函数来进行加载。
数据集下载地址:

1
https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz

这个可以说是比较简单的处理方式了,和上面csv的区别仅在于要用到np.load函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds

# 载入数据
dataset=np.load('./dataset/mnist.npz')
train_examples=dataset['x_train']
train_labels=dataset['y_train']
test_examples=dataset['x_test']
test_labels=dataset['y_test']

print(train_examples.shape)
print(train_labels.shape)
print(test_examples.shape)
print(test_labels.shape)

# 使用 tf.data.Dataset加载numpy数组

train_dataset=tf.data.Dataset.from_tensor_slices((train_examples,train_labels))
test_dataset=tf.data.Dataset.from_tensor_slices((test_examples,test_labels))

# 设置数据集的epochs和batch_size
batch_size=32
epochs=10
shuffle=10000

train_dataset=train_dataset.repeat(epochs).batch(batch_size).shuffle(shuffle)
test_dataset=test_dataset.repeat(1).batch(batch_size)

# 模型的构建和训练

model=tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=(28,28)),
tf.keras.layers.Dense(128,activation='relu'),
tf.keras.layers.Dense(10,activation='softmax')# 最后的分类概率
])

model.compile(
optimizer=tf.keras.optimizers.RMSprop(),
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=[tf.keras.metrics.SparseCategoricalCrossentropy()])

model.fit(
train_dataset,
epochs=10)# 这个训练耗时比较长...

model.evaluate(test_dataset)

至于pandas数据,上面在方式二中已经写过,不再赘述。

图像数据处理

讲真,看到这块的时候我心情有点复杂,想当年我入坑NLP的时候信誓旦旦的说这辈子绝对不碰cv相关。
鬼知道怎么就半只脚踏入了VQA的坑…行吧,那图像处理也得学。

这里使用的数据集下载地址:

1
https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz

以及,对于图像分类的例子,可以直接去Kaggle上看那个10-monkeys的例子。

图像处理这块只针对数据的预处理,后续在CNN搭建的过程中会详细描述如何将数据输入神经网络。

老规矩,首先把数据集下载下来,然后存到所需文件夹下面(以下代码格式以Jupyter模式为例)

1
2
3
4
5
import tensorflow as tf
import pathlib

data_root_orig='dataset/flower_photos'
data_root=pathlib.Path(data_root_orig)

pathlib是用来加载路径的,具体用法可参考:https://docs.python.org/zh-cn/3/library/pathlib.html

查看不同类型的花花所在的文件夹:

1
2
for item in data_root.iterdir():
print(item)

输出结果:

1
2
3
4
5
6
dataset\flower_photos\daisy
dataset\flower_photos\dandelion
dataset\flower_photos\LICENSE.txt
dataset\flower_photos\roses
dataset\flower_photos\sunflowers
dataset\flower_photos\tulips

打乱数据操作:

1
2
3
4
5
6
7
8
import random

all_image_paths=list(data_root.glob('*/*'))
all_image_paths=[str(path) for path in all_image_paths]
random.shuffle(all_image_paths) # 打乱顺序

image_count=len(all_image_paths)
print(image_count)# 打印出图片数量,共有3670

输出:

1
3670

查看all_image_paths中的数据格式:

1
print(('.\n').join(all_image_paths[:10]))

输出结果:

1
2
3
4
5
6
7
8
9
10
dataset\flower_photos\sunflowers\15266715291_dfa3f1d49f_n.jpg.
dataset\flower_photos\dandelion\4634716478_1cbcbee7ca.jpg.
dataset\flower_photos\daisy\1392946544_115acbb2d9.jpg.
dataset\flower_photos\sunflowers\6250692311_cb60c85ee9_n.jpg.
dataset\flower_photos\roses\3494252600_29f26e3ff0_n.jpg.
dataset\flower_photos\dandelion\5607256228_2294c201b3.jpg.
dataset\flower_photos\tulips\14027372499_30f934d24f_m.jpg.
dataset\flower_photos\daisy\3773181799_5def396456.jpg.
dataset\flower_photos\tulips\16862349256_0a1f91ab53.jpg.
dataset\flower_photos\sunflowers\18843967474_9cb552716b.jpg

检查图片,在处理之前需要知道图片的内容:

1
2
3
4
5
6
7
8
9
import os
attributions = (data_root/"LICENSE.txt").open(encoding='utf-8').readlines()[4:] # 这个文件包含了所有图片的说明
attributions = [line.split(' CC-BY ') for line in attributions]
attributions = dict(attributions)

for x,y in attributions.items():
print(x)
print(y)
break

输出值如下:

1
2
daisy/7568630428_8cf0fc16ff_n.jpg 
by A Guy Taking Pictures - https://www.flickr.com/photos/80901381@N04/7568630428/

取出图片并展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import IPython.display as display

def caption_image(image_path):
#print(image_path)
image_rel = pathlib.Path(image_path).relative_to(data_root)
print(image_rel)
try:
res="Image (CC BY 2.0) " + ' - '.join(attributions[str(image_rel)][:-1])
return res
except Exception as e:
pass

for n in range(3):
image_path = random.choice(all_image_paths)
display.display(display.Image(image_path))
print(caption_image(image_path))
print()

众所周知,一个数据集由x和label组成,在这组花类别数据集中,label就是花的类别,每种不同类别的花被单独放在不同的文件夹里面。
现在把这些label取出来进行排序并编号。

1
2
label_names=sorted(item.name for item in data_root.glob('*/') if item.is_dir())
print(label_names)

输出值如下:

1
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']

为每个标签分配索引值(即编号):

1
2
label_to_index=dict((name,index) for index,name in enumerate(label_names))
print(label_to_index)

输出值如下:

1
{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}

完成label的创建,需要把label对应给不同的图片构成一个相应的标签集合。
注意:这里的数据是先前已经打乱过的。

1
2
3
4
all_image_labels=[label_to_index[pathlib.Path(path).parent.name] # 取出当前图片的父级目录
for path in all_image_paths]
print(all_image_labels)
print(len(all_image_labels))

所以现在的图片和label就是一一对应的。

完成x和label的整理,现在需要将这些数据处理为tf模型可以训练的形式。
这就需要一个函数 tf.io.read_file .

1
2
3
4
# 取出一条单独的数据作示范
img_path=all_image_paths[0]
img_raw=tf.io.read_file(img_path)
print(repr(img_raw)[:100])

在此基础上将图片解码为图像tensor:

1
2
3
4
img_tensor=tf.image.decode_image(img_raw)

print(img_tensor.shape)
print(img_tensor.dtype)

输出值如下:

1
2
(259, 320, 3)
<dtype: 'uint8'>

从上面的输出中可以看出,每张图片的大小是不一致的,但是模型需要大小一致的矩阵合集作为输入,所以需要人工调节图片大小。

1
2
3
4
5
6
7
# 根据模型调整输入大小
img_final=tf.image.resize(img_tensor,[192,192])
# 对每个像素进行处理
img_final=img_final/255.0
print(img_final.shape)
print(img_final.numpy().min())
print(img_final.numpy().max())

输出结果:

1
2
3
(192, 192, 3)
0.0
0.98491627

为了方便处理每张图片时不需要重复写代码,可以将上述操作封装为函数。

1
2
3
4
5
6
7
8
9
10
11
# 处理图像
def preprocess_image(image):
image=tf.image.decode_jpeg(image,channels=3)
image=tf.image.resize(image,[192,192])
image/=255.0 # 将数值归一化在 [0,1]区间
return image

# 加载并处理图像
def load_and_preprocess_image(path):
image=tf.io.read_file(path)
return preprocess_image(image)

OK,正式开始调用 tf.data.Dataset.from_tensor_slices 构造数据集。

1
2
3
4
5
6
7
8
9
10
11
12
13
path_ds=tf.data.Dataset.from_tensor_slices(all_image_paths)
print(path_ds)

# 创建一个新的数据集,通过在路径数据集上映射preprocess_image来动态加载和格式化图片
AUTOTUNE=tf.data.experimental.AUTOTUNE
# 数据块
image_ds=path_ds.map(load_and_preprocess_image,num_parallel_calls=AUTOTUNE)
# 标签块
label_ds=tf.data.Dataset.from_tensor_slices(tf.cast(all_image_labels,tf.int64))

# 测试取出的labels
for label in label_ds.take(10):
print(label_names[label.numpy()])

对数据集进行打包(对x和label进行concate),tf框架提供了两种打包方式:

  1. tf.data.Dataset.zip
    1
    2
    image_label_ds=tf.data.Dataset.zip((image_ds,label_ds))
    print(image_label_ds)

  1. tf.data.Dataset.from_tensor_slices
    1
    2
    3
    4
    5
    6
    7
    8
    ds=tf.data.Dataset.from_tensor_slices((all_image_paths,all_image_labels))

    # 元组被解压缩到映射函数的位置参数中
    def load_and_preprocess_from_path_label(path,label):
    return load_and_preprocess_image(path),label

    image_label_ds=ds.map(load_and_preprocess_from_path_label)
    print(image_label_ds)

至此,完成数据集制作只需要设置完数据的batch_size,epochs,shuffle等参数即可。

1
2
3
4
5
6
7
8
9
batch_size=32
shuffle_size=3670
epochs=10
# 设置一个和数据集大小一致的shuffle buffer size来保证数据被充分打乱
# 另外,如果buffer_size设置过大,会引起延迟
ds=image_label_ds.shuffle(shuffle_size)
# 注意:先repeat再batch
ds=ds.repeat(epochs).batch(batch_size)
print(ds)

图片数据处理部分结束。

文本数据处理

这块内容不使用官网提供的数据集,使用keras下面的imdb数据集。
处理文本数据主要包含三个步骤:

  1. 生成词语及其序号对应的dict,正向和反向各一份;
  2. 将所有的文本处理成长度相同的向量,tf2.0提供了 tf.keras.preprocessing.pad_sequences 来完成填充。
1
2
3
4
5
tf.keras.preprocessing.sequence.pad_sequences 参数说明:
paramter1:需要进行处理的数据,这里直接输入 train_data,数据类型是list
value:需要填充的值,害,那<PAD>填呗
padding:此处可以填两个值,post和pre,区别在于post在于把值填充在list后面,pre在于把值填充在list前面
maxlen:填充后的最大长度

3.构造embedding层,embedding层的主要作用:
(1) 定义一个大小为 (vocab_size,embedding_dim) 的矩阵;
(2) 对于任意一个样本,例如[1,2,3,4,…],将每个样本中的单词按序号从里面取出对应的值,将每个样本变为大小为 (max_length,embedding_dim) 的矩阵,这里的max_length指输入句子设置的最大长度;
(3) 在训练时,输入就变成 (batch_size,max_length,embedding_dim).

完整版代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import tensorflow as tf
from tensorflow import keras
import os
import pandas as pd
import numpy as np

os.environ['HTTP_PROXY']='127.0.0.1:1080'
os.environ['HTTPS_PROXY']='127.0.0.1:1080'

# 获取数据集
imdb=keras.datasets.imdb
# 设置词表大小,后面构建模型时需要制作Embedding layer
vocab_size=10000
# 空出三个字符作为填充数据
index_from=3
# 加载数据集
(train_data,train_labels),(test_data,test_labels)=imdb.load_data(
num_words=vocab_size,index_from=index_from)

print(train_data[0],train_labels[0])
print(train_data.shape,train_labels.shape)
print(len(train_data[0]))
print(len(train_data[1]))

print(test_data.shape,test_labels.shape)

# 获取词在词表中的词序
word_index=imdb.get_word_index() # 取出来的值是dict类型
# 查看词表的长度
print(word_index)
print()
print(len(word_index))

# 对词序列表设置偏移
word_index={k:(v+3) for k,v in word_index.items()}

# 对偏移后的词表进行补位
word_index['<PAD>']=0
word_index['<START>']=1
word_index['<UNK>']=2
word_index['<END>']=3

# 生成反向词表
reverse_word_index=dict([(value,key) for key,value in word_index.items()])

# 显示训练数据原始的文本信息
def decode_review(text_ids):
return ' '.join([reverse_word_index.get(word_id,'<UNK>') for word_id in text_ids])

decode_review(train_data[0])

# 对数据进行预处理
# 补齐文本长度,对于长度小于或者大于500的文本,统一将文本补齐成大小是500的向量
max_length=500
# 处理训练集
train_data=keras.preprocessing.sequence.pad_sequences(
train_data,
value=word_index['<PAD>'],
padding='post',
maxlen=max_length)

# 对训练集的代码复制粘贴并修改得到测试集(逃
test_data=keras.preprocessing.sequence.pad_sequences(
test_data,
value=word_index['<PAD>'],
padding='post',
maxlen=max_length)

print(train_data[0])
print(len(train_data[0]))
print(train_data.shape)

# 构建Embedding层和模型
# 将每个单词映射成维度为16的向量

embedding_dim=16
batch_size=128

model=keras.models.Sequential([
keras.layers.Embedding(
vocab_size,
embedding_dim,
input_length=max_length),
# batch_size x max_length x embedding_dim -> batch_size x embedding_dim
# 消去max_length这个维度
keras.layers.SimpleRNN(
units=64,
return_sequences=False),
keras.layers.Dense(64,activation='relu'),
keras.layers.Dense(1,activation='sigmoid') # 最后的输出只有一种结果
])

model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])

# 开始训练
# 这个数据集本身没有设置验证集,所以将训练集中20%的数据划出来作为验证集
model.fit(
train_data,
train_labels,
epochs=30,
batch_size=batch_size,
validation_split=0.2)

# 验证数据
loss,accuracy=model.evaluate(test_data,test_labels,batch_size=batch_size)
print('loss:',loss)
print('accuracy:',accuracy)

Estimator

Estimator 是 Tensorflow 完整模型的高级表示,它被设计用于轻松扩展和异步训练。
这个章节主要讲两个部分:
1.预创建的Estimator;
2.将Keras模型转换为Estimator模型.

预创建的Estimator

这里以Estimator解决鸢尾花分类问题为例,数据集地址:

1
2
训练集:https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv
测试集:https://storage.googleapis.com/download.tensorflow.org/data/iris_test.csv

要基于一个数据集建立模型,首先需要对这个数据集的格式进行了解。
数据集有五列,包含了花萼长度(SepalLength),花萼宽度(SepalWidth),花瓣长度(PetalLength),花瓣宽度(PetalWidth)四个特征值数列和一个标签列(Species)。花的种类又包括 Setosa,Versicolor,Virginica 三种类别,数据集中已经处理为0,1,2三个标签。

加载运行环境并设置需要更新的列名称和种类名称

1
2
3
4
5
import tensorflow as tf
import pandas as pd

csv_column_names=['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'Species']
species=['Setosa', 'Versicolor', 'Virginica']

读取文件并更新列名

1
2
3
4
5
train=pd.read_csv('../dataset/iris_training.csv',
names=csv_column_names,header=0) # 设置header为0,取消原先的列名
test=pd.read_csv('../dataset/iris_test.csv',
names=csv_column_names,header=0)
print(train.head())

分别取出训练集和测试集的label

1
2
3
4
train_y=train.pop('Species')
test_y=test.pop('Species')

print(train.head())

数据集读取完毕,下面要通过Estimator来定义模型。Estimator要求完成以下工作(这段摘自官网):
(1) 创建一个或多个输入函数;
(2) 定义模型的特征列;
(3) 实例化一个Estimator,指定特征列和各种超参数;
(4) 在Estimator对象上调用一个或多个方法,传递合适的输入函数以作为数据源.

创建输入函数
输入函数是一个返回tf.data.Dataset对象的函数,此对象会输出下列两个元素的元组:
(1) feature:通过Python字典构建,键作为特征名称,值作为包含此特征所有值的数组;
(2) label:包含每个样本标签的数值.
e.g.

1
2
3
4
5
6
7
def input_evaluation_set():
features = {'SepalLength': np.array([6.4, 5.0]),
'SepalWidth': np.array([2.8, 2.3]),
'PetalLength': np.array([5.6, 3.3]),
'PetalWidth': np.array([2.2, 1.0])}
labels = np.array([2, 1])
return features, labels

输入函数可以自定义,不过官方的建议是使用tf的DataSet API,这个API可以解析各种类型的数据。使用Dataset API,可以轻松地从大量文件中并行读取记录,并将它们合并为单个数据流。

简化示例,以pandas加载数据,并利用此内存数据构建输入管道。

1
2
3
4
5
6
7
def input_fn(features,labels,training=True,batch_size=256):
# 将输入转换为数据集
dataset=tf.data.Dataset.from_tensor_slices((dict(features),labels))
# 在训练的条件下对数据进行shuffle
if training:
dataset=dataset.shuffle(1000).repeat()
return dataset.batch(batch_size)

定义特征列

特征列是一个对象,用于描述模型应该如何使用特征字典中的原始输入数据。构建Estimator模型时需要传递一个特征列表,包含模型需要使用的每个特征。
主要函数:tf.feature_column

对于鸢尾花的分类问题,四个原始特征都是数值,基于这些数值构建特征列表。

1
2
3
4
my_feature_columns=[]
for key in train.keys():
my_feature_columns.append(tf.feature_column.numeric_column(key=key))
print(my_feature_columns)

实例化Estimator

TF框架中提供了几个已经预创建的Estimator分类器,其中包括:
(1) tf.estimator.DNNClassifier 用于多类别分类的深度模型;
(2) tf.estimator.DNNLinearCombinedClassifier 用于广度与深度模型;
(3) tf.estimator.LinearClassifier 用于基于线性模型的分类器.

这里以tf.estimator.DNNClassifier为例。

1
2
3
4
5
6
7
8
9
10
11
# 设置保存模型的目录
output_dir='DNNClassifier'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
classifier=tf.estimator.DNNClassifier(
feature_columns=my_feature_columns,
model_dir=output_dir,
# 隐藏层中神经元的个数
hidden_units=[30,10],
# 模型必须从三个类别中选择
n_classes=3)

训练模型

1
2
3
4
classifier.train(
# 注意:input_fn调用封装在lambda中以获取参数
input_fn=lambda: input_fn(train,train_y,training=True),
steps=10000)

测试模型

1
2
3
4
eval_result = classifier.evaluate(
input_fn=lambda: input_fn(test, test_y, training=False))

print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))

Keras模型转Estimator

这里以泰坦尼克号生存概率文件做预测,数据集下载地址:

1
2
https://storage.googleapis.com/tf-datasets/titanic/train.csv
https://storage.googleapis.com/tf-datasets/titanic/eval.csv

转换的步骤主要用到的模型是 tf.keras.estimator.model_to_estimator

加载环境

1
2
3
4
5
import tensorflow as tf
import tensorflow_datasets as tfds
from tensorflow import keras
import numpy as np
import os

建立一个Keras模型

1
2
3
4
5
6
7
8
9
10
11
model=keras.models.Sequential([
keras.layers.Dense(16,activation='relu',input_shape=(4,)),
keras.layers.Dropout(0.2),
keras.layers.Dense(3)
])

model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer='adam')

model.summary()

定义输入函数

1
2
3
4
5
6
def input_fn():
split=tfds.Split.TRAIN
dataset=tfds.load('iris',split=split,as_supervised=True)
dataset=dataset.map(lambda features,labels:({'dense_input':features},labels))
dataset=dataset.batch(32).repeat()
return dataset

为Keras模型创建一个Estimator

1
2
3
4
5
6
model_dir='keras_to_estimator'
if not os.path.exists(model_dir):
os.mkdir(model_dir)
keras_estimator=keras.estimator.model_to_estimator(
keras_model=model,
model_dir=model_dir)

训练模型

1
keras_estimator.train(input_fn=input_fn,steps=5000)

测试模型

1
2
eval_result=keras_estimator.evaluate(input_fn=input_fn,steps=10)
print(eval_result)

输出结果是:

1
{'loss': 0.061623283, 'global_step': 5000}

自定义

害,我觉得这块真的需要重视,因为复现别人的论文,可能又是公式又是自定义层什么的。

张量和操作

一个张量是一个多维度的数组,类似于numpy创建的ndarray。tf.Tensor对象的参数包括一个数据类型和一个数据大小。
除此之外,tf.Tensor可以保存在加速器(GPU)内存中,TensorFlow提供了丰富的操作库(tf.add,tf.matmul等)。

这个部分,先加载一下环境,Jupyter真香。

1
import tensorflow as tf

张量的基本计算

1
2
3
4
5
6
print(tf.add(1,2))
print(tf.add([1,2],[3,4]))
print(tf.square(5))
print(tf.reduce_sum([1,2,3]))

print(tf.square(2)+tf.square(3))

1
2
3
4
x=tf.matmul([[1]],[[2,3]])
print(x)
print(x.shape)
print(x.dtype)

Numpy和Tensor的相互转换

将tensor转换为numpy的ndarrays只需要使用一个工具,在tensor后面加上.numpy()即可。

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np

ndarray=np.ones([3,3])
# 经过tensor操作可以把numpy数组转换为tensor
tensor=tf.multiply(ndarray,42)
print(tensor)
print()
# 经过numpy计算把tensor转换为numpy数组
print(np.add(tensor,1))
print()
# 把tensor取出作为numpy数组
print(tensor.numpy())

设置运行设备

with tf.device(),然后指定设备,本机运行设置为CPU:0,GPU设置为 GPU:0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time

def time_matmul(x):
start=time.time()
for loop in range(10):
tf.matmul(x,x)
result=time.time()-start
print("10 loops:{:0.2f}ms".format(1000*result))

print('On CPU:')
with tf.device('CPU:0'):
x=tf.random.uniform([1000,1000])
assert x.device.endswith('CPU:0')
time_matmul(x)

GPU加速这块…我没有GPU…就跳过吧…跳过吧…

自定义层

tf2.0把神经网络层都封装在keras.layers下面:

  1. 全连接层使用 keras.layers.Dense,里面的参数一般包括:全连接神经元的个数(必写),输入大小(可以省略)。
  2. RNN层:keras.layers.SimpleRNN;
  3. CNN层:keras.layers.Conv2D.

卷积神经网络实现

这块其实我比较纠结,如果在这写CNN的话,那这个篇幅可能会有点长,我纠结了大概五秒钟,突然想起来以前看过一篇写得特别好的blog,贴出来吧。
Conv Nets: A Modular Perspective
作者大大的很多文章都写得特别好,强推。

CNN这块我准备安排两个例子来讲,图像分类和文本分类。

CNN实现图像分类

步骤:
1.加载图像数据集;
2.数据集预处理(归一化,图像大小调整等);
3.搭建CNN模型(卷积层+池化层+Flatten+全连接层*2);
4.训练模型;
5.测试模型.

加载运行环境

1
2
3
4
5
6
import tensorflow as tf
from tensorflow import keras
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

加载数据集

这里使用tf自带的fashion_mnist数据集

1
2
3
4
5
6
7
8
fashion_mnist=keras.datasets.fashion_mnist
(x_train_all,y_train_all),(x_test,y_test)=fashion_mnist.load_data()
x_valid,x_train=x_train_all[:5000],x_train_all[5000:]
y_valid, y_train = y_train_all[:5000], y_train_all[5000:]

print(x_valid.shape, y_valid.shape)
print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)

图像数据预处理

这里需要对图像的像素进行归一化处理,但是归一化只能处理二维矩阵,训练数据的输入大小是[None,28,28],所以训练数据需要转化为[None,784]。
原图像大小:[28,28],黑白图像,单通道,over。

1
2
3
4
5
6
7
scaler=StandardScaler()
x_train_scaled=scaler.fit_transform(
x_train.astype(np.float32).reshape(-1,1)).reshape(-1,28,28,1)
x_valid_scaled=scaler.fit_transform(
x_valid.astype(np.float32).reshape(-1,1)).reshape(-1,28,28,1)
x_test_scaled=scaler.fit_transform(
x_test.astype(np.float32).reshape(-1,1)).reshape(-1,28,28,1)

搭建CNN模型

CNN模型=卷积层+池化层+全连接层+输出层,over。
其中,卷积层和池化层可以有多个。

第一层卷积层需要设置 input_shape,通常是图片大小 (width,height,channels)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
model=keras.models.Sequential()
'''
卷积层参数说明:
filters:有多少个卷积核
kernel_size:卷积核大小
padding:是否让输入和输出保持一致
activation:激活函数
input_shape:设置输入大小
'''
model.add(keras.layers.Conv2D(
filters=32,
# kernel_size:3 x 3
kernel_size=3,
activation='selu',
padding='same',
input_shape=(28,28,1)))
model.add(keras.layers.Conv2D(
filters=32,
kernel_size=3,
padding='same',
activation='selu'))
# 设置池化层
# pool_size:池化窗口大小
model.add(keras.layers.MaxPool2D(pool_size=2))

# 上述内容再来一遍
# 为了缓解信息损失,这里需要将filters翻倍
model.add(keras.layers.Conv2D(
filters=64,
kernel_size=3,
activation='selu',
padding='same'))
model.add(keras.layers.Conv2D(
filters=64,
kernel_size=3,
padding='same',
activation='selu'))
model.add(keras.layers.MaxPool2D(pool_size=2))

# 再来一遍,filters翻倍again
model.add(keras.layers.Conv2D(
filters=128,
kernel_size=3,
activation='selu',
padding='same'))
model.add(keras.layers.Conv2D(
filters=128,
kernel_size=3,
padding='same',
activation='selu'))
model.add(keras.layers.MaxPool2D(pool_size=2))

model.add(keras.layers.Flatten()) # 输出结果之前进行展平
model.add(keras.layers.Dense(128,activation='relu'))
model.add(keras.layers.Dense(10,activation='softmax'))

sgd=keras.optimizers.SGD(0.001)
model.compile(
loss="sparse_categorical_crossentropy",
optimizer=sgd,
metrics=["accuracy"])

模型搭建完毕,查看模型细节。

1
model.summary()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 28, 28, 32) 320
_________________________________________________________________
conv2d_1 (Conv2D) (None, 28, 28, 32) 9248
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 14, 14, 64) 18496
_________________________________________________________________
conv2d_3 (Conv2D) (None, 14, 14, 64) 36928
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 7, 7, 64) 0
_________________________________________________________________
conv2d_4 (Conv2D) (None, 7, 7, 128) 73856
_________________________________________________________________
conv2d_5 (Conv2D) (None, 7, 7, 128) 147584
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 3, 3, 128) 0
_________________________________________________________________
flatten (Flatten) (None, 1152) 0
_________________________________________________________________
dense (Dense) (None, 128) 147584
_________________________________________________________________
dense_1 (Dense) (None, 10) 1290
=================================================================
Total params: 435,306
Trainable params: 435,306
Non-trainable params: 0
_________________________________________________________________

设置停止训练的条件

如果在五轮训练中,loss没有降低,则停止模型训练。

1
2
callbacks=[
keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3)]

训练模型

这里定义一个history来保存模型训练的结果,以便后续matplotlib画图展示loss和acc的变化。

1
2
3
4
5
6
history=model.fit(
x_train_scaled,
y_train,
epochs=10,
validation_data=(x_valid_scaled,y_valid),
callbacks=callbacks)

查看loss和acc变化的曲线

matplotlib是个好东西。

1
2
3
4
5
6
7
8
9
10
11
12
def plot_result(history):
data=history.history
plt.plot(data['accuracy'],label='accuracy')
plt.plot(data['loss'],label='loss')
plt.plot(data['val_accuracy'],label='val_accuracy')
plt.plot(data['val_loss'],label='val_loss')
plt.xlabel('Epoch')
plt.ylabel('value')
plt.ylim([0,1])
plt.legend(loc='lower left')

plot_result(history)

结果如下:

查看测试结果

1
2
3
loss,accuracy=model.evaluate(x_test_scaled,y_test)
print('loss:',loss)
print('accuracy:',accuracy)

CNN实现文本分类

对于文本预处理的过程在前面已经写过,其实都一样,区别只在于最后建模的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import tensorflow as tf
from tensorflow import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 获取数据集
imdb=keras.datasets.imdb

vocab_size=10000
index_from=3

(x_train,y_train),(x_test,y_test)=imdb.load_data(
num_words=vocab_size,index_from=index_from)

print(x_train.shape,y_train.shape)
print(x_test.shape,y_test.shape)

print(x_train[0])
print(y_train[0])

# 获取词在词表中的词序
word_index=imdb.get_word_index()
print(word_index)
print()
print(len(word_index))

word_index={k:(v+3) for k,v in word_index.items()}

word_index['<PAD>']=0
word_index['<START>']=1
word_index['<UNK>']=2
word_index['<END>']=3

# 生成反向词表
reverse_word_index=dict([(value,key) for key,value in word_index.items()])
print(reverse_word_index)

# 显示训练数据的原始文本信息
def decode_review(text):
return ' '.join([reverse_word_index.get(word_id,'<UNK>') for word_id in text])

decode_review(x_train[0])

# 对数据进行预处理
max_length=300 # 500太长,这里只设置为300
# 处理训练集,保持所有句子的输入长度一致
train_data=keras.preprocessing.sequence.pad_sequences(
x_train,
value=word_index['<PAD>'],
maxlen=max_length,
padding='post')

# 处理测试集
test_data=keras.preprocessing.sequence.pad_sequences(
x_test,
value=word_index['<PAD>'],
maxlen=max_length,
padding='post')

print(train_data[0])
print(train_data.shape)
print(train_data[0].shape)

# 搭建CNN模型

embedding_dim=16
batch_size=128

model=keras.models.Sequential([
keras.layers.Embedding(
vocab_size,
embedding_dim,
input_length=max_length)
])

model.add(keras.layers.Conv1D(
filters=32,
# kernel_size:3 x 3
kernel_size=3,
activation='relu',
padding='same',
input_shape=(300,16)))
model.add(keras.layers.Conv1D(
filters=32,
kernel_size=3,
padding='same',
activation='relu'))
# 设置池化层
# pool_size:池化窗口大小
model.add(keras.layers.MaxPool1D(pool_size=2))
model.add(keras.layers.Dropout(0.1))

model.add(keras.layers.Conv1D(
filters=64,
# kernel_size:3 x 3
kernel_size=3,
activation='relu',
padding='same'))
model.add(keras.layers.Conv1D(
filters=64,
kernel_size=3,
padding='same',
activation='relu'))
# 设置池化层
# pool_size:池化窗口大小
model.add(keras.layers.MaxPool1D(pool_size=2))

model.add(keras.layers.Flatten()) # 输出结果之前进行展平
model.add(keras.layers.Dense(32,activation='relu'))
model.add(keras.layers.Dense(1,activation='sigmoid')) # 0/1

sgd=keras.optimizers.SGD(0.001)
model.compile(
optimizer=sgd,
loss='binary_crossentropy',
metrics=['accuracy'])

# 训练模型

epochs=10

callbacks=[
keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3)
]

history=model.fit(
train_data,
y_train,
epochs=epochs,
batch_size=batch_size,
validation_split=0.2)

model.evaluate(test_data,y_test)

这里分类的accuracy比较低,我估计原因是在词向量上,如果这里的词向量使用的是word2vec,结果可能会有很大的提高。

循环神经网络实现

RNN在NLP里面应用很广泛,这个部分主要是以NLP的相关任务作为示例。

普通RNN实现

前面文本预处理的部分无区别,只在模型部分做修改,把之前的Con1D改成SimpleRNN。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import os

imdb=keras.datasets.imdb
vocab_size=10000
index_from=3
(train_data,train_labels),(test_data,test_labels)=imdb.load_data(
num_words=vocab_size,
index_from=index_from)

print(train_data.shape,train_labels.shape)
print(test_data.shape,test_labels.shape)
print(train_data[0],train_labels[0])

word_index=imdb.get_word_index()
print(word_index)

word_index={k:(v+3) for k,v in word_index.items()}
word_index['<PAD>']=0
word_index['<START>']=1
word_index['<UNK>']=2
word_index['<END>']=3

# 生成反向词表
reverse_word_index={v:k for k,v in word_index.items()}
print(reverse_word_index)

# 文本数据预处理

max_length=300
train_data=keras.preprocessing.sequence.pad_sequences(
train_data,
value=word_index['<PAD>'],
padding='post',
maxlen=max_length)

test_data=keras.preprocessing.sequence.pad_sequences(
test_data,
value=word_index['<PAD>'],
padding='post',
maxlen=max_length)

print(train_data[0])
print(len(train_data[0]))

# 单个单词映射为长度32的向量
embedding_dim=16
batch_size=128

model=keras.Sequential([
keras.layers.Embedding(
vocab_size,
embedding_dim,
input_length=max_length),
keras.layers.SimpleRNN(
units=64,
return_sequences=False),
keras.layers.Dense(64,activation='relu'),
keras.layers.Dense(1,activation='sigmoid')
])

model.summary()
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])

model.fit(
train_data,
train_labels,
epochs=30,
batch_size=batch_size,
validation_split=0.2)

model.evaluate(test_data,test_labels,batch_size=batch_size)

Padding_Pooling

这个东西分类的精确度比RNN高多了,震惊ing。
数据预处理的部分都是一样的,这里就只放模型块了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
embedding_dim=16
batch_size=128

model=keras.models.Sequential([
keras.layers.Embedding(
vocab_size,
embedding_dim,
input_length=max_length),
keras.layers.GlobalAveragePooling1D(),
keras.layers.Dense(64,activation='relu'),
keras.layers.Dense(1,activation='sigmoid')
])
model.summary()
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])

分类准确率高达:0.85.

双向RNN

区别仍然只在模型部分,双向RNN可以设置多层:

1
2
3
4
keras.layers.Bidirectional(
keras.layers.SimpleRNN(
units=64,
return_sequences=False)),

这个值多复制几次,前面的return_sequences设置为True,最后一层设置为False即可,不过层数太多训练会很慢。

数据部分略过,只放模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 构建模型
embedding_dim=16
batch_size=128

model=keras.models.Sequential([
keras.layers.Embedding(
vocab_size,
embedding_dim,
input_length=max_length),
keras.layers.Bidirectional(
keras.layers.SimpleRNN(
units=64,
return_sequences=False)),
keras.layers.Dense(64,activation='relu'),
keras.layers.Dense(1,activation='sigmoid')
])

model.summary()
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])

LSTM

这个部分只需要在RNN的基础上把SimpleRNN改成LSTM,再对参数进行设置即可。
主要改动参数有两个,Embedding层的batch_input_shpe,LSTM层的stateful和recurrent_initializer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 单个单词映射为长度32的向量
embedding_dim=16
batch_size=25

model=keras.Sequential([
keras.layers.Embedding(
vocab_size,
embedding_dim,
batch_input_shape=[batch_size,None]),
keras.layers.LSTM(
units=64,
stateful=True,
recurrent_initializer='orthogonal',
return_sequences=True),
keras.layers.Dense(vocab_size,activation='relu'),
keras.layers.Dense(1,activation='sigmoid')
])

model.summary()
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])

模型保存

这个部分主要使用到两个功能 callbacks 和 load_weights。

首先这里先给出一个完整的神经网络模型,主要工作是对图片进行分类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
import pandas as pd
import numpy as np
import os

fashion_mnist = keras.datasets.fashion_mnist
(x_train_all, y_train_all), (x_test, y_test) = fashion_mnist.load_data()
x_valid, x_train = x_train_all[:5000], x_train_all[5000:]
y_valid, y_train = y_train_all[:5000], y_train_all[5000:]

print(x_valid.shape, y_valid.shape)
print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)

print(np.max(x_train),np.min(x_train))

scaler=StandardScaler()

x_train_scaled=scaler.fit_transform(
x_train.astype(np.float32).reshape(-1,1)).reshape(-1,28,28)
x_valid_scaled=scaler.transform(
x_valid.astype(np.float32).reshape(-1,1)).reshape(-1,28,28)
x_test_scaled=scaler.transform(
x_test.astype(np.float32).reshape(-1,1)).reshape(-1,28,28)

print(np.max(x_train_scaled),np.min(x_train_scaled))

model=keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28,28]))
model.add(keras.layers.Dense(300,activation="relu"))
model.add(keras.layers.Dense(100,activation="relu"))
model.add(keras.layers.Dense(10,activation="softmax"))

model.compile(
loss="sparse_categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])

模型已经构建完成,下面一步就是训练,我们需要做的就是,保存训练过程中的参数,或是训练过多少轮的数据。

设置保存模型的文件夹:

1
2
3
4
logdir=os.path.join('graph_def_and_weights')
if not os.path.exists(logdir):
os.mkdir(logdir)
output_model_file=os.path.join(logdir,'fashion_mnist_weights.h5')

设置一个callbacks,其中需要编写需要保存的信息以及训练停止的条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'''
patience:如果训练过程中,经过设定值的epoch数后没有改进,训练停止
min_delat:监视数量的最小变化,即最小变化小于min_delta,将不视为改进
'''
'''
save_best_only: True:只保留最好的模型 False:反
save_weights_only:True:只保存参数; False:同时保存模型和参数;
'''
callbacks=[
keras.callbacks.TensorBoard(logdir),
keras.callbacks.ModelCheckpoint(
output_model_file,
save_best_only=True,
save_weights_only=True),
keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3)
]

将callbacks放入训练中:

1
2
3
4
5
history=model.fit(
x_train_scaled,
y_train,epochs=10,
validation_data=(x_valid_scaled,y_valid),
callbacks=callbacks)

通过pandas查看loss和acc在训练中的变化曲线:

1
2
3
4
5
6
7
def plot_learning_curves(history):
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0, 1)
plt.show()

plot_learning_curves(history)

验证数据:

1
print(model.evaluate(x_test_scaled,y_test))

至此可以看到已经有保存好的模型,再重新编写一个网络模型,model.compile之后不执行训练,直接调用model.load_weights。再调用测试集。
结果是一样的:

1
2
3
output_file=os.path.join(logdir,'fashion_mnist.model.h5')
model.load_weights(output_file)
model.evaluate(x_test_scaled,y_test)