训练:model.fit()函数

  1. fit(x=None, y=None, batch_size=None, epochs=, verbose=, callbacks=None,
  2. validation_split=0.0, validation_data=None, shuffle=True, class_weight=None,
  3. sample_weight=None, initial_epoch=, steps_per_epoch=None, validation_steps=None, validation_freq=)
  1. x:输入数据。如果模型只有一个输入,那么x的类型是numpy array,如果模型有多个输入,那么x的类型应当为listlist的元素是对应于各个输入的numpy array
  2. y:标签,numpy array
  3. batch_size:整数,指定进行梯度下降时每个batch包含的样本数。训练时一个batch的样本会被计算一次梯度下降,使目标函数优化一步
  4. epochs:整数,训练终止时的epoch值,训练将在达到该epoch值时停止,当没有设置initial_epoch时,它就是训练的总轮数,否则训练的总轮数为epochs - inital_epoch
  5. verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
  6. callbackslist,其中的元素是keras.callbacks.Callback的对象。这个list中的回调函数将会在训练过程中的适当时机被调用,参考回调函数
  7. validation_split:~1之间的浮点数,用来指定训练集的一定比例数据作为验证集。验证集将不参与训练,并在每个epoch结束后测试的模型的指标,如损失函数、精确度等。注意,validation_split的划分在shuffle之前,因此如果你的数据本身是有序的,需要先手工打乱再指定validation_split,否则可能会出现验证集样本不均匀
  8. validation_data:形式为(Xy)的tuple,是指定的验证集。此参数将覆盖validation_spilt
  9. shuffle:布尔值或字符串,一般为布尔值,表示是否在训练过程中随机打乱输入样本的顺序。若为字符串“batch”,则是用来处理HDF5数据的特殊情况,它将在batch内部将数据打乱
  10. class_weight:字典,将不同的类别映射为不同的权值,该参数用来在训练过程中调整损失函数(只能用于训练)
  11. sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行11的加权,或者在面对时序数据时,传递一个的形式为(samplessequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode=’temporal
  12. initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用

效果可视化:keras.callbacks.History()函数

fit()函数训练时默认调用History函数,每轮训练收集损失和准确率,返回一个history的对象,其history.history属性记录了损失函数和其他指标的数值随epoch变化的情况,如果有验证集的话,也包含了验证集的这些指标变化情况

  1. # 查看history对象中收集的数据
  2. print(history.history.keys())
  1. ['acc', 'loss', 'val_acc', 'val_loss']

以此绘制训练精度/损失曲线图,观察:

  • 模型收敛的速度(斜率)
  • 模型是否已经收敛(稳定性)
  • 模型是否过拟合(验证数据集)
  1. # Visualize training history
  2. from keras.models import Sequential
  3. from keras.layers import Dense
  4. import matplotlib.pyplot as plt
  5. import numpy
  6. # Fix random seed for reproducibility
  7. numpy.random.seed(7)
  8. # Load pima indians dataset
  9. dataset = numpy.loadtxt("pima-indians-diabetes.csv", delimiter=",")
  10. # Split into input (X) and output (Y) variables
  11. X = dataset[:,0:8]
  12. Y = dataset[:,8]
  13. # Create model
  14. model = Sequential()
  15. model.add(Dense(12, input_dim=8, init='uniform', activation='relu'))
  16. model.add(Dense(8, init='uniform', activation='relu'))
  17. model.add(Dense(1, init='uniform', activation='sigmoid'))
  18. # Compile model
  19. model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
  20. # Fit the model
  21. history = model.fit(X, Y, validation_split=0.33, nb_epoch=150, batch_size=10, verbose=0) # List all data in history
  22. print(history.history.keys())
  23. # Summarize history for accuracy
  24. plt.plot(history.history['acc'])
  25. plt.plot(history.history['val_acc'])
  26. plt.title('model accuracy')
  27. plt.ylabel('accuracy')
  28. plt.xlabel('epoch')
  29. plt.legend(['train', 'test'], loc='upper left') plt.show()
  30. # Summarize history for loss
  31. plt.plot(history.history['loss'])
  32. plt.plot(history.history['val_loss'])
  33. plt.title('model loss')
  34. plt.ylabel('loss')
  35. plt.xlabel('epoch')
  36. plt.legend(['train', 'test'], loc='upper left') plt.show()
  1. # 训练-验证-精度:从精度图看,最后几次迭代的训练精度仍在上升,有可能过拟合,但对比验证集的效果差不多,应该没有过拟合

  1. # 训练-验证-损失:从损失图看,训练集和验证集两个数据集的性能差不多(如果两条线开始分开,有可能应该提前终止训练)

效果可视化:TensorBoard()函数

TensorBoard是一个机器学习的可视化工具,能够有效地展示Tensorflow在运行过程中的计算图、各种指标随着时间的变化趋势以及训练中使用到的数据信息。

  1. keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0, write_graph=True, write_images=False, embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None)
  1. log_dir:保存日志文件的地址,该文件将被TensorBoard解析以用于可视化
  2. histogram_freq:计算各个层激活值直方图的频率(每多少个epoch计算一次),如果设置为0则不计算。
  3. write_graph: 是否在Tensorboard上可视化图,当设为True时,log文件可能会很大
  4. write_images: 是否将模型权重以图片的形式可视化
  5. embeddings_freq: 依据该频率(以epoch为单位)筛选保存的embedding
  6. embeddings_layer_names:要观察的层名称的列表,若设置为None或空列表,则所有embedding层都将被观察。
  7. embeddings_metadata: 字典,将层名称映射为包含该embedding层元数据的文件名,参考这里获得元数据文件格式的细节。如果所有的embedding层都使用相同的元数据文件,则可传递字符串。
  1. from keras.callbacks import Tensorboard
  2. ......
  3. model_name = "kaggle_cat_dog-cnn-64x2-{}".format(int(time.time()))
  4. tensorboard = TensorBoard(log_dir='logs/{}'.format(model_name))
  5. callback_lists = [tensorboard] # 因为callback是list型,必须转化为list
  6. model.fit(x_train,y_train,bach_size=batch_size,epochs=epoch,shuffle='True',verbose='True',callbacks=callback_lists)# 程序运行完毕之后,在python文件的同级文件夹下面会出现一个logs文件夹,文件夹下面会有存放我们刚才训练的模型的tfevent文件的目录
 
 

