文章目录

  • 一、TensorFlow的建模流程
    • 1.1 结构化数据建模流程范例
      • 1.1.1 准备数据
      • 1.1.2 定义模型
      • 1.1.3 训练模型
      • 1.1.4 评估模型
      • 1.1.5 使用模型
      • 1.1.6 保存模型
    • 1.2 图片数据建模流程
      • 1.2.1 准备数据
      • 1.2.2 定义模型
      • 1.2.3 训练模型
      • 1.2.4 评估模型
      • 1.2.5 使用模型
      • 1.2.6 保存模型
    • 1.3 文本数据建模流程范例
      • 1.3.1 准备数据
      • 1.3.2 定义模型
      • 1.3.3 训练模型
      • 1.3.4 评估模型
      • 1.3.5 使用模型
      • 1.3.6 保存模型
    • 1.4 时间序列数据建模流程范例
      • 1.4.1 准备数据
      • 1.4.2 定义模型
      • 1.4.3 训练模型
      • 1.4.4 评估模型
      • 1.4.5 使用模型
      • 1.4.6 保存模型
  • 二、TensorFlow的核心概念
    • 2.1 张量数据结构
      • 2.1.1 常量张量
      • 2.1.1 变量张量
    • 2.2 三种计算图
      • 2.2.1 计算图简介
      • 2.2.2 静态计算图
        • TensorFlow 1.0静态计算图范例
          • TensorFlow2.0 怀旧版静态计算图
      • 2.2.3 动态计算图
      • 2.2.4 TensorFlow2.0的Autograph
    • 2.3 自动微分机制
      • 2.3.1 利用梯度磁带求导数
      • 2.3.2 利用梯度磁带和优化器求最小值
  • 三、TensorFlow的层次结构
    • 3.1低阶API示范
      • 3.1.1 线性回归模型
        • 数据准备
        • 定义模型
          • 训练模型
      • 3.1.2 DNN二分类模型
        • 准备数据
        • 定义模型
        • 训练模型
    • 3.2 中阶API示范
      • 3.2.1 线性回归模型
        • 准备数据
        • 定义模型
        • 训练模型
      • 3.2.2 DNN二分类模型
        • 准备数据
        • 定义模型
        • 训练模型
    • 3.3 高阶API示范
      • 3.3.1 线性回归模型
        • 准备数据
        • 定义模型
        • 训练模型
      • 3.3.2 DNN二分类模型
        • 数据准备
        • 定义模型
        • 训练模型
  • 四、TensorFlow的低阶API
    • 4.1 张量的结构操作
      • 4.1.1 创建张量
      • 4.1.2 索引切片
      • 4.1.3 维度变换
      • 4.1.4 合并分割
    • 4.2 张量的数学运算
      • 4.2.1 标量运算
      • 4.2.2 向量运算
      • 4.2.3 矩阵运算
      • 4.2.4 广播机制
    • 4.3 AutoGraph的使用规范
      • 4.3.1 Autograph编码规范总结
      • 4.3.2 Autograph编码规范解析

一、TensorFlow的建模流程

尽管TensorFlow设计上足够灵活,可以用于进行各种复杂的数值计算。
但通常人们使用TensorFlow来实现机器学习模型,尤其常用于实现神经网络模型。
从原理上说可以使用张量构建计算图来定义神经网络,并通过自动微分机制训练模型。
但为简洁起见,一般推荐使用TensorFlow的高层次keras接口来实现神经网络网模型。
使用TensorFlow实现神经网络模型的一般流程包括:

  • 准备数据
  • 定义模型
  • 训练模型
  • 评估模型
  • 使用模型
  • 保存模型

1.1 结构化数据建模流程范例

1.1.1 准备数据

titanic数据集的目标是根据乘客信息预测他们在Titanic号撞击冰山沉没后能否生存。

# 结构化数据一般会使用Pandas中的DataFrame进行预处理。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import models,layersdftrain_raw = pd.read_csv('./data/titanic/train.csv')
dftest_raw = pd.read_csv('./data/titanic/test.csv')
dftrain_raw.head(10)

利用Pandas的数据可视化功能我们可以简单地进行探索性数据分析EDA(Exploratory Data Analysis)。

# label分布情况
%matplotlib inline
%config InlineBackend.figure_format = 'png'
ax = dftrain_raw['Survived'].value_counts().plot(kind = 'bar',figsize = (12,8),fontsize=15,rot = 0)
ax.set_ylabel('Counts',fontsize = 15)
ax.set_xlabel('Survived',fontsize = 15)
plt.show()# 年龄分布情况
%matplotlib inline
%config InlineBackend.figure_format = 'png'
ax = dftrain_raw['Age'].plot(kind = 'hist',bins = 20,color= 'purple',figsize = (12,8),fontsize=15)ax.set_ylabel('Frequency',fontsize = 15)
ax.set_xlabel('Age',fontsize = 15)
plt.show()# 年龄和label的相关性
%matplotlib inline
%config InlineBackend.figure_format = 'png'
ax = dftrain_raw.query('Survived == 0')['Age'].plot(kind = 'density',figsize = (12,8),fontsize=15)
dftrain_raw.query('Survived == 1')['Age'].plot(kind = 'density',figsize = (12,8),fontsize=15)
ax.legend(['Survived==0','Survived==1'],fontsize = 12)
ax.set_ylabel('Density',fontsize = 15)
ax.set_xlabel('Age',fontsize = 15)
plt.show()

下面为正式的数据预处理

def preprocessing(dfdata):dfresult= pd.DataFrame()#PclassdfPclass = pd.get_dummies(dfdata['Pclass'])dfPclass.columns = ['Pclass_' +str(x) for x in dfPclass.columns ]dfresult = pd.concat([dfresult,dfPclass],axis = 1)#SexdfSex = pd.get_dummies(dfdata['Sex'])dfresult = pd.concat([dfresult,dfSex],axis = 1)#Agedfresult['Age'] = dfdata['Age'].fillna(0)dfresult['Age_null'] = pd.isna(dfdata['Age']).astype('int32')#SibSp,Parch,Faredfresult['SibSp'] = dfdata['SibSp']dfresult['Parch'] = dfdata['Parch']dfresult['Fare'] = dfdata['Fare']#Carbindfresult['Cabin_null'] =  pd.isna(dfdata['Cabin']).astype('int32')#EmbarkeddfEmbarked = pd.get_dummies(dfdata['Embarked'],dummy_na=True)dfEmbarked.columns = ['Embarked_' + str(x) for x in dfEmbarked.columns]dfresult = pd.concat([dfresult,dfEmbarked],axis = 1)return(dfresult)x_train = preprocessing(dftrain_raw)
y_train = dftrain_raw['Survived'].valuesx_test = preprocessing(dftest_raw)
y_test = dftest_raw['Survived'].valuesprint("x_train.shape =", x_train.shape )
print("x_test.shape =", x_test.shape )
x_train.shape = (712, 15)
x_test.shape = (179, 15)

1.1.2 定义模型

使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。

# 此处选择使用最简单的Sequential,按层顺序模型。tf.keras.backend.clear_session()model = models.Sequential()
model.add(layers.Dense(20,activation = 'relu',input_shape=(15,)))
model.add(layers.Dense(10,activation = 'relu'))
model.add(layers.Dense(1,activation = 'sigmoid'))model.summary()

1.1.3 训练模型

训练模型通常有3种方法,内置fit方法,内置train_on_batch方法,以及自定义训练循环。

