tensorflow demo

一、项目简介:

1.项目环境:

ANDROID 编译环境:androidstudio 2.2.2

测试设备:armabi-v7a,测试机型为vivo x3L

Tensroflow环境:GPU版、Linux系统(ubuntu 16.04)

2.MNIST数据集简介:

      MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。MNIST数据集是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。在MNIST数据集中的每一张图片都代表了0-9中的一个数字。图片的大小都为28×28,且数字都会出现在图片的正中间。下图展示了一张数字图片及和它对应的像素矩阵:  


在上图的左侧显示了一张数字1的图片,而右侧显示了这个图片所对应的像素矩阵。MNIST数据集中图片的像素矩阵大小为28×28,但为了更清楚的展示,图5右侧显示的为14×14的矩阵。在Yann LeCun教授的网站中(http://yann.lecun.com/exdb/mnist)对MNIST数据集做出了详细的介绍。

3.卷积神经网络简介:

      卷积神经网络是近年发展起来,并引起广泛重视的一种高效识别方法。20世纪60年代,Hubel和Wiesel在研究猫脑皮层中用于局部敏感和方向选择的神经元时发现其独特的网络结构可以有效地降低反馈神经网络的复杂性,继而提出了卷积神经网络(Convolutional Neural Networks-简称CNN)。现在,CNN已经成为众多科学领域的研究热点之一,特别是在模式分类领域,由于该网络避免了对图像的复杂前期预处理,可以直接输入原始图像,因而得到了更为广泛的应用。 K.Fukushima在1980年提出的新识别机是卷积神经网络的第一个实现网络。随后,更多的科研工作者对该网络进行了改进。其中,具有代表性的研究成果是Alexander和Taylor提出的“改进认知机”,该方法综合了各种改进方法的优点并避免了耗时的误差反向传播。

一般地,CNN的基本结构包括两层,其一为特征提取层,每个神经元的输入与前一层的局部接受域相连,并提取该局部的特征。一旦该局部特征被提取后,它与其它特征间的位置关系也随之确定下来;其二是特征映射层,网络的每个计算层由多个特征映射组成,每个特征映射是一个平面,平面上所有神经元的权值相等。特征映射结构采用影响函数核小的sigmoid函数作为卷积网络的激活函数,使得特征映射具有位移不变性。此外,由于一个映射面上的神经元共享权值,因而减少了网络自由参数的个数。卷积神经网络中的每一个卷积层都紧跟着一个用来求局部平均与二次提取的计算层,这种特有的两次特征提取结构减小了特征分辨率。

CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。由于CNN的特征检测层通过训练数据进行学习,所以在使用CNN时,避免了显示的特征抽取,而隐式地从训练数据中进行学习;再者由于同一特征映射面上的神经元权值相同,所以网络可以并行学习,这也是卷积网络相对于神经元彼此相连网络的一大优势。卷积神经网络以其局部权值共享的特殊结构在语音识别和图像处理方面有着独特的优越性,其布局更接近于实际的生物神经网络,权值共享降低了网络的复杂性,特别是多维输入向量的图像可以直接输入网络这一特点避免了特征提取和分类过程中数据重建的复杂度。

4.项目简介:

      该项目就是通过Google公司提供的tensorflow开源平台利用mnist数据源中提供的数据集训练可以快速高效准确识别手写数字的模型框架,训练过程中我们利用多层卷积神经网络,每一层提取特征,通过60000组数据进行训练,最终用10000组案例进行检测准确度,等到训练到较为满意的准确度的时候,读取出模型,并最终形成所需要的模型。

然后我们将模型导入android设备,构建我所需的两种应用,一种是手写数字识别,通过监听手势的移动,android设备上已经构建好的画板上会形成描绘的数字,点击识别按钮,就可以识别用户手写的数字,已此为基础,构建了一个简易的计算器,通过每次手写一个数字来输入数值,一种是拍摄图片,识别图片中的数字,用户可以通过拍摄或者图片选择,选取要识别的数字和要识别的区域,应用会根据用户要求识别数字,得到一个应用认为准确率最高的数字,这两个应用都获得了较高的准确率。

二、项目前期准备

      1.softmax函数简介:

 softmax回归中,解决的是多分类问题(相对于 logistic 回归解决的二分类问题),类标   可以取   个不同的值(而不是 2 个)。因此,对于训练集  ,我们有  。(注意此处的类别下标从 1 开始,而不是 0)。

对于给定的输入x和输出有,K类分类器每类的概率为P(y=k|x,Θ),即


模型参数 θ(1),θ(2),…,θ(K)∈Rn ,矩阵θ以K*n的形式比较方便(其中n为输入x的维度或特征数)。

Softmax回归的代价函数为:


其中1{y(i)=k}为指示函数,即y(i)为k时其值为1,否则为0,或则说括号内的表达式为真时其值为1,否则为0。

梯度公式为:

2.dropout简介:

dropout是指在模型训练时随机让网络某些隐含层节点的权重不工作,不工作的那些节点可以暂时认为不是网络结构的一部分,但是它的权重得保留下来(只是暂时不更新而已),因为下次样本输入时它可能又得工作了。

简单说,我的理解就是每次每个神经元都有可能不表达,就像人类社会的基因一样,进行下一代繁殖的时候,有些基因被抛弃,有些基因被突出,从而产生了不同的个体。

3.tensorflow一些常用符简介:

Placeholder:占位符,主要用来输入或输出,简单点说就是先申请一定大小的内存,每次计算输入数据时将数据放入该中,方便计算。

Variable:变量,主要用于寄存W和B值,方便逆向修改参数等。

4.RELU简介:

      一种激活函数,全称是修正线性单元:


它的特点是被认为是双曲正切,就是说更加符合神经元信号激励原理。采用了rectified linear function作为activation function的unit被称为rectified linear unit。它的一个平滑解析函数为f(x)=ln(1+ex),被称之为softplus function,softplus的微分就是logistic function:f′(x)=ex/(ex+1)=1/(1+e−x)。另外一种函数叫做softmax function或者normalizedexponential是logistic function的一个泛化,如下: 

σ(z)j=ezj∑Kk=1ezk for j=1,...,K.

5.卷积简介:


卷积是求和(积分)。对于线性时不变的系统,输入可以分解成很多强度不同的冲激的和的形式(对于时域就是积分),那么输出也就是这些冲激分别作用到系统产生的响应的和(或者积分)。所以卷积的物理意义就是表达了时域中输入,系统冲激响应,以及输出之间的关系。

以下就是一个卷积的例子:输入像素为7*7*3,卷积核为3*3*3

      简单理解,卷积就是选择一个合适大小的卷积核,然后对像素进行加权相加,最终得到一个加权后的像素集合,对于卷积核可以设置(上下左右)步长,每次从起始点开始,对卷积核内的像素点进行加权(x*w+b)。

      在卷积操作中卷积核是可学习的参数,每层卷积的参数大小为D×F×F×K。在多层感知器模型中,神经元通常是全部连接,参数较多。而卷积层的参数较少,这也是由卷积层的主要特性即局部连接和共享权重所决定。

局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。在图像卷积操作中,即神经元在空间维度(spatial dimension,即上图示例H和W所在的平面)是局部连接,但在深度上是全部连接。对于二维图像本身而言,也是局部像素关联较强。这种局部连接保证了学习后的过滤器能够对于局部的输入特征有最强的响应。局部连接的思想,也是受启发于生物学里面的视觉系统结构,视觉皮层的神经元就是局部接受信息的。

权重共享:计算同一个深度切片的神经元时采用的滤波器是共享的。例如图4中计算o[:,:,0]的每个神经元的滤波器均相同,都为W0,这样可以很大程度上减少参数。共享权重在一定程度上讲是有意义的,例如图片的底层边缘特征与特征在图中的具体位置无关。但是在一些场景中是无意的,比如输入的图片是人脸,眼睛和头发位于不同的位置,希望在不同的位置学到不同的特征。请注意权重只是对于同一深度切片的神经元是共享的,在卷积层,通常采用多组卷积核提取不同特征,即对应不同深度切片的特征,不同深度切片的神经元权重是不共享。另外,偏重对同一深度切片的所有神经元都是共享的。

从中可以看出卷积是线性操作,并具有平移不变性(shift-invariant),平移不变性即在图像每个位置执行相同的操作。卷积层的局部连接和权重共享使得需要学习的参数大大减小,这样也有利于训练较大卷积神经网络。

6.池化简介:(最大值池化)


池化是非线性下采样的一种形式,主要作用是通过减少网络的参数来减小计算量,并且能够在一定程度上控制过拟合。通常在卷积层的后面会加上一个池化层。池化包括最大池化、平均池化等。其中最大池化是用不重叠的矩形框将输入层分成不同的区域,对于每个矩形框的数取最大值作为输出层,通过池化,可以减少参数,比如一个2*2的池化,可以将原参数减小1/2。

三、项目流程及代码

1.tensorflow训练模型流程:

1.a大致过程:

1.b解释:

1.输入数据为784的像素点坐标,其中像素点为归一化后的值,即像素点原来是0-255的灰度值,输入前要将像素点进行处理,255代表白色,令白色为0,黑色的像素值归一化为(255-灰度值)/255.0,从而实现归一化。

2.卷积中的SAME代表经过卷积后保存下来的像素点个数与之前相同,尽可能保存下像素特征,为此系统可以在外围添加0圈,保证像素转化后的相同性。

3.这个项目中通过softmax来获取属于每一个数字的概率值

 

其中x为权值,b为偏置量,w为权值。

4.项目的流程是:输入一个训练数量*784的2维张量,重新转化成一个数量*28*28*1的张量,1代表颜色,输入到第一层卷积层中,第一层卷积层为5*5的卷积核,要求输入1个通道(即为之前的颜色值),输出32个通道,简单说就是提取32个特征,每个卷积核都采用正态分布生成,并利用ReLu打破平衡,防止零梯度问题,卷积后得到28*28*32的张量,进行池化(最大池化2*2)进行特征参数的减少,最终得到一个14*14*32的张量,输入到第二个卷积层,采取同样的步骤进行卷积和池化,提取64个特征,得到7*7*64张量,输入到全连接层,采用全连接的方式进行连接得到1024张量,最后再加一个dropout层,一定几率不表现神经元,最终输出,以梯度反向传递的方式修改w和b值,不断训练,最终得到比较好的模型。

1.python代码及其注释:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

flags = tf.app.flags
FLAGS = flags.FLAGS

print(FLAGS.data_dir)
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)