断点:keras.callbacks.Callback()函数 & keras.callbacks.ModelCheckpoint()函数

.Callback()

Blog:https://keras-cn.readthedocs.io/en/latest/other/callbacks/ https://machinelearningmastery.com/check-point-deep-learning-models-keras/ https://keras.io/callbacks/ http://keras-cn.readthedocs.io/en/latest/other/callbacks/ https://cloud.tencent.com/developer/article/1049579 http://keras-cn.readthedocs.io/en/latest/other/callbacks/#modelcheckpoint

回调函数callbacks是一组在训练的特定阶段被调用的函数集,训练阶段使用回调函数来观察训练过程中网络内部的状态和统计信息。然后在模型上调用Sequential或Model类型的fit()函数时,可以将包含ModelCheckpoint等关键字参数的列表的回调函数传递给训练过程。然后在训练时,相应的回调函数的方法就会被在各自的阶段被调用。训练深度学习模型时,Checkpoint是模型的权重,ModelCheckpoint回调类允许自定义:模型权重的位置,文件命名方式,以及在什么情况下创建模型的Checkpoint。

  1. # 回调函数的抽象类,定义新的回调函数必须继承自该类
  2. keras.callbacks.Callback()
  1. # 类属性
  2. params:字典,训练参数集(如信息显示方法verbositybatch大小,epoch数)
  3. modelkeras.models.Model对象,为正在训练的模型的引用
  1. # 回调函数以字典logs为参数,该字典包含了一系列与当前batch或epoch相关的信息# 目前,模型的.fit()中有下列参数会被记录到logs中:
  • 在每个epoch的结尾处(on_epoch_end),logs将包含训练的正确率和误差,acc和loss,如果指定了验证集,还会包含验证集正确率和误差val_acc和val_loss,val_acc还额外需要在.compile中启用metrics=['accuracy']
  • 在每个batch的开始处(on_batch_begin):logs包含size,即当前batch的样本数
  • 在每个batch的结尾处(on_batch_end):logs包含loss,若启用accuracy则还包含acc

