tensorflow入门

介绍

谷歌出品的第二代机器学习系统,
表达了高层次的机器学习计算.
(换言之比较方便,最方便的地方在于无需自行给出梯度的计算公式)
还可以运行一些非机器学习的程序.
对python的支持较为及时,C的支持稍微落后.

基础概念

  1. 图: 所有的操作放在一个由节点和边组成的有向图中

    data flow graph

  2. 节点: 意义上是各种数学操作,简称op(operation),也有的是源节点,不是操作而是常数

  3. 边:象征着数据的流动方向

  4. Tensor:节点间流动的数据,是多维数组

  5. session: 一个上下文(context),可以将图中的计算分配到CPU,GPU等设备中,
    因此图中定义的运算不会立即运行而是由session运行

基础操作

构建图

完整的构造方法

1
2
3
4
5
6
7
8
9
import tensorflow as tf

graph = tf.Graph()

with graph.as_default():
matrix1 = tf.constant([[3,1]])
matrix2 = tf.constant([[2],[2]])
product = tf.matmul(matrix1,matrix2)

tensorflow有一个默认的图,一般用不到多个图的情况下可以不在图的范围内定义代码

1
2
3
4
5
import tensorflow as tf

matrix1 = tf.constant([[3,1]])
matrix2 = tf.constant([[2],[2]])
product = tf.matmul(matrix1,matrix2)

运行图

使用Session对象的run方法,
指定一个节点的变量,运行以求值.
Session对象初始化时不指定参数则使用默认图.
sess需要关闭,有时也使用with环境来及时关闭session.
个人认为方式2更整洁.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a=tf.constant([1,2,3,4])
b=tf.constant([1,2,3,4])
result=a+b
sess=tf.Session()
print(sess.run(result))
sess.close

#输出 [2 4 6 8]

with tf.Session() as sess:
a=tf.constant([1,2,3,4])
b=tf.constant([1,2,3,4])
result=a+b
print(sess.run(result))

#输出 [2 4 6 8]

还可以指定具体的硬件,
如果系统安装有多个GPU,默认只使用第一个GPU,
可以使用语句指定GPU

1
2
3
4
5
6
with tf.Session() as sess:
with tf.device("/gpu:1")
matrix1 = tf.constant([[3,3]])
matrix1 = tf.constant([[2],[2]])
product = tf.matmul(matrix1,matrix2)
...

可以使用的设备举例

  • /cpu:0
  • /gpu:0
  • /gpu:1

交互使用

在脚本程序中使用session.run()方法,
在ipython等环境中可以使用 Tensor.eval(), Operation.run() 等方法交互地运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf

# sess = tf.InteractiveSession()

# graph difinition
x = tf.Variable([1,2])
a = tf.constant([3,3])
sub = tf.sub(x,a)

# run
x = initializer.run()
print sub.eval()

# sess.close()

fetch

即查看某一个变量的状态,可以同时查看多个

1
2
3
4
5
6
7
8
9
10
11
12
input1 = tf.constant(3.0)
input2 = tf.constant(2.0)
input3 = tf.constant(5.0)
intermed = tf.add(input2, input3)
mul = tf.mul(input1, intermed)

with tf.Session():
result = sess.run([mul, intermed])
print result

# 输出:
# [array([ 21.], dtype=float32), array([ 7.], dtype=float32)]

feed

可以在运算过程中替换Tensor
不过常常只是用于为placeholder数据类型填入数据

1
2
3
4
5
6
7
8
9
input1 = tf.placeholder(tf.types.float32)
input2 = tf.placeholder(tf.types.float32)
output = tf.mul(input1, input2)

with tf.Session() as sess:
print sess.run([output], feed_dict={input1:[7.], input2:[2.]})

# 输出:
# [array([ 14.], dtype=float32)]

