# 查看当前挂载的数据集目录, 该目录下的变更重启环境后会自动还原
!ls /home/aistudio/data
# 查看工作区文件, 该目录下的变更将会持久保存. 请及时清理不必要的文件, 避免加载过慢.
!ls /home/aistudio/work
实践任务:手势识别
主要步骤
1.准备数据
2.配置网络
3.训练网络
4.模型评估
5.模型预测
!cd /home/aistudio/data/data23668 && unzip -qo Dataset.zip #手势识别数据集下载地址,下载后进行解压
!cd /home/aistudio/data/data23668/Dataset.zip && !rm -f */.DS_Store # 删除无关文件
#解压指令:!cd 压缩包所在文件夹 && unzip -qo 名称.zip
#导入所需的包
import os
import numpy as np
from PIL import Image
import paddle
import paddle.fluid as fluid
import paddle.fluid.layers as layers #Fluid.layers包含了配置网络所需的丰富API
from multiprocessing import cpu_count #查看当前机器cpu核心数量
from paddle.fluid.dygraph import Pool2D,Conv2D #飞桨卷积算子对应的API是paddle.fluid.dygraph.nn.Conv2D,池化算子是...
from paddle.fluid.dygraph import Linear #全连接层
注:
Conv2D
该函数用于二维卷积,有以下参数 - num_channels 输入通道数 - num_filters 滤波器个数 - filter_size 卷积核大小 - stride 步长大小 - padding 填充大小 - dilation 膨胀系数 - groups 分组数 - act 激活函数形式,比如act='relu',表示卷积操作后接一个relu激活,卷积核也称为滤波器
from paddle.fluid.dygraph import Conv2D,
conv = Conv2D(input_channel, output_channel, filter_size=3, stride=1)
BatchNorm
这是批量归一化层,paddle中bn层的使用与其他框架不太一样,它需要提供前面tensor的通道数 有以下常用的参数 - num_channels 需要归一化的Tensor通道数目,不一致会报错 - act 应用激活函数,跟前面一样,通常都是在BN后接激活函数,这样写比较方便 - momentum 用于指数移动平均估算均值和方差,默认值是0.9
from paddle.fluid.dygraph import BatchNorm
bn1 = BatchNorm(input_channel, act='relu')
Pool2D
这是二维池化层 其参数有以下 - pool_size 池化核大小 - pool_type 池化方式,有'avg', 'max',分别对应平均池化,最大池化 - pool_stride 池化步长大小 - pool_padding 池化填充大小 - global_pooling 是否全局池化,当为True,则忽略掉pool_size, pool_padding这两个参数,直接做一个全局池化
from paddle.fluid.dygraph import Pool2D
# 大小为3的池化层
pool = Pool2D(pool_size=3, pool_type='avg', pool_stride=1, global_pooling=False)
# 全局平均池化
pool2 = Pool2D(pool_type='avg', global_pooling=True)
准备数据 网上公开的手势图像数据集:
包含0-9共9种数字手势,共2073张手势图片
图片为3*100*100,RGB格式文件
本次实验中,取其中的10%作为测试集,90%作为训练集
遍历图片,根据文件夹名称,生成label
按1:9比例划分测试集和训练集,生成train_list 和 test_list
# 生成图像列表
data_path = '/home/aistudio/data/data23668/Dataset'
character_folders = os.listdir(data_path) #os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。
# print(character_folders)
if(os.path.exists('./train_data.txt')): #判断当前路径下文件是否存在
os.remove('./train_data.txt') #删除指定路径下的文件
if(os.path.exists('./test_data.txt')):
os.remove('./test_data.txt')
for character_folder in character_folders:
with open('./train_data.txt', 'a') as f_train: #以追加方式打开文件
with open('./test_data.txt', 'a') as f_test:
if character_folder == '.DS_Store':
continue
character_imgs = os.listdir(os.path.join(data_path,character_folder))
count = 0
for img in character_imgs:
if img == '.DS_Store':
continue
if count%10 == 0:
f_test.write(os.path.join(data_path,character_folder,img) + '\t' + character_folder + '\n') #将字符串写入文件夹
else:
f_train.write(os.path.join(data_path,character_folder,img) + '\t' + character_folder + '\n')
count +=1
print('列表已生成')
注:
os.path.join()函数:连接两个或更多的路径名组件
1.如果各组件名首字母不包含’/’,则函数会自动加上
2.第一个以”/”开头的参数开始拼接,之前的参数全部丢弃,当有多个时,从最后一个开始
3.如果最后一个组件为空,则生成的路径以一个’/’分隔符结尾
data_mapper(): 读取图片,对图片进行归一化处理,返回图片和 标签。
data_reader(): 按照train_list和test_list批量化读取图片。
train_reader(): 用于训练的数据提供器,乱序、按批次提供数据
test_reader():用于测试的数据提供器
# 定义data_reader
def data_mapper(sample):
img, label = sample
img = Image.open(img) 从路径中读取图片并返回一个PIL对象
img = img.resize((100, 100), Image.ANTIALIAS) #图片是3*100*100
img = np.array(img).astype('float32')
img = img.transpose((2, 0, 1)) #读出来的图像是rgb,rgb,rbg..., 转置为 rrr...,ggg...,bbb...(分开颜色通道)
img = img/255.0 #归一化到[0,1]
return img, label
def data_reader(data_list_path): #图像列表
def reader():
with open(data_list_path, 'r') as f:
lines = f.readlines() #依次读取每行
for line in lines:
img, label = line.split('\t') #split() 方法可以实现将一个字符串
#按照指定的分隔符切分成多个子串,这些子串会被保存到列表中(不包含分隔符),作为方法的返回值反馈回来。
yield img, int(label) #生成器 返回图像地址和标签
return paddle.reader.xmap_readers(data_mapper, reader, cpu_count(), 512)# 512是buffed_size
注:
- 调用一次np.transpose函数,即np.transpose(npimg,(1,2,0)),将npimg的数据格式由(channels,imagesize,imagesize)转化为(imagesize,imagesize,channels)。
- 写data_reader函数def data_loader(*):
def data_loader(*): ... def reader(*): ... return reader
通过使用飞桨提供的API
paddle.reader.xmap_readers
可以开启多线程读取数据。
# 用于训练的数据提供器
train_reader = paddle.batch(reader=paddle.reader.shuffle(reader=data_reader('./train_data.txt'), buf_size=256), batch_size=32)
# 用于测试的数据提供器
test_reader = paddle.batch(reader=data_reader('./test_data.txt'), batch_size=32)
#定义DNN网络
class MyDNN(fluid.dygraph.Layer):
def __init__(self):
super(MyDNN,self).__init__() #继承父类的_init_()方法
self.hidden1 = Linear(100,100,act='relu')
self.hidden2 = Linear(100,100,act='relu')
self.hidden3 = Linear(100,100,act='relu')
self.hidden4 = Linear(3*100*100,10,act='softmax')
def forward(self,input):
# print(input.shape)
x = self.hidden1(input)
# print(x.shape)
x = self.hidden2(x)
# print(x.shape)
x = self.hidden3(x)
x = fluid.layers.reshape(x, shape=[-1,3*100*100])
y = self.hidden4(x)
# print(y.shape)
return y
paddle.fluid.layers.reshape
(x, shape, actual_shape=None, act=None, inplace=False, name=None) 该OP在保持输入x
数据不变的情况下,改变x
的形状。-1 表示这个维度的值是从x的元素总数和剩余维度推断出来的。因此,有且只有一个维度可以被设置为-1。
#用动态图进行训练
with fluid.dygraph.guard(): #通过with语句创建一个dygraph运行的context,执行context代码。
model=MyDNN() #模型实例化
model.train() #训练模式
opt=fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())#优化器选用SGD随机梯度下降,学习率为0.001.
epochs_num=1 #迭代次数
for pass_num in range(epochs_num):
for batch_id,data in enumerate(train_reader()):
images=np.array([x[0].reshape(3,100,100) for x in data],np.float32)
labels = np.array([x[1] for x in data]).astype('int64')
labels = labels[:, np.newaxis] 添加一个维度
#将Numpy转换为DyGraph接收的输入.该函数实现从numpy.ndarray对象创建一个Variable类型的对象。
image=fluid.dygraph.to_variable(images)
label=fluid.dygraph.to_variable(labels)
predict=model(image)#训练
loss=fluid.layers.cross_entropy(predict,label)
avg_loss=fluid.layers.mean(loss) #获取avg_loss值
acc=fluid.layers.accuracy(predict,label)#计算精度
if batch_id!=0 and batch_id%15==0:
print("train_pass:{},batch_id:{},train_loss:{},train_acc:{}".format(pass_num,batch_id,avg_loss.numpy(),acc.numpy()))
avg_loss.backward() #使用backward()方法可以执行反向网络
opt.minimize(avg_loss) #调用定义的优化器对象的minimize方法进行参数更新
model.clear_gradients() #每一轮参数更新完成后我们调用clear_gradients()来重置梯度,以保证下一轮的正确性
fluid.save_dygraph(model.state_dict(),'MyDNN')#保存模型
注:
- class
paddle.fluid.optimizer.
SGDOptimizer
(learning_rate, parameter_list=None, regularization=None, name=None) parameter_list (list, 可选) - 指定优化器需要优化的参数。在动态图模式下必须提供该参数;在静态图模式下默认值为None,这时所有的参数都将被优化。 paddle.fluid.dygraph.
save_dygraph
(state_dict, model_path) ,(1)该接口会根据state_dict
的内容,自动给model_path
添加.pdparams
或者.pdopt
后缀, 生成model_path + ".pdparms"
或者model_path + ".pdopt"
文件。(2)state_dict
是通过 Layer 的state_dict()
方法得到的。
#模型评估
with fluid.dygraph.guard():
accs = []
model_dict, _ = fluid.load_dygraph('MyDNN')
model = MyDNN()
model.load_dict(model_dict) #加载模型参数
model.eval() #评估模式
for batch_id,data in enumerate(test_reader()):#测试集
images=np.array([x[0].reshape(3,100,100) for x in data],np.float32)
labels = np.array([x[1] for x in data]).astype('int64')
labels = labels[:, np.newaxis]
image=fluid.dygraph.to_variable(images)
label=fluid.dygraph.to_variable(labels)
predict=model(image)
acc=fluid.layers.accuracy(predict,label)
accs.append(acc.numpy()[0])
avg_acc = np.mean(accs)
print(avg_acc)
注:
paddle.fluid.dygraph.
load_dygraph
(model_path) 该接口会同时加载model_path + ".pdparams"
和model_path + ".pdopt"
中的内容。参数或优化器的dict-
返回: 两个
dict
,即从文件中恢复的参数dict
和优化器dict。
para_dict: 从文件中恢复的参数dict
,opti_dict: 从文件中恢复的优化器dict
#读取预测图像,进行预测
def load_image(path):
img = Image.open(path)
img = img.resize((100, 100), Image.ANTIALIAS)
img = np.array(img).astype('float32')
img = img.transpose((2, 0, 1))
img = img/255.0
print(img.shape)
return img
#构建预测动态图过程
with fluid.dygraph.guard():
infer_path = 'work/手势.JPG'
model=MyDNN()#模型实例化
model_dict,_=fluid.load_dygraph('MyDNN') #fluid.load_dygraph(model_path)
model.load_dict(model_dict)#加载模型参数
model.eval()#评估模式
infer_img = load_image(infer_path)
infer_img=np.array(infer_img).astype('float32')
infer_img=infer_img[np.newaxis,:, : ,:]
infer_img = fluid.dygraph.to_variable(infer_img)
result=model(infer_img) #forward方法会被自动调用
display(Image.open(infer_path))
print(np.argmax(result.numpy())) #np.argmax()取出数组中元素最大值对应的索引