.ModelCheckpoint()

该回调函数将在每个epoch后保存模型到filepath,filepath可以包括命名格式选项,可以由epoch的值和logs的键(由on_epoch_end参数传递)来填充

  1. keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1)
  1. filepath: 是格式化的字符串,保存模型的路径,里面的占位符将会被epoch值和传入on_epoch_endlogs关键字所填入
  1. filepath = weights.{epoch:02d-{val_loss:.2f}}.hdf5 # 生成对应epoch和验证集loss的多个文件
  1. monitor: 被监测的数据。val_acc或这val_loss
  2. verbose: 详细信息模式,0 或者 1 0为不打印输出信息,1打印
  3. save_best_only: 如果 save_best_only=True 将只保存在验证集上性能最好的模型
  4. mode: {auto, min, max} 的其中之一。 如果 save_best_only=True,那么是否覆盖保存文件的决定就取决于被监测数据的最大或者最小值。 对于 val_acc,模式就会是 max,而对于 val_loss,模式就需要是 min,等等。在 auto 模式中,方向会自动从被监测的数据的名字中判断出来
  5. save_weights_only: 如果 True,那么只有模型的权重会被保存 (model.save_weights(filepath)), 否则的话,整个模型会被保存 (model.save(filepath))
  6. period: 每个检查点之间的间隔(训练轮数)

实现过程

① 从keras.callbacks导入ModelCheckpoint类

  1. from keras.callbacks import ModelCheckpoint

② 在训练阶段的model.compile之后加入下列代码实现每一次epoch(period=1)保存最好的参数

  1. checkpoint = ModelCheckpoint(filepath, monitor='val_loss', save_weights_only=True, verbose=1, save_best_only=True, period=1)

③ 在训练阶段的model.fit之前加载先前保存的参数

  1. if os.path.exists(filepath):
  2. model.load_weights(filepath)
  3. # 若成功加载前面保存的参数,输出下列信息
  4. print("checkpoint_loaded")

