前言

因为学习TensorFlow的内容较多,如果只看API会很无聊,可以结合实例去学习。但是在构建基本的模型之前,需要学一些准备知识:数据读取、预处理、优化器、损失函数

国际惯例,参考网址:

TensorFlow中文社区

TensorFlow官方文档

如何选择优化器 optimizer

TensorFlow-Examples

TensorFlow中的Neural Network

TensorFlow中的Train

深入浅出Tensorflow(三):训练神经网络模型的常用方法

理解tf.train.slice_input_producer()和tf.train.batch()

此外,为了模拟现实数据集,这里将mnist手写数字数据集转换成图片格式存储,放一下网盘链接:链接:https://pan.baidu.com/s/1ugEy85182vjcXQ8VoMJAbg 密码:1o83

处理数据集

从文本文件读取图像数据

按照caffe的读取习惯,把图片的路径和标签全部用txt文本文件存储起来,即路径\图片名 标签的格式:

./mnist/train/5/5_1.png 5
./mnist/train/0/0_2.png 0
./mnist/train/4/4_3.png 4
./mnist/train/1/1_4.png 1

然后参考TensorFlow-Examples中的build_an_image_dataset 方法按照文本文件中存储的路径和标签去按行读取

imagepaths, labels = list(), list()
data = open(dataset_path, 'r').read().splitlines()
for d in data:imagepaths.append(d.split(' ')[0])labels.append(int(d.split(' ')[1]))

在TF中打乱数据以及分批

因为我们要用tensorflow处理数据,所以必须将这些文本转换成tensorflow能处理的形式

 # 转换为张量
imagepaths = tf.convert_to_tensor(imagepaths, dtype=tf.string)
labels = tf.convert_to_tensor(labels, dtype=tf.int32)

打乱数据

# 建立TF队列,打乱数据
image, label = tf.train.slice_input_producer([imagepaths, labels],shuffle=True)

这里看一下这个slice_input_producer函数:

tf.train.slice_input_producer(tensor_list,num_epochs=None,shuffle=True,seed=None,capacity=32,shared_name=None,name=None
)
  • 作用:为tensor_list中的每个Tensor建立切片
  • 部分参数:
    tensor_listTensor形式的列表,第一个维度必须相同
    num_epochs:每个切片创建多少次
    shuffle:打乱顺序

所以在打乱顺序的时候将此函数的shuffle参数设置成True即可

图片读取和处理

打乱数据路径以后,就可以读取了,tensorflow也提供了图像处理相关函数,这里列部分有用的:

  • 调整图像亮度:

    tf.image.adjust_brightness(image,delta
    )
  • 调整图像对比度:

    tf.image.adjust_contrast(images,contrast_factor
    )
  • 伽马校正:

    tf.image.adjust_gamma(image,gamma=1,gain=1
    )
  • 从中间向边缘裁剪图像:

    tf.image.central_crop(image,central_fraction
    )
  • 裁剪图片,分别是左上角纵坐标、横坐标,裁剪的高度、宽度:

    tf.image.crop_to_bounding_box(image,offset_height,offset_width,target_height,target_width
    )
  • 图像解码:将对应图像解码为uint8张量(还有对应编码的encode函数):

    1. tf.image.decode_bmp
    2. tf.image.decode_gif
    3. tf.image.decode_image
    4. tf.image.decode_jpeg
    5. tf.image.decode_png
  • 图像翻转:

    1. tf.image.flip_left_right左右翻转
    2. tf.image.flip_up_down上下翻转
    3. tf.image.rot90翻转90度,可自定义次数
    4. tf.image.transpose_image图像转置
  • 图像转换:

    1. tf.image.grayscale_to_rgb:灰度图转换为RGB
    2. tf.image.hsv_to_rgb:HSV图转换为RGB
    3. tf.image.rgb_to_grayscale:RGB转灰度图
    4. tf.image.rgb_to_hsv:RGB转HSV
    5. tf.image.rgb_to_yiq:RGB转YIQ
    6. tf.image.rgb_to_yuv:RGB转YUV
    7. tf.image.yiq_to_rgb:YIQ转RGB
    8. tf.image.yuv_to_rgb:YUV转RGB
  • 图像填充:

    tf.image.pad_to_bounding_box(image,offset_height,offset_width,target_height,target_width
    )

    在图像上面填充offset_height高度的0,在图像左边填充offset_width宽度的0,再按照最终目标宽度和高度在下边和右边填充图像。

  • 图像归一化:tf.image.per_image_standardization(image)

  • 图像大小调整

    tf.image.resize_images(images,size,method=ResizeMethod.BILINEAR,align_corners=False
    )

    其中插值方法有:

    AREA
    BICUBIC
    BILINEAR
    NEAREST_NEIGHBOR

    所以在图像预处理过程中,先按照路径读取图像以及解码,然后调整图像大小,归一化:

    
    # 读取数据image = tf.read_file(image)
    image = tf.image.decode_jpeg(image, channels=CHANNELS)# 将图像resize成规定大小image = tf.image.resize_images(image, [IMG_HEIGHT, IMG_WIDTH])# 手动归一化image = image * 1.0/127.5 - 1.0

