https://aistudio.baidu.com/aistudio/course/introduce/1617
PaddlePaddle是百度开源的深度学习框架,类似的深度学习框架还有谷歌的Tensorflow、Facebook的Pytorch等,在入门深度学习时,学会并使用一门常见的框架,可以让学习效率大大提升。在PaddlePaddle中,计算的对象是张量,我们可以先使用PaddlePaddle来计算一个[[1, 1], [1, 1]] * [[1, 1], [1, 1]]。
首先导入PaddlePaddle库
import paddle.fluid as fluid
定义两个张量的常量x1和x2,并指定它们的形状是[2, 2],并赋值为1铺满整个张量,类型为int64.
# 定义两个张量
x1 = fluid.layers.fill_constant(shape=[2, 2], value=1, dtype='int64')
x2 = fluid.layers.fill_constant(shape=[2, 2], value=1, dtype='int64')
接着定义一个操作,该计算是将上面两个张量进行加法计算,并返回一个求和的算子。PaddlePaddle 提供了大量的操作,比如加减乘除、三角函数等,读者可以在 fluid.layers 找到。
# 将两个张量求和
y1 = fluid.layers.sum(x=[x1, x2])
然后创建一个解释器,可以在这里指定计算使用 CPU 或 GPU。当使用 CPUPlace () 时使用的是 CPU,如果是 CUDAPlace () 使用的是 GPU。解析器是之后使用它来进行计算过的,比如在执行计算之前我们要先执行参数初始化的 program 也是要使用到解析器的,因为只有解析器才能执行 program。
# 创建一个使用 CPU 的解释器
place = fluid.CPUPlace()
exe = fluid.executor.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())
最后执行计算,program 的参数值是主程序,不是上一步使用的是初始化参数的程序,program 默认一共有两个,分别是 default_startup_program () 和 default_main_program ()。fetch_list 参数的值是在解析器在 run 之后要输出的值,我们要输出计算加法之后输出结果值。最后计算得到的也是一个张量。
# 运行计算,并把 y 的结果输出
result = exe.run(program=fluid.default_main_program(),
fetch_list=[y1])
print(result)
'''
[array([[2, 2],
[2, 2]], dtype=int64)]
'''
上面计算的是张量常量的1+1,并不能随意修改常量的值,所以下面我们要编写一个使用张量变量作为乘数的程序,类似是一个占位符,等到将要计算时,再把要计算的值添加到占位符中进行计算。
导入PaddlePaddle库和numpy的库。
import paddle.fluid as fluid
import numpy as np
定义两个张量,并不指定该张量的形状和值,它们是之后动态赋值的。这里只是指定它们的类型和名字,这个名字是我们之后赋值的关键。
# 定义两个张量
a = fluid.layers.create_tensor(dtype='int64', name='a')
b = fluid.layers.create_tensor(dtype='int64', name='b')
使用同样的方式,定义这个两个张量的加法操作。
# 将两个张量求和
y = fluid.layers.sum(x=[a, b])
这里我们同样是创建一个使用 CPU 的解析器,和进行参数初始化。
# 创建一个使用 CPU 的解释器
place = fluid.CPUPlace()
exe = fluid.executor.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())
然后使用 numpy 创建两个张量值,之后我们要计算的就是这两个值。
# 定义两个要计算的变量
a1 = np.array([3, 2]).astype('int64')
b1 = np.array([1, 1]).astype('int64')
这次 exe.run () 的参数有点不一样了,多了一个 feed 参数,这个就是要对张量变量进行赋值的。赋值的方式是使用了键值对的格式,key 是定义张量变量是指定的名称,value 就是要传递的值。在 fetch_list 参数中,笔者希望把 a, b, y 的值都输出来,所以要使用 3 个变量来接受返回值。
# 进行运算,并把 y 的结果输出
out_a, out_b, result = exe.run(program=fluid.default_main_program(),
feed={
'a':a1, 'b':b1},
fetch_list=[a, b, y])
print(out_a, " + ", out_b, " = ", result)
'''
[3 2] + [1 1] = [4 3]
'''
在上面的教学中,教大家学会用PaddlePaddle做基本的算子运算,下面来教大家如何用PaddlePaddle来做简单的线性回归,包括从定义网络到使用自定义的数据进行训练,最后验证我们网络的预测能力。
首先导入PaddlePaddle库和一些工具类库。
import paddle.fluid as fluid
import paddle
import numpy as np
定义一个简单的线性网络,这个网络非常简单,结构是:输出层-->>隐层-->>输出层__
,这个网络一共有2层,因为输入层不算网络的层数。更具体的就是一个大小为100,激活函数是ReLU的全连接层和一个输出大小为1的全连接层,就这样构建了一个非常简单的网络。这里使用输入fluid.layers.data()定义的输入层类似fluid.layers.create_tensor()
,也是有name
属性,之后也是根据这个属性来填充数据的。这里定义输入层的形状为13,这是因为波士顿房价数据集的每条数据有13个属性,我们之后自定义的数据集也是为了符合这一个维度。
# 定义一个简单的线性网络
x = fluid.layers.data(name='x', shape=[13], dtype='float32')
hidden = fluid.layers.fc(input=x, size=100, act='relu')
net = fluid.layers.fc(input=hidden, size=1, act=None)
接着定义神经网络的损失函数,这里同样使用了fluid.layers.data()
这个接口,这个可以理解为数据对应的结果,上面name
为x的fluid.layers.data()
为属性数据。这里使用了平方差损失函数(square_error_cost)
,PaddlePaddle提供了很多的损失函数的接口,比如交叉熵损失函数(cross_entropy)
。因为本项目是一个线性回归任务,所以我们使用的是平方差损失函数。因为fluid.layers.square_error_cost()
求的是一个Batch的损失值,所以我们还要对他求一个平均值。
# 定义损失函数
y = fluid.layers.data(name='y', shape=[1], dtype='float32')
cost = fluid.layers.square_error_cost(input=net, label=y)
avg_cost = fluid.layers.mean(cost)
定义损失函数之后,可以在主程序(fluid.default_main_program)
中克隆一个程序作为预测程序,用于训练完成之后使用这个预测程序进行预测数据。这个定义的顺序不能错,因为我们定义的网络结构,损失函数等等都是更加顺序记录到PaddlePaddle的主程序中的。主程序定义了神经网络模型,前向反向计算,以及优化算法对网络中可学习参数的更新,是我们整个程序的核心,这个是PaddlePaddle已经帮我们实现的了,我们只需注重网络的构建和训练即可。
# 复制一个主程序,方便之后使用
test_program = fluid.default_main_program().clone(for_test=True)
接着是定义训练使用的优化方法,这里使用的是随机梯度下降优化方法。PaddlePaddle 提供了大量的优化函数接口,除了本项目使用的随机梯度下降法(SGD),还有 Momentum、Adagrad、Adagrad 等等,读者可以更加自己项目的需求使用不同的优化方法。
# 定义优化方法
optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01)
opts = optimizer.minimize(avg_cost)
然后是创建一个解析器,我们同样是使用 CPU 来进行训练。创建解析器之后,使用解析器来执行 fluid.default_startup_program () 初始化参数。
# 创建一个使用 CPU 的解释器
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())
我们使用 numpy 定义一组数据,这组数据的每一条数据有 13 个,这是因为我们在定义网络的输入层时,shape 是 13,但是每条数据的后面 12 个数据是没意义的,因为笔者全部都是使用 0 来填充,纯粹是为了符合数据的格式而已。这组数据是符合 y = 2 * x + 1,但是程序是不知道的,我们之后使用这组数据进行训练,看看强大的神经网络是否能够训练出一个拟合这个函数的模型。最后定义了一个预测数据,是在训练完成,使用这个数据作为 x 输入,看是否能够预测于正确值相近结果。
# 定义训练和测试数据
x_data = np.array([[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]).astype('float32')
y_data = np.array([[3.0], [5.0], [7.0], [9.0], [11.0]]).astype('float32')
test_data = np.array([[6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]).astype('float32')
定义数据之后,我们就可以使用数据进行训练了。我们这次训练了 10 个 pass,读者可根据情况设置更多的训练轮数,通常来说训练的次数越多,模型收敛的越好。同样我们使用的时 profram 是 fluid.default_main_program (),feed 中是在训练时把数据传入 fluid.layers.data () 定义的变量中,及那个键值对的 key 对用的就是 fluid.layers.data () 中的 name 的值。我们让训练过程中输出 avg_cost 的值。
在训练过程中,我们可以看到输出的损失值在不断减小,证明我们的模型在不断收敛。
for pass_id in range(10):
train_cost = exe.run(program=fluid.default_main_program(),
feed={
'x': x_data, 'y': y_data},
fetch_list=[avg_cost])
print("Pass:%d, Cost:%0.5f" % (pass_id, train_cost[0]))
'''
Pass:0, Cost:46.65071
Pass:1, Cost:11.96710
Pass:2, Cost:0.99339
Pass:3, Cost:0.04562
Pass:4, Cost:0.04421
Pass:5, Cost:0.04327
Pass:6, Cost:0.04236
Pass:7, Cost:0.04146
Pass:8, Cost:0.04058
Pass:9, Cost:0.03972
'''
训练完成之后,我们使用上面克隆主程序得到的预测程序了预测我们刚才定义的预测数据。预测数据同样作为 x 在 feed 输入,在预测时,理论上是不用输入 y 的,但是要符合输入格式,我们模拟一个 y 的数据值,这个值并不会影响我们的预测结果。fetch_list 的值,也就是我们执行预测之后要输出的结果,这是网络的最后一层,而不是平均损失函数(avg_cost),因为我们是想要预测程序输出预测结果。根据我们上面定义数据时,满足规律 y = 2 * x + 1,所以当 x 为 6 时,y 应该时 13,最后输出的结果也是应该接近 13 的。
# 开始预测
result = exe.run(program=test_program,
feed={
'x': test_data, 'y': np.array([[0.0]]).astype('float32')},
fetch_list=[net])
print("当x为6.0时,y为:%0.5f" % result[0][0][0])
'''
当x为6.0时,y为:13.30506
'''
数据集共506行,每行14列。前13列用来描述房屋的各种信息,最后一列为该类房屋价格中位数。
PaddlePaddle提供了读取uci_housing训练集和测试集的接口,分别为paddle.dataset.uci_housing.train()和paddle.dataset.uci_housing.test()。
paddle.reader.shuffle()表示每次缓存BUF_SIZE个数据项,并进行打乱
paddle.batch()表示每BATCH_SIZE组成一个batch
# 导入基本的库
import paddle.fluid as fluid
import paddle
import numpy as np
import os
BUF_SIZE=500
BATCH_SIZE=20
#用于训练的数据提供器,每次从缓存中随机读取批次大小的数据
train_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.uci_housing.train(),
buf_size=BUF_SIZE),
batch_size=BATCH_SIZE)
#用于测试的数据提供器,每次从缓存中随机读取批次大小的数据
test_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.uci_housing.test(),
buf_size=BUF_SIZE),
batch_size=BATCH_SIZE)
打印查看 uci_housing 数据
#用于打印,查看uci_housing数据
train_data=paddle.dataset.uci_housing.train();
sampledata=next(train_data())
print(sampledata)
'''
(array([-0.0405441 , 0.06636364, -0.32356227, -0.06916996, -0.03435197,
0.05563625, -0.03475696, 0.02682186, -0.37171335, -0.21419304,
-0.33569506, 0.10143217, -0.21172912]), array([24.]))
'''
(1)网络搭建:对于线性回归来讲,它就是一个从输入到输出的简单的全连接层。
对于波士顿房价数据集,假设属性和房价之间的关系可以被属性间的线性组合描述。
#定义张量变量x,表示13维的特征值
x = fluid.layers.data(name='x', shape=[13], dtype='float32')
#定义张量y,表示目标值
y = fluid.layers.data(name='y', shape=[1], dtype='float32')
#定义一个简单的线性网络,连接输入和输出的全连接层
#input:输入tensor;
#size:该层输出单元的数目
#act:激活函数
y_predict=fluid.layers.fc(input=x,size=1,act=None)
(2)定义损失函数
此处使用均方差损失函数。
square_error_cost(input,lable):接受输入预测值和目标值,并返回方差估计,即为(y-y_predict)的平方
cost = fluid.layers.square_error_cost(input=y_predict, label=y) #求一个batch的损失值
avg_cost = fluid.layers.mean(cost) #对损失值求平均值
(3)定义优化函数
此处使用的是随机梯度下降。
optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)
opts = optimizer.minimize(avg_cost)
test_program = fluid.default_main_program().clone(for_test=True)
在上述模型配置完毕后,得到两个fluid.Program
:fluid.default_startup_program()
与fluid.default_main_program()
。
参数初始化操作会被写入fluid.default_startup_program()
fluid.default_main_program()
用于获取默认或全局main program
(主程序)。该主程序用于训练和测试模型。fluid.layers
中的所有layer函数可以向 default_main_program
中添加算子和变量。default_main_program
是fluid的许多编程接口(API)的Program
参数的缺省值。例如,当用户program没有传入的时候,Executor.run()
会默认执行 default_main_program
。
(1)创建Executor
首先定义运算场所 fluid.CPUPlace()和 fluid.CUDAPlace(0)分别表示运算场所为CPU和GPU
Executor:接收传入的program,通过run()方法运行program。
use_cuda = False #use_cuda为False,表示运算场所为CPU;use_cuda为True,表示运算场所为GPU
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
exe = fluid.Executor(place) #创建一个Executor实例exe
exe.run(fluid.default_startup_program()) #Executor的run()方法执行startup_program(),进行参数初始化
(2)定义输入数据维度
DataFeeder负责将数据提供器(train_reader,test_reader)返回的数据转成一种特殊的数据结构,使其可以输入到Executor中。
feed_list设置向模型输入的向变量表或者变量表名
# 定义输入数据维度
feeder = fluid.DataFeeder(place=place, feed_list=[x, y])#feed_list:向模型输入的变量表或变量表名
(3)定义绘制训练过程的损失值变化趋势的方法draw_train_process
iter=0;
iters=[]
train_costs=[]
def draw_train_process(iters,train_costs):
title="training cost"
plt.title(title, fontsize=24)
plt.xlabel("iter", fontsize=14)
plt.ylabel("cost", fontsize=14)
plt.plot(iters, train_costs,color='red',label='training cost')
plt.grid()
plt.show()
(4)训练并保存模型
Executor接收传入的program,并根据feed map(输入映射表)和fetch_list(结果获取表) 向program中添加feed operators(数据输入算子)和fetch operators(结果获取算子)。 feed map为该program提供输入数据。fetch_list提供program训练结束后用户预期的变量。
注:enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,
EPOCH_NUM=50
model_save_dir = "/home/aistudio/work/fit_a_line.inference.model"
for pass_id in range(EPOCH_NUM): #训练EPOCH_NUM轮
# 开始训练并输出最后一个batch的损失值
train_cost = 0
for batch_id, data in enumerate(train_reader()): #遍历train_reader迭代器
train_cost = exe.run(program=fluid.default_main_program(),#运行主程序
feed=feeder.feed(data), #喂入一个batch的训练数据,根据feed_list和data提供的信息,将输入数据转成一种特殊的数据结构
fetch_list=[avg_cost])
if batch_id % 40 == 0:
print("Pass:%d, Cost:%0.5f" % (pass_id, train_cost[0][0])) #打印最后一个batch的损失值
iter=iter+BATCH_SIZE
iters.append(iter)
train_costs.append(train_cost[0][0])
# 开始测试并输出最后一个batch的损失值
test_cost = 0
for batch_id, data in enumerate(test_reader()): #遍历test_reader迭代器
test_cost= exe.run(program=test_program, #运行测试cheng
feed=feeder.feed(data), #喂入一个batch的测试数据
fetch_list=[avg_cost]) #fetch均方误差
print('Test:%d, Cost:%0.5f' % (pass_id, test_cost[0][0])) #打印最后一个batch的损失值
#保存模型
# 如果保存路径不存在就创建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print ('save models to %s' % (model_save_dir))
#保存训练参数到指定路径中,构建一个专门用预测的program
fluid.io.save_inference_model(model_save_dir, #保存推理model的路径
['x'], #推理(inference)需要 feed 的数据
[y_predict], #保存推理(inference)结果的 Variables
exe) #exe 保存 inference model
draw_train_process(iters,train_costs)
'''
Pass:0, Cost:23.17198
Test:0, Cost:10.28433
Pass:1, Cost:20.21272
Test:1, Cost:6.31516
Pass:2, Cost:46.81040
Test:2, Cost:67.78380
Pass:3, Cost:69.45305
Test:3, Cost:73.47388
Pass:4, Cost:47.74258
Test:4, Cost:4.58843
Pass:5, Cost:54.38498
Test:5, Cost:0.64186
Pass:6, Cost:47.80602
Test:6, Cost:19.92364
Pass:7, Cost:10.93918
Test:7, Cost:8.29712
Pass:8, Cost:60.57377
Test:8, Cost:11.69418
Pass:9, Cost:20.18164
Test:9, Cost:8.46512
Pass:10, Cost:29.85230
Test:10, Cost:5.16454
Pass:11, Cost:30.97670
Test:11, Cost:16.58811
Pass:12, Cost:37.52143
Test:12, Cost:1.82244
Pass:13, Cost:46.93736
Test:13, Cost:71.07934
Pass:14, Cost:63.60490
Test:14, Cost:26.35772
Pass:15, Cost:82.31865
Test:15, Cost:23.99706
Pass:16, Cost:48.71814
Test:16, Cost:8.01992
Pass:17, Cost:44.36085
Test:17, Cost:17.44140
Pass:18, Cost:11.30281
Test:18, Cost:1.62714
Pass:19, Cost:60.68908
Test:19, Cost:4.30170
Pass:20, Cost:62.06501
Test:20, Cost:64.06610
Pass:21, Cost:54.52251
Test:21, Cost:5.09180
Pass:22, Cost:52.06496
Test:22, Cost:20.40379
Pass:23, Cost:30.91603
Test:23, Cost:69.02875
Pass:24, Cost:64.99430
Test:24, Cost:43.75908
Pass:25, Cost:14.07041
Test:25, Cost:5.24547
Pass:26, Cost:9.66833
Test:26, Cost:18.56830
Pass:27, Cost:12.60221
Test:27, Cost:3.25742
Pass:28, Cost:24.31591
Test:28, Cost:0.20956
Pass:29, Cost:66.55178
Test:29, Cost:1.24070
Pass:30, Cost:56.40113
Test:30, Cost:25.30655
Pass:31, Cost:68.57676
Test:31, Cost:10.88470
Pass:32, Cost:18.08781
Test:32, Cost:7.39040
Pass:33, Cost:27.51897
Test:33, Cost:0.03244
Pass:34, Cost:110.05601
Test:34, Cost:4.69617
Pass:35, Cost:37.68198
Test:35, Cost:9.44382
Pass:36, Cost:37.43859
Test:36, Cost:8.86635
Pass:37, Cost:14.44765
Test:37, Cost:4.23983
Pass:38, Cost:35.20084
Test:38, Cost:8.64431
Pass:39, Cost:26.76350
Test:39, Cost:3.91833
Pass:40, Cost:45.94382
Test:40, Cost:1.04174
Pass:41, Cost:87.70818
Test:41, Cost:1.43486
Pass:42, Cost:16.40131
Test:42, Cost:1.03596
Pass:43, Cost:27.95711
Test:43, Cost:1.49454
Pass:44, Cost:38.05587
Test:44, Cost:35.52960
Pass:45, Cost:92.70495
Test:45, Cost:2.14922
Pass:46, Cost:25.43381
Test:46, Cost:18.75169
Pass:47, Cost:57.83112
Test:47, Cost:2.36115
Pass:48, Cost:76.60567
Test:48, Cost:4.85030
Pass:49, Cost:15.27220
Test:49, Cost:12.94289
save models to /home/aistudio/work/fit_a_line.inference.model
'''
(1)创建预测用的Executor
infer_exe = fluid.Executor(place) #创建推测用的executor
inference_scope = fluid.core.Scope() #Scope指定作用域
(2)可视化真实值与预测值方法定义
infer_results=[]
groud_truths=[]
#绘制真实值和预测值对比图
def draw_infer_result(groud_truths,infer_results):
title='Boston'
plt.title(title, fontsize=24)
x = np.arange(1,20)
y = x
plt.plot(x, y)
plt.xlabel('ground truth', fontsize=14)
plt.ylabel('infer result', fontsize=14)
plt.scatter(groud_truths, infer_results,color='green',label='training cost')
plt.grid()
plt.show()
(3)开始预测
通过fluid.io.load_inference_model,预测器会从params_dirname中读取已经训练好的模型,来对从未遇见过的数据进行预测。
with fluid.scope_guard(inference_scope):#修改全局/默认作用域(scope), 运行时中的所有变量都将分配给新的scope。
#从指定目录中加载 推理model(inference model)
[inference_program, #推理的program
feed_target_names, #需要在推理program中提供数据的变量名称
fetch_targets] = fluid.io.load_inference_model(#fetch_targets: 推断结果
model_save_dir, #model_save_dir:模型训练路径
infer_exe) #infer_exe: 预测用executor
#获取预测数据
infer_reader = paddle.batch(paddle.dataset.uci_housing.test(), #获取uci_housing的测试数据
batch_size=200) #从测试数据中读取一个大小为200的batch数据
#从test_reader中分割x
test_data = next(infer_reader())
test_x = np.array([data[0] for data in test_data]).astype("float32")
test_y= np.array([data[1] for data in test_data]).astype("float32")
results = infer_exe.run(inference_program, #预测模型
feed={
feed_target_names[0]: np.array(test_x)}, #喂入要预测的x值
fetch_list=fetch_targets) #得到推测结果
print("infer results: (House Price)")
for idx, val in enumerate(results[0]):
print("%d: %.2f" % (idx, val))
infer_results.append(val)
print("ground truth:")
for idx, val in enumerate(test_y):
print("%d: %.2f" % (idx, val))
groud_truths.append(val)
draw_infer_result(groud_truths,infer_results)
'''
infer results: (House Price)
0: 13.37
1: 14.01
2: 12.89
3: 16.41
4: 14.07
5: 16.03
6: 15.70
7: 15.11
8: 10.53
9: 14.13
10: 9.52
11: 13.09
12: 14.25
13: 12.76
14: 13.51
15: 15.03
16: 16.44
17: 16.02
18: 16.26
19: 14.25
20: 15.11
21: 13.39
22: 15.88
23: 15.59
24: 14.87
25: 14.18
26: 15.85
27: 15.83
28: 17.18
29: 15.79
30: 15.55
31: 14.51
32: 15.02
33: 13.01
34: 11.83
35: 14.49
36: 14.71
37: 15.80
38: 16.16
39: 15.97
40: 13.99
41: 14.01
42: 15.88
43: 16.19
44: 15.90
45: 15.68
46: 15.49
47: 16.38
48: 16.28
49: 17.34
50: 15.19
51: 15.40
52: 14.50
53: 14.87
54: 16.18
55: 16.62
56: 16.46
57: 16.91
58: 17.02
59: 17.77
60: 17.50
61: 16.98
62: 15.07
63: 15.45
64: 16.31
65: 17.05
66: 16.87
67: 17.59
68: 17.66
69: 18.70
70: 15.45
71: 15.00
72: 16.57
73: 13.92
74: 16.23
75: 17.31
76: 18.37
77: 19.28
78: 19.70
79: 18.27
80: 17.64
81: 18.45
82: 17.03
83: 17.92
84: 15.91
85: 14.56
86: 13.11
87: 16.35
88: 17.45
89: 22.19
90: 22.33
91: 21.51
92: 19.85
93: 21.58
94: 22.17
95: 21.16
96: 21.67
97: 23.08
98: 22.60
99: 23.90
100: 23.55
101: 22.63
ground truth:
0: 8.50
1: 5.00
2: 11.90
3: 27.90
4: 17.20
5: 27.50
6: 15.00
7: 17.20
8: 17.90
9: 16.30
10: 7.00
11: 7.20
12: 7.50
13: 10.40
14: 8.80
15: 8.40
16: 16.70
17: 14.20
18: 20.80
19: 13.40
20: 11.70
21: 8.30
22: 10.20
23: 10.90
24: 11.00
25: 9.50
26: 14.50
27: 14.10
28: 16.10
29: 14.30
30: 11.70
31: 13.40
32: 9.60
33: 8.70
34: 8.40
35: 12.80
36: 10.50
37: 17.10
38: 18.40
39: 15.40
40: 10.80
41: 11.80
42: 14.90
43: 12.60
44: 14.10
45: 13.00
46: 13.40
47: 15.20
48: 16.10
49: 17.80
50: 14.90
51: 14.10
52: 12.70
53: 13.50
54: 14.90
55: 20.00
56: 16.40
57: 17.70
58: 19.50
59: 20.20
60: 21.40
61: 19.90
62: 19.00
63: 19.10
64: 19.10
65: 20.10
66: 19.90
67: 19.60
68: 23.20
69: 29.80
70: 13.80
71: 13.30
72: 16.70
73: 12.00
74: 14.60
75: 21.40
76: 23.00
77: 23.70
78: 25.00
79: 21.80
80: 20.60
81: 21.20
82: 19.10
83: 20.60
84: 15.20
85: 7.00
86: 8.10
87: 13.60
88: 20.10
89: 21.80
90: 24.50
91: 23.10
92: 19.70
93: 18.30
94: 21.20
95: 17.50
96: 16.80
97: 22.40
98: 20.60
99: 23.90
100: 22.00
101: 11.90
'''