④ 在model.fit添加callbacks=[checkpoint]实现回调

  1. model.fit_generator(data_generator_wrap(lines[:num_train], batch_size, input_shape, anchors, num_classes),
  2. steps_per_epoch=max(1, num_train//batch_size),
  3. validation_data=data_generator_wrap(lines[num_train:], batch_size, input_shape, anchors, num_classes),
  4. validation_steps=max(1, num_val//batch_size),
  5. epochs=3,
  6. initial_epoch=0,
  7. callbacks=[checkpoint])

训练过程

第一次运行train.py执行训练,没有已训练模型可以加载,不会打印“checkpoint_loaded”

再次运行train.py执行训练,在model.fit之前会加载前一次训练保存的模型参数,继续训练。此轮训练输出“checkpoint_load”表示成功加载前面保存的模型参数

  1. from __future__ import print_function
  2. import keras
  3. from keras.datasets import cifar10
  4. from keras.preprocessing.image import ImageDataGenerator
  5. from keras.models import Sequential
  6. from keras.layers import Dense, Dropout, Activation, Flatten
  7. from keras.layers import Conv2D, MaxPooling2D
  8. from keras.callbacks import ModelCheckpoint
  9. import os
  10.  
  11. batch_size = 128
  12. num_classes = 10
  13. epochs = 30
  14. num_predictions = 20
  15. save_dir = os.path.join(os.getcwd(), 'saved_models')
  16. model_name = 'keras_cifar10_trained_model.h5'
  17.  
  18. # The data, split between train and test sets:
  19. (x_train, y_train), (x_test, y_test) = cifar10.load_data()
  20. print('x_train shape:', x_train.shape)
  21. print(x_train.shape[0], 'train samples')
  22. print(x_test.shape[0], 'test samples')
  23.  
  24. # Convert class vectors to binary class matrices.
  25. y_train = keras.utils.to_categorical(y_train, num_classes)
  26. y_test = keras.utils.to_categorical(y_test, num_classes)
  27. # Create model
  28. model = Sequential()
  29. model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:]))
  30. model.add(Activation('relu'))
  31. model.add(Conv2D(32, (3, 3)))
  32. model.add(Activation('relu'))
  33. model.add(MaxPooling2D(pool_size=(2, 2)))
  34. model.add(Dropout(0.25))
  35. model.add(Conv2D(64, (3, 3), padding='same'))
  36. model.add(Activation('relu'))
  37. model.add(Conv2D(64, (3, 3)))
  38. model.add(Activation('relu'))
  39. model.add(MaxPooling2D(pool_size=(2, 2)))
  40. model.add(Dropout(0.25))
  41. model.add(Flatten())
  42. model.add(Dense(512))
  43. model.add(Activation('relu'))
  44. model.add(Dropout(0.5))
  45. model.add(Dense(num_classes))
  46. model.add(Activation('softmax'))
  47.  
  48. # Initiate RMSprop optimizer
  49. opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)
  50.  
  51. # Let's train the model
  52. model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
  53.  
  54. x_train = x_train.astype('float32')
  55. x_test = x_test.astype('float32')
  56. x_train /= 255
  57. x_test /= 255
  58.  
  59. filepath="model_{epoch:02d}-{val_acc:.2f}.hdf5" # filepath = "best_weights.h5"
  60. checkpoint = ModelCheckpoint(os.path.join(save_dir, filepath), monitor='val_acc', verbose=1, save_best_only=True)
  61.  
  62. history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test), shuffle=True, callbacks=[checkpoint])
  63. print(history.history.keys())
  64. # Save model and weights
  65. if not os.path.isdir(save_dir):
  66. os.makedirs(save_dir)
  67. model_path = os.path.join(save_dir, model_name)
  68. model.save(model_path)
  69. print('Saved trained model at %s ' % model_path)
  70.  
  71. # Score trained model.
  72. scores = model.evaluate(x_test, y_test, verbose=1)
  73. print('Test loss:', scores[0])
  74. print('Test accuracy:', scores[1])
  1. # 训练过程 - 发现有部分epoch未保存checkpoint,如Epoch 00006
  2. x_train shape: (50000, 32, 32, 3)
  3. 50000 train samples
  4. 10000 test samples
  5. Train on 50000 samples, validate on 10000 samples
  6. Epoch 1/30
  7. 50000/50000 [==============================] - 5s 106us/step - loss: 1.9831 - acc: 0.2734 - val_loss: 1.7195 - val_acc: 0.3934
  8.  
  9. Epoch 00001: val_acc improved from -inf to 0.39340, saving model to /home/weiliu/PG/keras/saved_models/model_01-0.39.hdf5
  10. Epoch 2/30
  11. 50000/50000 [==============================] - 4s 84us/step - loss: 1.6651 - acc: 0.3963 - val_loss: 1.5001 - val_acc: 0.4617
  12.  
  13. Epoch 00002: val_acc improved from 0.39340 to 0.46170, saving model to /home/weiliu/PG/keras/saved_models/model_02-0.46.hdf5
  14. Epoch 3/30
  15. 50000/50000 [==============================] - 4s 88us/step - loss: 1.5249 - acc: 0.4482 - val_loss: 1.4158 - val_acc: 0.4923
  16.  
  17. Epoch 00003: val_acc improved from 0.46170 to 0.49230, saving model to /home/weiliu/PG/keras/saved_models/model_03-0.49.hdf5
  18. Epoch 4/30
  19. 50000/50000 [==============================] - 4s 87us/step - loss: 1.4479 - acc: 0.4775 - val_loss: 1.3469 - val_acc: 0.5199
  20.  
  21. Epoch 00004: val_acc improved from 0.49230 to 0.51990, saving model to /home/weiliu/PG/keras/saved_models/model_04-0.52.hdf5
  22. Epoch 5/30
  23. 50000/50000 [==============================] - 4s 89us/step - loss: 1.3838 - acc: 0.5048 - val_loss: 1.3058 - val_acc: 0.5332
  24.  
  25. Epoch 00005: val_acc improved from 0.51990 to 0.53320, saving model to /home/weiliu/PG/keras/saved_models/model_05-0.53.hdf5
  26. Epoch 6/30
  27. 50000/50000 [==============================] - 4s 87us/step - loss: 1.3359 - acc: 0.5226 - val_loss: 1.3543 - val_acc: 0.5226
  28.  
  29. Epoch 00006: val_acc did not improve from 0.53320
  30. Epoch 7/30
  31. 50000/50000 [==============================] - 4s 88us/step - loss: 1.2934 - acc: 0.5399 - val_loss: 1.1970 - val_acc: 0.5769
  32.  
  33. Epoch 00007: val_acc improved from 0.53320 to 0.57690, saving model to /home/weiliu/PG/keras/saved_models/model_07-0.58.hdf5
  34. Epoch 8/30
  35. 50000/50000 [==============================] - 4s 87us/step - loss: 1.2522 - acc: 0.5536 - val_loss: 1.1682 - val_acc: 0.5865