变量类型

  • 常量(constant)
    定义后值和维度不可变,
    一定会成为图的节点
    过多的节点导致程序运行缓慢

    1
    2
    3
    4
    5
    6
    7
    a = tf.constant(2, tf.int16)
    b = tf.constant(4, tf.float32)
    c = tf.constant(8, tf.float32)

    h = tf.zeros([11], tf.int16)
    i = tf.ones([2,2], tf.float32)
    j = tf.zeros([1000,4,3], tf.float64)
  • 变量(Variable)
    定义后维度不可变,值可变.
    常常用于表示权重
    还可以包装一个常量变成变量?

    1
    2
    3
    4
    5
    6
    7
    8
    d = tf.Variable(2, tf.int16)
    e = tf.Variable(4, tf.float32)
    f = tf.Variable(8, tf.float32)

    k = tf.Variable(tf.zeros([2,2], tf.float32))
    l = tf.Variable(tf.zeros([5,6,5], tf.float32))

    w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
  • 占位符(placeholder)
    没有初始值,只会分配内存
    常常用于训练时一个批次的数据的位置
    会和feed~dict搭配~
    不过由于feed~dict的存在使程序的调试变得比较困难~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    w1=tf.Variable(tf.random_normal([1,2],stddev=1,seed=1))
    # 因为需要重复输入x,而每建一个x常量就会生成一个结点,
    # 计算图的效率会低所以使用占位符.
    x=tf.placeholder(tf.float32,shape=(1,2))
    x1=tf.constant([[0.7,0.9]])

    a=x+w1
    b=x1+w1

    sess=tf.Session()
    sess.run(tf.global_variables_initializer())

    #运行y时将占位符填上,feed_dict为字典,变量名不可变
    y_1=sess.run(a,feed_dict={x:[[0.7,0.9]]})
    y_2=sess.run(b)
    print(y_1)
    print(y_2)
    sess.close

常用的数据预处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Tool functions
def flatten_tf_array(array):
shape = array.get_shape().as_list()
return tf.reshape(array, [shape[0], shape[1] * shape[2] * shape[3]])


def one_hot_encode(np_array):
'''
input size : [1,N]
'''
return (np.arange(NUM_LABELS) == np_array[:, None]).astype(np.float32)


def one_hot_decode(code):
return np.argmax(code, axis=1)


def randomize(dataset, labels):
permutation = np.random.permutation(labels.shape[0])
shuffled_dataset = dataset[permutation, :, :, :]
shuffled_label = labels[permutation, :]
return shuffled_dataset, shuffled_label

常用的正确率处理函数

1
2
3
4
5
6
# accuracy function
def accuracy(predictions, labels):
'''
input size: [batch_size, num_label]
'''
return (100.0 * np.sum(one_hot_decode(predictions) == one_hot_decode(labels)) / labels.shape[0])

常用的批次数据生成函数

1
2
offset = (step * BATCH_SIZE) % train_labels.shape[0]
end = min(offset + BATCH_SIZE, train_labels.shape[0])

Hello Worlds

过程总结

  1. 定义常量参数,学习率,patch size等
  2. 权重的定义
  3. 网络结构的定义
  4. 在session中训练
    1. 准备批次数据
    2. 训练
    3. 到一定次数后显示当前状态

人工神经网络ANN

三层全连接网络,无bias

输入层2节点
隐藏层3节点
输出层1节点

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
import tensorflow as tf
from numpy.random import RandomState


batch_size=10

# 权重定义
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

# 网络结构/图的定义
x=tf.placeholder(tf.float32,shape=(None,2)) # None 可以根据batch 大小确定维度,在shape的一个维度上使用None
y=tf.placeholder(tf.float32,shape=(None,1))
a=tf.nn.relu(tf.matmul(x,w1)) #激活函数使用ReLU,以前使用sigmoid
yhat=tf.nn.relu(tf.matmul(a,w2))
cross_entropy=-tf.reduce_mean(y*tf.log(tf.clip_by_value(yhat,1e-10,1.0))) #定义交叉熵为损失函数
train_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy) #训练过程使用Adam算法最小化交叉熵


# 准备训练用数据
rdm=RandomState(1)
data_size=516
X=rdm.rand(data_size,2)
Y = [[int(x1+x2 < 1)] for (x1, x2) in X] # 认为x1+x2<1为正样本

# 开始计算
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 初始化很必要
print(sess.run(w1))
print(sess.run(w2))

# 嵌套了python代码
steps=11000
for i in range(steps):

#批次数据的准备
start = i * batch_size % data_size
end = min(start + batch_size,data_size)

# 真正的训练开始
sess.run(train_step,feed_dict={x:X[start:end],y:Y[start:end]})

# 为了显示计算过程
if i % 1000 == 0:
training_loss= sess.run(cross_entropy,feed_dict={x:X,y:Y})
print("在迭代 %d 次后,训练损失为 %g"%(i,training_loss))

ANN衰减的学习率

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data


#加载MNIST数据集
mnist = input_data.read_data_sets("./data/MNIST/", one_hot=True)


INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500

BATCH_SIZE = 100