#此处我们选择最常用也最简单的内置fit方法
# 二分类问题选择二元交叉熵损失函数
model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['AUC'])history = model.fit(x_train,y_train,batch_size= 64,epochs= 30,validation_split=0.2 #分割一部分训练数据用于验证)

1.1.4 评估模型

我们首先评估一下模型在训练集和验证集上的效果。

%matplotlib inline
%config InlineBackend.figure_format = 'svg'import matplotlib.pyplot as pltdef plot_metric(history, metric):train_metrics = history.history[metric]val_metrics = history.history['val_'+metric]epochs = range(1, len(train_metrics) + 1)plt.plot(epochs, train_metrics, 'bo--')plt.plot(epochs, val_metrics, 'ro-')plt.title('Training and validation '+ metric)plt.xlabel("Epochs")plt.ylabel(metric)plt.legend(["train_"+metric, 'val_'+metric])plt.show()
plot_metric(history,"loss")
plot_metric(history,"AUC")
model.evaluate(x = x_test,y = y_test)

1.1.5 使用模型

# 预测概率
model.predict(x_test[0:10])
# model(tf.constant(x_test[0:10].values,dtype = tf.float32)) #等价写法#预测类别
model.predict_classes(x_test[0:10])

1.1.6 保存模型

可以使用Keras方式保存模型,也可以使用TensorFlow原生方式保存。前者仅仅适合使用Python环境恢复模型,后者则可以跨平台进行模型部署。

(1)Keras方式保存

# 保存模型结构及权重model.save('./data/keras_model.h5')  del model  #删除现有模型# identical to the previous one
model = models.load_model('./data/keras_model.h5')
model.evaluate(x_test,y_test)
[0.5191367897907448, 0.8122605]
# 保存模型结构
json_str = model.to_json()# 恢复模型结构
model_json = models.model_from_json(json_str)
#保存模型权重
model.save_weights('./data/keras_model_weight.h5')# 恢复模型结构
model_json = models.model_from_json(json_str)
model_json.compile(optimizer='adam',loss='binary_crossentropy',metrics=['AUC'])# 加载权重
model_json.load_weights('./data/keras_model_weight.h5')
model_json.evaluate(x_test,y_test)

(2)TensorFlow原生方式保存

# 保存权重,该方式仅仅保存权重张量
model.save_weights('./data/tf_model_weights.ckpt',save_format = "tf")
# 保存模型结构与模型参数到文件,该方式保存的模型具有跨平台性便于部署model.save('./data/tf_model_savedmodel', save_format="tf")
print('export saved model.')model_loaded = tf.keras.models.load_model('./data/tf_model_savedmodel')
model_loaded.evaluate(x_test,y_test)

1.2 图片数据建模流程

1.2.1 准备数据

cifar2数据集为cifar10数据集的子集,只包括前两种类别airplane和automobile。
训练集有airplane和automobile图片各5000张,测试集有airplane和automobile图片各1000张。
cifar2任务的目标是训练一个模型来对飞机airplane和机动车automobile两种图片进行分类。

在tensorflow中准备图片数据的常用方案有两种,第一种是使用tf.keras中的ImageDataGenerator工具构建图片数据生成器。
第二种是使用tf.data.Dataset搭配tf.image中的一些图片处理方法构建数据管道。

第一种方法更为简单:使用范例。

第二种方法是TensorFlow的原生方法,更加灵活,使用得当的话也可以获得更好的性能。

import tensorflow as tf
from tensorflow.keras import datasets,layers,modelsBATCH_SIZE = 100def load_image(img_path,size = (32,32)):label = tf.constant(1,tf.int8) if tf.strings.regex_full_match(img_path,".*automobile.*") \else tf.constant(0,tf.int8)img = tf.io.read_file(img_path)img = tf.image.decode_jpeg(img) #注意此处为jpeg格式img = tf.image.resize(img,size)/255.0return(img,label)
#使用并行化预处理num_parallel_calls 和预存数据prefetch来提升性能
ds_train = tf.data.Dataset.list_files("./data/cifar2/train/*/*.jpg") \.map(load_image, num_parallel_calls=tf.data.experimental.AUTOTUNE) \.shuffle(buffer_size = 1000).batch(BATCH_SIZE) \.prefetch(tf.data.experimental.AUTOTUNE)  ds_test = tf.data.Dataset.list_files("./data/cifar2/test/*/*.jpg") \.map(load_image, num_parallel_calls=tf.data.experimental.AUTOTUNE) \.batch(BATCH_SIZE) \.prefetch(tf.data.experimental.AUTOTUNE)
%matplotlib inline
%config InlineBackend.figure_format = 'svg'#查看部分样本
from matplotlib import pyplot as plt plt.figure(figsize=(8,8))
for i,(img,label) in enumerate(ds_train.unbatch().take(9)):ax=plt.subplot(3,3,i+1)ax.imshow(img.numpy())ax.set_title("label = %d"%label)ax.set_xticks([])ax.set_yticks([])
plt.show()for x,y in ds_train.take(1):print(x.shape,y.shape)

1.2.2 定义模型

使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。

# 此处选择使用函数式API构建模型。tf.keras.backend.clear_session() #清空会话inputs = layers.Input(shape=(32,32,3))
x = layers.Conv2D(32,kernel_size=(3,3))(inputs)
x = layers.MaxPool2D()(x)
x = layers.Conv2D(64,kernel_size=(5,5))(x)
x = layers.MaxPool2D()(x)
x = layers.Dropout(rate=0.1)(x)
x = layers.Flatten()(x)
x = layers.Dense(32,activation='relu')(x)
outputs = layers.Dense(1,activation = 'sigmoid')(x)model = models.Model(inputs = inputs,outputs = outputs)model.summary()

1.2.3 训练模型

训练模型通常有3种方法,内置fit方法,内置train_on_batch方法,以及自定义训练循环。

# 此处我们选择最常用也最简单的内置fit方法。
import datetime
import osstamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = os.path.join('data', 'autograph', stamp)## 在 Python3 下建议使用 pathlib 修正各操作系统的路径
# from pathlib import Path
# stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
# logdir = str(Path('./data/autograph/' + stamp))tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),loss=tf.keras.losses.binary_crossentropy,metrics=["accuracy"])history = model.fit(ds_train,epochs= 10,validation_data=ds_test,callbacks = [tensorboard_callback],workers = 4)

1.2.4 评估模型

%load_ext tensorboard
#%tensorboard --logdir ./data/keras_model
from tensorboard import notebook
notebook.list()
#在tensorboard中查看模型
notebook.start("--logdir ./data/keras_model")import pandas as pd
dfhistory = pd.DataFrame(history.history)
dfhistory.index = range(1,len(dfhistory) + 1)
dfhistory.index.name = 'epoch'dfhistory%matplotlib inline
%config InlineBackend.figure_format = 'svg'import matplotlib.pyplot as pltdef plot_metric(history, metric):train_metrics = history.history[metric]val_metrics = history.history['val_'+metric]epochs = range(1, len(train_metrics) + 1)plt.plot(epochs, train_metrics, 'bo--')plt.plot(epochs, val_metrics, 'ro-')plt.title('Training and validation '+ metric)plt.xlabel("Epochs")plt.ylabel(metric)plt.legend(["train_"+metric, 'val_'+metric])plt.show()
plot_metric(history,"loss")
plot_metric(history,"accuracy")#可以使用evaluate对数据进行评估
val_loss,val_accuracy = model.evaluate(ds_test,workers=4)
print(val_loss,val_accuracy)

1.2.5 使用模型

model.predict(ds_test)
for x,y in ds_test.take(1):print(model.predict_on_batch(x[0:20]))

1.2.6 保存模型

# 推荐使用TensorFlow原生方式保存模型。# 保存权重,该方式仅仅保存权重张量
model.save_weights('./data/tf_model_weights.ckpt',save_format = "tf")
# 保存模型结构与模型参数到文件,该方式保存的模型具有跨平台性便于部署model.save('./data/tf_model_savedmodel', save_format="tf")
print('export saved model.')model_loaded = tf.keras.models.load_model('./data/tf_model_savedmodel')
model_loaded.evaluate(ds_test)

1.3 文本数据建模流程范例

1.3.1 准备数据

imdb数据集的目标是根据电影评论的文本内容预测评论的情感标签。训练集有20000条电影评论文本,测试集有5000条电影评论文本,其中正面评论和负面评论都各占一半。

文本数据预处理较为繁琐,包括中文切词(本示例不涉及),构建词典,编码转换,序列填充,构建数据管道等等。在tensorflow中完成文本数据预处理的常用方案有两种,第一种是利用tf.keras.preprocessing中的Tokenizer词典构建工具和tf.keras.utils.Sequence构建文本数据生成器管道。第二种是使用tf.data.Dataset搭配keras.layers.experimental.preprocessing.TextVectorization预处理层。

第一种方法较为复杂:使用范例。

第二种方法为TensorFlow原生方式,相对也更加简单一些。

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow.keras import models,layers,preprocessing,optimizers,losses,metrics
from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
import re,stringtrain_data_path = "./data/imdb/train.csv"
test_data_path =  "./data/imdb/test.csv"MAX_WORDS = 10000  # 仅考虑最高频的10000个词
MAX_LEN = 200  # 每个样本保留200个词的长度
BATCH_SIZE = 20 #构建管道
def split_line(line):arr = tf.strings.split(line,"\t")label = tf.expand_dims(tf.cast(tf.strings.to_number(arr[0]),tf.int32),axis = 0)text = tf.expand_dims(arr[1],axis = 0)return (text,label)ds_train_raw =  tf.data.TextLineDataset(filenames = [train_data_path]) \.map(split_line,num_parallel_calls = tf.data.experimental.AUTOTUNE) \.shuffle(buffer_size = 1000).batch(BATCH_SIZE) \.prefetch(tf.data.experimental.AUTOTUNE)ds_test_raw = tf.data.TextLineDataset(filenames = [test_data_path]) \.map(split_line,num_parallel_calls = tf.data.experimental.AUTOTUNE) \.batch(BATCH_SIZE) \.prefetch(tf.data.experimental.AUTOTUNE)#构建词典
def clean_text(text):lowercase = tf.strings.lower(text)stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')cleaned_punctuation = tf.strings.regex_replace(stripped_html,'[%s]' % re.escape(string.punctuation),'')return cleaned_punctuationvectorize_layer = TextVectorization(standardize=clean_text,split = 'whitespace',max_tokens=MAX_WORDS-1, #有一个留给占位符output_mode='int',output_sequence_length=MAX_LEN)ds_text = ds_train_raw.map(lambda text,label: text)
vectorize_layer.adapt(ds_text)
print(vectorize_layer.get_vocabulary()[0:100])#单词编码
ds_train = ds_train_raw.map(lambda text,label:(vectorize_layer(text),label)) \.prefetch(tf.data.experimental.AUTOTUNE)
ds_test = ds_test_raw.map(lambda text,label:(vectorize_layer(text),label)) \.prefetch(tf.data.experimental.AUTOTUNE)

1.3.2 定义模型

使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。

# 此处选择使用继承Model基类构建自定义模型。
# 演示自定义模型范例,实际上应该优先使用Sequential或者函数式API
tf.keras.backend.clear_session()class CnnModel(models.Model):def __init__(self):super(CnnModel, self).__init__()def build(self,input_shape):self.embedding = layers.Embedding(MAX_WORDS,7,input_length=MAX_LEN)self.conv_1 = layers.Conv1D(16, kernel_size= 5,name = "conv_1",activation = "relu")self.pool_1 = layers.MaxPool1D(name = "pool_1")self.conv_2 = layers.Conv1D(128, kernel_size=2,name = "conv_2",activation = "relu")self.pool_2 = layers.MaxPool1D(name = "pool_2")self.flatten = layers.Flatten()self.dense = layers.Dense(1,activation = "sigmoid")super(CnnModel,self).build(input_shape)def call(self, x):x = self.embedding(x)x = self.conv_1(x)x = self.pool_1(x)x = self.conv_2(x)x = self.pool_2(x)x = self.flatten(x)x = self.dense(x)return(x)# 用于显示Output Shapedef summary(self):x_input = layers.Input(shape = MAX_LEN)output = self.call(x_input)model = tf.keras.Model(inputs = x_input,outputs = output)model.summary()model = CnnModel()
model.build(input_shape =(None,MAX_LEN))
model.summary()

1.3.3 训练模型

训练模型通常有3种方法,内置fit方法,内置train_on_batch方法,以及自定义训练循环。此处我们通过自定义训练循环训练模型。

#打印时间分割线
@tf.function
def printbar():today_ts = tf.timestamp()%(24*60*60)hour = tf.cast(today_ts//3600+8,tf.int32)%tf.constant(24)minite = tf.cast((today_ts%3600)//60,tf.int32)second = tf.cast(tf.floor(today_ts%60),tf.int32)def timeformat(m):if tf.strings.length(tf.strings.format("{}",m))==1:return(tf.strings.format("0{}",m))else:return(tf.strings.format("{}",m))timestring = tf.strings.join([timeformat(hour),timeformat(minite),timeformat(second)],separator = ":")tf.print("=========="*8+timestring)
optimizer = optimizers.Nadam()
loss_func = losses.BinaryCrossentropy()train_loss = metrics.Mean(name='train_loss')
train_metric = metrics.BinaryAccuracy(name='train_accuracy')valid_loss = metrics.Mean(name='valid_loss')
valid_metric = metrics.BinaryAccuracy(name='valid_accuracy')@tf.function
def train_step(model, features, labels):with tf.GradientTape() as tape:predictions = model(features,training = True)loss = loss_func(labels, predictions)gradients = tape.gradient(loss, model.trainable_variables)optimizer.apply_gradients(zip(gradients, model.trainable_variables))train_loss.update_state(loss)train_metric.update_state(labels, predictions)@tf.function
def valid_step(model, features, labels):predictions = model(features,training = False)batch_loss = loss_func(labels, predictions)valid_loss.update_state(batch_loss)valid_metric.update_state(labels, predictions)def train_model(model,ds_train,ds_valid,epochs):for epoch in tf.range(1,epochs+1):for features, labels in ds_train:train_step(model,features,labels)for features, labels in ds_valid:valid_step(model,features,labels)#此处logs模板需要根据metric具体情况修改logs = 'Epoch={},Loss:{},Accuracy:{},Valid Loss:{},Valid Accuracy:{}' if epoch%1==0:printbar()tf.print(tf.strings.format(logs,(epoch,train_loss.result(),train_metric.result(),valid_loss.result(),valid_metric.result())))tf.print("")train_loss.reset_states()valid_loss.reset_states()train_metric.reset_states()valid_metric.reset_states()train_model(model,ds_train,ds_test,epochs = 6)

1.3.4 评估模型

通过自定义训练循环训练的模型没有经过编译,无法直接使用model.evaluate(ds_valid)方法


def evaluate_model(model,ds_valid):for features, labels in ds_valid:valid_step(model,features,labels)logs = 'Valid Loss:{},Valid Accuracy:{}' tf.print(tf.strings.format(logs,(valid_loss.result(),valid_metric.result())))valid_loss.reset_states()train_metric.reset_states()valid_metric.reset_states()
evaluate_model(model,ds_test)

1.3.5 使用模型

可以使用以下方法:

  • model.predict(ds_test)
  • model(x_test)
  • model.call(x_test)
  • model.predict_on_batch(x_test)

推荐优先使用model.predict(ds_test)方法,既可以对Dataset,也可以对Tensor使用。

model.predict(ds_test)
for x_test,_ in ds_test.take(1):print(model(x_test))#以下方法等价:#print(model.call(x_test))#print(model.predict_on_batch(x_test))

1.3.6 保存模型

推荐使用TensorFlow原生方式保存模型。

model.save('./data/tf_model_savedmodel', save_format="tf")
print('export saved model.')model_loaded = tf.keras.models.load_model('./data/tf_model_savedmodel')
model_loaded.predict(ds_test)

1.4 时间序列数据建模流程范例

1.4.1 准备数据

本文的数据集取自tushare,获取该数据集的方法参考了以下文章。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import models,layers,losses,metrics,callbacks
%matplotlib inline
%config InlineBackend.figure_format = 'svg'df = pd.read_csv("./data/covid-19.csv",sep = "\t")
df.plot(x = "date",y = ["confirmed_num","cured_num","dead_num"],figsize=(10,6))
plt.xticks(rotation=60)dfdata = df.set_index("date")
dfdiff = dfdata.diff(periods=1).dropna()
dfdiff = dfdiff.reset_index("date")dfdiff.plot(x = "date",y = ["confirmed_num","cured_num","dead_num"],figsize=(10,6))
plt.xticks(rotation=60)
dfdiff = dfdiff.drop("date",axis = 1).astype("float32")#用某日前8天窗口数据作为输入预测该日数据
WINDOW_SIZE = 8def batch_dataset(dataset):dataset_batched = dataset.batch(WINDOW_SIZE,drop_remainder=True)return dataset_batchedds_data = tf.data.Dataset.from_tensor_slices(tf.constant(dfdiff.values,dtype = tf.float32)) \.window(WINDOW_SIZE,shift=1).flat_map(batch_dataset)ds_label = tf.data.Dataset.from_tensor_slices(tf.constant(dfdiff.values[WINDOW_SIZE:],dtype = tf.float32))#数据较小,可以将全部训练数据放入到一个batch中,提升性能
ds_train = tf.data.Dataset.zip((ds_data,ds_label)).batch(38).cache()

1.4.2 定义模型

使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。

# 此处选择使用函数式API构建任意结构模型。#考虑到新增确诊,新增治愈,新增死亡人数数据不可能小于0,设计如下结构
class Block(layers.Layer):def __init__(self, **kwargs):super(Block, self).__init__(**kwargs)def call(self, x_input,x):x_out = tf.maximum((1+x)*x_input[:,-1,:],0.0)return x_outdef get_config(self):  config = super(Block, self).get_config()return config
tf.keras.backend.clear_session()
x_input = layers.Input(shape = (None,3),dtype = tf.float32)
x = layers.LSTM(3,return_sequences = True,input_shape=(None,3))(x_input)
x = layers.LSTM(3,return_sequences = True,input_shape=(None,3))(x)
x = layers.LSTM(3,return_sequences = True,input_shape=(None,3))(x)
x = layers.LSTM(3,input_shape=(None,3))(x)
x = layers.Dense(3)(x)#考虑到新增确诊,新增治愈,新增死亡人数数据不可能小于0,设计如下结构
#x = tf.maximum((1+x)*x_input[:,-1,:],0.0)
x = Block()(x_input,x)
model = models.Model(inputs = [x_input],outputs = [x])
model.summary()

1.4.3 训练模型

训练模型通常有3种方法,内置fit方法,内置train_on_batch方法,以及自定义训练循环。此处我们选择最常用也最简单的内置fit方法。
注:循环神经网络调试较为困难,需要设置多个不同的学习率多次尝试,以取得较好的效果。

#自定义损失函数,考虑平方差和预测目标的比值
class MSPE(losses.Loss):def call(self,y_true,y_pred):err_percent = (y_true - y_pred)**2/(tf.maximum(y_true**2,1e-7))mean_err_percent = tf.reduce_mean(err_percent)return mean_err_percentdef get_config(self):config = super(MSPE, self).get_config()return config
import os
import datetimeoptimizer = tf.keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=optimizer,loss=MSPE(name = "MSPE"))stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = os.path.join('data', 'autograph', stamp)## 在 Python3 下建议使用 pathlib 修正各操作系统的路径
# from pathlib import Path
# stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
# logdir = str(Path('./data/autograph/' + stamp))tb_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
#如果loss在100个epoch后没有提升,学习率减半。
lr_callback = tf.keras.callbacks.ReduceLROnPlateau(monitor="loss",factor = 0.5, patience = 100)
#当loss在200个epoch后没有提升,则提前终止训练。
stop_callback = tf.keras.callbacks.EarlyStopping(monitor = "loss", patience= 200)
callbacks_list = [tb_callback,lr_callback,stop_callback]history = model.fit(ds_train,epochs=500,callbacks = callbacks_list)

1.4.4 评估模型

评估模型一般要设置验证集或者测试集,由于此例数据较少,我们仅仅可视化损失函数在训练集上的迭代情况。

%matplotlib inline
%config InlineBackend.figure_format = 'svg'import matplotlib.pyplot as pltdef plot_metric(history, metric):train_metrics = history.history[metric]epochs = range(1, len(train_metrics) + 1)plt.plot(epochs, train_metrics, 'bo--')plt.title('Training '+ metric)plt.xlabel("Epochs")plt.ylabel(metric)plt.legend(["train_"+metric])plt.show()
plot_metric(history,"loss")

1.4.5 使用模型

#使用dfresult记录现有数据以及此后预测的疫情数据
dfresult = dfdiff[["confirmed_num","cured_num","dead_num"]].copy()
dfresult.tail()#预测此后100天的新增走势,将其结果添加到dfresult中
for i in range(100):arr_predict = model.predict(tf.constant(tf.expand_dims(dfresult.values[-38:,:],axis = 0)))dfpredict = pd.DataFrame(tf.cast(tf.floor(arr_predict),tf.float32).numpy(),columns = dfresult.columns)dfresult = dfresult.append(dfpredict,ignore_index=True)
dfresult.query("confirmed_num==0").head()# 第55天开始新增确诊降为0,第45天对应3月10日,也就是10天后,即预计3月20日新增确诊降为0
# 注:该预测偏乐观dfresult.query("cured_num==0").head()# 第164天开始新增治愈降为0,第45天对应3月10日,也就是大概4个月后,即7月10日左右全部治愈。
# 注: 该预测偏悲观,并且存在问题,如果将每天新增治愈人数加起来,将超过累计确诊人数。dfresult.query("dead_num==0").head()# 第60天开始,新增死亡降为0,第45天对应3月10日,也就是大概15天后,即20200325
# 该预测较为合理

1.4.6 保存模型

# 推荐使用TensorFlow原生方式保存模型。model.save('./data/tf_model_savedmodel', save_format="tf")
print('export saved model.')
model_loaded = tf.keras.models.load_model('./data/tf_model_savedmodel',compile=False)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
model_loaded.compile(optimizer=optimizer,loss=MSPE(name = "MSPE"))
model_loaded.predict(ds_train)

二、TensorFlow的核心概念

TensorFlow™ 是一个采用 数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。它灵活的架构让你可以在多种平台上展开计算,例如台式计算机中的一个或多个CPU(或GPU),服务器,移动设备等等。TensorFlow 最初由Google大脑小组(隶属于Google机器智能研究机构)的研究员和工程师们开发出来,用于机器学习和深度神经网络方面的研究,但这个系统的通用性使其也可广泛用于其他计算领域。

TensorFlow的主要优点:

  • 灵活性:支持底层数值计算,C++自定义操作符
  • 可移植性:从服务器到PC到手机,从CPU到GPU到TPU
  • 分布式计算:分布式并行计算,可指定操作符对应计算设备

俗话说,万丈高楼平地起,TensorFlow这座大厦也有它的地基。
Tensorflow底层最核心的概念是张量,计算图以及自动微分。

2.1 张量数据结构

程序 = 数据结构+算法。

TensorFlow程序 = 张量数据结构 + 计算图算法语言

张量和计算图是 TensorFlow的核心概念。

Tensorflow的基本数据结构是张量Tensor。张量即多维数组。Tensorflow的张量和numpy中的array很类似。

从行为特性来看,有两种类型的张量,常量constant和变量Variable.

常量的值在计算图中不可以被重新赋值,变量可以在计算图中用assign等算子重新赋值。

2.1.1 常量张量

张量的数据类型和numpy.array基本一一对应。

import numpy as np
import tensorflow as tfi = tf.constant(1) # tf.int32 类型常量
l = tf.constant(1,dtype = tf.int64) # tf.int64 类型常量
f = tf.constant(1.23) #tf.float32 类型常量
d = tf.constant(3.14,dtype = tf.double) # tf.double 类型常量
s = tf.constant("hello world") # tf.string类型常量
b = tf.constant(True) #tf.bool类型常量print(tf.int64 == np.int64)
print(tf.bool == np.bool)
print(tf.double == np.float64)
print(tf.string == np.unicode) # tf.string类型和np.unicode类型不等价

不同类型的数据可以用不同维度(rank)的张量来表示。

标量为0维张量,向量为1维张量,矩阵为2维张量。

彩色图像有rgb三个通道,可以表示为3维张量。

视频还有时间维,可以表示为4维张量。

可以简单地总结为:有几层中括号,就是多少维的张量。

scalar = tf.constant(True)  #标量,0维张量print(tf.rank(scalar))
print(scalar.numpy().ndim)  # tf.rank的作用和numpy的ndim方法相同tf.Tensor(0, shape=(), dtype=int32)
vector = tf.constant([1.0,2.0,3.0,4.0]) #向量,1维张量print(tf.rank(vector))
print(np.ndim(vector.numpy()))tf.Tensor(1, shape=(), dtype=int32)matrix = tf.constant([[1.0,2.0],[3.0,4.0]]) #矩阵, 2维张量print(tf.rank(matrix).numpy())
print(np.ndim(matrix))tensor3 = tf.constant([[[1.0,2.0],[3.0,4.0]],[[5.0,6.0],[7.0,8.0]]])  # 3维张量
print(tensor3)
print(tf.rank(tensor3))tensor4 = tf.constant([[[[1.0,1.0],[2.0,2.0]],[[3.0,3.0],[4.0,4.0]]],[[[5.0,5.0],[6.0,6.0]],[[7.0,7.0],[8.0,8.0]]]])  # 4维张量
print(tensor4)
print(tf.rank(tensor4))

可以用tf.cast改变张量的数据类型。

可以用numpy方法将tensorflow中的张量转化成numpy中的张量。

可以用shape方法查看张量的尺寸。

h = tf.constant([123,456],dtype = tf.int32)
f = tf.cast(h,tf.float32)
print(h.dtype, f.dtype)y = tf.constant([[1.0,2.0],[3.0,4.0]])
print(y.numpy()) #转换成np.array
print(y.shape)u = tf.constant(u"你好 世界")
print(u.numpy())
print(u.numpy().decode("utf-8"))

2.1.1 变量张量

模型中需要被训练的参数一般被设置成变量。

# 常量值不可以改变,常量的重新赋值相当于创造新的内存空间
c = tf.constant([1.0,2.0])
print(c)
print(id(c))
c = c + tf.constant([1.0,1.0])
print(c)
print(id(c))tf.Tensor([1. 2.], shape=(2,), dtype=float32)
5276289568
tf.Tensor([2. 3.], shape=(2,), dtype=float32)
5276290240# 变量的值可以改变,可以通过assign, assign_add等方法给变量重新赋值
v = tf.Variable([1.0,2.0],name = "v")
print(v)
print(id(v))
v.assign_add([1.0,1.0])
print(v)
print(id(v))

2.2 三种计算图

有三种计算图的构建方式:静态计算图,动态计算图,以及Autograph.

在TensorFlow1.0时代,采用的是静态计算图,需要先使用TensorFlow的各种算子创建计算图,然后再开启一个会话Session,显式执行计算图。

而在TensorFlow2.0时代,采用的是动态计算图,即每使用一个算子后,该算子会被动态加入到隐含的默认计算图中立即执行得到结果,而无需开启Session。

使用动态计算图即Eager Excution的好处是方便调试程序,它会让TensorFlow代码的表现和Python原生代码的表现一样,写起来就像写numpy一样,各种日志打印,控制流全部都是可以使用的。

使用动态计算图的缺点是运行效率相对会低一些。因为使用动态图会有许多次Python进程和TensorFlow的C++ 进程之间的通信。而静态计算图构建完成之后几乎全部在TensorFlow内核上使用C++ 代码执行,效率更高。此外静态图会对计算步骤进行一定的优化,剪去和结果无关的计算步骤。

如果需要在TensorFlow2.0中使用静态图,可以使用@tf.function装饰器将普通Python函数转换成对应的TensorFlow计算图构建代码。运行该函数就相当于在TensorFlow1.0中用Session执行代码。使用tf.function构建静态图的方式叫做 Autograph.

2.2.1 计算图简介

计算图由节点(nodes)和线(edges)组成。

节点表示操作符Operator,或者称之为算子,线表示计算间的依赖。

实线表示有数据传递依赖,传递的数据即张量。

虚线通常可以表示控制依赖,即执行先后顺序。

2.2.2 静态计算图

TensorFlow 1.0静态计算图范例

import tensorflow as tf#定义计算图
g = tf.Graph()
with g.as_default():#placeholder为占位符,执行会话时候指定填充对象x = tf.placeholder(name='x', shape=[], dtype=tf.string)  y = tf.placeholder(name='y', shape=[], dtype=tf.string)z = tf.string_join([x,y],name = 'join',separator=' ')#执行计算图
with tf.Session(graph = g) as sess:print(sess.run(fetches = z,feed_dict = {x:"hello",y:"world"}))
TensorFlow2.0 怀旧版静态计算图

TensorFlow2.0为了确保对老版本tensorflow项目的兼容性,在tf.compat.v1子模块中保留了对TensorFlow1.0那种静态计算图构建风格的支持。

可称之为怀旧版静态计算图,已经不推荐使用了。

import tensorflow as tfg = tf.compat.v1.Graph()
with g.as_default():x = tf.compat.v1.placeholder(name='x', shape=[], dtype=tf.string)y = tf.compat.v1.placeholder(name='y', shape=[], dtype=tf.string)z = tf.strings.join([x,y],name = "join",separator = " ")with tf.compat.v1.Session(graph = g) as sess:# fetches的结果非常像一个函数的返回值,而feed_dict中的占位符相当于函数的参数序列。result = sess.run(fetches = z,feed_dict = {x:"hello",y:"world"})print(result)

2.2.3 动态计算图

在TensorFlow2.0中,使用的是动态计算图和Autograph.

在TensorFlow1.0中,使用静态计算图分两步,第一步定义计算图,第二步在会话中执行计算图。

动态计算图已经不区分计算图的定义和执行了,而是定义后立即执行。因此称之为 Eager Excution.

Eager这个英文单词的原意是"迫不及待的",也就是立即执行的意思。

# 动态计算图在每个算子处都进行构建,构建后立即执行x = tf.constant("hello")
y = tf.constant("world")
z = tf.strings.join([x,y],separator=" ")tf.print(z)# 可以将动态计算图代码的输入和输出关系封装成函数def strjoin(x,y):z =  tf.strings.join([x,y],separator = " ")tf.print(z)return zresult = strjoin(tf.constant("hello"),tf.constant("world"))
print(result)

2.2.4 TensorFlow2.0的Autograph

动态计算图运行效率相对较低。

可以用@tf.function装饰器将普通Python函数转换成和TensorFlow1.0对应的静态计算图构建代码。

在TensorFlow1.0中,使用计算图分两步,第一步定义计算图,第二步在会话中执行计算图。

在TensorFlow2.0中,如果采用Autograph的方式使用计算图,第一步定义计算图变成了定义函数,第二步执行计算图变成了调用函数。

不需要使用会话了,一些都像原始的Python语法一样自然。

实践中,我们一般会先用动态计算图调试代码,然后在需要提高性能的的地方利用@tf.function切换成Autograph获得更高的效率。

当然,@tf.function的使用需要遵循一定的规范,我们后面章节将重点介绍。

import tensorflow as tf# 使用autograph构建静态图@tf.function
def strjoin(x,y):z =  tf.strings.join([x,y],separator = " ")tf.print(z)return zresult = strjoin(tf.constant("hello"),tf.constant("world"))print(result)
import datetime# 创建日志
import os
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = os.path.join('data', 'autograph', stamp)## 在 Python3 下建议使用 pathlib 修正各操作系统的路径
# from pathlib import Path
# stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
# logdir = str(Path('../../data/autograph/' + stamp))writer = tf.summary.create_file_writer(logdir)#开启autograph跟踪
tf.summary.trace_on(graph=True, profiler=True) #执行autograph
result = strjoin("hello","world")#将计算图信息写入日志
with writer.as_default():tf.summary.trace_export(name="autograph",step=0,profiler_outdir=logdir)#启动 tensorboard在jupyter中的魔法命令
%load_ext tensorboard#启动tensorboard
%tensorboard --logdir ../../data/autograph/

2.3 自动微分机制

神经网络通常依赖反向传播求梯度来更新网络参数,求梯度过程通常是一件非常复杂而容易出错的事情。

而深度学习框架可以帮助我们自动地完成这种求梯度运算。

Tensorflow一般使用梯度磁带tf.GradientTape来记录正向运算过程,然后反播磁带自动得到梯度值。

这种利用tf.GradientTape求微分的方法叫做Tensorflow的自动微分机制。

2.3.1 利用梯度磁带求导数

import tensorflow as tf
import numpy as np # f(x) = a*x**2 + b*x + c的导数x = tf.Variable(0.0,name = "x",dtype = tf.float32)
a = tf.constant(1.0)
b = tf.constant(-2.0)
c = tf.constant(1.0)with tf.GradientTape() as tape:y = a*tf.pow(x,2) + b*x + cdy_dx = tape.gradient(y,x)
print(dy_dx)# 对常量张量也可以求导,需要增加watchwith tf.GradientTape() as tape:tape.watch([a,b,c])y = a*tf.pow(x,2) + b*x + cdy_dx,dy_da,dy_db,dy_dc = tape.gradient(y,[x,a,b,c])
print(dy_da)
print(dy_dc)# 可以求二阶导数
with tf.GradientTape() as tape2:with tf.GradientTape() as tape1:   y = a*tf.pow(x,2) + b*x + cdy_dx = tape1.gradient(y,x)
dy2_dx2 = tape2.gradient(dy_dx,x)print(dy2_dx2)# 可以在autograph中使用@tf.function
def f(x):   a = tf.constant(1.0)b = tf.constant(-2.0)c = tf.constant(1.0)# 自变量转换成tf.float32x = tf.cast(x,tf.float32)with tf.GradientTape() as tape:tape.watch(x)y = a*tf.pow(x,2)+b*x+cdy_dx = tape.gradient(y,x) return((dy_dx,y))tf.print(f(tf.constant(0.0)))
tf.print(f(tf.constant(1.0)))

2.3.2 利用梯度磁带和优化器求最小值

# 求f(x) = a*x**2 + b*x + c的最小值
# 使用optimizer.apply_gradientsx = tf.Variable(0.0,name = "x",dtype = tf.float32)
a = tf.constant(1.0)
b = tf.constant(-2.0)
c = tf.constant(1.0)optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
for _ in range(1000):with tf.GradientTape() as tape:y = a*tf.pow(x,2) + b*x + cdy_dx = tape.gradient(y,x)optimizer.apply_gradients(grads_and_vars=[(dy_dx,x)])tf.print("y =",y,"; x =",x)# 求f(x) = a*x**2 + b*x + c的最小值
# 使用optimizer.minimize
# optimizer.minimize相当于先用tape求gradient,再apply_gradientx = tf.Variable(0.0,name = "x",dtype = tf.float32)#注意f()无参数
def f():   a = tf.constant(1.0)b = tf.constant(-2.0)c = tf.constant(1.0)y = a*tf.pow(x,2)+b*x+creturn(y)optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
for _ in range(1000):optimizer.minimize(f,[x])   tf.print("y =",f(),"; x =",x)# 在autograph中完成最小值求解
# 使用optimizer.apply_gradientsx = tf.Variable(0.0,name = "x",dtype = tf.float32)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)@tf.function
def minimizef():a = tf.constant(1.0)b = tf.constant(-2.0)c = tf.constant(1.0)for _ in tf.range(1000): #注意autograph时使用tf.range(1000)而不是range(1000)with tf.GradientTape() as tape:y = a*tf.pow(x,2) + b*x + cdy_dx = tape.gradient(y,x)optimizer.apply_gradients(grads_and_vars=[(dy_dx,x)])y = a*tf.pow(x,2) + b*x + creturn ytf.print(minimizef())
tf.print(x)# 在autograph中完成最小值求解
# 使用optimizer.minimizex = tf.Variable(0.0,name = "x",dtype = tf.float32)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)   @tf.function
def f():   a = tf.constant(1.0)b = tf.constant(-2.0)c = tf.constant(1.0)y = a*tf.pow(x,2)+b*x+creturn(y)@tf.function
def train(epoch):  for _ in tf.range(epoch):  optimizer.minimize(f,[x])return(f())tf.print(train(1000))
tf.print(x)

三、TensorFlow的层次结构

本章我们介绍TensorFlow中5个不同的层次结构:即硬件层,内核层,低阶API,中阶API,高阶API。并以线性回归和DNN二分类模型为例,直观对比展示在不同层级实现模型的特点。

TensorFlow的层次结构从低到高可以分成如下五层。

最底层为硬件层,TensorFlow支持CPU、GPU或TPU加入计算资源池。

第二层为C++实现的内核,kernel可以跨平台分布运行。

第三层为Python实现的操作符,提供了封装C++内核的低级API指令,主要包括各种张量操作算子、计算图、自动微分.
如tf.Variable,tf.constant,tf.function,tf.GradientTape,tf.nn.softmax…
如果把模型比作一个房子,那么第三层API就是【模型之砖】。

第四层为Python实现的模型组件,对低级API进行了函数封装,主要包括各种模型层,损失函数,优化器,数据管道,特征列等等。
如tf.keras.layers,tf.keras.losses,tf.keras.metrics,tf.keras.optimizers,tf.data.DataSet,tf.feature_column…
如果把模型比作一个房子,那么第四层API就是【模型之墙】。

第五层为Python实现的模型成品,一般为按照OOP方式封装的高级API,主要为tf.keras.models提供的模型的类接口。
如果把模型比作一个房子,那么第五层API就是模型本身,即【模型之屋】。

  • 高级API:tf.keras.models
    • 中阶API:tf.keras.layers,tf.keras.losses,tf.data.Dataset,tf.keras.optimizers等
    • 低阶API:tf.Variable,tf.constant,tf.GradientTape,tf.function等
    • 内核层:C++ 实现
    • 硬件层:CPU、GPU、TPU

3.1低阶API示范

下面的范例使用TensorFlow的低阶API实现线性回归模型和DNN二分类模型。

低阶API主要包括张量操作,计算图和自动微分。

import tensorflow as tf#打印时间分割线
@tf.function
def printbar():today_ts = tf.timestamp()%(24*60*60)hour = tf.cast(today_ts//3600+8,tf.int32)%tf.constant(24)minite = tf.cast((today_ts%3600)//60,tf.int32)second = tf.cast(tf.floor(today_ts%60),tf.int32)def timeformat(m):if tf.strings.length(tf.strings.format("{}",m))==1:return(tf.strings.format("0{}",m))else:return(tf.strings.format("{}",m))timestring = tf.strings.join([timeformat(hour),timeformat(minite),timeformat(second)],separator = ":")tf.print("=========="*8+timestring)

3.1.1 线性回归模型

数据准备

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf#样本数量
n = 400# 生成测试用数据集
X = tf.random.uniform([n,2],minval=-10,maxval=10)
w0 = tf.constant([[2.0],[-3.0]])
b0 = tf.constant([[3.0]])
Y = X@w0 + b0 + tf.random.normal([n,1],mean = 0.0,stddev= 2.0)  # @表示矩阵乘法,增加正态扰动# 构建数据管道迭代器
def data_iter(features, labels, batch_size=8):num_examples = len(features)indices = list(range(num_examples))np.random.shuffle(indices)  #样本的读取顺序是随机的for i in range(0, num_examples, batch_size):indexs = indices[i: min(i + batch_size, num_examples)]yield tf.gather(features,indexs), tf.gather(labels,indexs)# 测试数据管道效果
batch_size = 8
(features,labels) = next(data_iter(X,Y,batch_size))
print(features)
print(labels)

定义模型

w = tf.Variable(tf.random.normal(w0.shape))
b = tf.Variable(tf.zeros_like(b0,dtype = tf.float32))# 定义模型
class LinearRegression:     #正向传播def __call__(self,x): return x@w + b# 损失函数def loss_func(self,y_true,y_pred):  return tf.reduce_mean((y_true - y_pred)**2/2)model = LinearRegression()
训练模型
# 使用动态图调试
def train_step(model, features, labels):with tf.GradientTape() as tape:predictions = model(features)loss = model.loss_func(labels, predictions)# 反向传播求梯度dloss_dw,dloss_db = tape.gradient(loss,[w,b])# 梯度下降法更新参数w.assign(w - 0.001*dloss_dw)b.assign(b - 0.001*dloss_db)return loss
# 测试train_step效果
batch_size = 10
(features,labels) = next(data_iter(X,Y,batch_size))
train_step(model,features,labels)def train_model(model,epochs):for epoch in tf.range(1,epochs+1):for features, labels in data_iter(X,Y,10):loss = train_step(model,features,labels)if epoch%50==0:printbar()tf.print("epoch =",epoch,"loss = ",loss)tf.print("w =",w)tf.print("b =",b)train_model(model,epochs = 200)##使用autograph机制转换成静态图加速@tf.function
def train_step(model, features, labels):with tf.GradientTape() as tape:predictions = model(features)loss = model.loss_func(labels, predictions)# 反向传播求梯度dloss_dw,dloss_db = tape.gradient(loss,[w,b])# 梯度下降法更新参数w.assign(w - 0.001*dloss_dw)b.assign(b - 0.001*dloss_db)return lossdef train_model(model,epochs):for epoch in tf.range(1,epochs+1):for features, labels in data_iter(X,Y,10):loss = train_step(model,features,labels)if epoch%50==0:printbar()tf.print("epoch =",epoch,"loss = ",loss)tf.print("w =",w)tf.print("b =",b)train_model(model,epochs = 200)# 结果可视化%matplotlib inline
%config InlineBackend.figure_format = 'svg'plt.figure(figsize = (12,5))
ax1 = plt.subplot(121)
ax1.scatter(X[:,0],Y[:,0], c = "b",label = "samples")
ax1.plot(X[:,0],w[0]*X[:,0]+b[0],"-r",linewidth = 5.0,label = "model")
ax1.legend()
plt.xlabel("x1")
plt.ylabel("y",rotation = 0)ax2 = plt.subplot(122)
ax2.scatter(X[:,1],Y[:,0], c = "g",label = "samples")
ax2.plot(X[:,1],w[1]*X[:,1]+b[0],"-r",linewidth = 5.0,label = "model")
ax2.legend()
plt.xlabel("x2")
plt.ylabel("y",rotation = 0)plt.show()

3.1.2 DNN二分类模型

准备数据

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
%matplotlib inline
%config InlineBackend.figure_format = 'svg'#正负样本数量
n_positive,n_negative = 2000,2000#生成正样本, 小圆环分布
r_p = 5.0 + tf.random.truncated_normal([n_positive,1],0.0,1.0)
theta_p = tf.random.uniform([n_positive,1],0.0,2*np.pi)
Xp = tf.concat([r_p*tf.cos(theta_p),r_p*tf.sin(theta_p)],axis = 1)
Yp = tf.ones_like(r_p)#生成负样本, 大圆环分布
r_n = 8.0 + tf.random.truncated_normal([n_negative,1],0.0,1.0)
theta_n = tf.random.uniform([n_negative,1],0.0,2*np.pi)
Xn = tf.concat([r_n*tf.cos(theta_n),r_n*tf.sin(theta_n)],axis = 1)
Yn = tf.zeros_like(r_n)#汇总样本
X = tf.concat([Xp,Xn],axis = 0)
Y = tf.concat([Yp,Yn],axis = 0)#可视化
plt.figure(figsize = (6,6))
plt.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r")
plt.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g")
plt.legend(["positive","negative"]);# 构建数据管道迭代器
def data_iter(features, labels, batch_size=8):num_examples = len(features)indices = list(range(num_examples))np.random.shuffle(indices)  #样本的读取顺序是随机的for i in range(0, num_examples, batch_size):indexs = indices[i: min(i + batch_size, num_examples)]yield tf.gather(features,indexs), tf.gather(labels,indexs)# 测试数据管道效果
batch_size = 10
(features,labels) = next(data_iter(X,Y,batch_size))
print(features)
print(labels)

定义模型

此处范例我们利用tf.Module来组织模型变量,关于tf.Module的较详细介绍参考本书第四章最后一节: Autograph和tf.Module。

class DNNModel(tf.Module):def __init__(self,name = None):super(DNNModel, self).__init__(name=name)self.w1 = tf.Variable(tf.random.truncated_normal([2,4]),dtype = tf.float32)self.b1 = tf.Variable(tf.zeros([1,4]),dtype = tf.float32)self.w2 = tf.Variable(tf.random.truncated_normal([4,8]),dtype = tf.float32)self.b2 = tf.Variable(tf.zeros([1,8]),dtype = tf.float32)self.w3 = tf.Variable(tf.random.truncated_normal([8,1]),dtype = tf.float32)self.b3 = tf.Variable(tf.zeros([1,1]),dtype = tf.float32)# 正向传播@tf.function(input_signature=[tf.TensorSpec(shape = [None,2], dtype = tf.float32)])  def __call__(self,x):x = tf.nn.relu(x@self.w1 + self.b1)x = tf.nn.relu(x@self.w2 + self.b2)y = tf.nn.sigmoid(x@self.w3 + self.b3)return y# 损失函数(二元交叉熵)@tf.function(input_signature=[tf.TensorSpec(shape = [None,1], dtype = tf.float32),tf.TensorSpec(shape = [None,1], dtype = tf.float32)])  def loss_func(self,y_true,y_pred):  #将预测值限制在 1e-7 以上, 1 - 1e-7 以下,避免log(0)错误eps = 1e-7y_pred = tf.clip_by_value(y_pred,eps,1.0-eps)bce = - y_true*tf.math.log(y_pred) - (1-y_true)*tf.math.log(1-y_pred)return  tf.reduce_mean(bce)# 评估指标(准确率)@tf.function(input_signature=[tf.TensorSpec(shape = [None,1], dtype = tf.float32),tf.TensorSpec(shape = [None,1], dtype = tf.float32)]) def metric_func(self,y_true,y_pred):y_pred = tf.where(y_pred>0.5,tf.ones_like(y_pred,dtype = tf.float32),tf.zeros_like(y_pred,dtype = tf.float32))acc = tf.reduce_mean(1-tf.abs(y_true-y_pred))return accmodel = DNNModel()# 测试模型结构
batch_size = 10
(features,labels) = next(data_iter(X,Y,batch_size))predictions = model(features)loss = model.loss_func(labels,predictions)
metric = model.metric_func(labels,predictions)tf.print("init loss:",loss)
tf.print("init metric",metric)print(len(model.trainable_variables))

训练模型

##使用autograph机制转换成静态图加速@tf.function
def train_step(model, features, labels):# 正向传播求损失with tf.GradientTape() as tape:predictions = model(features)loss = model.loss_func(labels, predictions) # 反向传播求梯度grads = tape.gradient(loss, model.trainable_variables)# 执行梯度下降for p, dloss_dp in zip(model.trainable_variables,grads):p.assign(p - 0.001*dloss_dp)# 计算评估指标metric = model.metric_func(labels,predictions)return loss, metricdef train_model(model,epochs):for epoch in tf.range(1,epochs+1):for features, labels in data_iter(X,Y,100):loss,metric = train_step(model,features,labels)if epoch%100==0:printbar()tf.print("epoch =",epoch,"loss = ",loss, "accuracy = ", metric)train_model(model,epochs = 600)# 结果可视化
fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5))
ax1.scatter(Xp[:,0],Xp[:,1],c = "r")
ax1.scatter(Xn[:,0],Xn[:,1],c = "g")
ax1.legend(["positive","negative"]);
ax1.set_title("y_true");Xp_pred = tf.boolean_mask(X,tf.squeeze(model(X)>=0.5),axis = 0)
Xn_pred = tf.boolean_mask(X,tf.squeeze(model(X)<0.5),axis = 0)ax2.scatter(Xp_pred[:,0],Xp_pred[:,1],c = "r")
ax2.scatter(Xn_pred[:,0],Xn_pred[:,1],c = "g")
ax2.legend(["positive","negative"]);
ax2.set_title("y_pred");