# 权重初始化  利用ReLU 打破平衡,防止零梯度问题
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1) # 变量的初始值为截断正太分布
    return tf.Variable(initial)

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

def conv2d(x, W):

    tf.nn.conv2d功能:给定4维的input和filter,计算出一个2维的卷积结果
    前几个参数分别是input, filter, strides, padding, use_cudnn_on_gpu, ...
    input   的格式要求为一个张量,[batch, in_height, in_width, in_channels],批次数,图像高度,图像宽度,通道数
    filter  的格式为[filter_height, filter_width, in_channels, out_channels],滤波器高度,宽度,输入通道数,输出通道数
    strides 一个长为4的list. 表示每次卷积以后在input中滑动的距离
    padding 有SAME和VALID两种选项,表示是否要保留不完全卷积的部分。如果是SAME,则保留
    use_cudnn_on_gpu 是否使用cudnn加速。默认是True
    """
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
    """
    tf.nn.max_pool 进行最大值池化操作,而avg_pool 则进行平均值池化操作
    几个参数分别是:value, ksize, strides, padding,
    value:  一个4D张量,格式为[batch, height, width, channels],与conv2d中input格式一样
    ksize:  长为4的list,表示池化窗口的尺寸
    strides: 窗口的滑动值,与conv2d中的一样
    padding: 与conv2d中用法一样。
    """
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                          strides=[1, 2, 2, 1], padding='SAME')

sess = tf.InteractiveSession()

x = tf.placeholder(tf.float32, [None, 784],name=’input’)
x_image = tf.reshape(x, [-1,28,28,1]) #将输入按照 conv2d中input的格式来reshape,reshape

"""
# 第一层
# 卷积核(filter)的尺寸是5*5, 通道数为1,输出通道为32,即feature map 数目为32
# 又因为strides=[1,1,1,1] 所以单个通道的输出尺寸应该跟输入图像一样。即总的卷积输出应该为?*28*28*32
# 也就是单个通道输出为28*28,共有32个通道,共有?个批次
# 在池化阶段,ksize=[1,2,2,1] 那么卷积结果经过池化以后的结果,其尺寸应该是?*14*14*32
"""
W_conv1 = weight_variable([5, 5, 1, 32])  # 卷积是在每个5*5的patch中算出32个特征,分别是patch大小,输入通道数目,输出通道数目
b_conv1 = bias_variable([32]) #输出对应同样大小的偏置向量
x_image = tf.reshape(x,[-1,28,28,1]) #为了用这一层,把x变成一个4d向量,第二三维对应图片的宽高,最后一维代表颜色通道
h_conv1 = tf.nn.elu(conv2d(x_image, W_conv1) + b_conv1)  # x_image 和 权值向量进行卷积相乘,加上偏置,使用RELU激活函数,最后max maxpooling
h_pool1 = max_pool_2x2(h_conv1)

"""
# 第二层
# 卷积核5*5,输入通道为32,输出通道为64。
# 卷积前图像的尺寸为 14*14*32, 卷积后为14*14*64
# 池化后,输出的图像尺寸为7*7*64
"""
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.elu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

# 第三层 是个密集连接层,输入维数7*7*64, 输出维数为1024
# 把池化层输出的张量reshape成一个一些向量,乘上权重矩阵,加上偏置,使用RELU激活
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.elu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

# 这里使用了drop out,即随机安排一些cell输出值为0,可以防止过拟合
keep_prob = tf.placeholder(tf.float32) 
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# 第四层,输入1024维,输出10维,也就是具体的0~9分类
# 最后加上一个softmax层作为输出
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2,name=’output’) # 使用softmax作为多分类激活函数
y_ = tf.placeholder(tf.float32, [None, 10])

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1])) # 损失函数,交叉熵
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) # 使用adam优化
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) # 计算准确度
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
sess.run(tf.initialize_all_variables()) # 变量初始化
for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i%100 == 0:
        # print(batch[1].shape)
        train_accuracy = accuracy.eval(feed_dict={
            x:batch[0], y_: batch[1], keep_prob: 1.0})
        print("step %d, training accuracy %g"%(i, train_accuracy))
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

print("test accuracy %g"%accuracy.eval(feed_dict={
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))


1.d生成.so文件(利用官方提供的Bazel):

      命令行键入:

      在bazel-bin/tensorflow/contrib/android/目录下会生成:.so文件,直接复制粘贴到android项目中的


1.e生成.jar包

      命令行键入:

在bazel-bin/tensorflow/contrib/android/目录下会生成:.jar文件,直接复制粘贴到android项目中的


2.计算器设计流程:

2.a流程:

<1>.定义28*28的画板

<2>.用户通过手写,在画板上进行手写数字

<3>.获取手写数字的像素点28*28

<4>.把白色像素点标记0值,黑色像素点归一化,即是/255

<5>.输入tensorflow中处理,获得处理后的结果

2.b代码类说明:

画板组件:DrawModel、DrawRenderer、DrawView

识别组件:Classification、Classifier

视图类:MainActivity

2.c部分代码及其注释:

创建解析器:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//创建解析器
static public Classifier create(AssetManager assetManager, String modelPath, String labelPath,
                         int inputSize, String inputName, String outputName)
                         throws IOException {
  
    Classifier c = new Classifier();
  
    c.inputName = inputName;
    c.outputName = outputName;
  
    // 读入标签
    String labelFile = labelPath.split("file:///android_asset/")[1];
    c.labels = readLabels(c, assetManager, labelFile);
  
    //初始化接口
    c.tfHelper = new TensorFlowInferenceInterface();
    if (c.tfHelper.initializeTensorFlow(assetManager, modelPath) != 0) {
        throw new RuntimeException("TF initialization failed");
    }
  
    int numClasses = 10;
  
    c.inputSize = inputSize;
  
    // 输出初始化
    c.outputNames = new String[]{ outputName };
  
    c.outputName = outputName;
    c.output = new float[numClasses];
  
    return c;
}

 

跟随手势划线并记录:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

import java.util.ArrayList;

import java.util.List;

 

public class DrawModel {

 

    public static class LineElem {

        public float x;

        public float y;

 

        private LineElem(float x, float y) {

            this.x = x;

            this.y = y;

        }

    }

 

    public static class Line {

        private List<LineElem> elems = new ArrayList<>();

 

        private Line() {

        }

 

        private void addElem(LineElem elem) {

            elems.add(elem);

        }

 

        public int getElemSize() {

            return elems.size();

        }

 

        public LineElem getElem(int index) {

            return elems.get(index);

        }

    }

 

    private Line mCurrentLine;

 

    private int mWidth;  // pixel 宽度 = 28

    private int mHeight; // pixel 高度 = 28

 

    private List<Line> mLines = new ArrayList<>();

 

    public DrawModel(int width, int height) {

        this.mWidth = width;

        this.mHeight = height;

    }

 

    public int getWidth() {

        return mWidth;

    }

 

    public int getHeight() {

        return mHeight;

    }

 

    public void startLine(float x, float y) {

        mCurrentLine = new Line();

        mCurrentLine.addElem(new LineElem(x, y));

        mLines.add(mCurrentLine);

    }

 

    public void endLine() {

        mCurrentLine = null;

    }

 

    public void addLineElem(float x, float y) {

        if (mCurrentLine != null) {

            mCurrentLine.addElem(new LineElem(x, y));

        }

    }

 

    public int getLineSize() {

        return mLines.size();

    }

 

    public Line getLine(int index) {

        return mLines.get(index);

    }

 

    public void clear() {

        mLines.clear();

    }

}

 

一些名字说明:


      解析:

 

3.图片识别数字流程:

      3.a流程:

<1>.获取图片源文件(文件或者拍照)

<2>.剪切图片-》获取要识别的部分

<3>.获取图片RGB像素点

<4>.RGB像素点转化成灰度像素值(255)

<5>.导入tensorflow模型解析

3.b部分代码及其注释:

裁剪图片到我们需要的那部分,其它部分省略,避免不必要的像素计算:


按比例缩小图片的像素以达到压缩的目的:



多线程压缩图片质量


      RGB到灰度值的转化:

      公式=(R*299+G*587+B*114)/1000


3.c遇到的问题和解决办法:

      问题一:普通图片转换为灰度图片时,由于像素不清,图片容易整张都为有点黑色,不好过滤出突出数字?

      解决:利用两种方式,一种是浅层的优化,从灰度值60开始,到160,每次增长5个像素点,每次将大于设定灰度值的像素点设为255即全白,低于设定像素点的灰度值归一化,转化为(0,1)的值,经过这样处理,突出比较黑的部分,大致忽略掉偏白的部分,从而获得较好的效果,识别结果后选择多次识别中概率最大的那个数值。深度检测是指将每次增加5个像素增加到1个像素,识别区间扩大到10-240。

 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值