# 模型相关的参数
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 10000
MOVING_AVERAGE_DECAY = 0.99



def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
# 使用滑动平均类
if avg_class == None:
layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
return tf.matmul(layer1, weights2) + biases2

else:

layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1))
return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2)


def train(mnist):
x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')

# 生成隐藏层的参数。
weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))

# 生成输出层的参数。
weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))


# 计算不含滑动平均类的前向传播结果
y = inference(x, None, weights1, biases1, weights2, biases2)


# 定义训练轮数及相关的滑动平均类
global_step = tf.Variable(0, trainable=False)
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
variables_averages_op = variable_averages.apply(tf.trainable_variables())
average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)

# 计算交叉熵及其平均值
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
cross_entropy_mean = tf.reduce_mean(cross_entropy)

# 定义交叉熵损失函数加上正则项为模型损失函数
regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
regularaztion = regularizer(weights1) + regularizer(weights2)
loss = cross_entropy_mean + regularaztion

# 设置指数衰减的学习率。
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
mnist.train.num_examples / BATCH_SIZE,
LEARNING_RATE_DECAY,
staircase=True)

# 随机梯度下降优化器优化损失函数
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

# 反向传播更新参数和更新每一个参数的滑动平均值
with tf.control_dependencies([train_step, variables_averages_op]):
train_op = tf.no_op(name='train')

# 计算准确度
correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 初始化会话并开始训练过程。
with tf.Session() as sess:
tf.global_variables_initializer().run()
validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
test_feed = {x: mnist.test.images, y_: mnist.test.labels}

# 循环地训练神经网络。
for i in range(TRAINING_STEPS):
if i % 1000 == 0:
validate_acc = sess.run(accuracy, feed_dict=validate_feed)
print("After %d training step(s), validation accuracy using average model is %g " % (i, validate_acc))

xs,ys=mnist.train.next_batch(BATCH_SIZE)
sess.run(train_op,feed_dict={x:xs,y_:ys})

test_acc=sess.run(accuracy,feed_dict=test_feed)
print(("After %d training step(s), test accuracy using average model is %g" %(TRAINING_STEPS, test_acc)))

类LeNet5网络

重要的是网络的结构,也会在另外的文件中说明
网络的结构(一张图片来说)

  1. 输入: [IMAGE~HEIGHT~,IMAGE~WIDTH~,IMAGE~DEPTH~]
  2. 第一层
    1. 卷积层: 卷积核大小: [PATCH~SIZE1~,PATCH~SIZE1~],数量:PATCH~DEPTH1~,滑动时的步长:宽高各1
    2. 激活: relu(conv + bias1),由于使用SAME的padding方法,会填充空白使卷积层的大小等于输入图像大小
    3. 池化: 池化窗口大小: 2 x 2,移动步长: 宽高各2 x 2,导致池化后大小只有原有的1/2.即 [IMAGE~HEIGHT~/2,IMAGE~WIDTH~/2,PATCH~DEPTH1~]
  3. 第二层
    1. 卷积层: 卷积核大小: PATCH~SIZE2~ x PATCH~SIZE2~,数量:PATCH~DEPTH2~,滑动时的步长:宽高各1
    2. 激活,所有层都需要激活
    3. 池化,结果大小为[IMAGE~HEIGHT~/4,IMAGE~WIDTH~/4,PATCH~DEPTH2~]
  4. 中间层
    将第二层得到的池化后的多层数据摊平
  5. 第三层
    1. 全连接层: HIDDEN~SIZE1个神经元~,计算则使用w3 * a3 + b3
    2. 激活
    3. 可以选择dropout
  6. 第四层
    1. 全连接层: HIDDEN~SIZE2个神经元~
    2. 激活
    3. 可选的dropout
  7. 输出层
    1. NUM~LABEL个神经元~
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
graph = tf.Graph()

with graph.as_default():
tf_train_dataset = tf.placeholder(tf.float32, shape=(BATCH_SIZE, IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_DEPTH))
tf_train_labels = tf.placeholder(tf.float32, shape=((BATCH_SIZE, NUM_LABELS)))
tf_validate_dataset = tf.constant(validate_dataset, tf.float32)
tf_test_dataset = tf.constant(test_dataset, tf.float32)