3.2 中阶API示范

下面的范例使用TensorFlow的中阶API实现线性回归模型和和DNN二分类模型。

TensorFlow的中阶API主要包括各种模型层,损失函数,优化器,数据管道,特征列等等。

import tensorflow as tf#打印时间分割线
@tf.function
def printbar():today_ts = tf.timestamp()%(24*60*60)hour = tf.cast(today_ts//3600+8,tf.int32)%tf.constant(24)minite = tf.cast((today_ts%3600)//60,tf.int32)second = tf.cast(tf.floor(today_ts%60),tf.int32)def timeformat(m):if tf.strings.length(tf.strings.format("{}",m))==1:return(tf.strings.format("0{}",m))else:return(tf.strings.format("{}",m))timestring = tf.strings.join([timeformat(hour),timeformat(minite),timeformat(second)],separator = ":")tf.print("=========="*8+timestring)

3.2.1 线性回归模型

准备数据

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow.keras import layers,losses,metrics,optimizers#样本数量
n = 400# 生成测试用数据集
X = tf.random.uniform([n,2],minval=-10,maxval=10)
w0 = tf.constant([[2.0],[-3.0]])
b0 = tf.constant([[3.0]])
Y = X@w0 + b0 + tf.random.normal([n,1],mean = 0.0,stddev= 2.0)  # @表示矩阵乘法,增加正态扰动# 数据可视化
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
plt.figure(figsize = (12,5))
ax1 = plt.subplot(121)
ax1.scatter(X[:,0],Y[:,0], c = "b")
plt.xlabel("x1")
plt.ylabel("y",rotation = 0)ax2 = plt.subplot(122)
ax2.scatter(X[:,1],Y[:,0], c = "g")
plt.xlabel("x2")
plt.ylabel("y",rotation = 0)
plt.show()#构建输入数据管道
ds = tf.data.Dataset.from_tensor_slices((X,Y)) \.shuffle(buffer_size = 100).batch(10) \.prefetch(tf.data.experimental.AUTOTUNE)

定义模型

model = layers.Dense(units = 1)
model.build(input_shape = (2,)) #用build方法创建variables
model.loss_func = losses.mean_squared_error
model.optimizer = optimizers.SGD(learning_rate=0.001)

训练模型

#使用autograph机制转换成静态图加速@tf.function
def train_step(model, features, labels):with tf.GradientTape() as tape:predictions = model(features)loss = model.loss_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1]))grads = tape.gradient(loss,model.variables)model.optimizer.apply_gradients(zip(grads,model.variables))return loss# 测试train_step效果
features,labels = next(ds.as_numpy_iterator())
train_step(model,features,labels)def train_model(model,epochs):for epoch in tf.range(1,epochs+1):loss = tf.constant(0.0)for features, labels in ds:loss = train_step(model,features,labels)if epoch%50==0:printbar()tf.print("epoch =",epoch,"loss = ",loss)tf.print("w =",model.variables[0])tf.print("b =",model.variables[1])
train_model(model,epochs = 200)# 结果可视化%matplotlib inline
%config InlineBackend.figure_format = 'svg'w,b = model.variablesplt.figure(figsize = (12,5))
ax1 = plt.subplot(121)
ax1.scatter(X[:,0],Y[:,0], c = "b",label = "samples")
ax1.plot(X[:,0],w[0]*X[:,0]+b[0],"-r",linewidth = 5.0,label = "model")
ax1.legend()
plt.xlabel("x1")
plt.ylabel("y",rotation = 0)ax2 = plt.subplot(122)
ax2.scatter(X[:,1],Y[:,0], c = "g",label = "samples")
ax2.plot(X[:,1],w[1]*X[:,1]+b[0],"-r",linewidth = 5.0,label = "model")
ax2.legend()
plt.xlabel("x2")
plt.ylabel("y",rotation = 0)plt.show()