创建批量数据

就一个函数:

tf.train.batch(tensors,batch_size,num_threads=1,capacity=32,enqueue_many=False,shapes=None,dynamic_pad=False,allow_smaller_final_batch=False,shared_name=None,name=None
)
  • 作用:利用队列的方法存储批数据
  • 部分参数:
    tensors:张量列表或者字典
    batch_size:批大小
    num_thread:并行入队,采用线程数
    capacity:整数,队列里面最多有多少个元素

在制作数据集中,直接这样:

 # 创建batch
X, Y = tf.train.batch([image, label], batch_size=batch_size,capacity=batch_size * 8,num_threads=4)

具体可以参考这篇博客的描述:TensorFlow 组合训练数据(batching)、TensorFlow 笔记(九):数据读取

TF中神经网络相关函数

这里需要注意的是tf.layerstf.nn中均有相关层的实现,它们之间的区别我也不太清楚,但是从网上的观点来看,大部分人认为前者是以后者作为后端的,具体分析可看:

tf.nn.conv2d vs tf.layers.conv2d

tensorflow学习:tf.nn.conv2d 和 tf.layers.conv2d

Neural Network

层相关

目前构建神经网络还是使用前者的吧:

average_pooling1d(...): 一维输入平均池化
average_pooling2d(...): 二维输入平均池化(比如图像)
average_pooling3d(...): 三维输入平均池化(比如立体)
batch_normalization(...): 批归一化
conv1d(...): 一维卷积
conv2d(...): 二维卷积
conv2d_transpose(...): 2D反卷积
conv3d(...): 三维卷积
conv3d_transpose(...): 三维转置卷积
dense(...): 全连接
dropout(...): 随机丢失神经元
flatten(...): 保存第一个维度去展平数据
max_pooling1d(...): 一维输入最大池化
max_pooling2d(...): 二维输入最大池化
max_pooling3d(...): 三维输入最大池化
separable_conv1d(...): 按照深度独立方法一维卷积
separable_conv2d(...): 按照深度独立方法二维卷积

激活函数和损失相关

tf.nn也不是完全没用,它存储了激活函数以及部分损失函数:

sigmoid(...): sigmoid激活函数
tanh(...): tanh激活函数
softplus(...): 激活函数 log(exp(features) + 1).
relu(...): ReLU激活函数
leaky_relu(...): Leaky ReLU 激活函数max(features,leak*features)
elu(...): ELU激活函数:features if features>0 else alpha*(e^features-1)
selu(...): scaled exponential linear: scale * alpha * (exp(features) - 1)
crelu(...): 关联ReLU激活函数
relu6(...): Rectified Linear 6: min(max(features, 0), 6).
softmax(...): softmax 激活函数(一般用于最后一层)
softsign(...): 激活函数 features / (abs(features) + 1).
dropout(...):神经元抑制ctc_loss(...): CTC (Connectionist Temporal Classification)损失
nce_loss(...):计算noise-contrastive estimation训练损失
l2_loss(...): L2 损失.
log_poisson_loss(...): log Poisson损失.
weighted_cross_entropy_with_logits(...): 加权交叉熵损失(默认已将features用softmax激活)
sparse_softmax_cross_entropy_with_logits(...): 计算logits 和 labels的稀疏交叉熵损失
softmax_cross_entropy_with_logits(...): 计算logits 和 labels的交叉熵损失(默认已将features用softmax激活)
softmax_cross_entropy_with_logits_v2(...):  计算logits 和 labels的交叉熵损失(默认已将features用softmax激活)
sigmoid_cross_entropy_with_logits(...): 经sigmoid 函数激活之后的交叉熵(默认已将features用softmax激活)moments(...): 计算输入数据的均值和方差.
normalize_moments(...): 基于sufficient statistics计算输入数据的均值和方差top_k(...): 返回最后一个维度的k个最大值和索引
in_top_k(...):查询目标值是否在k个预测值中
xw_plus_b(...): 计算 matmul(x, weights) + biases.
relu_layer(...): 计算 Relu(x * weight + biases).

