本文内容整理自中国大学MOOC “北京大学-人工智能实践:Tensorflow笔记” 课程,部分内容可能在原笔记内容上进行了修改,转载请注明出处。
授课老师:曹健
中国大学MOOC 人工智能实践:Tensorflow笔记课程链接
本讲目标:理解神经网络计算过程,使用基于TF2原生代码搭建第一个神经网络训练模型
本节内容:介绍TensorFlow2.1 中的基本概念与部分常用函数,为后续章节编程部分提供基础
TensorFlow 中的Tensor 表示张量,是多维数组、多维列表,用阶表示张量的维数。
维数 | 阶 | 名字 | 例子 |
---|---|---|---|
0-D | 0 | 标量 scalar | s=1 2 3 |
1-D | 1 | 向量 vector | v=[1,2,3] |
2-D | 2 | 矩阵 matrix | m=[[1,2,3],[4,5,6],[7,8,9]] |
n-D | n | 张量 tensor | t=[[[···(n个括号) |
张量可以表示0 阶到 n 阶的数组。
高维数组可通过reshape
的方式得到。
eg:#输入: import numpy as np c = np.arange(24).reshape(2,4,3) print(c) 输出: [[[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]] [[12 13 14] [15 16 17] [18 19 20] [21 22 23]]]
TensorFlow 中数据类型包括 32 位整型(tf.int32
)、32 位浮点(tf.float32
)、64 位浮点(tf.float64
)、布尔型(tf.bool
)、字符串型(tf.string
)。
tf.constant(张量内容,dtype=数据类型(可选))
第一个参数表示张量内容,第二个参数表示张量的数据类型。
举例如下:
# 输入:
import tensorflow as tf
a=tf.constant([1,5],dtype=tf.int64)
print(a)
print(a.dtype)
print(a.shape)
输出:
tf.Tensor([1 5], shape=(2,), dtype=int64)
<dtype: 'int64'>
(2,)
说明:去掉 dtype 项,不同电脑环境不同导致默认值不同,可能导致后续程序 bug。
很多时候数据是由 numpy 格式给出的,此时可以通过如下函数将 numpy 格式化为 Tensor 格式:
tf. convert_to_tensor(数据名,dtype=数据类型(可选))
举例如下:
# 输入:
import tensorflow as tf
import numpy as np
a = np.arange(0, 5)
b = tf.convert_to_tensor( a, dtype=tf.int64 )
print(a)
print(b)
输出:
[0 1 2 3 4]
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
采用不同函数可以创建不同初始值的张量,如
~~ 创建全为0的张量 ~~ tf.zeros(维度)
~~ 创建全为1的张量 ~~ tf.ones(维度)
~~ 创建全为指定值的张量 ~~ tf.fill(维度,指定值)
其中维度参数部分,如一维则直接写个数,二维用[行,列]表示,多维用[n,m,j…]表示。
举例如下:
# 输入:
import tensorflow as tf
a = tf.zeros([2, 3])
b = tf.ones(4)
c = tf.fill([2, 2], 9)
print(a)
print(b)
print(c)
输出:
tf.Tensor(
[[0. 0. 0.]
[0. 0. 0.]], shape=(2, 3), dtype=float32)
tf.Tensor([1. 1. 1. 1.], shape=(4,), dtype=float32)
tf.Tensor(
[[9 9]
[9 9]], shape=(2, 2), dtype=int32)
实际使用中,经常会用到符合正态分布的随机数,与此相关的函数主要有:
输出正态分布的随机数,默认均值为0,标准差为1
tf. random.normal (维度,mean=均值,stddev=标准差)
输出截断式正态分布的随机数,默认均值为0,标准差为1
tf. random.truncated_normal (维度,mean=均值,stddev=标准差)
说明: tf. random.truncated_normal函数能使生成的随机数更集中一些,该函数如果随机生成数据的取值在(µ-2σ,µ+2σ)之外则重新进行生成,保证了生成值在均值附近。
其中µ为均值,σ为标准差,标准差的计算公式为
σ = ∑ i = 1 n ( x − x ˉ ) 2 n \sigma = \sqrt{\frac{\sum_{i=1}^n(x-\bar{x})^2}{n}} σ=n∑i=1n(x−xˉ)2
输出指定维度的均匀分布随机数
tf.random. uniform(维度,minval=最小值,maxval=最大值)
说明:最小、最大值是前闭后开区间。
举例如下:
# 输入:
import tensorflow as tf
d = tf.random.normal ([2, 2], mean=0.5, stddev=1)
print(d)
e = tf.random.truncated_normal ([2, 2], mean=0.5, stddev=1)
print(e)
f = tf.random.uniform([2, 2], minval=0, maxval=1)
print(f)
输出:
tf.Tensor(
[[ 0.8413248 -0.3154987 ]
[ 0.58313763 1.033226 ]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[ 1.5442729 -0.10547781]
[ 0.993157 0.3965847 ]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[0.74545753 0.29780662]
[0.8949863 0.6474674 ]], shape=(2, 2), dtype=float32)
利用 tf.cast (张量名,dtype=数据类型) 强制将 Tensor 转换为该数据类型;
利用 tf.reduce_min (张量名) 计算张量维度上元素的最小值;
利用 tf.reduce_max (张量名) 计算张量维度上元素的最大值。
举例如下:
# 输入:
import tensorflow as tf
x1 = tf.constant ([1., 2., 3.], dtype=tf.float64)
print(x1)
x2 = tf.cast (x1, tf.int32)
print(x2)
print (tf.reduce_min(x2), tf.reduce_max(x2))
输出:
tf.Tensor([1. 2. 3.], shape=(3,), dtype=float64)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor(1, shape=(), dtype=int32) tf.Tensor(3, shape=(), dtype=int32)
可用 tf.reduce_mean (张量名,axis=操作轴) 计算张量沿着指定维度的平均值;
可用 tf.reduce_sum (张量名,axis=操作轴) 计算张量沿着指定维度的和。
如不指定 axis,则表示对所有元素进行操作。
例如:在一个二维张量或数组中,可以通过调整axis等于 0 或 1 控制执行维度。
若axis=0代表跨行(经度,down),而axis=1代表跨列(纬度,across),如果不指定axis,则所有元素参与计算。
图9 二维数组中的维度定义
举例如下:
#输入:
import tensorflow as tf
x=tf.constant([[ 1, 2, 3],[ 2, 2, 3]])
print(x)
print(tf.reduce_mean( x ))
print(tf.reduce_sum( x, axis=1 ))
输出:
tf.Tensor(
[[1 2 3]
[2 2 3]], shape=(2, 3), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)
tf.Tensor([6 7], shape=(2,), dtype=int32)
tf.Variable(initial_value,trainable,validate_shape,name) 函数可以将变量标记为“可训练”的,被它标记了的变量,会在反向传播中记录自己的梯度信息。
神经网络训练中,常用该函数标记代训练参数。
其中initial_value
默认为 None,可以搭配 tensorflow 随机生成函数来初始化参数;
trainable
默认为 True,表示可以后期被算法优化的,如果不想该变量被优化,即改为 False;
validate_shape
默认为 True,形状不接受更改,如果需要更改 validate_shape = False;
name
默认为 None,默认命名为“Variable”并获取自动去重(Variable_1,Variable_2…)。
举例如下:
import tensorflow as tf
w = tf.Variable(tf.random.normal([2, 2], mean=0, stddev=1))
说明:上述代码表示首先随机生成正态分布随机数,再给生成的随机数标记为可训练,这样在反向传播中就可以通过梯度下降更新参数 w 了。
tensorflow中的数学函数之 四则运算:
利用 tf.add (张量 1,张量2) 实现两个张量的对应元素相加;
利用 tf.subtract (张量 1,张量 2) 实现两个张量的对应元素相减;
利用 tf.multiply (张量 1,张量 2) 实现两个张量的对应元素相乘;
利用 tf.divide (张量 1,张量 2) 实现两个张量的对应元素相除。
举例如下:
# 输入:
import tensorflow as tf
a = tf.ones([1, 3])
b = tf.fill([1, 3], 3.)
print(a)
print(b)
print(tf.add(a,b))
print(tf.subtract(a,b))
print(tf.multiply(a,b))
print(tf.divide(b,a))
输出:
tf.Tensor([[1. 1. 1.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
tf.Tensor([[4. 4. 4.]], shape=(1, 3), dtype=float32)
tf.Tensor([[-2. -2. -2.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
注意:只有维度相同的张量才可以做四则运算。
tensorflow中的数学函数之 平方、次方与开方:
利用 tf.square (张量名) 实现计算某个张量的平方;
利用 tf.pow (张量名,n 次方数) 实现计算某个张量的 n 次方;
利用 tf.sqrt (张量名) 实现计算某个张量的开方。
举例如下:
# 输入:
import tensorflow as tf
a = tf.fill([1, 2], 3.)
print(a)
print(tf.pow(a, 3))
print(tf.square(a))
print(tf.sqrt(a))
输出:
tf.Tensor([[3. 3.]], shape=(1, 2), dtype=float32)
tf.Tensor([[27. 27.]], shape=(1, 2), dtype=float32)
tf.Tensor([[9. 9.]], shape=(1, 2), dtype=float32)
tf.Tensor([[1.7320508 1.7320508]], shape=(1, 2), dtype=float32)
tensorflow中的数学函数之 矩阵乘法:
利用 tf.matmul(矩阵 1,矩阵 2) 实现两个矩阵的相乘。
举例如下:
# 输入:
import tensorflow as tf
a = tf.ones([3, 2])
b = tf.fill([2, 3], 3.)
print(tf.matmul(a, b))
输出:
tf.Tensor(
[[6. 6. 6.]
[6. 6. 6.]
[6. 6. 6.]], shape=(3, 3), dtype=float32)
利用 tf.data.Dataset.from_tensor_slices((输入特征, 标签)) 切分传入张量的第一维度,生成输入特征/标签对,构建数据集。
此函数对 Tensor 格式与 Numpy格式均适用,其切分的是 第一维度,表征数据集中数据的数量,之后切分 batch等操作都以第一维为基础。
举例如下:
# 输入:
import tensorflow as tf
features = tf.constant([12,23,10,17])
labels = tf.constant([0, 1, 1, 0])
dataset = tf.data.Dataset.from_tensor_slices((features, labels))
print(dataset)
for element in dataset:
print(element)
输出:
<TensorSliceDataset shapes: ((), ()), types: (tf.int32, tf.int32)>
(<tf.Tensor: shape=(), dtype=int32, numpy=12>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)
(<tf.Tensor: shape=(), dtype=int32, numpy=23>, <tf.Tensor: shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: shape=(), dtype=int32, numpy=10>, <tf.Tensor: shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: shape=(), dtype=int32, numpy=17>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)
利用 tf.GradientTape( ) 函数搭配 with 结构 计算损失函数在某一张量处的梯度 。
参考格式:
with tf.GradientTape( ) as tape: 若干个计算过程 grad = tape.gradient(函数,对谁求导)
举例如下:
# 输入:
import tensorflow as tf
with tf.GradientTape() as tape:
w = tf.Variable(tf.constant(3.0))
loss = tf.pow(w,2)
grad = tape.gradient(loss,w)
print(grad)
输出:
tf.Tensor(6.0, shape=(), dtype=float32)
说明:在上例中,损失函数为 w 2 w^2 w2, w w w当前取值为 3,故计算方式为 ∂ w 2 ∂ w = 2 w = 2 × 3.0 = 6.0 \frac{\partial{w^2}}{\partial{w}}=2w=2\times3.0=6.0 ∂w∂w2=2w=2×3.0=6.0。
利用 enumerate(列表名) 函数 遍历元素 。
enumerate是python的内建函数,它可遍历每个元素(如列表、元组或字符串),组合为:索引 元素
,常在 for 循环中使用。
举例如下:
# 输入:
import tensorflow as tf
seq = [one, two, three]
for i, element in enumerate(seq):
print(i, element)
输出:
0 one
1 two
2 three
利用 tf.one_hot(待转换数据,depth=几分类) 函数实现 用独热码表示标签。
独热编码(one-hot encoding):在分类问题中,常使用独热码做标签;
编辑类别:1表示是,0表示非。
例如在鸢尾花分类任务中,如果标签是 1,表示分类结果是 1 杂色鸢尾,其用把它用独热码表示就是
(0. 1. 0.)
,这样可以表示出每个分类的概率:也就是百分之 0 的可能是 0狗尾草鸢尾,百分百的可能是 1 杂色鸢尾,百分之 0 的可能是弗吉尼亚鸢尾。
举例如下:
# 输入:
import tensorflow as tf
classes = 3
labels = tf.constant([1,0,2])#输入元素值最小为0,最大为2
output = tf.one_hot( labels, depth=classes )
print(output)
输出:
tf.Tensor(
[[0. 1. 0.]
[1. 0. 0.]
[0. 0. 1.]], shape=(3, 3), dtype=float32)
注意:索引从 0 开始,待转换数据中各元素值应小于depth,若带转换元素值大于等于depth,则该元素输出编码为 [0, 0 … 0, 0]。
即 depth 确定列数,待转换元素的个数确定行数。
举例如下:# 输入: import tensorflow as tf classes = 3 labels = tf.constant([1,4,2]) #输入的元素值 4 超出depth-1 output = tf.one_hot(labels,depth=classes) print(output) 输出: tf.Tensor( [[0. 1. 0.] [0. 0. 0.] [0. 0. 1.]], shape=(3, 3), dtype=float32)
利用 tf.nn.softmax( ) 函数 使前向传播的输出值符合概率分布,进而与独热码形式的标签作比较。
即当n分类的n个输出 ( y 0 , y 1 , ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ , y n − 1 ) (y_0,y_1,······,y_{n-1}) (y0,y1,⋅⋅⋅⋅⋅⋅,yn−1) 通过softmax( ) 函数便符合概率分布:
∀ x P ( X = x ) ∈ [ 0 , 1 ] 且 ∑ x P ( X = x ) = 1 \boxed{\forall{x} ~~ P(X=x)\in[0,1] 且 \displaystyle\sum_{x} P(X=x)=1} ∀x P(X=x)∈[0,1]且x∑P(X=x)=1
其计算公式为:
S o f t m a x ( y i ) = e y i ∑ j = 0 n e y i Softmax(y_i)=\frac{e^{y_i}}{\sum_{j=0}^ne^{y_i}} Softmax(yi)=∑j=0neyieyi
公式中的 e y i e^{y_i} eyi是前向传播的输出。
例如,在之前的例子中(见上节 3.2.2),某组鸢尾花数据前向传播的输出值为 [ 1.01 , 2.01 , -0.66 ],通过上述计算公式,可计算对应的概率值:
{ e y 0 e y 0 + e y 1 + e y 2 = 2.75 10.73 = 0.256 e y 1 e y 0 + e y 1 + e y 2 = 7.46 10.73 = 0.695 e y 2 e y 0 + e y 1 + e y 2 = 0.52 10.73 = 0.048 \left\{\begin{array}{l} \frac{e^{y_{0}}}{e^{y_{0}}+e^{y_{1}}+e^{y_{2}}}=\frac{2.75}{10.73}=0.256 \\ \frac{e^{y_{1}}}{e^{y_{0}}+e^{y_{1}}+e^{y_{2}}}=\frac{7.46}{10.73}=0.695 \\ \frac{e^{y_{2}}}{e^{y_{0}}+e^{y_{1}}+e^{y_{2}}}=\frac{0.52}{10.73}=0.048 \end{array}\right. ⎩ ⎨ ⎧ey0+ey1+ey2ey0=10.732.75=0.256ey0+ey1+ey2ey1=10.737.46=0.695ey0+ey1+ey2ey2=10.730.52=0.048
上式中,0.256 表示为 0 类鸢尾的概率是 25.6%,0.695 表示为 1 类鸢尾的概率是69.5%,0.048 表示为 2 类鸢尾的概率是 4.8%,概率论总和为1。
举例如下:
# 输入:
import tensorflow as tf
y = tf.constant ( [1.01, 2.01, -0.66] )
y_pro = tf.nn.softmax(y)
print("After softmax, y_pro is:", y_pro)
输出:
After softmax, y_pro is: tf.Tensor([0.25598174 0.69583046 0.04818781], shape=(3,), dtype=float32)
assign系列函数可用于对参数w实现自更新并返回,其中:
使用 w.assign(value, validate_shape=True, use_locking=None, name=None) 函数将 value 赋值给 w,即 w = value;
使用 w.assign_add(value, use_locking=None, nam=None) 函数将值 value 加到变量 w 上,即 w = w + value;
使用 w.assign_sub(value, use_locking=None, name=None) 函数变量 w 减去 value值,即 w = w - value;
函数中的其他参数含义如下:
validate_shape
:默认 True,表示值 shape 需要与变量 shape 一致;若设为 False,则值 shape 覆盖变量 shape;
use_locking
:默认 False,若为 True,则受锁保护;
name
:变量名称。
注意:调用assign系列函数前,必须先利用 tf.Variable定义变量 w 为可训练(可自更新)。
举例如下:
# 输入:
import tensorflow as tf
x = tf.Variable(1)
print(x)
x.assign(2)
print(x)
x.assign_add(3)
print(x)
x.assign_sub(4)
print(x)
输出:
<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=1>
<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=2>
<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=5>
<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=1>
注意:该组函数是以
可训练参数.成员函数
的方式调用的,而并非 "tf.assign\tf.assign_add\tf.assign_sub"形式,但这种形式在tensorflow早期版本中曾有过支持。
利用 tf.argmax (张量名,axis=操作轴) 返回张量沿指定维度最大值的索引。
其中axis参数含义与本节 2.2 中的axis保持一致。
举例如下:
# 输入:
import tensorflow as tf
import numpy as np
test = np.array([[1, 2, 3], [2, 3, 4], [5, 4, 3], [8, 7, 2]])
print(test)
print( tf.argmax (test, axis=0)) # 返回每一列(经度)最大值的索引
print( tf.argmax (test, axis=1)) # 返回每一行(纬度)最大值的索引
输出:
tf.where函数有两种不同的用法:
- tf.where(tensor)
其中 tensor 为一个bool 型张量,where函数将返回其中为true的元素的索引。- tf.where(tensor,a,b)
其中 a,b 为和 tensor 相同维度的张量,where函数将 tensor 中的 true 位置元素替换为a中对应位置元素,false 位置元素替换为b中对应位置元素后返回。
举例如下:
# 输入:
import tensorflow as tf
a=tf.constant([[1,0],[0,1]],dtype=tf.int32)
b=tf.cast(a,tf.bool)
print("b:",b)
print( tf.where(b) )
c=tf.constant([1,2,3,1,1])
d=tf.constant([0,1,3,4,5])
print( tf.where(tf.greater(c,d),c,d) )
输出:
b: tf.Tensor(
[[ True False]
[False True]], shape=(2, 2), dtype=bool)
tf.Tensor(
[[0 0]
[1 1]], shape=(2, 2), dtype=int64)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)
说明:以上代码展示了tf.where函数两种不同的用法。
- 在第一组代码中 tf.where 函数的输入为逻辑数组 [[True False] [False True]],函数输出[[0 0] [1 1]]代表逻辑数组中真值坐标,即(0,0)和(1,1);
- 在第一组代码中,使用了通过比较相同位置元素大小产生逻辑数组的 tf.greater 函数,整行代码的效果为:若a>b ,返回a对应位置的元素,否则 对应位置的元素,否则返回b对应位置的元素。
该函数属于 numpy 库;
函数 np.random.RandomState.rand() 作用为返回一个[0,1)之间的随机数;
使用 np.random.RandomState.rand(维度) 可控制返回数组的维度,当维度为空时,返回标量;
使用相同的随机数种子可控制每次生成随机数相同;
举例如下:
# 输入:
import numpy as np
rdm=np.random.RandomState(seed=1) #seed=常数使程序每次生成的随机数相同
a=rdm.rand() # 返回一个随机标量 返回一个随机标量
b=rdm.rand(2,3) # 返回维度为2 行3列随机数矩阵 列随机数矩阵
print("a:",a)
print("b:",b)
输出:
a: 0.417022004702574
b: [[7.20324493e-01 1.14374817e-04 3.02332573e-01]
[1.46755891e-01 9.23385948e-02 1.86260211e-01]]
可用 np.vstack( 数组1 ,数组2) 将两个数组按垂直方向叠加;
举例如下:
#输入:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.vstack((a, b))
print("c:\n", c)
输出:
c:
[[1 2 3]
[4 5 6]]
np.mgrid[ ] 用于 生成一个n维立方体并以一定形式返回该n维立方体的顶点坐标。
函数输入格式:
np.mgrid[ 起始值:结束值:步长 , 起始值:结束值:步长 , … ]
其中输入时的步长为复数表示点数,区间左闭右闭;步长为实数表示间隔,区间左闭右开。
函数输出为n个矩阵:
第1个矩阵为第1维数据在最终结构中的分布,
第2个矩阵为第2维数据在最终结构中的分布,
······
第n个矩阵以此类推。
x.ravel( ) 可以将x拉成一维数组,即“把
.
前的变量拉直”。
np.c_[ ] 可以使返回的间隔数值点配对,其调用格式为:
np.c_[ 数组1 ,数组2, , … ]
举例如下:
#输入:
import numpy as np
import tensorflow as tf
# 生成等间隔数值点
x, y = np.mgrid[1:3:1, 2:4:0.5]
# 将x, y拉直,并合并配对为二维张量,生成二维坐标点
grid = np.c_[x.ravel(), y.ravel()]
print("x:\n", x)
print("y:\n", y)
print("x.ravel():\n", x.ravel())
print("y.ravel():\n", y.ravel())
print('grid:\n', grid)
输出:
x:
[[1. 1. 1. 1.]
[2. 2. 2. 2.]]
y:
[[2. 2.5 3. 3.5]
[2. 2.5 3. 3.5]]
x.ravel():
[1. 1. 1. 1. 2. 2. 2. 2.]
y.ravel():
[2. 2.5 3. 3.5 2. 2.5 3. 3.5]
grid:
[[1. 2. ]
[1. 2.5]
[1. 3. ]
[1. 3.5]
[2. 2. ]
[2. 2.5]
[2. 3. ]
[2. 3.5]]
说明:此处主要解释上述代码中较难理解的np.mgrid[ ]函数。
此时函数输入np.mgrid[1:3:1
,2:4:0.5
]中共有两对参数,即1:3:1
和2:4:0.5
,故函数将生成一个二维网格。
其中参数1:3:1
按照1为起始值,3为结束值,1为步长,步长为实数区间左闭右开,故生成数列[1,2]
,数列长度为2;
同理参数2:4:0.5
生成数列[2 ,2.5 ,3 ,3.5]
,数列长度为4;
函数将生成如下图所示的二维网格:图10 函数np.mgrid[1:3:1,2:4:0.5]所生成的二维网格
上图中的二维网格共有8个顶点,坐标分别为(1,2),(1,2.5),(1,3),(1,3.5),(1,4),(2,2),(2,2.5),(2,3),(2,3.5),(2,4)。
其横坐标构成数组[ [1,1,1,1] [2,2,2,2]],其横坐标构成数组[ [2,2.5,3,3.5] [2,2.5,3,3.5]],这即是np.mgrid[ ]函数返回的x、y数组。
上一节介绍了神经网络相关的基本概念。
人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-1)
下一节我们将借助Tensorflow库中的底层代码自己动手搭建一个最基础的神经网络并对代码进行初步优化。
人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-3)