3.2.2 DNN二分类模型

准备数据

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow.keras import layers,losses,metrics,optimizers
%matplotlib inline
%config InlineBackend.figure_format = 'svg'#正负样本数量
n_positive,n_negative = 2000,2000#生成正样本, 小圆环分布
r_p = 5.0 + tf.random.truncated_normal([n_positive,1],0.0,1.0)
theta_p = tf.random.uniform([n_positive,1],0.0,2*np.pi)
Xp = tf.concat([r_p*tf.cos(theta_p),r_p*tf.sin(theta_p)],axis = 1)
Yp = tf.ones_like(r_p)#生成负样本, 大圆环分布
r_n = 8.0 + tf.random.truncated_normal([n_negative,1],0.0,1.0)
theta_n = tf.random.uniform([n_negative,1],0.0,2*np.pi)
Xn = tf.concat([r_n*tf.cos(theta_n),r_n*tf.sin(theta_n)],axis = 1)
Yn = tf.zeros_like(r_n)#汇总样本
X = tf.concat([Xp,Xn],axis = 0)
Y = tf.concat([Yp,Yn],axis = 0)#可视化
plt.figure(figsize = (6,6))
plt.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r")
plt.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g")
plt.legend(["positive","negative"]);#构建输入数据管道
ds = tf.data.Dataset.from_tensor_slices((X,Y)) \.shuffle(buffer_size = 4000).batch(100) \.prefetch(tf.data.experimental.AUTOTUNE)