这里几个地方需要注意:

  1. l2_loss损失函数:没有开方,并且只取一半output=12∑x2output=12∑x2output=\frac{1}{2}\sum x^2
  2. xxx__with_logits:类似于这样的都是已经将输出用softmax激活,然后计算误差,相当于做了两步工作:softmax激活输出、计算其log值与原始标签的乘积的和,具体介绍戳这里

正则项

两种正则化方法,三种函数:

  • 一范式正则化:

    tf.contrib.layers.l1_regularizer(scale,scope=None
    )
  • 二范式正则化:

    tf.contrib.layers.l2_regularizer(scale,scope=None
    )
  • 两种范式一起用

    tf.contrib.layers.l1_l2_regularizer(scale_l1=1.0,scale_l2=1.0,scope=None
    )

最后要将正则化应用到权重中:

tf.contrib.layers.apply_regularization(regularizer,weights_list=None
)

TF中的训练方法

包含优化器、梯度计算、梯度裁剪、学习率及其衰减,参考这里

优化器

Optimizer
GradientDescentOptimizer
AdadeltaOptimizer
AdagradOptimizer
AdagradDAOptimizer
MomentumOptimizer
AdamOptimizer
FtrlOptimizer
ProximalGradientDescentOptimizer
ProximalAdagradOptimizer
RMSPropOptimizer

各种梯度优化器,注意他们是属于,对应有很多函数可调用,比如minimize

梯度计算

gradients
AggregationMethod
stop_gradient
hessians

梯度裁剪

clip_by_value
clip_by_norm
clip_by_average_norm
clip_by_global_norm
global_norm

学习率及其衰减

exponential_decay
inverse_time_decay
natural_exp_decay
piecewise_constant
polynomial_decay
cosine_decay
linear_cosine_decay
noisy_linear_cosine_decay

一般不用搞那么复杂,随便用两个就行:

  • 指数形式的衰减方法

    tf.train.exponential_decay(learning_rate,#初始学习率global_step,#非负,衰减指数decay_steps,#正数,衰减周期decay_rate,#衰减率staircase=False,#如果是True,就是离散形式的衰减name=None
    )
    '''
    decayed_learning_rate = learning_rate *decay_rate ^ (global_step / decay_steps)
    staircase是True的时候(global_step / decay_steps)转换为整数
    '''
  • 多项式衰减:

    tf.train.polynomial_decay(learning_rate,global_step,decay_steps,end_learning_rate=0.0001,power=1.0,cycle=False,name=None
    )

    其中的cycle参数是决定lr是否在下降后重新上升的过程。cycle参数的初衷是为了防止网络后期lr十分小导致一直在某个局部最小值中振荡,突然调大lr可以跳出注定不会继续增长的区域探索其他区域。

    cycle时的计算方法:

    global_step = min(global_step, decay_steps)
    decayed_learning_rate = (learning_rate - end_learning_rate) *(1 - global_step / decay_steps) ^ (power) +end_learning_rate

    有cycle时的计算方法:

    decay_steps = decay_steps * ceil(global_step / decay_steps)
    decayed_learning_rate = (learning_rate - end_learning_rate) *(1 - global_step / decay_steps) ^ (power) +end_learning_rate

一般使用流程

一般是先定义学习率,然后使用优化器最小化损失

比如使用指数衰减:

...
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.1
learning_rate = tf.train.exponential_decay(starter_learning_rate, global_step,100000, 0.96, staircase=True)
# Passing global_step to minimize() will increment it at each step.
learning_step = (tf.train.GradientDescentOptimizer(learning_rate).minimize(...my loss..., global_step=global_step)
)

