【北京大学】Tensorflow1笔记
6.1 实现把任意图片放进训练好的网络进行测试
输入的图片是白底黑字的数字图片进行测试,测试前需要做两步 (1)转换图片矩阵大小为28*28符合网络的输入 (2)把图片的转换成白字黑底的黑白图片
√网络输入:一维数组(784 个像素点)
√像素点:0-1 之间的浮点数(接近 0 越黑,接近 1 越白) √网络输出:一维数组(十个可能性概率),数组中最大的那个元素所对应的索 引号就是预测的结果。 √关键处理: def application(): testNum = input(“input the number of test pictures:”) for i in range(testNum): testPic = raw_input(“the path of test picture:”) testPicArr = pre_pic(testPic) preValue = restore_model(testPicArr) print “The prediction number is:”, preValue 注解: 任务分成两个函数完成 1)testPicArr = pre_pic(testPic)对手写数字图片做预处理 2)preValue = restore_model(testPicArr) 将符合神经网络输入要求的图片喂 给复现的神经网络模型,输出预测值
√具体代码:fc3
与fc2前向后向传播测试一样 mnist_app.py 倒着看代码
import tensorflow as tf
import numpy as np
from PIL import Image
import mnist_backward
import mnist_forward
def restore_model(testPicArr):
with tf.Graph().as_default() as tg:
x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
y = mnist_forward.forward(x, None)
preValue = tf.argmax(y, 1)
variable_averages = tf.train.ExponentialMovingAverage(mnist_backward.MOVING_AVERAGE_DECAY)
variables_to_restore = variable_averages.variables_to_restore()
saver = tf.train.Saver(variables_to_restore)
with tf.Session() as sess:
ckpt = tf.train.get_checkpoint_state(mnist_backward.MODEL_SAVE_PATH)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
preValue = sess.run(preValue, feed_dict={x: testPicArr})
return preValue
else:
print("No checkpoint file found")
return -1
def pre_pic(picName):
img = Image.open(picName)
reIm = img.resize((28, 28), Image.ANTIALIAS)
#把图片转换为灰度值图片
im_arr = np.array(reIm.convert('L'))
threshold = 50 %动态调整
for i in range(28):
for j in range(28):
im_arr[i][j] = 255 - im_arr[i][j]
if (im_arr[i][j] < threshold):
im_arr[i][j] = 0
else:
im_arr[i][j] = 255
nm_arr = im_arr.reshape([1, 784])
nm_arr = nm_arr.astype(np.float32)
img_ready = np.multiply(nm_arr, 1.0 / 255.0)
return img_ready
def application():
testNum = int(input("input the number of test pictures:"))
for i in range(testNum):
testPic = input("the path of test picture:")
testPicArr = pre_pic(testPic)
preValue = restore_model(testPicArr)
print("The prediction number is:", preValue)
def main():
application()
if __name__ == '__main__':
main()
报错from PIL import Image ImportError: No module named PIL
pip install Pillow
运行后 输入 10(表示循环验证十张图片) the path of test picture:pic/0.png 2021-07-29 20:58:47.023986: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 The prediction number is: [0]
6. 2 自定义制作数据集
关键代码解析 1)数据集生成读取文件(mnist_generateds.py)
tfrecords 文件
1)tfrecords:是一种二进制文件,可先将图片和标签制作成该格式的文件。
2)tf.train.Example: 用来存储训练数据。训练数据的特征用键值对的形式表示。 如:‘ img_raw ’ :值 ‘ label ’ :值 值是 Byteslist/FloatList/Int64List #img_raw是原始图片,值可以是字符串 实数列表 正数列表
3)SerializeToString( ):把数据序列化成字符串存储。 √生成 tfrecords 文件 具体代码: #生成 tfrecords 文件
解析生成 tfrecords 文件 的图片和标签
filename_queue = tf.train.string_input_producer([tfRecord_path], shuffle=True)
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(serialized_example,
features={
'label': tf.FixedLenFeature([10], tf.int64),
'img_raw': tf.FixedLenFeature([], tf.string)
})
img = tf.decode_raw(features['img_raw'], tf.uint8)
img.set_shape([784])
img = tf.cast(img, tf.float32) * (1. / 255)
label = tf.cast(features['label'], tf.float32)
注解: 1)==filename_queue = tf.train.string_input_producer([tfRecord_path]) ==
tf.train.string_input_producer( string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)
该函数会生成一个先入先出的队列,文件阅读器会使用它来读取数据。 参数说明:string_tensor: 存储图像和标签信息的 TFRecord 文件名列表 num_epochs: 循环读取的轮数(可选) shuffle:布尔值(可选),如果为 True,则在每轮随机打乱读取顺序 seed:随机读取时设置的种子(可选) capacity:设置队列容量 shared_name:(可选) 如果设置,该队列将在多个会话中以给定名 称共享。所有具有此队列的设备都可以通过 shared_name 访问它。在分布式设置 中使用这种方法意味着每个名称只能被访问此操作的其中一个会话看到。 name:操作的名称(可选) cancel_op:取消队列(None)
2)reader = tf.TFRecordReader() #新建一个 reader
3)_, serialized_example = reader.read(filename_queue) features = tf.parse_single_example(serialized_example,features={ ‘img_raw’: tf.FixedLenFeature([ ], tf.string) , ‘label’: tf.FixedLenFeature([10], tf.int64)}) #把读出的每个样本保存在 serialized_example 中进行解序列化,标签和图片的键名应该和制作 tfrecords 的键名相同,其中标签给出几分类。
tf.parse_single_example(serialized, features, name=None, example_names=None) 该函数可以将 tf.train.Example 协议内存块(protocol buffer)解析为张量。 参数说明:serialized: 一个标量字符串张量 features: 一个字典映射功能键 FixedLenFeature 或 VarLenFeature 值,也就是在协议内存块中储存的 name:操作的名称(可选) example_names: 标量字符串联的名称(可选)
4)img = tf.decode_raw(features[‘img_raw’], tf.uint8) #将 img_raw 字符串转换为 8 位无符号整型 5)img.set_shape([784]) #将形状变为一行 784 列 6)img = tf.cast(img, tf.float32) * (1. / 255) #变成 0 到 1 之间的浮点数 7)label = tf.cast(features[‘label’], tf.float32)#把标签列表变为浮点数 8)return image,label #返回图片和标签(跳回到 get_tfrecord)
生成自定义数据的完整代码 fc4
mnist_generateds.py文件
import tensorflow as tf
import numpy as np
from PIL import Image
import os
image_train_path = './mnist_data_jpg/mnist_train_jpg_60000/'
label_train_path = './mnist_data_jpg/mnist_train_jpg_60000.txt'
tfRecord_train = './data/mnist_train.tfrecords'
image_test_path = './mnist_data_jpg/mnist_test_jpg_10000/'
label_test_path = './mnist_data_jpg/mnist_test_jpg_10000.txt'
tfRecord_test = './data/mnist_test.tfrecords'
data_path = './data'
resize_height = 28
resize_width = 28
def write_tfRecord(tfRecordName, image_path, label_path):
writer = tf.python_io.TFRecordWriter(tfRecordName)
num_pic = 0
f = open(label_path, 'r')
contents = f.readlines()
f.close()
for content in contents:
value = content.split()
img_path = image_path + value[0]
img = Image.open(img_path)
img_raw = img.tobytes()
labels = [0] * 10
labels[int(value[1])] = 1
example = tf.train.Example(features=tf.train.Features(feature={
'img_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
'label': tf.train.Feature(int64_list=tf.train.Int64List(value=labels))
}))
writer.write(example.SerializeToString())
num_pic += 1
print("the number of picture:", num_pic)
writer.close()
print("write tfrecord successful")
def generate_tfRecord():
isExists = os.path.exists(data_path)
if not isExists:
os.makedirs(data_path)
print('The directory was created successfully')
else:
print('directory already exists')
write_tfRecord(tfRecord_train, image_train_path, label_train_path)
write_tfRecord(tfRecord_test, image_test_path, label_test_path)
def read_tfRecord(tfRecord_path):
filename_queue = tf.train.string_input_producer([tfRecord_path], shuffle=True)
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(serialized_example,
features={
'label': tf.FixedLenFeature([10], tf.int64),
'img_raw': tf.FixedLenFeature([], tf.string)
})
img = tf.decode_raw(features['img_raw'], tf.uint8)
img.set_shape([784])
img = tf.cast(img, tf.float32) * (1. / 255)
label = tf.cast(features['label'], tf.float32)
return img, label
def get_tfrecord(num, isTrain=True):
if isTrain:
tfRecord_path = tfRecord_train
else:
tfRecord_path = tfRecord_test
img, label = read_tfRecord(tfRecord_path)
img_batch, label_batch = tf.train.shuffle_batch([img, label],
batch_size=num,
num_threads=2,
capacity=1000,
min_after_dequeue=700)
return img_batch, label_batch
def main():
generate_tfRecord()
if __name__ == '__main__':
main()
其中解析tfrecords文件函数 tf.train.shuffle_batch( tensors, batch_size, capacity, min_after_dequeue, num_threads=1, seed=None, enqueue_many=False, shapes=None, allow_smaller_final_batch=False, shared_name=None, name=None) 这个函数随机读取一个 batch 的数据。 参数说明:tensors: 待乱序处理的列表中的样本(图像和标签) batch_size: 从队列中提取的新批量大小 capacity:队列中元素的最大数量 min_after_dequeue: 出队后队列中的最小数量元素,用于确保元素 的混合级别 num_threads: 排列 tensors 的线程数 seed:用于队列内的随机洗牌 enqueue_many: tensor 中的每个张量是否是一个例子 shapes: 每个示例的形状 allow_smaller_final_batch: (可选)布尔值。 如果为 True,则在 队列中剩余数量不足时允许最终批次更小。 shared_name:(可选)如果设置,该队列将在多个会话中以给定名称 共享。 name:操作的名称(可选) 10)return img_batch,label_batch #返回的图片和标签为随机抽取的 batch_size 组
mnist_backward.py(反向传播文件修改图片标签获取的接口)
关键操作:利用多线程 提高图片和标签的批获取效率
方法:将批获取的操作放到线程协调器开启和关闭之间
==开启线程协调器: coord = tf.train.Coordinator( ) threads = tf.train.start_queue_runners(sess=sess, coord=coord) 关闭线程协调器: coord.request_stop( ) coord.join(threads) ==
注解: tf.train.start_queue_runners( sess=None, coord=None, daemon=True, start=True, collection=tf.GraphKeys.QUEUE_RUNNERS) 这个函数将会启动输入队列的线程,填充训练样本到队列中,以便出队操作可以从队列中拿到样本。这种情况下最好配合使用一个 tf.train.Coordinator ,这样可以在发生错误的情况下正确地关闭这些线程。 参数说明:sess:用于运行队列操作的会话。 默认为默认会话。 coord:可选协调器,用于协调启动的线程。 daemon: 守护进程,线程是否应该标记为守护进程,这意味着它们不 会阻止程序退出。 start:设置为 False 只创建线程,不启动它们。 collection:指定图集合以获取启动队列的 GraphKey。默认为 GraphKeys.QUEUE_RUNNERS。 √具体对比反向传播中的 fc4 与 fc3 代码 线程协调器的代码是用################################################括起来的
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_forward
import os
import mnist_generateds
BATCH_SIZE = 200
LEARNING_RATE_BASE = 0.1
LEARNING_RATE_DECAY = 0.99
REGULARIZER = 0.0001
STEPS = 50000
MOVING_AVERAGE_DECAY = 0.99
MODEL_SAVE_PATH = "./model/"
MODEL_NAME = "mnist_model"
train_num_examples = 60000
def backward():
x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])
y = mnist_forward.forward(x, REGULARIZER)
global_step = tf.Variable(0, trainable=False)
ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
cem = tf.reduce_mean(ce)
loss = cem + tf.add_n(tf.get_collection('losses'))
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
train_num_examples / BATCH_SIZE,
LEARNING_RATE_DECAY,
staircase=True)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
ema_op = ema.apply(tf.trainable_variables())
with tf.control_dependencies([train_step, ema_op]):
train_op = tf.no_op(name='train')
saver = tf.train.Saver()
img_batch, label_batch = mnist_generateds.get_tfrecord(BATCH_SIZE, isTrain=True)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
for i in range(STEPS):
xs, ys = sess.run([img_batch, label_batch])
_, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
if i % 1000 == 0:
print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)
coord.request_stop()
coord.join(threads)
def main():
backward()
if __name__ == '__main__':
main()
mnist_test.py文件
线程协调器的代码是用################################################括起来的 手动给出样本量 ################################################ # 用函数get_tfrecord替换读取所有测试集1万张图片 img_batch, label_batch = mnist_generateds.get_tfrecord(TEST_NUM, isTrain=False) # 2 ################################################ pic文件是测试数据集 ———————————————— 原文链接:https://blog.csdn.net/weixin_43935696/article/details/111480327
7.1 CNN:有效提取图片特征
全连接神经网络是:每个神经元与前后相邻层的每一个神经元都有连接关系,输入是 特征,输出为预测的结果。 参数个数:∑ (前层 神经元个数× 后层神经元个数 + 后层)=Σ(w+b)
现实生活中高分辨率的彩色图像,像素点更多,且为红绿蓝三通道信息。 待优化的参数过多,容易导致模型过拟合。为避免这种现象,实际应用中一 般不会将原始图片直接喂入全连接网络。 √在实际应用中,会先对原始图像进行特征提取,把提取到的特征喂给全连接 网络,再让全连接网络计算出分类评估值。
5x5x1 的灰度图片,1 表示单通道,5x5 表示分辨率,共有 5 行 5列个灰度值。若用一个 3x3x1 的卷积核对此 5x5x1 的灰度图片进行卷积,偏置项b=1,则求卷积的计算是:(-1)x1+0x0+1x2+(-1)x5+0x4+1x2+(-1)x3+0x4+1x5+1=1(注 意不要忘记加偏置 1)。 输出图片边长=(输入图片边长–卷积核长+1)/步长,此图为:(5 – 3 + 1)/ 1 = 3,输出图片是 3x3 的分辨率,用了 1 个卷积核,输出深度是 1,最后输出的是3x3x1 的图片。
√全零填充 Padding 有时会在输入图片周围进行全零填充,这样可以保证输出图片的尺寸和输入图片一致 如果用全零填充,也就是 padding=SAME。如果不用全零填充,也就是 padding=VALID。
在TensorFlow框架中,用参数padding ='SAME’或padding = ‘VALID’ 表示
tf.nn.conv2d(输入描述,eg,[batch,5,5,1]
#5 5表示每张图片的分辨率大小5行5列,1表示通道数,如果是灰度图示1,如果是彩色图,有红绿蓝三种颜色,就是3通道
卷积核描述, eg.[3,3,1,16]
#3 3 行列分辨率 ,1,通道数,16核个数
核滑动步长,eg.[1,1,1,1]
#第一四个元素固定为1;中间表示行列步长
padding= 'VALID'
)
对多通道的图片求卷积
多数情况下,输入的图片是 RGB 三个颜色组成的彩色图,输入的图片包含了 红、绿、蓝三层数据,卷积核的深度应该等于输入图片的通道数,所以使用 3x3x3 的卷积核,最后一个 3 表示匹配输入图像的 3 个通道,这样这个卷积核有三层, 每层会随机生成 9 个待优化的参数,一共有 27 个待优化参数 w 和一个偏置 b。
池化 Pooling
池化用于减少特征数量;最大值池化可提取图片纹理,均值池化可保留背景特征 TensorFlow实现
pool =tf.nn.max_pool(输入描述,eg.[batch,28,28,6])
pool = tf.nn.avg_pool
舍弃Dropout
在神经网络训练的过程汇总,将一部分神经元按照一定概率从神经网络中暂时舍弃,使用时被舍弃的神经元恢复连接,减少过拟合,加快训练速度 TensorFlow实现
tf.nn.dropout(上层输出,暂时舍弃的概率) if train: 输出 = tf.nn.dropout(上层输出,暂时舍弃的概率)
卷积神经网络可以认为由两部分组成,一部分是对输入图片进行特征提取,另一部分就是全连接网络,只不过喂入全连接网络的不再是原始图片,而是经过若干次卷积、激活和池化后的特征信息。 卷积神经网络从诞生到现在,已经出现了许多经典网络结构,比如 Lenet-5、 Alenet、VGGNet、GoogleNet 和 ResNet 等。每一种网络结构都是以卷积、激活、池化、全连接这四种操作为基础进行扩展。 Lenet5实现代码 Mnist 数据集中图片大小为 28281 的灰度图片,而 Lenet 神经网络的输入为 32321,故需要对 Lenet 神经网络进行微调。
Lenet 5神经网络
在 Mnist 数据集上的实现,主要分为三个部分:前向传播过程(mnist_lenet5_forward.py)、反向传播过程(mnist_lenet5_backword.py)、测试过程(mnist_lenet5_test.py)。
1)mnist_lenet5_forward.py
import tensorflow as tf #每张图片分辨率为28*28 IMAGE_SIZE = 28 #Mnist数据集为灰度图,故输入图片通道数NUM_CHANNELS取值为1 NUM_CHANNELS = 1 #第一层卷积核大小为5 CONV1_SIZE = 5 #卷积核个数为32 CONV1_KERNEL_NUM = 32 #第二层卷积核大小为5 CONV2_SIZE = 5 #卷积核个数为64 CONV2_KERNEL_NUM = 64 #全连接层第一层为 512 个神经元 FC_SIZE = 512 #全连接层第二层为 10 个神经元 OUTPUT_NODE = 10 #权重w计算 def get_weight(shape, regularizer): w = tf.Variable(tf.truncated_normal(shape, stddev=0.1)) if regularizer != None: tf.add_to_collection(‘losses’, tf.contrib.layers.l2_regularizer(regularizer)(w)) return w #偏置b计算 def get_bias(shape): b = tf.Variable(tf.zeros(shape)) return b
#卷积层计算
def conv2d(x, w): return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding=‘SAME’) #w是卷积核描述,padding='SAME’使用零填充
#最大池化层计算 def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=‘SAME’) #池化核大小为 2*2,垂直方向步长为 1,水平方向步长为 1,填充方式为全零填充
#定义前向传播过程 def forward(x, train, regularizer): # 1实现第一层卷积 核体积551 核个数32 #shape=[CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_KERNEL_NUM]=[5,5,1,32] conv1_w = get_weight([CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_KERNEL_NUM], regularizer) conv1_b = get_bias([CONV1_KERNEL_NUM]) conv1 = conv2d(x, conv1_w) # 非线性激活 relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b)) tf.nn.relu()用来实现非线性激活,相比 sigmoid 和 tanh 函数,relu 函数可以实现快速的收敛。 v # 最大池化 pool1 = max_pool_2x2(relu1)
#2 实现第二层卷积 5,5,32,64 第二层卷积核的个数=第一层卷积核的个数 conv2_w = get_weight([CONV2_SIZE, CONV2_SIZE, CONV1_KERNEL_NUM, CONV2_KERNEL_NUM], regularizer) conv2_b = get_bias([CONV2_KERNEL_NUM]) conv2 = conv2d(pool1, conv2_w) relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_b)) pool2 = max_pool_2x2(relu2)
#3 将第二层池化层的输出 pool2 矩阵转化为全连接层的输入格式即向量形式 #三维张量变为二维张量 7* 7* 64变为 二维[1,7* 7*64] # 获取一个张量的维度 pool_shape = pool2.get_shape().as_list() #根据.get_shape()函数得到 pool2 输出矩阵的维度 # pool_shape[1] 为长 pool_shape[2] 为宽 pool_shape[3]为高 nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
#得到矩阵被拉长后的长度,pool_shape[0]为batch值 reshaped = tf.reshape(pool2, [pool_shape[0], nodes])
#4 实现第三层全连接层 #初始化全连接层的权重,并加入正则化。 fc1_w = get_weight([nodes, FC_SIZE], regularizer) fc1_b = get_bias([FC_SIZE]) fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w) + fc1_b) # 如果是训练阶段,则对该层输出使用50%dropout #即随机的将该层输出中的一半神经元置为无效,是为了避免过拟合而设置的,一般只在全连接层中使用。 if train: fc1 = tf.nn.dropout(fc1, 0.5)
# 5 实现第四层全连接层
fc2_w = get_weight([FC_SIZE, OUTPUT_NODE], regularizer)
fc2_b = get_bias([OUTPUT_NODE])
y = tf.matmul(fc1, fc2_w) + fc2_b
return y
get_shape 函数用于获取一个张量的维度,并且输出张量每个维度上面的值。 例如: A = tf.random_normal(shape=[3,4]) print A.get_shape() 输出结果为:(3,4)
2)mnist_lenet5_backward.py文件
#卷积输入为四阶张量 #第一阶表示每轮喂入的图片数量,第二阶和第三阶分别表示图片的行分辨率和列分辨率,第四阶表示通道数 x = tf.placeholder(tf.float32, [ BATCH_SIZE, mnist_lenet5_forward.IMAGE_SIZE, mnist_lenet5_forward.IMAGE_SIZE, mnist_lenet5_forward.NUM_CHANNELS]) y_ = tf.placeholder(tf.float32, [None, mnist_lenet5_forward.OUTPUT_NODE])
———————————————— # 读取一个batch数据,.reshape将输入数据xs转成与网络输入相同形状的矩阵 xs, ys = mnist.train.next_batch(BATCH_SIZE) reshaped_xs = np.reshape(xs, ( BATCH_SIZE, mnist_lenet5_forward.IMAGE_SIZE, mnist_lenet5_forward.IMAGE_SIZE, mnist_lenet5_forward.NUM_CHANNELS)) # 读取一个batch数据,将输入数据xs转成与网络输入相同形状的矩阵 , loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: reshaped_xs, y: ys}) if i % 100 == 0: print(“After %d training step(s), loss on training batch is %g.” % (step, loss_value)) saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step) def main(): ————————————————
3)mnist_lenet5_test.py文件,也对x形状进行改变,不使用dropout
#训练好的网络,故不使用 dropout y = mnist_lenet5_forward.forward(x,False,None)
|