定义模型

class DNNModel(tf.Module):def __init__(self,name = None):super(DNNModel, self).__init__(name=name)self.dense1 = layers.Dense(4,activation = "relu") self.dense2 = layers.Dense(8,activation = "relu")self.dense3 = layers.Dense(1,activation = "sigmoid")# 正向传播@tf.function(input_signature=[tf.TensorSpec(shape = [None,2], dtype = tf.float32)])  def __call__(self,x):x = self.dense1(x)x = self.dense2(x)y = self.dense3(x)return ymodel = DNNModel()
model.loss_func = losses.binary_crossentropy
model.metric_func = metrics.binary_accuracy
model.optimizer = optimizers.Adam(learning_rate=0.001)# 测试模型结构
(features,labels) = next(ds.as_numpy_iterator())predictions = model(features)loss = model.loss_func(tf.reshape(labels,[-1]),tf.reshape(predictions,[-1]))
metric = model.metric_func(tf.reshape(labels,[-1]),tf.reshape(predictions,[-1]))tf.print("init loss:",loss)
tf.print("init metric",metric)

训练模型

#使用autograph机制转换成静态图加速@tf.function
def train_step(model, features, labels):with tf.GradientTape() as tape:predictions = model(features)loss = model.loss_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1]))grads = tape.gradient(loss,model.trainable_variables)model.optimizer.apply_gradients(zip(grads,model.trainable_variables))metric = model.metric_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1]))return loss,metric# 测试train_step效果
features,labels = next(ds.as_numpy_iterator())
train_step(model,features,labels)def train_model(model,epochs):for epoch in tf.range(1,epochs+1):loss, metric = tf.constant(0.0),tf.constant(0.0)for features, labels in ds:loss,metric = train_step(model,features,labels)if epoch%10==0:printbar()tf.print("epoch =",epoch,"loss = ",loss, "accuracy = ",metric)
train_model(model,epochs = 60)# 结果可视化
fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5))
ax1.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r")
ax1.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g")
ax1.legend(["positive","negative"]);
ax1.set_title("y_true");Xp_pred = tf.boolean_mask(X,tf.squeeze(model(X)>=0.5),axis = 0)
Xn_pred = tf.boolean_mask(X,tf.squeeze(model(X)<0.5),axis = 0)ax2.scatter(Xp_pred[:,0].numpy(),Xp_pred[:,1].numpy(),c = "r")
ax2.scatter(Xn_pred[:,0].numpy(),Xn_pred[:,1].numpy(),c = "g")
ax2.legend(["positive","negative"]);
ax2.set_title("y_pred");

