基于CNN的婴儿睡觉状态识别(上)—模型训练

前言

   最近在和同学做一个智能婴儿床的创业项目,项目涉及到了对婴儿的睡眠状态的识别,整个流程下来相当于一次简单的CNN图像分类,虽然只是个Toy Model,但作为练手也是很有收获的,识别内容主要有两点:

1、很多婴儿在睡觉时候会乱动踢掉被子,识别婴儿有没有盖着被子反馈给温控器,如果被子被踢掉了,就相应升高温度。

2、婴儿长时间不换睡姿容易得扁头症,识别婴儿的睡姿,若持续时间太长则辅助婴儿翻身。

扁头症

以下主要介绍识别的实现流程,首先是采集训练集

训练集采集

训练集主要用模型娃娃,然后拿单反拍摄,效果如下

好像有点吓人

  为了识别结果的鲁棒性,训练集应该尽量全面一些,因此我们对于不同被子褶皱纹理、不同被子倾斜角度、婴儿不同睡姿:平躺、左侧躺、右侧躺、趴睡等不同睡姿,甚至于灯光的角度亮度都做了变化,不盖被子状态也是同理,最终共拍摄不盖被子样本+盖被子样本共277张。

  但是显然,277张样本对于CNN来说不够多,过拟合的风险很高,因此需要数据增强。

数据增强

  所谓数据增强,就是把图片缩小一点,放大一点,旋转一点,翻转一下来扩充训练集,如果不这么做的话,过拟合的几率是非常大的,直接上代码

导入图片

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
img = Image.open('./baby.png')
img = np.array(img)
plt.imshow(img)
plt.show()

翻转

flipped_img = np.fliplr(img)
plt.imshow(flipped_img)
plt.show()

平移

for i in range(HEIGHT, 1, -1):
  for j in range(WIDTH):
     if (i < HEIGHT-20):
       img[j][i] = img[j][i-20]
     elif (i < HEIGHT-1):
       img[j][i] = 0
plt.imshow(img)
plt.show()

其余不再赘述,可自行查看相关博客。
最终图像增强了大概4倍,得到训练集共1300张左右。

训练集预处理

前处理主要包括两点:压缩图像,归一化,做完前处理后才能丢进网络训练。

图像压缩

  我们的单反非常牛啤,每张都有4K的分辨率(4096×2160)。。。 因此图像压缩是必要的,否则这么大的图像不知道训练到猴年马月了。

  通常训练的图像会把图像压缩至32X32的像素大小( 李飞飞的Imagenet好像就是这么做的)不过我们本身样本不多,而且网络不大,压到这么小会严重失真,因此我们最终把图像压缩到128X128像素大小,上代码

from PIL import Image
import os
from glob import glob
 
fpath = filepath+"baby"
size = (128, 128)   # 要调整成为的尺寸
files = glob( fpath + "**/*.JPG", recursive=True) + glob(fpath + "**/*.jpg", recursive=True)
total = len(files) #总文件数
cur = 1 #当前文件序号
print("共有" + str(total) + "个文件,开始处理")
print("-----------------------------------")
for infile in files:
    try:
       # 分离文件名和后缀
        print("进度:" + str(cur) + "/" + str(total) + "   " + infile)
        img = Image.open(infile) # 打开图片文件
        mg.thumbnail(size, Image.ANTIALIAS) # 使用抗锯齿模式生成缩略图(压缩图片)
        img.save(infile, "JPEG") # 自动覆盖源文件

        cur = cur + 1
 
    except OSError:
        print(infile + "文件错误,忽略")

我们来看下缩小后的效果

未压缩.png
压缩后.png

归一化

  归一化是通过特定操作,让输入等比例缩放或平移,最终保持在(0,1)的范围内。归一化非常有必要,否则很难收敛甚至不能收敛,归一化同时能有效增加收敛速度,提高模型精度。

未归一化:

归一化:


  可见归一化能有效防止训练时梯度走之子型,步长也能比较均等,对于训练的收敛是有帮助的。

  对于RGB图像类数据,每个像素点有RGB三通道,每个通道的值为0~255之间,通常图像归一化有两种方法:
   1.以0.5为中心的压缩方式,将每个像素的RGB值简单除以255,就能将所有值压缩到0~1的范围内了。
   2.以0为中心的压缩方式,将每个像素的RGB值除以127.5,这样值在0~2之间,之后再减去一个1,使得值的分布为(-1—1)之间。

  我采用的是方法1,方法1与2的区别应该只是表达习惯的区别,实际上对训练结果应当不影响。

Tensorflow下小型CNN模型建立

  以前做类似图像二分类的时候用过迁移学习,就是把VGG-16的网络最后几层包括softmax拆掉,补上两层全连接,再对全连接层的参数做训练即可。

  然而因为此次需要把模型导入至树莓派,内存不能超过500M,而VGG-16光是参数就好几个G了,所以需要自己写小型CNN来实现。

1、读取训练集

先前归一化后的训练集用numpy保存为了train_x.npy与train_y.npy两个文件,用Load指令加载到环境中,因为要采用Mini—batch的方式,对训练集作了打乱

train_x = np.load("C:/Users/Administrator/Desktop/fzdl/data/train_x.npy")
train_y = np.load("C:/Users/Administrator/Desktop/fzdl/data/train_y.npy")
index=np.arange(1108)
np.random.shuffle(index)
train_x = train_x[index]
train_y = train_y[index]
2、定义权重、偏置、卷积层、池化操作

唯一要注意的是权重要用tf.truncated_normal的方式来定义,以实现权重初始化的随机性,否则可能造成梯度无法下降导致训练失败。