早停止:keras.callbacks.EarlyStopping()函数

  1. callbacks = [
  2. EarlyStopping(monitor='val_loss', patience=2, verbose=0), # 当两次迭代损失未改善,Keras停止训练
  3. ModelCheckpoint(kfold_weights_path, monitor='val_loss', save_best_only=True, verbose=0),
  4. ]
  5. model.fit(X_train.astype('float32'), Y_train, batch_size=batch_size, nb_epoch=nb_epoch, shuffle=True, verbose=1, validation_data=(X_valid, Y_valid), callbacks=callbacks)

自定义早停止回调函数 - 在损失小于常数“THR”之后停止训练:

  1. if val_loss < THR:
  2. break
  1. class EarlyStoppingByLossVal(Callback):
  2. def __init__(self, monitor='val_loss', value=0.00001, verbose=0):
  3. super(Callback, self).__init__()
  4. self.monitor = monitor
  5. self.value = value
  6. self.verbose = verbose
  7.  
  8. def on_epoch_end(self, epoch, logs={}):
  9. current = logs.get(self.monitor)
  10. if current is None:
  11. warnings.warn("Early stopping requires %s available!" % self.monitor, RuntimeWarning)
  12.  
  13. if current < self.value:
  14. if self.verbose > 0:
  15. print("Epoch %05d: early stopping THR" % epoch)
  16. self.model.stop_training = True
  1. callbacks = [
  2. EarlyStoppingByLossVal(monitor='val_loss', value=0.00001, verbose=1),
  3. ModelCheckpoint(kfold_weights_path, monitor='val_loss', save_best_only=True, verbose=0),
  4. ]
  5. model.fit(X_train.astype('float32'), Y_train, batch_size=batch_size, nb_epoch=nb_epoch, shuffle=True, verbose=1, validation_data=(X_valid, Y_valid), callbacks=callbacks)

