tensorflow IO操作
线程和队列
TensorFlow提供了两个类来帮助多线程的实现:tf.Coordinator和 tf.QueueRunner。Coordinator类可以用来同时停止多个工作线程并且向那个在等待所有工作线程终止的程序报告异常,QueueRunner类用来协调多个工作线程同时将多个张量推入同一个队列中。
队列概述
队列,如FIFOQueue和RandomShuffleQueue,在TensorFlow的张量异步计算时都非常重要。
例如,一个典型的输入结构:是使用一个RandomShuffleQueue来作为模型训练的输入:
- 多个线程准备训练样本,并且把这些样本推入队列。
- 一个训练线程执行一个训练操作
队列(queue)本身也是图中的一个节点,是一种有状态的节点,其他节点,如入队节点(enqueue)和出队节点(dequeue),可以修改它的内容。例如,入队节点可以把新元素插到队列末尾,出队节点可以把队列前面的元素删除。
import tensorflow as tf
# 模拟同步先处理数据,然后才能取数据训练
# 1.定义队列
# tf.FIFOQueue(队列中元素个数,dtype类型形状,name=)
Q = tf.FIFOQueue(3,tf.float32)
# 放数据
# Q.enqueue(vals,name=None),vals列表或元组,返回一个进队列操作
# [[0.1,0.2,0.3],]是为了将tensor转变为列表
enq_many = Q.enqueue_many([[0.1,0.2,0.3],])
# 2.定义一些读取数据,定义出队、+1,入队操作
out_q = Q.dequeue() # # 出队是从队首出,返回值为队首的元素
data = out_q +1
en_q = Q.enqueue(data) # # 入队是队尾
with tf.Session() as sess:
# 初始化队列
sess.run(enq_many)
# 处理数据
for i in range(100):
# tensorflow运行操作有依赖性
# 当执行 第2次 操作时,队列中的值变为 0.3,1.1,1.2...第100次时候 ,33.2 33.3 34.1
sess.run(en_q)
# 训练数据
for i in range(Q.size().eval()):
print(sess.run(Q.dequeue())) # 33.2 33.3 34.1
tf.QueueRunner
QueueRunner类会创建一组线程, 这些线程可以重复的执行Enquene操作, 他们使用同一个Coordinator来处理线程同步终止。此外,一个QueueRunner会运行一个closer thread,当Coordinator收到异常报告时,这个closer thread会自动关闭队列。
可以使用一个queue runner,来实现上述结构。 首先建立一个TensorFlow图表,这个图表使用队列来输入样本。增加处理样本并将样本推入队列中的操作。增加training操作来移除队列中的样本。
tf.Coordinator
Coordinator类用来帮助多个线程协同工作,多个线程同步终止。 其主要方法有:
- should_stop():如果线程应该停止则返回True。
- request_stop(): 请求该线程停止。
- join():等待被指定的线程终止。
首先创建一个Coordinator对象,然后建立一些使用Coordinator对象的线程。这些线程通常一直循环运行,一直到should_stop()返回True时停止。 任何线程都可以决定计算什么时候应该停止。它只需要调用request_stop(),同时其他线程的should_stop()将会返回True,然后都停下来。
异步执行队列
import tensorflow as tf
# 模拟异步子线程存入样本、读取样本
# 1.定义一个队列,1000
Q = tf.FIFOQueue(1000,tf.float32)
# 2.定义子线程要做的事情,循环 值 +1,放入队列当中
var = tf.Variable(0.0)
# 实现一个自增 tf.assig_add()
data = tf.assign_add(var,tf.constant(1.0))
en_q = Q.enqueue(data)
# 3.定义队列管理器op,指定子线程该做的事情
# 创建一个队列管理器,指定线程数,执行队列的操作
qr = tf.train.QueueRunner(Q,enqueue_ops=[en_q]*2)
init = tf.global_variables_initializer()
with tf.Session() as sess:
# 初始化变量
sess.run(init)
# 开启线程管理器,即生成一个线程协调器
coord = tf.train.Coordinator()
# 真正开启子线程 start=True启动线程, 启动线程执行操作
threads = qr.create_threads(sess,start=True)
# 主线程,不断读取数据训练
for i in range(10):
print(sess.run(Q.dequeue()))
# 主线程结束意味着Session()关闭,资源释放
# 回收子线程
coord.request_stop() # 请求其它线程终止
coord.join(threads) # 关闭线程
读取数据
小数量数据读取
这仅用于可以完全加载到存储器中的小的数据集有两种方法:
- 存储在常数中。
- 存储在变量中,初始化后,永远不要改变它的值。
使用常数更简单一些,但是会使用更多的内存,因为常数会内联的存储在数据流图数据结构中,这个结构体可能会被复制几次。
training_data = ...
training_labels = ...
with tf.Session():
input_data = tf.constant(training_data)
input_labels = tf.constant(training_labels)
要改为使用变量的方式,就需要在数据流图建立后初始化这个变量。
training_data = ...
training_labels = ...
with tf.Session() as sess:
data_initializer = tf.placeholder(dtype=training_data.dtype,
shape=training_data.shape)
label_initializer = tf.placeholder(dtype=training_labels.dtype,
shape=training_labels.shape)
input_data = tf.Variable(data_initalizer, trainable=False, collections=[])
input_labels = tf.Variable(label_initalizer, trainable=False, collections=[])
...
sess.run(input_data.initializer,
feed_dict={data_initializer: training_data})
sess.run(input_labels.initializer,
feed_dict={label_initializer: training_lables})
设定trainable=False可以防止该变量被数据流图的GraphKeys.TRAINABLE_VARIABLES收集,这样就不会在训练的时候尝试更新它的值;设定collections=[]可以防止GraphKeys.VARIABLES收集后做为保存和恢复的中断点。设定这些标志,是为了减少额外的开销
文件读取
标准TensorFlow格式
TensorFlow还提供了一种内置文件格式TFRecord,二进制数据和训练类别标签数据存储在同一文件。模型训练前图像等文本信息转换为TFRecord格式。TFRecord文件是protobuf格式。数据不压缩,可快速加载到内存。TFRecords文件包含 tf.train.Example protobuf,需要将Example填充到协议缓冲区,将协议缓冲区序列化为字符串,然后使用该文件将该字符串写入TFRecords文件
数据读取实现
文件队列生成函数
tf.train.string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, name=None)
产生指定文件张量
文件阅读器类
- class tf.TextLineReader
阅读文本文件逗号分隔值(CSV)格式 - tf.FixedLengthRecordReader
要读取每个记录是固定数量字节的二进制文件 - tf.TFRecordReader
读取TfRecords文件
解码
由于从文件中读取的是字符串,需要函数去解析这些字符串到张量
- tf.decode_csv(records,record_defaults,field_delim = None,name = None)将CSV转换为张量,与tf.TextLineReader搭配使用
- tf.decode_raw(bytes,out_type,little_endian = None,name = None) 将字节转换为一个数字向量表示,字节为一字符串类型的张量,与函数tf.FixedLengthRecordReader搭配使用
生成文件队列
将文件名列表交给tf.train.string_input_producer函数。string_input_producer来生成一个先入先出的队列,文件阅读器会需要它们来取数据。string_input_producer提供的可配置参数来设置文件名乱序和最大的训练迭代数,QueueRunner会为每次迭代(epoch)将所有的文件名加入文件名队列中,如果shuffle=True的话,会对文件名进行乱序处理。一过程是比较均匀的,因此它可以产生均衡的文件名队列。
这个QueueRunner工作线程是独立于文件阅读器的线程,因此乱序和将文件名推入到文件名队列这些过程不会阻塞文件阅读器运行。根据文件格式,选择对应的文件阅读器,然后将文件名队列提供给阅读器的 read 方法。阅读器的read方法会输出一个键来表征输入的文件和其中纪录(对于调试非常有用),同时得到一个字符串标量,这个字符串标量可以被一个或多个解析器,或者转换操作将其解码为张量并且构造成为样本。
# 读取CSV格式文件
# 1、构建文件队列
# 2、构建读取器,读取内容
# 3、解码内容
# 4、现读取一个内容,如果有需要,就批处理内容
import tensorflow as tf
import os
def readcsv_decode(filelist):
"""
读取并解析文件内容
:param filelist: 文件列表
:return: None
"""
# 把文件目录和文件名合并
flist = [os.path.join("./csvdata/",file) for file in filelist]
# 构建文件队列
file_queue = tf.train.string_input_producer(flist,shuffle=False)
# 构建阅读器,读取文件内容
reader = tf.TextLineReader()
key,value = reader.read(file_queue)
record_defaults = [["null"],["null"]] # [[0],[0],[0],[0]]
# 解码内容,按行解析,返回的是每行的列数据
example,label = tf.decode_csv(value,record_defaults=record_defaults)
# 通过tf.train.batch来批处理数据
example_batch,label_batch = tf.train.batch([example,label],batch_size=9,num_threads=1,capacity=9)
with tf.Session() as sess:
# 线程协调员
coord = tf.train.Coordinator()
# 启动工作线程
threads = tf.train.start_queue_runners(sess,coord=coord)
# 这种方法不可取
# for i in range(9):
# print(sess.run([example,label]))
# 打印批处理的数据
print(sess.run([example_batch,label_batch]))
coord.request_stop()
coord.join(threads)
return None
if __name__=="__main__":
filename_list = os.listdir("./csvdata")
readcsv_decode(filename_list)
每次read的执行都会从文件中读取一行内容,注意,(这与图片和TfRecords读取不一样),decode_csv操作会解析这一行内容并将其转为张量列表。如果输入的参数有缺失,record_default参数可以根据张量的类型来设置默认值。在调用run或者eval去执行read之前,你必须调用tf.train.start_queue_runners来将文件名填充到队列。否则read操作会被阻塞到文件名队列中有值为止。
读取bytes文件
import tensorflow as tf
import os
# 定义cifar的数据等命令行参数
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_string('cifar_dir','./data/summary/cifar','文件的目录')
tf.app.flags.DEFINE_string('cifar_tfrecords','./temp/summary/tfrecords/cifar.tfrecords','存入tfrecords文件的目录')
class CifarRead(object):
"""
完成读取二进制文件,写进tfrecords,读取tfrecords
"""
def __init__(self,filelist):
# 文件列表
self.file_list = filelist
# 读取图片的一些属性
self.height = 32
self.width = 32
self.channel = 3
self.label_bytes = 1 # 存储的每个二进制图片标签字节
self.image_bytes = self.height*self.width*self.channel # 存储的每个二进制图片的图像字节
self.bytes = self.label_bytes + self.image_bytes # 存储的每个二进制图片的总字节数
def read_and_decode(self):
# 1.构造文件队列
file_queue = tf.train.string_input_producer(self.file_list)
# 2.构造二进制文件读取器-需传入每个样本的字节数
reader = tf.FixedLengthRecordReader(self.bytes)
key,value= reader.read(file_queue)
# 3.解码内容-二进制文件内容解码
label_image = tf.decode_raw(value,tf.uint8)
# 4.分割出图片和标签数据,切出特征值和目标值
# tf.slice(目标, [start], [stop])
# 由于label_image 存储时候为uint8类型,计算时候需要int32类型,因此进行cast转换
labels = tf.cast(tf.slice(label_image,[0],[self.label_bytes]),tf.int32)
image = tf.slice(label_image,[self.label_bytes],[self.image_bytes])
# 5.可以对图片的特征数据进行形状改变,[3072]-->[32,32,3]
image_reshape = tf.reshape(image,[self.height,self.width,self.channel])
# 6.批处理
image_batch,label_batch = tf.train.batch([image_reshape,labels],num_threads=1,batch_size=10,capacity=10)
return image_batch,label_batch
def write_to_tfrecords(self,image_batch,label_batch):
"""
将图片的特征值和目标值存入tfrecords
:param image_batch:张图片的特征值
:param label_batch:图片目标值
:return:None
"""
# 1.构造一个tfrecords存储器
writer = tf.python_io.TFRecordWriter(FLAGS.cifar_tfrecords)
# 2.循环将所有样本写入文件,每张图片构造example协议
for i in range(10):
# 取出第i个图片的特征值和目标值
image = image_batch[i].eval().tostring()
label = label_batch[i].eval()[0]
# 构造样本的example协议
example = tf.train.Example(features = tf.train.Features(feature={
'image':tf.train.Feature(bytes_list = tf.train.BytesList(value=[image])),
'label':tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
}))
# 写入单独样本
writer.write(example.SerializeToString())
# 关闭
writer.close()
def read_from_tfrecords(self):
"""
:return:
"""
# 1.构造文件队列
file_queue = tf.train.string_input_producer([FLAGS.cifar_tfrecords])
# 2.构造文件阅读器,读取内容example
reader = tf.TFRecordReader()
key,value = reader.read(file_queue)
# 3.解析example
features = tf.parse_single_example(value,features={
'image':tf.FixedLenFeature([],tf.string),
'label':tf.FixedLenFeature([],tf.int64)
})
# 4.解码内容,读取类型为string需要解码,其他不需要
image = tf.decode_raw(features['image'],tf.uint8)
label = features['label']
# 固定图片形状,方便批处理
image_reshape = tf.reshape(image,[self.height,self.width,self.channel])
label = tf.cast(label,tf.int32)
# 5.批处理
image_batch,label_batch = tf.train.batch([image_reshape,label],num_threads=1,batch_size=10,capacity=10)
return image_batch,label_batch
if __name__=='__main__':
# 1.获取文件名+路径列表
file_name = os.listdir(FLAGS.cifar_dir)
file_list = [os.path.join(FLAGS.cifar_dir,file) for file in file_name if file[-3:]=='bin']
# 2.实例化对象
cr = CifarRead(file_list)
# 从文件中读取
# image_batch,label_batch = cr.read_and_decode()
# 从tfrecords中读取
image_batch,label_batch = cr.read_from_tfrecords()
# 3.开启会话
with tf.Session() as sess:
# 构造线程协调器
coord = tf.train.Coordinator()
# 开启子线程
threads = tf.train.start_queue_runners(sess,coord=coord)
# 存入tfrecords文件
# print('开始存储')
# cr.write_to_tfrecords(image_batch,label_batch)
# print('存储完毕')
# 打印结果
print(sess.run([image_batch,label_batch]))
# 关闭线程
coord.request_stop()
coord.join()
读取csv文件
import tensorflow as tf
import os
# 1.找到文件,构造一个列表
# 2.构造文件队列
# 3.构造阅读器,读取队列内容(一行)
# 4.解码内容
# 5.批处理
def csvread(filelist):
"""
读取csv文件
:param filelist:文件路径+名字的列表
:return:读取的内容
"""
# 2.构造文件队列
file_queue = tf.train.string_input_producer(filelist)
# 3.构造阅读器读取数据
reader = tf.TextLineReader()
key,value = reader.read(file_queue) # 返回key为文件名,value为文件内容
# 4.解码内容
# record_defaults指定每一个样本每一列类型,指定默认值
records = [['None'],['None']] # 默认该列为字符串类型,缺失值采用None替换
example,labels = tf.decode_csv(value,field_delim=',',record_defaults=records) # 几列用几个值接受返回值
# 5.批处理-读取多个数据
# batch(tensor,batch_size=从队列中读取批处理大小,num_thread=进入队列线程数量,capacity=队列中元素最大数量)
# 批处理大小和队列数据数量没有影响,只决定这一批次取多少数据
example_batch,lable_batch = tf.train.batch([example,labels],batch_size=9,num_threads=1,capacity=9)
return example_batch,lable_batch
if __name__ =='__main__':
# 1.找到文件,构造一个列表 路径+名字
file_name = os.listdir('./data/csvdata')
filelist = [os.path.join('./data/csvdata',file) for file in file_name]
example,labels = csvread(filelist=filelist)
# 开启会话
with tf.Session() as sess:
# 开启读取文件线程
# 定义一个线程协调器
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess,coord=coord)
# 打印读取内容
print(sess.run([example,labels]))
# 回收线程
coord.request_stop()
coord.join(threads)
图像操作
在图像数字化表示当中,分为黑白和彩色两种。在数字化表示图片的时候,有三个因素。分别是图片的长、图片的宽、图片的颜色通道数。那么黑白图片的颜色通道数为1,它只需要一个数字就可以表示一个像素位;而彩色照片就不一样了,它有三个颜色通道,分别为RGB,通过三个数字表示一个像素位。TensorFlow支持JPG、PNG图像格式,RGB、RGBA颜色空间。图像用与图像尺寸相同(heightwidthchnanel)张量表示。图像所有像素存在磁盘文件,需要被加载到内存。
图像大小压缩
大尺寸图像输入占用大量系统内存。训练CNN需要大量时间,加载大文件增加更多训练时间,也难存放多数系统GPU显存。大尺寸图像大量无关本征属性信息,影响模型泛化能力。最好在预处理阶段完成图像操作,缩小、裁剪、缩放、灰度调整等。图像加载后,翻转、扭曲,使输入网络训练信息多样化,缓解过拟合。Python图像处理框架PIL、OpenCV。TensorFlow提供部分图像处理方法。
- tf.image.resize_images 压缩图片导致定大小
图像数据读取实例
同样图像加载与二进制文件相同。图像需要解码。输入生成器(tf.train.string_input_producer)找到所需文件,加载到队列。tf.WholeFileReader 加载完整图像文件到内存,WholeFileReader.read 读取图像,tf.image.decode_jpeg 解码JPEG格式图像。图像是三阶张量。RGB值是一阶张量。加载图像格 式为[batch_size,image_height,image_width,channels]。批数据图像过大过多,占用内存过高,系统会停止响应。直接加载TFRecord文件,可以节省训练时间。支持写入多个样本
- tf.train.batch 读取指定大小(个数)的张量
- tf.train.shuffle_batch 乱序读取指定大小(个数)的张量
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
def read_picture(filelist):
"""
读取狗图片,并转换为tensor
:param filelist: 文件名+路径的列表
:return:每张图片张量
"""
# 1.构造文件队列
file_queue = tf.train.string_input_producer(filelist)
# 2.构造读取器-默认读取一张图片
reader = tf.WholeFileReader()
# 读取内容-返回key为文件名,value为文件内容
key,value = reader.read(file_queue)
# 3.对读取数据图片进行解码
image = tf.image.decode_jpeg(value)
# 4.进行批处理-统一图片大小
image_resize = tf.image.resize_images(image,[200,200])
# 注意:一定要把样本形状固定[200,200,3],在批处理时候,要求所有数据形状必须需定义
image_resize.set_shape([200,200,3])
# 进行批处理
image_batch = tf.train.batch([image_resize],batch_size=20,num_threads=1,capacity=20)
return image_batch
if __name__=='__main__':
# 1.找到文件,放入列表
file_name = os.listdir('./data/summary/images') # 返回文件名字列表
# 将文件名与路径拼接为列表
filelist = [os.path.join('./data/summary/images',file) for file in file_name]
image_batch = read_picture(filelist)
# 开启会话
with tf.Session() as sess:
# 定义一个线程协调器
coord = tf.train.Coordinator()
# 开启文件读取线程
threads = tf.train.start_queue_runners(sess,coord=coord)
# 打印读取内容
print(sess.run([image_batch]))
# 关闭线程
coord.request_stop()
coord.join()
读取TfRecords文件数据
#CIFAR-10的数据读取以及转换成TFRecordsg格式
#1、数据的读取
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_string("data_dir","./cifar10/cifar-10-batches-bin/","CIFAR数据目录")
tf.app.flags.DEFINE_integer("batch_size",50000,"样本个数")
tf.app.flags.DEFINE_string("records_file","./cifar10/cifar.tfrecords","tfrecords文件位置")
class CifarRead(object):
def __init__(self,filename):
self.filelist = filename
# 定义图片的长、宽、深度,标签字节,图像字节,总字节数
self.height = 32
self.width = 32
self.depth = 3
self.label_bytes = 1
self.image_bytes = self.height*self.width*self.depth
self.bytes = self.label_bytes + self.image_bytes
def readcifar_decode(self):
"""
读取数据,进行转换
:return: 批处理的图片和标签
"""
# 1、构造文件队列
file_queue = tf.train.string_input_producer(self.filelist)
# 2、构造读取器,读取内容
reader = tf.FixedLengthRecordReader(self.bytes)
key,value = reader.read(file_queue)
# 3、文件内容解码
image_label = tf.decode_raw(value,tf.uint8)
# 分割标签与图像张量,转换成相应的格式
label = tf.cast(tf.slice(image_label,[0],[self.label_bytes]),tf.int32)
image = tf.slice(image_label,[self.label_bytes],[self.image_bytes])
print(image)
# 给image设置形状,防止批处理出错
image_tensor = tf.reshape(image,[self.height,self.width,self.depth])
print(image_tensor.eval())
# depth_major = tf.reshape(image, [self.depth,self.height, self.width])
# image_tensor = tf.transpose(depth_major, [1, 2, 0])
# 4、处理流程
image_batch,label_batch = tf.train.batch([image_tensor,label],batch_size=10,num_threads=1,capacity=10)
return image_batch,label_batch
def convert_to_tfrecords(self,image_batch,label_batch):
"""
转换成TFRecords文件
:param image_batch: 图片数据Tensor
:param label_batch: 标签数据Tensor
:param sess: 会话
:return: None
"""
# 创建一个TFRecord存储器
writer = tf.python_io.TFRecordWriter(FLAGS.records_file)
# 构造每个样本的Example
for i in range(10):
print("---------")
image = image_batch[i]
# 将单个图片张量转换为字符串,以可以存进二进制文件
image_string = image.eval().tostring()
# 使用eval需要注意的是,必须存在会话上下文环境
label = int(label_batch[i].eval()[0])
# 构造协议块
example = tf.train.Example(features=tf.train.Features(feature={
"image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_string])),
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
})
)
# 写进文件
writer.write(example.SerializeToString())
writer.close()
return None
def read_from_tfrecords(self):
"""
读取tfrecords
:return: None
"""
file_queue = tf.train.string_input_producer(["./cifar10/cifar.tfrecords"])
reader = tf.TFRecordReader()
key, value = reader.read(file_queue)
features = tf.parse_single_example(value, features={
"image":tf.FixedLenFeature([], tf.string),
"label":tf.FixedLenFeature([], tf.int64),
})
image = tf.decode_raw(features["image"], tf.uint8)
# 设置静态形状,可用于转换动态形状
image.set_shape([self.image_bytes])
print(image)
image_tensor = tf.reshape(image,[self.height,self.width,self.depth])
print(image_tensor)
label = tf.cast(features["label"], tf.int32)
print(label)
image_batch, label_batch = tf.train.batch([image_tensor, label],batch_size=10,num_threads=1,capacity=10)
print(image_batch)
print(label_batch)
with tf.Session() as sess:
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess,coord=coord)
print(sess.run([image_batch, label_batch]))
coord.request_stop()
coord.join(threads)
return None
if __name__=="__main__":
# 构造文件名字的列表
filename = os.listdir(FLAGS.data_dir)
file_list = [os.path.join(FLAGS.data_dir, file) for file in filename if file[-3:] == "bin"]
cfar = CifarRead(file_list)
# image_batch,label_batch = cfar.readcifar_decode()
cfar.read_from_tfrecords()
with tf.Session() as sess:
# 构建线程协调器
coord = tf.train.Coordinator()
# 开启线程
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
# print(sess.run(image_batch))
# 存进文件
# cfar.convert_to_tfrecords(image_batch, label_batch)
coord.request_stop()
coord.join(threads)
神经网络与深度学习
深度学习(deep learning)是机器学习拉出的分支,它试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。深度学习是机器学习中一种基于对数据进行表征学习的方法。观测值(例如一幅图像)可以使用多种方式来表示,如每个像素强度值的向量,或者更抽象地表示成一系列边、特定形状的区域等。而使用某些特定的表示方法更容易从实例中学习任务(例如,人脸识别或面部表情识别)。深度学习的好处是用非监督式的特征学习和分层特征提取高效算法来替代手工获取特征。
至今已有数种深度学习框架,如深度神经网络、卷积神经网络和深度置信网络和递归神经网络已被应用计算机视觉、语音识别、自然语言处理、音频识别与生物信息学等领域并获取了极好的效果。
神经网络,一种启发自生物学的优美的编程范式,能够从观测到的数据中进行学习
图像分类
图像分类问题,这是从固定的一组分类中分配输入图像一个标签的任务。这是计算机视觉的核心问题之一,尽管它的简单性,有各种各样的实际应用。此外,正如我们将在后面看到的,许多其他看似不同的计算机视觉任务(如对象检测,分割)可以减少到图像分类。
图像分类模型需要单个图像,并将概率分配给4个标签{cat,dog,hat,mug}。如图所示,请记住,对于计算机,图像被表示为数字的一个大的3维数组。在这个例子中,猫图像是248像素宽,400像素高,并且有三个颜色通道红色,绿色,蓝色(或简称RGB)。因此,该图像由248 x 400 x 3数字组成,总共297,600个数字。每个数字是一个整数,范围从0(黑色)到255(白色)。我们的任务是把这个四分之一的数字转成一个单一的标签,如“猫”。
由于识别视觉概念(例如猫)的这个任务对于人类来说相对微不足道,所以值得从计算机视觉算法的角度考虑所涉及的挑战。
数据驱动的方法
我们可以如何编写一个可以将图像分类到不同类别的算法?与编写一个算法(例如排序数字列表)不同的是,如何编写用于识别图像中的猫的算法是不明显的。因此,我们不会试图指定每个类别的每一个代码直接在代码中,我们将为计算机提供许多示例每个类,然后开发学习算法,查看这些例子,并了解每个类的视觉外观。这种方法被称为数据驱动方法,因为它依赖于首先累积标记图像的训练数据集。
神经网络
人工神经网络( Artificial Neural Network, 简写为ANN)也简称为神经网络(NN)。是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)结构和功能的 计算模型。经典的神经网络结构包含三个层次的神经网络。分别输入层,输出层以及隐藏层。
其中每层的圆圈代表一个神经元,隐藏层和输出层的神经元有输入的数据计算后输出,输入层的神经元只是输入。
神经网络其实就是按照一定规则连接起来的多个神经元。
神经网络的特点
- 每个连接都有个权值
- 输入向量的维度和输入层神经元个数相同
- 同一层神经元之间没有连接,即层与层之间的神经元有连接,而层内之间的神经元没有连接
- 最后的输出结果对应的层也称之为全连接层
对于每一个样本来说,我们可以得到输入值x1,x2,x3,也就是节点1,2,3的输入值,那么对于隐层每一个神经元来说都对应有一个偏置项b,它和权重一起才是一个完整的线性组合
这样得出隐层的输出,也就是输出层的输入值
矩阵表示
分类问题的类别个数决定了你的输出层的神经元个数
神经网络的训练
可以说神经网络是一个模型,那么这些权值就是模型的参数,也就是模型要学习的东西。然而,一个神经网络的连接方式、网络的层数、每层的节点数这些参数,则不是学习出来的,而是人为事先设置的。对于这些人为设置的参数,我们称之为超参数。
前向传播
神经网络的训练类似于之前线性回归中的训练优化过程,可以分为这么几步:
- 计算结果误差
- 通过梯度下降找到误差最小
- 更新权重以及偏置项
这样就可以得出每一个参数在进行一次计算结果之后,通过特定的数学理论优化误差后会得出一个变化率α
反向传播
就是说通过误差最小得到新的权重等信息,然后更新整个网络参数。通常指定学习的速率λ(超参数),通过 变化率和学习速率 乘积,得出各个权重以及偏置项在一次训练之后变化多少,以提供给第二次训练使用
神经元就是要模拟人的神经元结构,神经元也称之为感知机
感知机(PLA: Perceptron Learning Algorithm))
感知机就是模拟这样的大脑神经网络处理数据的过程。感知机模型如下图:
感知机是一种最基础的分类模型,前半部分类似于回归模型。感知机最基础是这样的函数,而逻辑回归用的sigmoid。这个感知机具有连接的权重和偏置
神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。
任意事件发生的概率都在0和1之间,且总有某一个事件发生(概率的和为1)。如果将分类问题中“一个样例属于某一个类别”看成一个概率事件,那么训练数据的正确答案就符合一个概率分布。Softmax回归就是一个常用的方法。
softmax回归
Softmax回归将神经网络输出转换成概率结果
假设输出结果为:2.3, 4.1, 5.6
softmax的计算输出结果为:
y1_p = e^2.3/(e^2.3+e^4.1+e^5.6)
y1_p = e^4.1/(e^2.3+e^4.1+e^5.6)
y1_p = e^5.6/(e^2.3+e^4.1+e^5.6)
损失计算-交叉熵损失
由于经过softmax处理的输出值是概率并且还有标签。那么就需要一种更好的方法形容这个分类过程的好坏。这里就要用到交叉熵损失。
它表示的是目标标签值与经过权值求和过后的对应类别输出值
- tf.nn.softmax_cross_entropy_with_logits
- tf.nn.softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, dim=-1, name=None)
计算logits与labels之间的softmax交叉熵损失,该函数已经包含了softmax功能,logits和labels必须有相同的形状[batch_size, num_classes]和相同的类型(float16, float32, or float64)。
- labels one-hot编码过的标签值
- logits 没有log调用过的输入值
- 返回 交叉熵损失列表
tf.nn.softmax_cross_entropy_with_logits(labels=y_label, logits=y))
one-hot编码
为每个类别生成一个布尔列。这些列中只有一列可以为每个样本取值1。
API
tf.one_hot
- tf.one_hot(indices, depth, on_value=None, off_value=None, axis=None, dtype=None, name=None)
- indices 在独热编码中位置,即数据集标签
- depth 张量的深度,即类别数
indices = [0, 1, 2, 1]
depth = 3
[[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.],
[1., 1., 0.]]
为了能够衡量损失,目标值需要进行one-hot编码,能与概率值一一对应,如下图
0log(0.10)+0log(0.05)+0log(0.15)+0log(0.10)+0log(0.05)+0log(0.20)+1log(0.10)+0log(0.05)+0log(0.10)+0log(0.10)
上述的结果为1log(0.10),那么为了减少这一个样本的损失,提高对应目标值为1的位置输出概率大小,由于softmax公式影响,其它的概率必定会减少。只要这样进行调整就会预测成功。
梯度下降算法
目的:使损失函数的值找到最小值
方式:梯度下降
函数的梯度(gradient)指出了函数的最陡增长方向。梯度的方向走,函数增长得就越快。那么按梯度的负方向走,函数值自然就降低得最快了。模型的训练目标即是寻找合适的 w 与 b 以最小化代价函数值。假设 w 与 b 都是一维实数,那么可以得到如下的 J 关于 w 与 b 的图:
可以看到,此成本函数 J 是一个凸函数
参数w和b的更新公式为:
注:其中 α 表示学习速率,即每次更新的 w 的步伐长度。当 w 大于最优解 w′ 时,导数大于 0,那么 w 就会向更小的方向更新。反之当 w 小于最优解 w′ 时,导数小于 0,那么 w 就会向更大的方向更新。迭代直到收敛。
通过平面来理解梯度下降过程:
API
- tf.train.GradientDescentOptimizer
在使用梯度下降时候,一般需要指定学习速率
tf.train.GradientDescentOptimizer(0.5)
ANN网络分析-Mnist手写数字识别
Mnist数据集,数据集被分成两部分:60000行的训练数据集(mnist.train)和10000行的测试数据集(mnist.test)。每一个MNIST数据单元有两部分组成:一张包含手写数字的图片和一个对应的标签。我们把这些图片设为“xs”,把这些标签设为“ys”。训练数据集和测试数据集都包含xs和ys,比如训练数据集的图片是 mnist.train.images ,训练数据集的标签是 mnist.train.labels。
图片是黑白图片,每一张图片包含28像素X28像素。把这个数组展开成一个向量,长度是 28x28 = 784。因此,在MNIST训练数据集中,mnist.train.images 是一个形状为 [60000, 784] 的张量。
MNIST数据集的标签是介于0-9的数字,将标签转化为“one-hot vectors” 。一个onehot向量除了某一位数字是1以外,其余维度数字都是0,比如标签0将表示为([1,0,0,0,0,0,0,0,0,0]),标签3将表示为([0,0,0,1,0,0,0,0,0,0])。
因此, mnist.train.labels 是一个 [60000, 10] 的数字矩阵。
实现神经网络模型
- 获取数据
tensorflow提供给我们下载mnist数据集的接口,需要指定下载目录
from tensorflow.examples.tutorials.mnist import input_data
# 输入数据
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
- 计算数据
需要生成权重和偏置初始值,为了随时能够接收输入值,需要构造输入占位符
with tf.variable_scope('data'):
x = tf.placeholder(tf.float32,[None,784])
y_true = tf.placeholder(tf.int32,[None,10])
# 2.建立一个全连接层的神经网络 w[784,10],b[10]
with tf.variable_scope('fc_model'):
# 随机初始化权重和偏置
weight = tf.Variable(tf.random_normal([784,10],mean=0,stddev=1.0),name='weight')
bias = tf.Variable(tf.constant(0.0,shape=[10]),name='bias')
# 预测None结果的输出结果[None,784] * [784,10]+[10] =[None,10]
y_predict = tf.matmul(x,weight)+bias
- 梯度下降优化与训练
# 3.计算交叉熵损失
with tf.variable_scope('soft_loss'):
# 平均交叉熵损失
# softmax_cross_entropy_with_logits 返回一个列表
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true,logits=y_predict))
# 4.梯度下降求出损失
with tf.variable_scope('optimizer'):
optimizer_op = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(loss)
每次提供一部分值,然后多次进行训练optimizer_op,使之不断的更新
# 迭代训练,更新参数训练
for i in range(2000):
# 取出真实存在的特征值和目标值
mnist_x,mnist_y = mnist.train.next_batch(100)
# 运行optimizer_op
sess.run(optimizer_op,feed_dict={
x:mnist_x,
y_true:mnist_y
})
- 模型正确率评估
tf.argmax,这是一个非常有用的功能,它给出沿某个轴的张量中最高条目的索引
# 5.计算准确率
with tf.variable_scope('accuracy'):
# argmax求出y_true中最大的一个值的下标
equal_list = tf.equal(tf.argmax(y_true,1),tf.argmax(y_predict,1))
accuracy = tf.reduce_mean(tf.cast(equal_list,tf.float32))
- 完整代码1
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_integer('is_train',1,'指定模型是预测或训练')
def full_connected():
# 1.获取数据-建立数据的占位符,x[None,784],y_true[None,10]
mnist = input_data.read_data_sets('./data/mnist/input_data',one_hot=True)
with tf.variable_scope('data'):
x = tf.placeholder(tf.float32,[None,784])
y_true = tf.placeholder(tf.int32,[None,10])
# 2.建立一个全连接层的神经网络 w[784,10],b[10]
with tf.variable_scope('fc_model'):
# 随机初始化权重和偏置
weight = tf.Variable(tf.random_normal([784,10],mean=0,stddev=1.0),name='weight')
bias = tf.Variable(tf.constant(0.0,shape=[10]),name='bias')
# 预测None结果的输出结果[None,784] * [784,10]+[10] =[None,10]
y_predict = tf.matmul(x,weight)+bias
# 3.计算交叉熵损失
with tf.variable_scope('soft_loss'):
# 平均交叉熵损失
# softmax_cross_entropy_with_logits 返回一个列表
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true,logits=y_predict))
# 4.梯度下降求出损失
with tf.variable_scope('optimizer'):
optimizer_op = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(loss)
# 5.计算准确率
with tf.variable_scope('accuracy'):
# argmax求出y_true中最大的一个值的下标
equal_list = tf.equal(tf.argmax(y_true,1),tf.argmax(y_predict,1))
accuracy = tf.reduce_mean(tf.cast(equal_list,tf.float32))
# 初始化变量
init = tf.global_variables_initializer()
# 收集变量
tf.summary.scalar('loss',loss) # scalar单个数字值收集
tf.summary.scalar('accuracy',accuracy)
tf.summary.histogram('weight',weight) # 高维度变量收集
tf.summary.histogram('bias',bias)
# 合并变量
merged = tf.summary.merge_all()
# 创建一个saver保存模型
saver = tf.train.Saver()
# 开启会话
with tf.Session() as sess:
sess.run(init)
# 建立events
filewriter = tf.summary.FileWriter('./temp/mnist',graph=sess.graph)
if FLAGS.is_train == 1:
# 迭代训练,更新参数训练
for i in range(2000):
# 取出真实存在的特征值和目标值
mnist_x,mnist_y = mnist.train.next_batch(100)
# 运行optimizer_op
sess.run(optimizer_op,feed_dict={
x:mnist_x,
y_true:mnist_y
})
# 写入每步训练的值
summary = sess.run(merged,feed_dict={
x:mnist_x,
y_true:mnist_y
})
filewriter.add_summary(summary,i)
print('第%d次训练,准确率为%lf' % (i,sess.run(accuracy,feed_dict={
x:mnist_x,
y_true:mnist_y
})))
# 保存模型
saver.save(sess,'./temp/ckpt/mnist/mnist_model')
# 如果is_train 不为1,预测
else:
# 加载模型
saver.restore(sess,'./temp/ckpt/mnist/mnist_model')
for i in range(100):
# 每次测试一张图片
x_test,y_test = mnist.test.next_batch(1)
# 预测
print("第%d张图片,手写数字图片目标是:%d, 预测结果是:%d" % (
i,
tf.argmax(y_test, 1).eval(),
tf.argmax(sess.run(y_predict, feed_dict={x: x_test, y_true: y_test}), 1).eval()
))
return None
if __name__ =='__main__':
full_connected()
- 完整代码2
from __future__ import absolute_import
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_string('data_dir', '/tmp/tensorflow/mnist/input_data',
"""数据集目录""")
tf.app.flags.DEFINE_integer('max_steps', 2000,
"""训练次数""")
tf.app.flags.DEFINE_string('summary_dir', '/tmp/summary/mnist/train',
"""事件文件目录""")
def main(sess):
# 输入数据
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
# 建立输入数据占位符
x = tf.placeholder(tf.float32, [None, 784])
# 初始化权重和偏置
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
# 输出结果y
y = tf.matmul(x, W) + b
# 建立类别占位符
y_label = tf.placeholder(tf.float32, [None, 10])
# 计算交叉熵损失平均值
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_label, logits=y))
# 生成优化损失操作
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
# 比较结果
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_label, 1))
# 计算正确率平均值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.summary.scalar("loss",cross_entropy)
tf.summary.scalar("accuracy", accuracy)
tf.summary.histogram("W",W)
tf.global_variables_initializer().run()
# 合并所有摘要
merged = tf.summary.merge_all()
summary_writer = tf.summary.FileWriter(FLAGS.summary_dir, graph=sess.graph)
# 训练
for i in range(1000):
print("第%d次训练"%(i))
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, y_label: batch_ys})
print(sess.run(accuracy,feed_dict={x: batch_xs, y_label: batch_ys}))
summary = sess.run(merged,feed_dict={x: batch_xs, y_label: batch_ys})
summary_writer.add_summary(summary,i)
# 模型在测试数据的准确率
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_label, 1))
test_accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print("测试数据准确率:")
print(sess.run(test_accuracy, feed_dict={x: mnist.test.images,y_label: mnist.test.labels}))
if __name__ == '__main__':
with tf.Session() as sess:
main(sess)
卷积神经网络与图像识别
人工神经网络网络VS卷积神经网络
人工神经网络神经网络之所以不太适合图像识别任务,主要有以下几个方面的问题:
- 参数数量太多,在CIFAR-10(一个比赛数据集)中,图像只有大小为32x32x3(32宽,32高,3色通道),因此在正常神经网络的第一隐藏层中的单个完全连接的神经元将具有32 32 3 = 3072个权重。这个数量仍然是可控的,但显然这个完全连接的结构不会扩大到更大的图像。例如,一个更可观的大小的图像,例如200x200x3,会导致具有200 200 3 = 120,000重量的神经元。此外,几乎肯定会有几个这样的神经元,所以参数会加快!显然,这种完全连接是浪费的,而且大量的参数会很快导致过度配套。
- 没有利用像素之间的位置信息 对于图像识别任务来说,每个像素和其周围像素的联系是比较紧密的,和离得很远的像素的联系可能就很小了。如果一个神经元和上一层所有神经元相连,那么就相当于对于一个像素来说,把图像的所有像素都等同看待,这不符合前面的假设。当我们完成每个连接权重的学习之后,最终可能会发现,有大量的权重,它们的值都是很小的(也就是这些连接其实无关紧要)。努力学习大量并不重要的权重,这样的学习必将是非常低效的。
- 网络层数限制 网络层数越多其表达能力越强,但是通过梯度下降方法训练深度人工神经网络很困难,因为全连接神经网络的梯度很难传递超过3层。因此,不可能得到一个很深的全连接神经网络,也就限制了它的能力。
那么,卷积神经网络又是怎样解决这个问题的呢?主要有三个思路:
- 局部连接 这个是最容易想到的,每个神经元不再和上一层的所有神经元相连,而只和一小部分神经元相连。这样就减少了很多参数。
- 权值共享 一组连接可以共享同一个权重,而不是每个连接有一个不同的权重,这样又减少了很多参数。
- 下采样 可以使用Pooling来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果
卷积神经网络CNN
它们由具有学习权重和偏差的神经元组成。每个神经元接收一些输入,执行点积,并且可选地以非线性跟随它。整个网络仍然表现出单一的可微分评分功能:从一端的原始图像像素到另一个类的分数。并且在最后(完全连接)层上它们仍然具有损失函数(例如SVM / Softmax),并且我们为学习正常神经网络开发的所有技巧/技巧仍然适用。
CNN每一层都通过可微分的函数将一个激活的值转换为另一个,一般来说CNN具有卷积层,池化层和完全连接层FC(正如在常规神经网络中所见),在池化层之前一般会有个激活函数,我们将堆叠这些层,形成一个完整的架构。
CNN它将一个输入3D体积变换为输出3D体积,正常的神经网络不同,CNN具有三维排列的神经元:宽度,高度,深度。
卷积层
参数及结构
四个超参数控制输出体积的大小:过滤器大小,深度,步幅和零填充。得到的每一个深度也叫一个Feature Map。
卷积层的处理,在卷积层有一个重要的就是过滤器大小(需要自己指定),若输入值是一个[32x32x3]的大小(例如RGB CIFAR-10彩色图像)。如果每个过滤器(Filter)的大小为5×5,则CNN层中的每个Filter将具有对输入体积中的[5x5x3]区域的权重,总共5 5 3 = 75个权重(和+1偏置参数),输入图像的3个深度分别与Filter的3个深度进行运算。请注意,沿着深度轴的连接程度必须为3,因为这是输入值的深度,并且也要记住这只是一个Filter。
- 假设输入卷的大小为[16x16x20]。然后使用3x3的示例接收字段大小,CNN中的每个神经元现在将具有总共3 3 20 = 180个连接到输入层的连接。
卷积层的输出深度,那么一个卷积层的输出深度是可以指定的,输出深度是由你本次卷积中Filter的个数决定。加入上面我们使用了64个Filter,也就是[5,5,3,64],这样就得到了64个Feature Map,这样这64个Feature Map可以作为下一次操作的输入值。
卷积层的输出宽度,输出宽度可以通过特定算数公式进行得出,后面会列出公式。
卷积输出值的计算
用一个简单的例子来讲述如何计算卷积,然后,抽象出卷积层的一些重要概念和计算方法。
假设有一个55的图像,使用一个33的filter进行卷积,得到了到一个33的Feature Map,至于得到33大小,可以自己去计算一下。如下所示:
首先计算公式如下:
根据计算的例子,第一次:
第二次:
通过这样可以依次计算出Feature Map中所有元素的值。
步长
那么在卷积神经网络中有一个概念叫步长,也就是Filter移动的间隔大小。上面的计算过程中,步幅(stride)为1。步幅可以设为大于1的数。例如,当步幅为2时,可以看到得出2*2大小的Feature Map,发现这也跟步长有关。Feature Map计算如下:
外围补充与多Filter
每个卷积层可以有多个filter。每个filter和原始图像进行卷积后,都可以得到一个Feature Map。因此,卷积后Feature Map的深度(个数)和卷积层的filter个数是相同的。
以上就是卷积层的计算方法。这里面体现了局部连接和权值共享:每层神经元只和上一层部分神经元相连(卷积计算规则),且filter的权值对于上一层所有神经元都是一样的。
总结输出大小
新的激活函数-Relu
一般在进行卷积之后就会提供给激活函数得到一个输出值。不使用sigmoid,softmax,而使用Relu。该激活函数的定义是:
Relu函数如下:
特点
- 速度快 和sigmoid函数需要计算指数和倒数相比,relu函数其实就是一个max(0,x),计算代价小很多
- 稀疏性 通过对大脑的研究发现,大脑在工作的时候只有大约5%的神经元是激活的,而采用sigmoid激活函数的人工神经网络,其激活率大约是50%。有论文声称人工神经网络在15%-30%的激活率时是比较理想的。因为relu函数在输入小于0时是完全不激活的,因此可以获得一个更低的激活率。
Pooling计算
Pooling层主要的作用是下采样,通过去掉Feature Map中不重要的样本,进一步减少参数数量。Pooling的方法很多,最常用的是Max Pooling。Max Pooling实际上就是在nn的样本中取最大值,作为采样后的样本值。下图是22 max pooling:
除了Max Pooing之外,常用的还有Mean Pooling——取各样本的平均值。对于深度为D的Feature Map,各层独立做Pooling,因此Pooling后的深度仍然为D。
过拟合解决办法
Dropout
为了减少过拟合,在输出层之前加入dropout。用一个placeholder来代表一个神经元的输出在dropout中保持不变的概率。这样可以在训练过程中启用dropout,在测试过程中关闭dropout。 TensorFlow的tf.nn.dropout操作除了可以屏蔽神经元的输出外,还会自动处理神经元输出值的scale。所以用dropout的时候可以不用考虑scale。一般在全连接层之后进行Dropout
x= tf.nn.dropout(x_in, 1.0)
FC层
前面的卷积和池化相当于做特征工程,后面的全连接相当于做特征加权。最后的全连接层在整个卷积神经网络中起到“分类器”的作用
实例探究
卷积网络领域有几种架构,名称。最常见的是:
- LeNet。卷积网络的第一个成功应用是由Yann LeCun于1990年代开发的。其中最着名的是LeNet架构,用于读取邮政编码,数字等。
- AlexNet。该推广卷积网络计算机视觉中的第一部作品是AlexNet,由亚历克斯·克里维斯基,伊利亚·萨茨基弗和吉奥夫·欣顿发展。AlexNet在2012年被提交给ImageNet ILSVRC挑战,明显优于第二名(与亚军相比,前5名错误为16%,26%的错误)。该网络与LeNet具有非常相似的体系结构,但是更深入,更大和更具特色的卷积层叠在彼此之上(以前通常只有一个CONV层紧随着一个POOL层)。
- ZFNet。ILSVRC 2013获奖者是Matthew Zeiler和Rob Fergus的卷积网络。它被称为ZFNet(Zeiler&Fergus Net的缩写)。通过调整架构超参数,特别是通过扩展中间卷积层的大小,使第一层的步幅和过滤器尺寸更小,这是对AlexNet的改进。
- GoogleNet。ILSVRC 2014获奖者是Szegedy等人的卷积网络。来自Google。其主要贡献是开发一个初始模块,大大减少了网络中的参数数量(4M,与AlexNet的60M相比)。此外,本文使用ConvNet顶部的“平均池”而不是“完全连接”层,从而消除了大量似乎并不重要的参数。GoogLeNet还有几个后续版本,最近的是Inception-v4。
- VGGNet。2011年ILSVRC的亚军是来自Karen Simonyan和Andrew Zisserman的网络,被称为VGGNet。它的主要贡献在于表明网络的深度是良好性能的关键组成部分。他们最终的最佳网络包含16个CONV / FC层,并且吸引人的是,具有非常均匀的架构,从始至终只能执行3x3卷积和2x2池。他们预先训练的模型可用于Caffe的即插即用。VGGNet的缺点是评估和使用更多的内存和参数(140M)是更昂贵的。这些参数中的大多数都在第一个完全连接的层中,因此发现这些FC层可以在没有性能降级的情况下被去除
- ResNet。Kaiming He等人开发的残留网络 是ILSVRC 2015的获胜者。它具有特殊的跳过连接和批量归一化的大量使用。该架构在网络末端也缺少完全连接的层。读者也参考了凯明的演讲(视频,幻灯片),以及一些最近在火炬中复制这些网络的实验。ResNets目前是迄今为止最先进的卷积神经网络模型,并且是实际使用ConvNets的默认选择(截至2016年5月10日)。特别是,也看到最近从Kaiming He等人调整原有架构的发展。
下面就是VGGNet的结构:
INPUT: [224x224x3] memory: 224*224*3=150K weights: 0
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*3)*64 = 1,728
CONV3-64: [224x224x64] memory: 224*224*64=3.2M weights: (3*3*64)*64 = 36,864
POOL2: [112x112x64] memory: 112*112*64=800K weights: 0
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*64)*128 = 73,728
CONV3-128: [112x112x128] memory: 112*112*128=1.6M weights: (3*3*128)*128 = 147,456
POOL2: [56x56x128] memory: 56*56*128=400K weights: 0
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*128)*256 = 294,912
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
CONV3-256: [56x56x256] memory: 56*56*256=800K weights: (3*3*256)*256 = 589,824
POOL2: [28x28x256] memory: 28*28*256=200K weights: 0
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*256)*512 = 1,179,648
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [28x28x512] memory: 28*28*512=400K weights: (3*3*512)*512 = 2,359,296
POOL2: [14x14x512] memory: 14*14*512=100K weights: 0
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512] memory: 14*14*512=100K weights: (3*3*512)*512 = 2,359,296
POOL2: [7x7x512] memory: 7*7*512=25K weights: 0
FC: [1x1x4096] memory: 4096 weights: 7*7*512*4096 = 102,760,448
FC: [1x1x4096] memory: 4096 weights: 4096*4096 = 16,777,216
FC: [1x1x1000] memory: 1000 weights: 4096*1000 = 4,096,000
TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
TOTAL params: 138M parameters
与卷积网络一样,注意大多数内存(以及计算时间)都是在早期的CONV层中使用的,大多数参数都在最后的FC层。在这种特殊情况下,第一个FC层包含100M的权重,总共140M。
图像识别卷积网络实现案例
在Tensorflow中,神经网络相关的操作都在tf.nn模块中,包含了卷积、池化和损失等相关操作。
- 初始化卷积层权重
为了创建这个模型,需要创建大量的权重和偏置项。这个模型中的权重在初始化时应该加入少量的噪声来打破对称性以及避免0梯度。由于使用的是ReLU神经元,因此比较好的做法是用一个较小的正数来初始化偏置项,以避免神经元节点输出恒为0的问题(dead neurons)。为了不在建立模型的时候反复做初始化操作,定义两个函数用于初始化。
def weight_variables(shape):
"""
定义一个初始化权重的函数
:return:
"""
w = tf.Variable(tf.random_normal(shape,mean=0,stddev=1.0))
return w
def bias_variables(shape):
"""
随机初始化bias
:return:
"""
bias = tf.Variable(tf.constant(0.0,shape=shape))
return bias
-
卷积和池化
- tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
- input:A Tensor。必须是以下类型之一:float32,float64。
- filter:A Tensor。必须有相同的类型input。
- strides:列表ints。1-D长度4.每个尺寸的滑动窗口的步幅input。
- paddingA string来自:“SAME”, “VALID”。使用的填充算法的类型。
- use_cudnn_on_gpu:可选bool。默认为True。
- name:操作的名称(可选)。
- tf.nn.max_pool(value, ksize, strides, padding, name=None)
- value:A 4-D Tensor具有形状[batch, height, width, channels]和类型float32,float64,qint8,quint8,qint32。
- ksize:长度> = 4的int列表。输入张量的每个维度的窗口大小。
- strides:长度> = 4的int列表。输入张量的每个维度的滑动窗口的跨度。
- padding:一个字符串,或者’VALID’或’SAME’。填补算法。
- name:操作的可选名称。
TensorFlow在卷积和池化上有很强的灵活性。我们使用步长为2,1个零填充边距的模版。池化选择2*2大小。
tf.nn.conv2d(x_reshape,w_conv1,strides=[1,1,1,1],padding='SAME')
tf.nn.max_pool(x_relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
该案例使用两层卷积池化,两个全连接层以及添加一个解决过拟合方法
- tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
-
输入数据占位符准备
在训练的过程中需要一直提供数据,所以我们准备一些占位符,以备训练的时候填充。并且把x变成一个4d向量,其第2、第3维对应图片的宽、高,最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。
# 准备数据的占位符x[None,784] ,y[None,10]
x = tf.placeholder(tf.float32,[None,784])
y_true = tf.placeholder(tf.int32,[None,10])
x_reshape = tf.reshape(x,[-1,28,28,1]) # 这里-1代表None
- 第一层卷积加池化
卷积的权重张量形状是[5, 5, 1, 32],前两个维度是patch的大小,接着是输入的通道数目,最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量。 把x_image和权值向量进行卷积,加上偏置项,然后应用ReLU激活函数,最后进行max pooling。同样为了便于观察将高位变量W_con1、b_con1添加到事件文件中。
# 一卷积层-卷积:5*5*1,32个filter,strides=1-激活-池化
with tf.variable_scope('conv1'):
# 初始化权重
w_conv1 = weight_variables([5,5,1,32])
# 初始化偏置 [32]
b_conv1 = bias_variables([32])
# [None,28,28,1] -->[None,28,28,32]
x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape,w_conv1,strides=[1,1,1,1],padding='SAME') + b_conv1)
# 池化 2*2,strides2 [None,28,28,32] -->[None,14,14,32]
x_pool1 = tf.nn.max_pool(x_relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
- 第二层卷积加池化
为了构建一个更深的网络,我们会把几个类似的层堆叠起来。第二层中,接受上一层的输出32各通道,同样用5x5的的过滤器大小,指定输出64个通道。将高位变量W_con2、b_con2添加到事件文件中。
# 二卷积层-卷积:5*5*32,64个filter,strides=1-激活-池化
with tf.variable_scope('conv2'):
# 初始化权重[5,5,32,64] 偏置[64]
w_conv2 = weight_variables([5,5,32,64])
b_conv2 = bias_variables([64])
# 卷积、激活、池化
x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1,w_conv2,strides=[1,1,1,1],padding='SAME') + b_conv2)
# 池化 2*2,strides 2,[None,14,14,64]--->[None,7,7,64]
x_pool2 = tf.nn.max_pool(x_relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
- 全连接层
进行了两次卷积池化之后,数据量得到有效减少,并且也相当于进行了一些特征选择。那么现在就需要将所有数据进行权重乘积求和,来进行特征加权,这样就得到了输出结果,同时也可以提供给交叉熵进行优化器优化。将高位变量W_fc1、b_fc1、W_fc2、b_fc2添加到事件文件中。
# 全连接层 [None,7,7,64] ->[None,7*7*64]*[7*7*64,10]+[10] = [None,10]
with tf.variable_scope('fc_model'):
# 随机初始化权重和偏置
w_fc = weight_variables([7*7*64,10])
b_fc = bias_variables([10])
# 修改x_pool2形状 [None,7,7,64]-->[None,7*7*64]
x_fc_reshape = tf.reshape(x_pool2,[-1,7*7*64])
# 进行矩阵运算得出结果
y_predict = tf.matmul(x_fc_reshape,w_fc)+b_fc
- 计算损失
通过交叉熵进行计算
# 进行交叉熵损失计算
with tf.variable_scope('soft_loss'):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true,logits=y_predict))
- 完整代码一
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_integer('is_train',1,'指定模型是预测或训练')
def weight_variables(shape):
"""
定义一个初始化权重的函数
:return:
"""
w = tf.Variable(tf.random_normal(shape,mean=0,stddev=1.0))
return w
def bias_variables(shape):
"""
随机初始化bias
:return:
"""
bias = tf.Variable(tf.constant(0.0,shape=shape))
return bias
def model():
"""
自定义的卷积模型
:return:
"""
# 1.准备数据的占位符x[None,784] ,y[None,10]
with tf.variable_scope('data'):
x = tf.placeholder(tf.float32,[None,784])
y_true = tf.placeholder(tf.int32,[None,10])
# 2.一卷积层-卷积:5*5*1,32个filter,strides=1-激活-池化
with tf.variable_scope('conv1'):
# 初始化权重
w_conv1 = weight_variables([5,5,1,32])
# 初始化偏置 [32]
b_conv1 = bias_variables([32])
# 对X进行形状改变 [None,784]->[None,28,28,1],并激活
x_reshape = tf.reshape(x,[-1,28,28,1]) # 这里-1代表None
# [None,28,28,1] -->[None,28,28,32]
x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape,w_conv1,strides=[1,1,1,1],padding='SAME') + b_conv1)
# 池化 2*2,strides2 [None,28,28,32] -->[None,14,14,32]
x_pool1 = tf.nn.max_pool(x_relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
# 3.二卷积层-卷积:5*5*32,64个filter,strides=1-激活-池化
with tf.variable_scope('conv2'):
# 初始化权重[5,5,32,64] 偏置[64]
w_conv2 = weight_variables([5,5,32,64])
b_conv2 = bias_variables([64])
# 卷积、激活、池化
x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1,w_conv2,strides=[1,1,1,1],padding='SAME') + b_conv2)
# 池化 2*2,strides 2,[None,14,14,64]--->[None,7,7,64]
x_pool2 = tf.nn.max_pool(x_relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
# 全连接层 [None,7,7,64] ->[None,7*7*64]*[7*7*64,10]+[10] = [None,10]
with tf.variable_scope('fc_model'):
# 随机初始化权重和偏置
w_fc = weight_variables([7*7*64,10])
b_fc = bias_variables([10])
# 修改x_pool2形状 [None,7,7,64]-->[None,7*7*64]
x_fc_reshape = tf.reshape(x_pool2,[-1,7*7*64])
# 进行矩阵运算得出结果
y_predict = tf.matmul(x_fc_reshape,w_fc)+b_fc
return x,y_true,y_predict
def conv_fc():
# 获取真实数据
mnist = input_data.read_data_sets('./data/mnist/input_data',one_hot=True)
# 定义模型,得出输出
x,y_true,y_predict = model()
# 进行交叉熵损失计算
with tf.variable_scope('soft_loss'):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true,logits=y_predict))
# 梯度下降优化
with tf.variable_scope('optimizer'):
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(loss)
# 计算准确率
with tf.variable_scope('accuracy'):
equal_list = tf.equal(tf.argmax(y_true,1),tf.argmax(y_predict,1))
accuracy = tf.reduce_mean(tf.cast(equal_list,tf.float32))
init = tf.global_variables_initializer()
# 收集变量
tf.summary.scalar('loss',loss)
tf.summary.scalar('accuracy',accuracy)
merged = tf.summary.merge_all()
# 保存模型
saver = tf.train.Saver()
# 开启会话运行
with tf.Session() as sess:
sess.run(init)
# 实例化events
file_writer = tf.summary.FileWriter('./temp/mnist_CNN', graph=sess.graph)
# 循环训练
for i in range(1000):
# 取出真实的特征值和目标值
mnist_x,mnist_y = mnist.test.next_batch(50)
# 训练
sess.run(optimizer,feed_dict={x:mnist_x,y_true:mnist_y})
# 写入events
summary = sess.run(merged,feed_dict={x:mnist_x,y_true:mnist_y})
file_writer.add_summary(summary,i)
print('第%d次训练,准确率为%lf' % (i, sess.run(accuracy, feed_dict={x: mnist_x,y_true: mnist_y})))
# 保存模型
saver.save(sess, './temp/ckpt/mnist_CNN/mnist_CNN_model')
return None
if __name__=='__main__':
# full_connected()
conv_fc()
- 完整代码二(2个FC层,优化器采用AdamOptimizer)
from __future__ import absolute_import
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_string('data_dir', '/tmp/tensorflow/mnist/input_data',
"""数据集目录""")
tf.app.flags.DEFINE_integer('max_steps', 2000,
"""训练次数""")
tf.app.flags.DEFINE_string('summary_dir', '/tmp/summary/mnist/convtrain',
"""事件文件目录""")
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
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):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1], padding='SAME')
def reference():
"""
得到模型输出
:return: 模型输出与数据标签
"""
with tf.variable_scope("data") as scope:
y_label = tf.placeholder(tf.float32, [None, 10])
x = tf.placeholder(tf.float32, [None, 784])
x_image = tf.reshape(x, [-1, 28, 28, 1])
with tf.variable_scope("conv1") as scope:
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
tf.summary.histogram('W_con1', W_conv1)
tf.summary.histogram('b_con1', b_conv1)
with tf.variable_scope("conv2") as scope:
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
tf.summary.histogram('W_con2', W_conv2)
tf.summary.histogram('b_con2', b_conv2)
with tf.variable_scope("fc1") as scope:
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.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
tf.summary.histogram('W_fc1', W_fc1)
tf.summary.histogram('b_fc1', b_fc1)
with tf.variable_scope("fc2") as scope:
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
tf.summary.histogram('W_fc2', W_fc2)
tf.summary.histogram('b_fc2', b_fc2)
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
return y_conv,y_label,(x,y_label,keep_prob)
def total_loss(y_conv,y_label):
"""
计算损失
:param y_conv: 模型输出
:param y_label: 数据标签
:return: 返回损失
"""
with tf.variable_scope("train") as scope:
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_label, logits=y_conv))
tf.summary.scalar("loss", cross_entropy)
return cross_entropy
def train(loss,sess,placeholder):
"""
训练模型
:param loss: 损失
:param sess: 会话
:param placeholder: 占位符,用于填充数据
:return: None
"""
# 生成梯度下降优化器
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)
# 计算准确率
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_label, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.summary.scalar("accuracy", accuracy)
# 初始化变量
tf.global_variables_initializer().run()
# 合并所有摘要
merged = tf.summary.merge_all()
summary_writer = tf.summary.FileWriter(FLAGS.summary_dir, graph=sess.graph)
# 循环训练
for i in range(FLAGS.max_steps):
batch_xs,batch_ys = mnist.train.next_batch(50)
# 每过100次进行一次输出
if i % 100 == 0:
train_accuracy = accuracy.eval(feed_dict={placeholder[0]: batch_xs, placeholder[1]: batch_ys, placeholder[2]: 1.0})
print("第%d轮,准确率:%f" % (i, train_accuracy))
summary = sess.run(merged, feed_dict={placeholder[0]: batch_xs, placeholder[1]: batch_ys, placeholder[2]: 1.0})
summary_writer.add_summary(summary,i)
train_step.run(feed_dict={placeholder[0]: batch_xs, placeholder[1]: batch_ys, placeholder[2]: 1.0})
print("测试数据准确率:%g" % accuracy.eval(feed_dict={placeholder[0]: mnist.test.images, placeholder[1]: mnist.test.labels, placeholder[2]: 1.0}))
if __name__ == '__main__':
with tf.Session() as sess:
y_conv,y_label,placeholder = reference()
loss = total_loss(y_conv,y_label)
train(loss,sess,placeholder)
网络优化改进
在模型训练时候,会有一个重要的因素需要设定,就是学习率。那么在手动设定学习率的时候不一定准确。 这种人为的设定对于模型的输出影响较大。所以在这里引入了一种自动更新学习率的函数。
指数衰减学习率exponential_decay
class tf.train.exponential_decay(learning_rate, global_step, decay_steps, decay_rate, staircase=False, name=None)
"""
实现学习率指数衰减
:param learning_rate:初始的学习率
:param global_step:全局的训练步数(样本学习了多少次)
:param decay_steps:衰减系数(每多少步衰减decay_rate)
:param decay_rate:衰减的速度
:param staircase:默认为False,如果设置为True时,将(global_step / decay_ steps)转换成整数
"""
那么衰减的公式为learningrate * decay_rate ^ (global_step / decay steps)
使用
# 让学习率根据步伐,自动变换学习率,指定了每10步衰减基数为0.99,0.001为初始的学习率
lr = tf.train.exponential_decay(0.001,
global_step,
10,
0.99,
staircase=True)
# 优化器
train_op = tf.train.GradientDescentOptimizer(lr).minimize(loss, global_step=global_step)