比如使用多项式衰减

...
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.1
end_learning_rate = 0.01
decay_steps = 10000
learning_rate = tf.train.polynomial_decay(starter_learning_rate, global_step,decay_steps, end_learning_rate,power=0.5)
# Passing global_step to minimize() will increment it at each step.
learning_step = (tf.train.GradientDescentOptimizer(learning_rate).minimize(...my loss..., global_step=global_step)
)

这个global_step随着训练自增,具体可以看这里

用这段代码可以看出来:

import tensorflow as tf;
import numpy as np;
import matplotlib.pyplot as plt;  x = tf.placeholder(tf.float32, shape=[None, 1], name='x')
y = tf.placeholder(tf.float32, shape=[None, 1], name='y')
w = tf.Variable(tf.constant(0.0))global_steps = tf.Variable(0, trainable=False)
learning_rate = tf.train.exponential_decay(0.1, global_steps, 10, 2, staircase=False)
loss = tf.pow(w*x-y, 2)train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_steps)with tf.Session() as sess:sess.run(tf.initialize_all_variables())for i in range(10):sess.run(train_step, feed_dict={x:np.linspace(1,2,10).reshape([10,1]),y:np.linspace(1,2,10).reshape([10,1])})print (sess.run(global_steps))

注意,经常无需人工计算梯度然后apply到相关参数列表上,因为minimize已经包含这两步骤了。

简单例子

按照这个例子很容易利用CNN实现一个手写数字识别网络
先引入相关包以及定义数据相关信息

import tensorflow as tf
import osDATASET_PATH = './mnist/train_labels.txt' # the dataset file or root folder path.
N_CLASSES = 10 # 类别数
IMG_HEIGHT = 28 # 高
IMG_WIDTH = 28 # 宽
CHANNELS = 1 # 通道数

然后按照标签中定义的图片路径和标签,制作数据集:

def read_images(dataset_path, batch_size):imagepaths, labels = list(), list()data = open(dataset_path, 'r').read().splitlines()for d in data:imagepaths.append(d.split(' ')[0])labels.append(int(d.split(' ')[1]))# 转换为张量imagepaths = tf.convert_to_tensor(imagepaths, dtype=tf.string)labels = tf.convert_to_tensor(labels, dtype=tf.int32)# 建立TF队列,打乱数据image, label = tf.train.slice_input_producer([imagepaths, labels],shuffle=True)# 读取数据image = tf.read_file(image)image = tf.image.decode_jpeg(image, channels=CHANNELS)# 将图像resize成规定大小image = tf.image.resize_images(image, [IMG_HEIGHT, IMG_WIDTH])# 手动归一化image = image * 1.0/127.5 - 1.0# 创建batchX, Y = tf.train.batch([image, label], batch_size=batch_size,capacity=batch_size * 8,num_threads=4)return X, Y

设置网络参数

#网络参数
learning_rate = 0.001#学习率
num_steps = 100#迭代次数
batch_size = 128#每批大小
display_step = 100#显示调试信息
dropout = 0.75 # dropout保留比率
X, Y = read_images(DATASET_PATH, batch_size)#读取数据集

定义网络结构:

# 创建卷积模型
def conv_net(x, n_classes, dropout, reuse, is_training):# Define a scope for reusing the variableswith tf.variable_scope('ConvNet', reuse=reuse):# 第一层卷积conv1 = tf.layers.conv2d(x, 32, 5, activation=tf.nn.relu)# 最大池化conv1 = tf.layers.max_pooling2d(conv1, 2, 2)# 第二层卷积conv2 = tf.layers.conv2d(conv1, 64, 3, activation=tf.nn.relu)# 最大池化conv2 = tf.layers.max_pooling2d(conv2, 2, 2)# 拉成一维向量fc1 = tf.layers.flatten(conv2)# 全连接层fc1 = tf.layers.dense(fc1, 1024)# 应用dropoutfc1 = tf.layers.dropout(fc1, rate=dropout, training=is_training)# 输出out = tf.layers.dense(fc1, n_classes)# softmax输出out = tf.nn.softmax(out) if not is_training else outreturn out