Keras模型训练的断点续训、早停、效果可视化的更多相关文章

  1. Keras入门(六)模型训练实时可视化

      在北京做某个项目的时候,客户要求能够对数据进行训练.预测,同时能导出模型,还有在页面上显示训练的进度.前面的几个要求都不难实现,但在页面上显示训练进度当时笔者并没有实现.   本文将会分享如何在K ...

  2. A TensorBoard plugin for visualizing arbitrary tensors in a video as your network trains.Beholder是一个TensorBoard插件,用于在模型训练时查看视频帧。

    Beholder is a TensorBoard plugin for viewing frames of a video while your model trains. It comes wit ...

  3. 在Keras模型中one-hot编码,Embedding层,使用预训练的词向量/处理图片

    最近看了吴恩达老师的深度学习课程,又看了python深度学习这本书,对深度学习有了大概的了解,但是在实战的时候, 还是会有一些细枝末节没有完全弄懂,这篇文章就用来总结一下用keras实现深度学习算法的 ...

  4. 人脸检测及识别python实现系列(5)——利用keras库训练人脸识别模型

    人脸检测及识别python实现系列(5)——利用keras库训练人脸识别模型 经过前面稍显罗嗦的准备工作,现在,我们终于可以尝试训练我们自己的卷积神经网络模型了.CNN擅长图像处理,keras库的te ...

  5. keras 保存训练的最佳模型

    转自:https://anifacc.github.io/deeplearning/machinelearning/python/2017/08/30/dlwp-ch14-keep-best-mode ...

  6. 早停!? earlystopping for keras

    为了获得性能良好的神经网络,网络定型过程中需要进行许多关于所用设置(超参数)的决策.超参数之一是定型周期(epoch)的数量:亦即应当完整遍历数据集多少次(一次为一个epoch)?如果epoch数量太 ...

  7. 谷歌大规模机器学习:模型训练、特征工程和算法选择 (32PPT下载)

    本文转自:http://mp.weixin.qq.com/s/Xe3g2OSkE3BpIC2wdt5J-A 谷歌大规模机器学习:模型训练.特征工程和算法选择 (32PPT下载) 2017-01-26  ...

  8. Keras/Tensorflow训练逻辑研究

    Keras是什么,以及相关的基础知识,这里就不做详细介绍,请参考Keras学习站点http://keras-cn.readthedocs.io/en/latest/ Tensorflow作为backe ...

  9. 将keras模型在django中应用时出现的小问题——ValueError: Tensor Tensor("dense_2/Softmax:0", shape=(?, 8), dtype=float32) is not an element of this graph.

    本文原出处(感谢作者提供):https://zhuanlan.zhihu.com/p/27101000 将keras模型在django中应用时出现的小问题 王岳王院长 10 个月前 keras 一个做 ...

随机推荐

  1. 【CSS】三栏/两栏宽高自适应布局大全

    页面布局 注意方案多样性.各自原理.各自优缺点.如果不定高呢.兼容性如何 三栏自适应布局,左右两侧300px,中间宽度自适应 (1) 给出5种方案 方案一: float (左右浮动,中间不用给宽,设置 ...

  2. koa 项目实战(二)连接 mongodb 数据库

    1.配置文件 根目录/config/keys.js module.exports = { mongoURI: 'mongodb://127.0.0.1:27017/mongodb' } 2.启动文件 ...

  3. NAT(地址转换技术)详解

    目录 NAT产生背景ip地址基础知识NAT技术的工作原理和特点静态NAT动态NATNAT重载(经常应用到实际中)NAT技术的优缺点优点缺点NAT穿越技术应用层网关(ALG)ALG的实际应用NAT技术的 ...

  4. tensorflow卷积神经网络与手写字识别

    1.知识点 """ 基础知识: 1.神经网络(neural networks)的基本组成包括输入层.隐藏层.输出层.而卷积神经网络的特点在于隐藏层分为卷积层和池化层(po ...

  5. Jmeter如何使用数据库返回值实践

    Jmeter如何使用数据库返回值实践 最近使用Jmeter针对产品做性能测试,测试内容是要模拟300并发用户审批休假申请时的性能.由于每个申请人的主管不同,且会根据不同的休假类型,会有一级审批或者二级 ...

  6. docker commit命令

    docker commit命令用于基于一个容器来创建一个新的docker镜像. docker commit制作的镜像,除了制定镜像的人知道执行过什么命令,怎么生成的镜像,别人根本无从得知.建议使用的是 ...

  7. MySQL递归查询父子节点

    1.表结构 CREATE TABLE folder( id BIGINT(20) NOT NULL, parent_id BIGINT(20) DEFAULT NULL, PRIMARY KEY id ...

  8. php使用装饰模式无侵入式加缓存

    <?php namespace App\Services; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\ ...

  9. Python:Django 项目中可用的各种装备和辅助

    1 Redis 数据库 2 MySQL 数据库 3 前端服务器 live-server 4 定时任务 django-crontab扩展 5 Docker 容器 --用来运行 FastDFS 分布式文件 ...

  10. KCP - A Fast and Reliable ARQ Protocol

    KCP - A Fast and Reliable ARQ Protocol README in English 简介 KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均 ...