3.3 高阶API示范

下面的范例使用TensorFlow的高阶API实现线性回归模型和DNN二分类模型。

TensorFlow的高阶API主要为tf.keras.models提供的模型的类接口。

使用Keras接口有以下3种方式构建模型:使用Sequential按层顺序构建模型,使用函数式API构建任意结构模型,继承Model基类构建自定义模型。

此处分别演示使用Sequential按层顺序构建模型以及继承Model基类构建自定义模型。

import tensorflow as tf#打印时间分割线
@tf.function
def printbar():today_ts = tf.timestamp()%(24*60*60)hour = tf.cast(today_ts//3600+8,tf.int32)%tf.constant(24)minite = tf.cast((today_ts%3600)//60,tf.int32)second = tf.cast(tf.floor(today_ts%60),tf.int32)def timeformat(m):if tf.strings.length(tf.strings.format("{}",m))==1:return(tf.strings.format("0{}",m))else:return(tf.strings.format("{}",m))timestring = tf.strings.join([timeformat(hour),timeformat(minite),timeformat(second)],separator = ":")tf.print("=========="*8+timestring)

3.3.1 线性回归模型

此范例我们使用Sequential按层顺序构建模型,并使用内置model.fit方法训练模型【面向新手】。

准备数据

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow.keras import models,layers,losses,metrics,optimizers#样本数量
n = 400# 生成测试用数据集
X = tf.random.uniform([n,2],minval=-10,maxval=10)
w0 = tf.constant([[2.0],[-3.0]])
b0 = tf.constant([[3.0]])
Y = X@w0 + b0 + tf.random.normal([n,1],mean = 0.0,stddev= 2.0)  # @表示矩阵乘法,增加正态扰动# 数据可视化%matplotlib inline
%config InlineBackend.figure_format = 'svg'
plt.figure(figsize = (12,5))
ax1 = plt.subplot(121)
ax1.scatter(X[:,0],Y[:,0], c = "b")
plt.xlabel("x1")
plt.ylabel("y",rotation = 0)ax2 = plt.subplot(122)
ax2.scatter(X[:,1],Y[:,0], c = "g")
plt.xlabel("x2")
plt.ylabel("y",rotation = 0)
plt.show()

定义模型

tf.keras.backend.clear_session()model = models.Sequential()
model.add(layers.Dense(1,input_shape =(2,)))
model.summary()

训练模型

### 使用fit方法进行训练model.compile(optimizer="adam",loss="mse",metrics=["mae"])
model.fit(X,Y,batch_size = 10,epochs = 200)  tf.print("w = ",model.layers[0].kernel)
tf.print("b = ",model.layers[0].bias)# 结果可视化%matplotlib inline
%config InlineBackend.figure_format = 'svg'w,b = model.variablesplt.figure(figsize = (12,5))
ax1 = plt.subplot(121)
ax1.scatter(X[:,0],Y[:,0], c = "b",label = "samples")
ax1.plot(X[:,0],w[0]*X[:,0]+b[0],"-r",linewidth = 5.0,label = "model")
ax1.legend()
plt.xlabel("x1")
plt.ylabel("y",rotation = 0)ax2 = plt.subplot(122)
ax2.scatter(X[:,1],Y[:,0], c = "g",label = "samples")
ax2.plot(X[:,1],w[1]*X[:,1]+b[0],"-r",linewidth = 5.0,label = "model")
ax2.legend()
plt.xlabel("x2")
plt.ylabel("y",rotation = 0)plt.show()

3.3.2 DNN二分类模型

此范例我们使用继承Model基类构建自定义模型,并构建自定义训练循环【面向专家】

数据准备

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
from tensorflow.keras import layers,losses,metrics,optimizers
%matplotlib inline
%config InlineBackend.figure_format = 'svg'#正负样本数量
n_positive,n_negative = 2000,2000
n = n_positive+n_negative#生成正样本, 小圆环分布
r_p = 5.0 + tf.random.truncated_normal([n_positive,1],0.0,1.0)
theta_p = tf.random.uniform([n_positive,1],0.0,2*np.pi)
Xp = tf.concat([r_p*tf.cos(theta_p),r_p*tf.sin(theta_p)],axis = 1)
Yp = tf.ones_like(r_p)#生成负样本, 大圆环分布
r_n = 8.0 + tf.random.truncated_normal([n_negative,1],0.0,1.0)
theta_n = tf.random.uniform([n_negative,1],0.0,2*np.pi)
Xn = tf.concat([r_n*tf.cos(theta_n),r_n*tf.sin(theta_n)],axis = 1)
Yn = tf.zeros_like(r_n)#汇总样本
X = tf.concat([Xp,Xn],axis = 0)
Y = tf.concat([Yp,Yn],axis = 0)#样本洗牌
data = tf.concat([X,Y],axis = 1)
data = tf.random.shuffle(data)
X = data[:,:2]
Y = data[:,2:]#可视化
plt.figure(figsize = (6,6))
plt.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r")
plt.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g")
plt.legend(["positive","negative"]);ds_train = tf.data.Dataset.from_tensor_slices((X[0:n*3//4,:],Y[0:n*3//4,:])) \.shuffle(buffer_size = 1000).batch(20) \.prefetch(tf.data.experimental.AUTOTUNE) \.cache()ds_valid = tf.data.Dataset.from_tensor_slices((X[n*3//4:,:],Y[n*3//4:,:])) \.batch(20) \.prefetch(tf.data.experimental.AUTOTUNE) \.cache()

定义模型

tf.keras.backend.clear_session()
class DNNModel(models.Model):def __init__(self):super(DNNModel, self).__init__()def build(self,input_shape):self.dense1 = layers.Dense(4,activation = "relu",name = "dense1") self.dense2 = layers.Dense(8,activation = "relu",name = "dense2")self.dense3 = layers.Dense(1,activation = "sigmoid",name = "dense3")super(DNNModel,self).build(input_shape)# 正向传播@tf.function(input_signature=[tf.TensorSpec(shape = [None,2], dtype = tf.float32)])  def call(self,x):x = self.dense1(x)x = self.dense2(x)y = self.dense3(x)return ymodel = DNNModel()
model.build(input_shape =(None,2))model.summary()

训练模型

### 自定义训练循环optimizer = optimizers.Adam(learning_rate=0.01)
loss_func = tf.keras.losses.BinaryCrossentropy()train_loss = tf.keras.metrics.Mean(name='train_loss')
train_metric = tf.keras.metrics.BinaryAccuracy(name='train_accuracy')valid_loss = tf.keras.metrics.Mean(name='valid_loss')
valid_metric = tf.keras.metrics.BinaryAccuracy(name='valid_accuracy')@tf.function
def train_step(model, features, labels):with tf.GradientTape() as tape:predictions = model(features)loss = loss_func(labels, predictions)grads = tape.gradient(loss, model.trainable_variables)optimizer.apply_gradients(zip(grads, model.trainable_variables))train_loss.update_state(loss)train_metric.update_state(labels, predictions)@tf.function
def valid_step(model, features, labels):predictions = model(features)batch_loss = loss_func(labels, predictions)valid_loss.update_state(batch_loss)valid_metric.update_state(labels, predictions)def train_model(model,ds_train,ds_valid,epochs):for epoch in tf.range(1,epochs+1):for features, labels in ds_train:train_step(model,features,labels)for features, labels in ds_valid:valid_step(model,features,labels)logs = 'Epoch={},Loss:{},Accuracy:{},Valid Loss:{},Valid Accuracy:{}'if  epoch%100 ==0:printbar()tf.print(tf.strings.format(logs,(epoch,train_loss.result(),train_metric.result(),valid_loss.result(),valid_metric.result())))train_loss.reset_states()valid_loss.reset_states()train_metric.reset_states()valid_metric.reset_states()train_model(model,ds_train,ds_valid,1000)# 结果可视化
fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5))
ax1.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r")
ax1.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g")
ax1.legend(["positive","negative"]);
ax1.set_title("y_true");Xp_pred = tf.boolean_mask(X,tf.squeeze(model(X)>=0.5),axis = 0)
Xn_pred = tf.boolean_mask(X,tf.squeeze(model(X)<0.5),axis = 0)ax2.scatter(Xp_pred[:,0].numpy(),Xp_pred[:,1].numpy(),c = "r")
ax2.scatter(Xn_pred[:,0].numpy(),Xn_pred[:,1].numpy(),c = "g")
ax2.legend(["positive","negative"]);
ax2.set_title("y_pred");

四、TensorFlow的低阶API

TensorFlow的低阶API主要包括张量操作,计算图和自动微分。

如果把模型比作一个房子,那么低阶API就是【模型之砖】。

在低阶API层次上,可以把TensorFlow当做一个增强版的numpy来使用。

TensorFlow提供的方法比numpy更全面,运算速度更快,如果需要的话,还可以使用GPU进行加速。

前面几章我们对低阶API已经有了一个整体的认识,本章我们将重点详细介绍张量操作和Autograph计算图。

张量的操作主要包括张量的结构操作和张量的数学运算。

张量结构操作诸如:张量创建,索引切片,维度变换,合并分割。

张量数学运算主要有:标量运算,向量运算,矩阵运算。另外我们会介绍张量运算的广播机制。

Autograph计算图我们将介绍使用Autograph的规范建议,Autograph的机制原理,Autograph和tf.Module.

4.1 张量的结构操作

张量的操作主要包括张量的结构操作和张量的数学运算。

张量结构操作诸如:张量创建,索引切片,维度变换,合并分割。

张量数学运算主要有:标量运算,向量运算,矩阵运算。另外我们会介绍张量运算的广播机制。

本篇我们介绍张量的结构操作。

4.1.1 创建张量

张量创建的许多方法和numpy中创建array的方法很像。

import tensorflow as tf
import numpy as np a = tf.constant([1,2,3],dtype = tf.float32)
tf.print(a)b = tf.range(1,10,delta = 2)
tf.print(b)c = tf.linspace(0.0,2*3.14,100)
tf.print(c)a = tf.ones([3,3])
b = tf.zeros_like(a,dtype= tf.float32)
tf.print(a)
tf.print(b)b = tf.fill([3,2],5)
tf.print(b)#均匀分布随机
tf.random.set_seed(1.0)
a = tf.random.uniform([5],minval=0,maxval=10)
tf.print(a)#正态分布随机
b = tf.random.normal([3,3],mean=0.0,stddev=1.0)
tf.print(b)#正态分布随机,剔除2倍方差以外数据重新生成
c = tf.random.truncated_normal((5,5), mean=0.0, stddev=1.0, dtype=tf.float32)
tf.print(c)# 特殊矩阵
I = tf.eye(3,3) #单位矩阵
tf.print(I)
tf.print(" ")
t = tf.linalg.diag([1,2,3]) #对角阵
tf.print(t)

4.1.2 索引切片

张量的索引切片方式和numpy几乎是一样的。切片时支持缺省参数和省略号。

对于tf.Variable,可以通过索引和切片对部分元素进行修改。

对于提取张量的连续子区域,也可以使用tf.slice.

此外,对于不规则的切片提取,可以使用tf.gather,tf.gather_nd,tf.boolean_mask。

tf.boolean_mask功能最为强大,它可以实现tf.gather,tf.gather_nd的功能,并且tf.boolean_mask还可以实现布尔索引。

如果要通过修改张量的某些元素得到新的张量,可以使用tf.where,tf.scatter_nd。

tf.random.set_seed(3)
t = tf.random.uniform([5,5],minval=0,maxval=10,dtype=tf.int32)
tf.print(t)#第0行
tf.print(t[0])#倒数第一行
tf.print(t[-1])#第1行第3列
tf.print(t[1,3])
tf.print(t[1][3])#第1行至第3行
tf.print(t[1:4,:])
tf.print(tf.slice(t,[1,0],[3,5])) #tf.slice(input,begin_vector,size_vector)#第1行至最后一行,第0列到最后一列每隔两列取一列
tf.print(t[1:4,:4:2])#对变量来说,还可以使用索引和切片修改部分元素
x = tf.Variable([[1,2],[3,4]],dtype = tf.float32)
x[1,:].assign(tf.constant([0.0,0.0]))
tf.print(x)a = tf.random.uniform([3,3,3],minval=0,maxval=10,dtype=tf.int32)
tf.print(a)#省略号可以表示多个冒号
tf.print(a[...,1])

以上切片方式相对规则,对于不规则的切片提取,可以使用tf.gather,tf.gather_nd,tf.boolean_mask。

考虑班级成绩册的例子,有4个班级,每个班级10个学生,每个学生7门科目成绩。可以用一个4×10×7的张量来表示。

scores = tf.random.uniform((4,10,7),minval=0,maxval=100,dtype=tf.int32)
tf.print(scores)#抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩
p = tf.gather(scores,[0,5,9],axis=1)
tf.print(p)#抽取每个班级第0个学生,第5个学生,第9个学生的第1门课程,第3门课程,第6门课程成绩
q = tf.gather(tf.gather(scores,[0,5,9],axis=1),[1,3,6],axis=2)
tf.print(q)# 抽取第0个班级第0个学生,第2个班级的第4个学生,第3个班级的第6个学生的全部成绩
#indices的长度为采样样本的个数,每个元素为采样位置的坐标
s = tf.gather_nd(scores,indices = [(0,0),(2,4),(3,6)])
s

以上tf.gather和tf.gather_nd的功能也可以用tf.boolean_mask来实现。

#抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩
p = tf.boolean_mask(scores,[True,False,False,False,False,True,False,False,False,True],axis=1)
tf.print(p)#抽取第0个班级第0个学生,第2个班级的第4个学生,第3个班级的第6个学生的全部成绩
s = tf.boolean_mask(scores,[[True,False,False,False,False,False,False,False,False,False],[False,False,False,False,False,False,False,False,False,False],[False,False,False,False,True,False,False,False,False,False],[False,False,False,False,False,False,True,False,False,False]])
tf.print(s)#利用tf.boolean_mask可以实现布尔索引#找到矩阵中小于0的元素
c = tf.constant([[-1,1,-1],[2,2,-2],[3,-3,3]],dtype=tf.float32)
tf.print(c,"\n")tf.print(tf.boolean_mask(c,c<0),"\n")
tf.print(c[c<0]) #布尔索引,为boolean_mask的语法糖形式

以上这些方法仅能提取张量的部分元素值,但不能更改张量的部分元素值得到新的张量。

如果要通过修改张量的部分元素值得到新的张量,可以使用tf.where和tf.scatter_nd。

tf.where可以理解为if的张量版本,此外它还可以用于找到满足条件的所有元素的位置坐标。

tf.scatter_nd的作用和tf.gather_nd有些相反,tf.gather_nd用于收集张量的给定位置的元素,

而tf.scatter_nd可以将某些值插入到一个给定shape的全0的张量的指定位置处。

#找到张量中小于0的元素,将其换成np.nan得到新的张量
#tf.where和np.where作用类似,可以理解为if的张量版本c = tf.constant([[-1,1,-1],[2,2,-2],[3,-3,3]],dtype=tf.float32)
d = tf.where(c<0,tf.fill(c.shape,np.nan),c) #如果where只有一个参数,将返回所有满足条件的位置坐标
indices = tf.where(c<0)
indices#将张量的第[0,0]和[2,1]两个位置元素替换为0得到新的张量
d = c - tf.scatter_nd([[0,0],[2,1]],[c[0,0],c[2,1]],c.shape)#scatter_nd的作用和gather_nd有些相反
#可以将某些值插入到一个给定shape的全0的张量的指定位置处。
indices = tf.where(c<0)
tf.scatter_nd(indices,tf.gather_nd(c,indices),c.shape)

4.1.3 维度变换

维度变换相关函数主要有 tf.reshape, tf.squeeze, tf.expand_dims, tf.transpose.

tf.reshape 可以改变张量的形状。

tf.squeeze 可以减少维度。

tf.expand_dims 可以增加维度。

tf.transpose 可以交换维度。

tf.reshape可以改变张量的形状,但是其本质上不会改变张量元素的存储顺序,所以,该操作实际上非常迅速,并且是可逆的。

a = tf.random.uniform(shape=[1,3,3,2],minval=0,maxval=255,dtype=tf.int32)
tf.print(a.shape)
tf.print(a)# 改成 (3,6)形状的张量
b = tf.reshape(a,[3,6])
tf.print(b.shape)
tf.print(b)# 改回成 [1,3,3,2] 形状的张量
c = tf.reshape(b,[1,3,3,2])
tf.print(c)

如果张量在某个维度上只有一个元素,利用tf.squeeze可以消除这个维度。

和tf.reshape相似,它本质上不会改变张量元素的存储顺序。

张量的各个元素在内存中是线性存储的,其一般规律是,同一层级中的相邻元素的物理地址也相邻。

s = tf.squeeze(a)
tf.print(s.shape)
tf.print(s)d = tf.expand_dims(s,axis=0) #在第0维插入长度为1的一个维度

tf.transpose可以交换张量的维度,与tf.reshape不同,它会改变张量元素的存储顺序。

tf.transpose常用于图片存储格式的变换上。

# Batch,Height,Width,Channel
a = tf.random.uniform(shape=[100,600,600,4],minval=0,maxval=255,dtype=tf.int32)
tf.print(a.shape)# 转换成 Channel,Height,Width,Batch
s= tf.transpose(a,perm=[3,1,2,0])
tf.print(s.shape)

4.1.4 合并分割

和numpy类似,可以用tf.concat和tf.stack方法对多个张量进行合并,可以用tf.split方法把一个张量分割成多个张量。

tf.concat和tf.stack有略微的区别,tf.concat是连接,不会增加维度,而tf.stack是堆叠,会增加维度。

a = tf.constant([[1.0,2.0],[3.0,4.0]])
b = tf.constant([[5.0,6.0],[7.0,8.0]])
c = tf.constant([[9.0,10.0],[11.0,12.0]])tf.concat([a,b,c],axis = 0)tf.concat([a,b,c],axis = 1)tf.stack([a,b,c])tf.stack([a,b,c],axis=1)a = tf.constant([[1.0,2.0],[3.0,4.0]])
b = tf.constant([[5.0,6.0],[7.0,8.0]])
c = tf.constant([[9.0,10.0],[11.0,12.0]])c = tf.concat([a,b,c],axis = 0)

tf.split是tf.concat的逆运算,可以指定分割份数平均分割,也可以通过指定每份的记录数量进行分割。

#tf.split(value,num_or_size_splits,axis)
tf.split(c,3,axis = 0)  #指定分割份数,平均分割tf.split(c,[2,2,2],axis = 0) #指定每份的记录数量

4.2 张量的数学运算

张量的操作主要包括张量的结构操作和张量的数学运算。

张量结构操作诸如:张量创建,索引切片,维度变换,合并分割。

张量数学运算主要有:标量运算,向量运算,矩阵运算。另外我们会介绍张量运算的广播机制。

本篇我们介绍张量的数学运算。

4.2.1 标量运算

张量的数学运算符可以分为标量运算符、向量运算符、以及矩阵运算符。

加减乘除乘方,以及三角函数,指数,对数等常见函数,逻辑比较运算符等都是标量运算符。

标量运算符的特点是对张量实施逐元素运算。

有些标量运算符对常用的数学运算符进行了重载。并且支持类似numpy的广播特性。

许多标量运算符都在 tf.math模块下。

import tensorflow as tf
import numpy as np a = tf.constant([[1.0,2],[-3,4.0]])
b = tf.constant([[5.0,6],[7.0,8.0]])
a+b  #运算符重载
a-b
a*b
a/b
a**2
a**(0.5)
a%3 #mod的运算符重载,等价于m = tf.math.mod(a,3)
a//3  #地板除法
(a>=2)
(a>=2)&(a<=3)
(a>=2)|(a<=3)
a==5 #tf.equal(a,5)
tf.sqrt(a)a = tf.constant([1.0,8.0])
b = tf.constant([5.0,6.0])
c = tf.constant([6.0,7.0])
tf.add_n([a,b,c])
tf.print(tf.maximum(a,b))x = tf.constant([2.6,-2.7])tf.print(tf.math.round(x)) #保留整数部分,四舍五入
tf.print(tf.math.floor(x)) #保留整数部分,向下归整
tf.print(tf.math.ceil(x))  #保留整数部分,向上归整# 幅值裁剪
x = tf.constant([0.9,-0.8,100.0,-20.0,0.7])
y = tf.clip_by_value(x,clip_value_min=-1,clip_value_max=1)
z = tf.clip_by_norm(x,clip_norm = 3)
tf.print(y)
tf.print(z)

4.2.2 向量运算

向量运算符只在一个特定轴上运算,将一个向量映射到一个标量或者另外一个向量。
许多向量运算符都以reduce开头。

#向量reduce
a = tf.range(1,10)
tf.print(tf.reduce_sum(a))
tf.print(tf.reduce_mean(a))
tf.print(tf.reduce_max(a))
tf.print(tf.reduce_min(a))
tf.print(tf.reduce_prod(a))#张量指定维度进行reduce
b = tf.reshape(a,(3,3))
tf.print(tf.reduce_sum(b, axis=1, keepdims=True))
tf.print(tf.reduce_sum(b, axis=0, keepdims=True))#bool类型的reduce
p = tf.constant([True,False,False])
q = tf.constant([False,False,True])
tf.print(tf.reduce_all(p))
tf.print(tf.reduce_any(q))#利用tf.foldr实现tf.reduce_sum
s = tf.foldr(lambda a,b:a+b,tf.range(10))
tf.print(s)#cum扫描累积
a = tf.range(1,10)
tf.print(tf.math.cumsum(a))
tf.print(tf.math.cumprod(a))#arg最大最小值索引
a = tf.range(1,10)
tf.print(tf.argmax(a))
tf.print(tf.argmin(a))#tf.math.top_k可以用于对张量排序
a = tf.constant([1,3,7,5,4,8])values,indices = tf.math.top_k(a,3,sorted=True)
tf.print(values)
tf.print(indices)#利用tf.math.top_k可以在TensorFlow中实现KNN算法

4.2.3 矩阵运算

矩阵必须是二维的。类似tf.constant([1,2,3])这样的不是矩阵。

矩阵运算包括:矩阵乘法,矩阵转置,矩阵逆,矩阵求迹,矩阵范数,矩阵行列式,矩阵求特征值,矩阵分解等运算。

除了一些常用的运算外,大部分和矩阵有关的运算都在tf.linalg子包中。

#矩阵乘法
a = tf.constant([[1,2],[3,4]])
b = tf.constant([[2,0],[0,2]])
a@b  #等价于tf.matmul(a,b)#矩阵转置
a = tf.constant([[1,2],[3,4]])
tf.transpose(a)#矩阵逆,必须为tf.float32或tf.double类型
a = tf.constant([[1.0,2],[3,4]],dtype = tf.float32)
tf.linalg.inv(a)#矩阵逆,必须为tf.float32或tf.double类型
a = tf.constant([[1.0,2],[3,4]],dtype = tf.float32)
tf.linalg.inv(a)#矩阵求trace
a = tf.constant([[1.0,2],[3,4]],dtype = tf.float32)
tf.linalg.trace(a)#矩阵求范数
a = tf.constant([[1.0,2],[3,4]])
tf.linalg.norm(a)#矩阵行列式
a = tf.constant([[1.0,2],[3,4]])
tf.linalg.det(a)#矩阵特征值
a = tf.constant([[1.0,2],[-5,4]])
tf.linalg.eigvals(a)#矩阵QR分解, 将一个方阵分解为一个正交矩阵q和上三角矩阵r
#QR分解实际上是对矩阵a实施Schmidt正交化得到qa = tf.constant([[1.0,2.0],[3.0,4.0]],dtype = tf.float32)
q,r = tf.linalg.qr(a)
tf.print(q)
tf.print(r)
tf.print(q@r)#矩阵svd分解
#svd分解可以将任意一个矩阵分解为一个正交矩阵u,一个对角阵s和一个正交矩阵v.t()的乘积
#svd常用于矩阵压缩和降维a  = tf.constant([[1.0,2.0],[3.0,4.0],[5.0,6.0]], dtype = tf.float32)
s,u,v = tf.linalg.svd(a)
tf.print(u,"\n")
tf.print(s,"\n")
tf.print(v,"\n")
tf.print(u@tf.linalg.diag(s)@tf.transpose(v))#利用svd分解可以在TensorFlow中实现主成分分析降维

4.2.4 广播机制

TensorFlow的广播规则和numpy是一样的:

  • 1、如果张量的维度不同,将维度较小的张量进行扩展,直到两个张量的维度都一样。
  • 2、如果两个张量在某个维度上的长度是相同的,或者其中一个张量在该维度上的长度为1,那么我们就说这两个张量在该维度上是相容的。
  • 3、如果两个张量在所有维度上都是相容的,它们就能使用广播。
  • 4、广播之后,每个维度的长度将取两个张量在该维度长度的较大值。
  • 5、在任何一个维度上,如果一个张量的长度为1,另一个张量长度大于1,那么在该维度上,就好像是对第一个张量进行了复制。
    tf.broadcast_to 以显式的方式按照广播机制扩展张量的维度。
a = tf.constant([1,2,3])
b = tf.constant([[0,0,0],[1,1,1],[2,2,2]])
b + a  #等价于 b + tf.broadcast_to(a,b.shape)
tf.broadcast_to(a,b.shape)#计算广播后计算结果的形状,静态形状,TensorShape类型参数
tf.broadcast_static_shape(a.shape,b.shape)#计算广播后计算结果的形状,动态形状,Tensor类型参数
c = tf.constant([1,2,3])
d = tf.constant([[1],[2],[3]])
tf.broadcast_dynamic_shape(tf.shape(c),tf.shape(d))#广播效果
c+d #等价于 tf.broadcast_to(c,[3,3]) + tf.broadcast_to(d,[3,3])

4.3 AutoGraph的使用规范

有三种计算图的构建方式:静态计算图,动态计算图,以及Autograph。

TensorFlow 2.0主要使用的是动态计算图和Autograph。

动态计算图易于调试,编码效率较高,但执行效率偏低。

静态计算图执行效率很高,但较难调试。

而Autograph机制可以将动态图转换成静态计算图,兼收执行效率和编码效率之利。

当然Autograph机制能够转换的代码并不是没有任何约束的,有一些编码规范需要遵循,否则可能会转换失败或者不符合预期。

我们将着重介绍Autograph的编码规范和Autograph转换成静态图的原理。

并介绍使用tf.Module来更好地构建Autograph。

本篇我们介绍使用Autograph的编码规范。

4.3.1 Autograph编码规范总结

  • 1,被@tf.function修饰的函数应尽可能使用TensorFlow中的函数而不是Python中的其他函数。例如使用tf.print而不是print,使用tf.range而不是range,使用tf.constant(True)而不是True.
  • 2,避免在@tf.function修饰的函数内部定义tf.Variable.
  • 3,被@tf.function修饰的函数不可修改该函数外部的Python列表或字典等数据结构变量。

4.3.2 Autograph编码规范解析

1,被@tf.function修饰的函数应尽量使用TensorFlow中的函数而不是Python中的其他函数。

import numpy as np
import tensorflow as tf@tf.function
def np_random():a = np.random.randn(3,3)tf.print(a)@tf.function
def tf_random():a = tf.random.normal((3,3))tf.print(a)

TensorFlow2.0学习相关推荐

  1. TensorFlow2.0 学习笔记(三):卷积神经网络(CNN)

    欢迎关注WX公众号:[程序员管小亮] 专栏--TensorFlow学习笔记 文章目录 欢迎关注WX公众号:[程序员管小亮] 专栏--TensorFlow学习笔记 一.神经网络的基本单位:神经元 二.卷 ...

  2. Tensorflow2.0学习笔记(一)

    Tensorflow2.0学习笔记(一)--MNIST入门 文章目录 Tensorflow2.0学习笔记(一)--MNIST入门 前言 一.MNIST是什么? 二.实现步骤及代码 1.引入库 2.下载 ...

  3. Tensorflow2.0学习笔记(二)

    Tensorflow2.0学习笔记(二)--Keras练习 文章目录 Tensorflow2.0学习笔记(二)--Keras练习 前言 二.使用步骤 1.实现步骤及代码 2.下载 Fashion MN ...

  4. Tensorflow2.0学习笔记(一)北大曹健老师教学视频1-4讲

    Tensorflow2.0学习笔记(一)北大曹健老师教学视频1-4讲 返回目录 这个笔记现在是主要根据北京大学曹健老师的视频写的,这个视频超级棒,非常推荐. 第一讲 常用函数的使用(包含了很多琐碎的函 ...

  5. Tensorflow2.0学习笔记(二)北大曹健老师教学视频第五讲

    Tensorflow2.0学习笔记(二)北大曹健老师教学视频第五讲 返回目录 理论部分主要写点以前看吴恩达视频没有的或者不太熟悉的了. 5.1卷积计算过程 实际项目中的照片多是高分辨率彩色图,但待优化 ...

  6. Tensorflow2.0学习(五) — Keras基础应用(IMDb电影集情感分析)

    今天这一节内容是关于Keras应用分析的最后一节,在熟悉了Keras的基础知识之后,下面几节我们就可以正式接触Tensorflow2.0.根据博主多处查阅,最终还是发现Tensorflow的官方教程好 ...

  7. TensorFlow2.0学习笔记2-tf2.0两种方式搭建神经网络

    目录 一,TensorFlow2.0搭建神经网络八股 1)import  [引入相关模块] 2)train,test  [告知喂入网络的训练集测试集以及相应的标签] 3)model=tf.keras. ...

  8. tensorflow2.0学习笔记(五)

    Keras高层API 基本就是4步: Matrics update_state result().numpy() reset_states(就是清除缓存) tensorflow2.0代码很简单,就MN ...

  9. tensorflow2.0 学习笔记:一、神经网络计算

    mooc课程Tensorflow2.0 笔记 人工智能三学派 行为主义:基于控制论,构建感知-动作控制系统(自适应控制系统) 符号主义:基于算数逻辑表达式,求解问题时先把问题描述为表达式,再求解表达式 ...