定义训练网络和评估网络,并进行训练

#训练网络
logits_train = conv_net(X, N_CLASSES, dropout, reuse=False, is_training=True)
# 测试网络
logits_test = conv_net(X, N_CLASSES, dropout, reuse=True, is_training=False)# 定义损失和优化器
loss_op = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits_train, labels=Y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
train_op = optimizer.minimize(loss_op)# 评估模型
correct_pred = tf.equal(tf.argmax(logits_test, 1), tf.cast(Y, tf.int64))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))# 初始化变量
init = tf.global_variables_initializer()# 保存模型参数
saver = tf.train.Saver()# 开始训练
with tf.Session() as sess:coord=tf.train.Coordinator()# 初始化参数sess.run(init)# 数据集队列tf.train.start_queue_runners(sess=sess,coord=coord)# 循环训练for step in range(1, num_steps+1):if step % display_step == 0:# Run optimization and calculate batch loss and accuracy_, loss, acc = sess.run([train_op, loss_op, accuracy])print("Step " + str(step) + ", Minibatch Loss= " + \"{:.4f}".format(loss) + ", Training Accuracy= " + \"{:.3f}".format(acc))else:            sess.run(train_op)coord.request_stop()#请求线程结束coord.join()#等待线程结束print("Optimization Finished!")# 保存模型saver.save(sess, './cnn_mnist_model/my_tf_model')

结果

Step 100, Minibatch Loss= 0.1098, Training Accuracy= 0.969
Step 200, Minibatch Loss= 0.1070, Training Accuracy= 0.969
Step 300, Minibatch Loss= 0.0393, Training Accuracy= 1.000
Step 400, Minibatch Loss= 0.0688, Training Accuracy= 0.984
Step 500, Minibatch Loss= 0.0559, Training Accuracy= 0.992
Step 600, Minibatch Loss= 0.0433, Training Accuracy= 0.984
Step 700, Minibatch Loss= 0.0341, Training Accuracy= 0.992
Step 800, Minibatch Loss= 0.0309, Training Accuracy= 0.984
Step 900, Minibatch Loss= 0.0825, Training Accuracy= 0.969
Step 1000, Minibatch Loss= 0.0211, Training Accuracy= 1.000
Optimization Finished!

个人感觉相对于theano使用tensorflow的好处在于,无需自己写梯度优化了,创建模型貌似不用自己去挨个权重定义和初始化,这里就没针对每个卷积核定义变量和单独初始化,除此之外,没感觉有啥其它便利性了。
还有一个重要问题是,这个模型保存了以后,好像无法拿过来单独测试一张图片,因为这里没有函数提供额外输入,即使想调用logits_test来测试单张图片,也不好弄,因为X无法在重载模型后指定单张图片,所以还得改代码,后续再研究研究模型保存与载入。

后记

这一部分主要了解一下在TensorFlow中构建神经网络可以使用的部分函数。
我嘞个乖乖,感觉好难啊,跟Theano差不多,好底层,想脱坑转TensorLayer或者TFLearn这两个基于tensorflow的二次封装库,知乎上有对应讨论如何比较Keras, TensorLayer, TFLearn ?,貌似大部分人都推荐TL,先继续折腾TensorFlow的保存和加载模型,如果实在不行,后续去了解一下对工程实现的支持程度以及模型的保存和载入难度,再选择框架。