卷积层与池化层都采用了same padding,即操作时会自动在图像外围补充一圈数字0,使得卷积或池化操作后图像规格不会小一圈;池化采用了常用的最大池化max_pool

def weight_variable(shape, name):
    initializer = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initializer, name = name)

def bias_variable(shape, name):
    initializer = tf.constant(0.1, shape=shape)
    return tf.Variable(initializer, name = name)

def conv2d(x, W):
    
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x, name):
    
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name = name)

3、搭建网络

来到最关键的部分,首先定义两个placeholder做参数传入,要记得给输入xs命名,因为以后单片机模型转化过程会需要输入节点名字

第一维参数定义成None,代表可以为任意的值,这样传多大的batch进去都不用回头修改,是比较方便的写法

xs = tf.placeholder(tf.float32, [None, 128, 128,3], name = 'input')   # 128x128
ys = tf.placeholder(tf.float32, [None, 2])

来到正式搭建网络步骤,我们将网络写成一个inference(x)的函数,规格为两层卷积,两层全连接,最后softmax输出。

激活函数统一采用relu,每层卷积操作后接最大池化,卷积层过度到全连接层要reshape一遍

def inference(x):
    
    W_conv1 = weight_variable([3, 3, 3, 16], 'W_conv1') # patch 5x5, in size 3, out size 16
    b_conv1 = bias_variable([16], 'b_conv1')
    h_conv1 = tf.nn.relu((conv2d(x, W_conv1) + b_conv1), name = 'conv1') # output size 128x128x16
    h_pool1 = max_pool_2x2(h_conv1, 'pool1')                                         # output size 64x64x16
    
    ## conv2 layer 
    W_conv2 = weight_variable([3, 3, 16, 32], 'W_conv2') # patch 5x5, in size 16, out size 32
    b_conv2 = bias_variable([32], 'b_conv2')
    h_conv2 = tf.nn.relu((conv2d(h_pool1, W_conv2) + b_conv2), name = 'conv2') # output size 64x64x32
    h_pool2 = max_pool_2x2(h_conv2, name = 'pool2')                                         # output size 32x32x32
    
    ## flatten
    h_pool_flat = tf.reshape(h_pool2, [-1, 32 * 32 * 32])
    
    ## fc1 layer
    W_fc1 = weight_variable([32 * 32 * 32, 64], 'W_fc1')
    b_fc1 = bias_variable([64], 'b_fc1')
    h_fc1 = tf.nn.relu((tf.matmul(h_pool_flat, W_fc1) + b_fc1), name = 'fc1')
    h_fc1 = tf.nn.dropout(h_fc1, keep_prob = 0.9, name = 'dropout')
    
    ## fc2 layer
    W_fc2 = weight_variable([64, 2], 'W_fc2')
    b_fc2 = bias_variable([2], 'b_fc2')
    prediction = tf.nn.bias_add(tf.matmul(h_fc1, W_fc2), b_fc2, name = 'output')  
    prediction = tf.nn.softmax(prediction, name = 'softmax_output')
    
    return prediction

4、损失函数、优化器

损失函数为交叉熵,优化器为adam,很平常的选择,没什么好说的

prediction = inference(xs)
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels = ys, logits = prediction)
_loss = tf.reduce_mean(cross_entropy)
train_step = tf.train.AdamOptimizer(1e-4).minimize(_loss)

5、开始训练

首先定义准确率acc,再定义saver以保存训练好的模型,然后就把定义好的各种东西直接run就好了。

bool_acc = tf.equal(tf.arg_max(prediction, 1), tf.arg_max(ys, 1))
acc = tf.reduce_mean(tf.cast(bool_acc, tf.float32))
saver = tf.train.Saver()

with tf.Session() as sess:
    
    init = tf.global_variables_initializer()
    summary_writer = tf.summary.FileWriter(log_dir, sess.graph)
    sess.run(init)
    
    loss_vec = []
    acc_vec = []
    
    for i in range(max_epoch):
        start = time.clock()
        loss, accuracy, _ = sess.run([_loss, acc, train_step],feed_dict={xs: train_x, ys: train_y})
        
        loss_vec.append(loss)

        time_cost = time.clock() - start
        print ('step %d, loss = %.4f, accuracy = %.4f, it costs %g' % (i + 1, loss, accuracy, time_cost),'s')
            
        if i + 1 == max_epoch:
            accc = sess.run(_acc, feed_dict={xs: train_x, ys:train_y})
            print(accc)
            saver.save(sess, os.path.join(model_save_path,model_name), global_step = i)

另外,本来打算用mini-batch的,然而发现运行的飞快,根本用不着了,最后还是用fullbatch完成了训练,共训练250步,约220步的时候就达到收敛,Loss降到了0.01左右,accuracy达到了100%


训练结果

结语

  实际上还是缺了一些步骤,做得还不够讲究:
  比如没有防止过拟合的措施,应该加入dropout;
  没有分割出一部分交叉训练集,准确率虽然在训练集上很高,但是可能是过拟合造成的假象;
  按Andrew ng的说法,还应该调整各种参数去画学习曲线,然后觉得下一步的优化;

  不过由于项目时间有限,目前以能用就行作为唯一要求,而且在后续导入树莓派后发现运气很好的,并没有过拟合,识别效果非常好~~~

  不过未来有时间总之还是得继续优化的。。
模型训练的代码请移步至我的github:https://github.com/huangchuhccc/baby_recognition_by_Horned_sungem_using_tensorflow
后续嵌入树莓派+角峰鸟的流程请移步至下篇
//www.greatytc.com/p/dd190c5dcbcd
后续又实现了利用婴儿是否入睡的判定与微信的实时提醒,请移步这篇博客:
//www.greatytc.com/p/45918d2ed025

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345