# set the variables before using it in model_lenet5 function
weights = {
'wc1': tf.Variable(tf.truncated_normal([PATCH_SIZE_1, PATCH_SIZE_1, IMAGE_DEPTH, PATCH_DEPTH_1], stddev=0.1)),
'wc2': tf.Variable(tf.truncated_normal([PATCH_SIZE_2, PATCH_SIZE_2, PATCH_DEPTH_1, PATCH_DEPTH_2], stddev=0.1)),
'wf3': tf.Variable(
tf.truncated_normal([IMAGE_HEIGHT // 4 * IMAGE_WIDTH // 4 * PATCH_DEPTH_2, NUM_HIDDEN_1], stddev=0.1)),
'wf4': tf.Variable(tf.truncated_normal([NUM_HIDDEN_1, NUM_HIDDEN_2], stddev=0.1)),
'wlo': tf.Variable(tf.truncated_normal([NUM_HIDDEN_2, NUM_LABELS], stddev=0.1))
}

bias = {
'bc1': tf.Variable(tf.zeros([PATCH_DEPTH_1])),
'bc2': tf.Variable(tf.constant(1.0, shape=[PATCH_DEPTH_2])),
'bf3': tf.Variable(tf.constant(1.0, shape=[NUM_HIDDEN_1])),
'bf4': tf.Variable(tf.constant(1.0, shape=[NUM_HIDDEN_2])),
'blo': tf.Variable(tf.constant(1.0, shape=[NUM_LABELS]))
}

logits = model_lenet5(tf_train_dataset)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels))
optimizer = tf.train.AdamOptimizer(LEARNING_RATE).minimize(loss)
train_prediction = tf.nn.softmax(logits)
validate_prediction = tf.nn.softmax(model_lenet5(tf_validate_dataset))
test_prediction = tf.nn.softmax(model_lenet5(tf_test_dataset))

其中的 model_lenet5 用于定义网络结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Model function
def model_lenet5(data):
layer1_conv = tf.nn.conv2d(data, weights['wc1'], [1, 1, 1, 1], padding='SAME')
layer1_actv = tf.nn.relu(layer1_conv + bias['bc1'])
layer1_pool = tf.nn.avg_pool(layer1_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

layer2_conv = tf.nn.conv2d(layer1_pool, weights['wc2'], [1, 1, 1, 1], padding='SAME')
layer2_actv = tf.nn.relu(layer2_conv + bias['bc2'])
layer2_pool = tf.nn.avg_pool(layer2_actv, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

flat_layer = flatten_tf_array(layer2_pool)

layer3_fccd = tf.matmul(flat_layer, weights['wf3']) + bias['bf3']
layer3_actv = tf.nn.relu(layer3_fccd)
#layer3_drop = tf.nn.dropout(layer3_actv,0.5)

layer4_fccd = tf.matmul(layer3_actv, weights['wf4']) + bias['bf4']
layer4_actv = tf.nn.relu(layer4_fccd)
# layer4_drop = tf.nn.dropout(layer4_actv,0.5)

logits = tf.matmul(layer4_actv, weights['wlo']) + bias['blo']

return logits

原版的LeNet5网络使用的是
sigmoid激活函数,不过在有些时候表脸不好

1
layer1_actv = tf.nn.simoid(layer1_conv + bias['bc1'])

遇到sigmod函数计算的结果超出了0到1.0范围时,可以使用函数截断

1
2
tf.clip_by_value(yhat,1e-10,1.0)
# 将结果截断到1e-10到1.0的范围内,以免J不好算

conv2d函数

参数:

  • 输入图像,一个张量,[batch size, height, weight, image depth]
  • 权重矩阵,主要表示卷积核的大小,是一个张量
    [patch height, patch weight, image depth, patch numbers]
  • 在图像维度([batch size, height, weight, image depth])上的步长
  • padding,处理边缘的方法,只有SAME和VALID两种方法,
    • SAME: 卷积的小窗口在图像上滑动时,使用0填充图像的边缘以时卷积图像与输入图像的大小相同
    • VALID:
      不在边缘填充0,得到的图像大小为:

      O=1+(WK+2P)/SO = 1 + (W-K + 2P)/S

      • 输出尺寸O
      • 图像尺寸W
      • 滤波器尺寸K
      • padding尺寸P
      • 步长S

avg~pool函数~

配置时与conv2d相似
参数:

  • 输入
  • 权重矩阵,主要表示池化窗口的大小,注意patch depth与输入匹配
  • 在四个维度上的步长
  • padding方法

网络结构和优化器的选择

  1. 不同网络的性能对比
  2. 不同优化器的性能对比

参考

  1. 机器之心tensorflowCNN教程
  2. W3Cschool教程