【TensorFlow-windows】学习笔记三——实战准备相关推荐

  1. tensorflow(神经网络)学习笔记(三)之调参数实战(笔记)

    tensorboard # 指定目录 LOG_DIR = '.' run_label = 'run_vgg_tensorboard_fine_tune' run_dir = os.path.join( ...

  2. tensorflow学习笔记(三十二):conv2d_transpose (解卷积)

    tensorflow学习笔记(三十二):conv2d_transpose ("解卷积") deconv解卷积,实际是叫做conv_transpose, conv_transpose ...

  3. tensorflow 语义slam_研究《视觉SLAM十四讲从理论到实践第2版》PDF代码+《OpenCV+TensorFlow深度学习与计算机视觉实战》PDF代码笔记...

    我们知道随着人工神经网络和深度学习的发展,通过模拟视觉所构建的卷积神经网络模型在图像识别和分类上取得了非常好的效果,借助于深度学习技术的发展,使用人工智能去处理常规劳动,理解语音语义,帮助医学诊断和支 ...

  4. Hadoop学习笔记(8) ——实战 做个倒排索引

    Hadoop学习笔记(8) --实战 做个倒排索引 倒排索引是文档检索系统中最常用数据结构.根据单词反过来查在文档中出现的频率,而不是根据文档来,所以称倒排索引(Inverted Index).结构如 ...

  5. python3常用模块_Python学习笔记三(常用模块)

    Python 学习笔记三 (常用模块) 1.os模块 os模块包装了不同操作系统的通用接口,使用户在不同操作系统下,可以使用相同的函数接口,返回相同结构的结果. os.name:返回当前操作系统名称( ...

  6. Redis学习笔记(实战篇)(自用)

    Redis学习笔记(实战篇)(自用) 本文根据黑马程序员的课程资料与百度搜索的资料共同整理所得,仅用于学习使用,如有侵权,请联系删除 文章目录 Redis学习笔记(实战篇)(自用) 1.基于Sessi ...

  7. K8S 学习笔记三 核心技术 Helm nfs prometheus grafana 高可用集群部署 容器部署流程

    K8S 学习笔记三 核心技术 2.13 Helm 2.13.1 Helm 引入 2.13.2 使用 Helm 可以解决哪些问题 2.13.3 Helm 概述 2.13.4 Helm 的 3 个重要概念 ...

  8. OpenCasCade学习笔记(三):加载显示STEP格式图片,并实现平移、缩放和旋转操作

    OpenCasCade学习笔记(三):加载显示STEP格式图片,并实现平移.缩放和旋转操作 C3DWidget.h #pragma once#include <QtWidgets/QApplic ...

  9. Hive学习笔记三之函数操作

    文章目录 5 函数 5.1 系统内置函数 5.2 常用内置函数 5.2.1 空字段赋值 5.2.2 CASE WHEN THEN ELSE END(类似于java中的switch case) 5.2. ...

最新文章

  1. python 命名空间冲突_通过修改命名空间绕过pb冲突
  2. 如何查看本机端口_怎样查看Mac的端口号以及占用情况
  3. 【计蒜客 - 2019南昌邀请赛网络赛 - H】Coloring Game(找规律,思维dp)
  4. 【Foreign】采蘑菇 [点分治]
  5. 字节跳动九周年张一鸣演讲:反对all-in、抽象概念和方法论
  6. js获取url后面的参数值
  7. jquery列表插件jqgrid
  8. 美国联邦政府2020财年网络安全预算分析:174亿美元如何分配?
  9. java日期格式化返回date_Java日期时间格式化操作DateUtils 的整理
  10. python网络编程 交互式游戏设计——吹牛(RemoteBet)(无封帧)
  11. opencv 摄像机标定
  12. 计算机键盘不能用怎么办,电脑键盘空格键失灵无法使用怎么办|电脑键盘空格键失灵的解决方法...
  13. vi编辑器的使用   快捷键
  14. 浏览量(PV)、访客数(UV)、访问次数、跳出率是什么意思?
  15. 音视频开发之基于某三方音效的Android native层四声道音频输出
  16. DataWhale组队学习——DCIC赛事 task1
  17. 开源网络情报(OSINT)定义:对您的企业意味着什么
  18. 华为机试—拼音翻译成阿拉伯数字(只有数字拼音)
  19. 国外邮箱安全性排名,国外邮箱哪个安全好用?
  20. 第六章、坐标轴的定制

热门文章

  1. linux怎么远程命令,Linux远程命令
  2. linux错误代码0x8008005,利用Windows10自带Linux学习(附带:0x8007019e错误解决方法)...
  3. CC2540、nRF51822应用开发比较
  4. php特殊字符转化函数,PHP函数将Unicode转换成特殊字符?
  5. java.lang.ClassNotFoundException: Cannot find class: com.mysql.jdbc.Driver解决办法
  6. 面对面交流的好处_我们的交流方式是如何被网络社交媒体一步步改变的
  7. ACM卡常处理办法(虽然我到现在没遇到)
  8. React with Webpack - 3: 内联image、font
  9. 基于Java NIO的Socket通信
  10. 分类检测分割中的损失函数和评价指标