最新文章

  1. Error in xy.coords(x, y, xlabel, ylabel, log) : ‘x‘ and ‘y‘ lengths differ
  2. VC 中字符串比较和查找
  3. java enum in class_Java 8需要一个转换,而Java 7没有 – enum.getClass/getDeclaringClass
  4. 二维数组最长递增java_动态规划设计之最长递增子序列
  5. 《JavaEE黑马》 第一阶段 JavaSE基础 第一章
  6. 【LaTeX入门】15、在文章中添加脚注
  7. 1人工智能概述------人工智能发展历程(人工智能的起源、人工智能的发展经历了六个阶段)
  8. visio绘制立方体
  9. python房地产_如何用Python爬虫投资房产,走向人生巅峰?
  10. 华为云服务的使用方法详解--以照片备份与恢复为例
  11. 代码角度理解SGX的认证机制(一):本地认证
  12. 以java为扩展名_Java源程序文件的扩展名为.java。
  13. JavaScript进阶 - 第9章 DOM对象,控制HTML元素
  14. snidel 2014春夏新品 纱质长半裙
  15. 留学生日常英语46~50
  16. CG.DYJ-顺序表比较
  17. Hive 客户端工具
  18. [创业之路-48] :动态股权机制 -3- 静态股权分配 VS 动态股权分配
  19. 一加到100分之一c语言,r1c和一加_c语言和c加加的区别_雅马哈r1恐怖加速
  20. wagon-maven-plugin 实现远程部署

热门文章

  1. 南陵中学2021高考成绩查询,南陵中学举行2021届高三距高考200天动员大会
  2. RK1126从入门到放弃:番外篇(二)Win10 WSL系统下编译buildroot报错不支持SYSV IPC,导致fakeroot无法正常工作
  3. 华为认证证书有什么好处
  4. Vue 中使用高德地图api
  5. 【滤镜算法】低多边形风格介绍及Matlab实现
  6. python人工智能入门书籍推荐-有哪些关于人工智能的书籍可供推荐?
  7. 【Unity3D游戏开发】之游戏目录结构之最佳实践和优化 (十一)
  8. 录像机中码流类型中定时、事件、网传代表什么意思?
  9. 被svn版本控制的文件上显示红色叉号
  10. CTFSHOW 萌新赛 萌新记忆