关于入门
- 入门总要采坑,像我,开始看了tensorflow架构,跑了一些代码,随后就没有继续,而是投入到导师的项目中去了。
- 该来的躲不掉,项目深入无论是nlp还是ocr都要用到深度学习,开始选择了很容易上手的keras,并花了一个多礼拜的时间用来研究了正则化和优化方法的理论知识。其实cnn、rnn等说实话从入门到现在也是循环往复,每一次看的时候都是发现又有些忘了,哦原来这个点是这样的,balabala。。。理论与实践要并行啊。
- 说这么多废话,想告诉大家的是,重复是必须的,既然要成为一个DLer,就不怕采坑(其实还是脑子不够用)。
- 废话不多说了,开始正文。
1.TensorFlow架构
这里从整体结构上介绍tf架构,并针对客户端内容说的多点~毕竟我们面向的是客户端编程,底层架构和并行计算之类的目前就不涉及了(能力所限)。
- 基本概念与特性
- TensorFlow基于数据流图,用于大规模分布式数值计算的开源框架。
- 特性——跨平台、架构灵活、通用性强、良好的扩展性
- 支持各种异构的平台,服务器,移动设备
- 支持多CPU/GPU
- 支持各种网络模型,具有良好的通用性
- 具有良好的可扩展性(对op的扩展支持)
体系架构
Tensorflow的系统结构以C-API为界,将整个系统分为前端和后端两个部分,同时也将用户层的多种类语言的代码和内核的运行时环境分离开来。前端系统:提供编程模型,负责构造计算图。 后端系统:提供运行时环境,负责执行计算图。
- 1) Client
将计算定义为一个数据流图。
用session(会话)初始化图的执行。 - 2) Distributed Master
根据Session.run()中的参数剥离特定子图。
划分子图并分配给不同的进程和设备运行。
将子图派发给Worker Services。
Worker Service启动图片段计算的执行。 - 3) Worker Services(每一个任务启动一个Worker Services)
根据当前的可用硬件(CPU、GPU等),选择合适的kernel,调度图op的执行。
不同的Worker Services之间发送和接收op结果。 - 4) Kernel Implementions
基于特定的设备,为特定的图op执行运算。
总结一下
1.简单来说,使用框架的编程人员主要面对的是客户端,就是你设计好了图的结构(深度神经网络的结构),最终图的计算交给后台来执行。
2.从图中可以看出,后台涉及到了分布式的计算以及基于硬件的高性能计算。
3.此外如果要对tensorflow框架进行扩展,就涉及到了kernel,TensorFlow的运行时包含200多个标准的OP,包括数值计算,多维数组操作,控制流,状态管理等。每一个OP根据设备类型都会存在一个优化了的Kernel实现。在运行时,运行时根据本地设备的类型,为OP选择特定的Kernel实现,完成该OP的计算。
这里再对client进行说明,毕竟入门关注的是如何使用这个问题。。。
1.Client是前端系统的主要组成部分,它是一个支持多语言的编程环境。它提供基于计算图的编程模型,方便用户构造各种复杂的计算图,实现各种形式的模型设计。目前,Tensorflow主流支持Python和C++的编程接口,并对其他编程语言接口的支持日益完善。
2.Client通过Session为桥梁,连接TensorFlow后端的运行时(runtime),并启动计算图的执行过程。
3.用户构造一个图,并启动session(会话),将图的构造定义(tf.GraphDef)发送给distributed master(分布式主机)作为一个protocal buffer(Google的一种数据格式)。
2.基本使用
TensorFlow一些核心概念的理解
Graph—使用图来表示计算任务,这个也是使用框架的重中之重,将计算图(神经网络结构)构造好了,扔给框架后台去执行就好了。具体地,用来表示计算的数据流图,包括一个由op-节点组成的集合,而节点又是对tensor-张量进行运算的operation。默认的在tf中,有一个默认图,调用tf.get_default_graph()可以获得。一般的,在定义运算(如tf.add())或者声明数据(tf.constant(4.0))时相当于给图增加节点,可以理解为增加数据op或者计算op。一些基本操作:
import tensorflow as tf # 获得默认图 tf.get_default_graph() # 新建一个空图 tf.graph.__init__() # 新建图并作为默认图 Graph.as_default() g = tf.Graph() with g.as_default(): .... # 也可以这样写 with tf.Graph().as_default() as g: ...
Session——利用会话执行图,就是对你构造好的图(神经网络结构)执行计算的交互式组件。
tensor——tensorflow中所有的数据都属于一种数据结构tensor(张量)。可以看做一个n维的数组或者列表,其实就是一个
numpy array
。官方文档中写到Represents a value produced by an Operation.
其实就是表示节点op的输出,这里需要注意的是表示,即tensor其实是符号标志,实际不存储值,但提供了通过会话计算这些值得方式。tensor的用途,一是用作op的输入(在op之间传递),二是在会话中执行计算图时,tenseor被传入并且它的值被计算出来。具体地,看一下这段代码:# 添加op构造图 c = tf.constant([[1,2],[3,4]]) # 数据op d = tf.constant([[1,1],[0,1]]) # 数据op e = tf.matmul(c,d) # 计算op # 构造会话执行计算图 with tf.Session() as sess: result = sess.run(e)
# tensor的一些属性 tf.Tensor.dtype—数据类型 tf.Tensor.name—名称 tf.Tensor.Graph—所属的构造图 tf.Tensor.op—产生tensor的op tf.Tensor.eval(feed_dict=None, session=None)—在会话中评估张量,即可以执行计算返回值。具体的,调用时,需要在会话中启动图。
op——operation。图中的节点,对张量执行运算。这里要特殊注意的是,在tensorflow中强调的是节点,这里我目前的理解是,数据或者计算都是op,即可以分为数据型的op和计算型的op,边不像一般的图里表示的是计算,这里就是传递tensor的作用。
# op的一些属性 tf.Operation.name tf.Operation.type (e.g. "MatMul") tf.Operation.inputs tf.Operation.outputs tf.Operation.graph tf.Operation.device tf.Operation.run(feed_dict=None, session=None)
variable-tf.Variable。变量用来维护图执行过程中的状态信息,这里可以这么理解:相比较常量,变量可以进行更新。所以这里的维护就是存储和更新,官方文档说的也是hold and update parameters.其实这里的定义可以参考tensor,即进行了统一化。这样一目了然,一个Variable,有name,数据-tensor。涉及到的操作有创建、初始化、保存、加载。
- tf.placeholder-占位符。其实可以理解为形参,在执行图的计算时,再传入实际的参数。
入门实战演习
这里推荐大家分析一下官方入门的代码,极客学院进行了翻译。其中我自己详细分析了字词的向量表示这个代码。自己用jupyter notebook手敲了一遍来理解,供大家参考。其中重点关注的可以是图的构造和计算的执行,以及Variable的理解(embeddings->embed->normalized_embedding->final_embeddings)。
import collections
import math
import os
import random
from tempfile import gettempdir
import zipfile
import numpy as np
from six.moves import urllib
from six.moves import xrange
import tensorflow as tf
# download data
url = 'http://mattmahoney.net/dc/'
def download_file(filename, expected_bytes):
local_file = os.path.join(gettempdir(), filename)
if not os.path.exists(local_file):
local_file, _ = urllib.request.urlretrieve(url+filename, local_file)
stateinfo = os.stat(local_file)
if stateinfo.st_size == expected_bytes:
print('found & verified', filename)
else:
print(stateinfo.st_size)
raise Exception('Failed to verify' + local_file + '. can you get it with a browser?')
return local_file
filename = download_file('text8.zip', 31344016)
def read_data(filename):
with zipfile.ZipFile(filename) as f:
data = tf.compat.as_str(f.read(f.namelist()[0])).split()
return data
vocab = read_data(filename)
print('Data size', len(vocab))
# Build dictionary
vocab_size = 50000
def build_dataset(words, n_words):
count = [['UNK', -1]]
count.extend(collections.Counter(words).most_common(n_words-1))
dictionary = dict()
for word, _ in count:
dictionary[word] = len(dictionary)
data = list()
unk_count = 0
for word in words:
index = dictionary.get(word, 0)
if index == 0:
unk_count += 1
data.append(index)
count[0][1] = unk_count
reversed_dict = dict(zip(dictionary.values(), dictionary.keys()))
return data, count, dictionary, reversed_dict
# data - list of words code(intergers from 0 to vocab_size-1)
# count - map of words to count of occurances
# dictionary - map of words to codes
# reversed_dict - map of codes to words
data, count, dictionary, reversed_dict = build_dataset(vocab, vocab_size)
del vocab
print('Most common words (+UNK)', count[:5])
print('Sample data', data[:10], [reversed_dict[i] for i in data[:10]])
# generate training batch
data_index = 0
def generate_batch(batch_size, num_skips, skip_window):
global data_index
assert batch_size % num_skips == 0
assert num_skips <= 2 * skip_window
batch = np.ndarray(shape=(batch_size), dtype=np.int32)
labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
span = 2 * skip_window + 1
buffer = collections.deque(maxlen=span)
if data_index + span > len(data):
data_index = 0
buffer.extend(data[data_index:data_index+span])
data_index += span
for i in range(batch_size // num_skips):
context_words = [w for w in range(span) if w != skip_window]
words_to_use = random.sample(context_words, num_skips)
for j, context_word in enumerate(words_to_use):
batch[i * num_skips + j] = buffer[skip_window]
labels[i * num_skips +j, 0] = buffer[context_word]
if data_index == len(data):
buffer[:] = data[:span]
data_index = span
else:
buffer.append(data[data_index])
data_index += 1
data_index = (data_index + len(data) - span) % len(data)
return batch, labels
batch, labels = generate_batch(batch_size=8, num_skips=2, skip_window=1)
for i in range(8):
print(batch[i], reversed_dict[batch[i]], '->', labels[i, 0], reversed_dict[labels[i, 0]])
# construct skip-gram model
batch_size = 128
embedding_size = 128
skip_window = 1
num_skips = 2
num_sampled = 64
valid_size = 16
valid_window = 100
valid_examples = np.random.choice(valid_window, valid_size, replace=True)
graph = tf.Graph()
with graph.as_default():
# input data
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# Ops & variables pinned to cpu(or gpu)
with tf.device('/cpu:0'):
embeddings = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0))
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
# nce loss
nce_weights = tf.Variable(tf.truncated_normal([vocab_size, embedding_size], stddev=1.0 / math.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocab_size]))
loss = tf.reduce_mean(
tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
labels=train_labels,
inputs=embed,
num_sampled=num_sampled,
num_classes=vocab_size))
optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b = True)
init = tf.global_variables_initializer()
# train skip-gram model
num_steps = 100001
with tf.Session(graph=graph) as sess:
init.run()
print('Initialized')
average_loss = 0
for step in xrange(num_steps):
batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)
feed_dict = {train_inputs: batch_inputs, train_labels: batch_labels}
_, loss_val = sess.run([optimizer, loss], feed_dict=feed_dict)
average_loss += loss_val
if step % 2000 == 0:
if step > 0:
average_loss /= 2000
print('Average loss at step', step, ':', average_loss)
average_loss = 0
if step % 10000 == 0:
sim = similarity.eval()
for i in xrange(valid_size):
valid_word = reversed_dict[valid_examples[i]]
top_k = 8
nearest = (-sim[i, :]).argsort()[1:top_k+1]
log_str = 'Nearest to %s:' % valid_word
for k in xrange(top_k):
close_word = reversed_dict[nearest[k]]
log_str = '%s %s,' % (log_str, close_word)
print(log_str)
final_embeddings = normalized_embeddings.eval()
# embedding visualization
def plot_with_labels(low_dim_embs, labels, filename):
assert low_dim_embs.shape[0] >= len(labels), 'More labels than embeddings'
plt.figure(figsize=(18, 18))
for i, label in enumerate(labels):
x, y = low_dim_embs[i, :]
plt.scatter(x,y)
plt.annotate(label,
xy=(x,y),
xytext=(5,2),
textcoords='offset points',
ha='right',
va='bottom')
plt.savefig(filename)
print(filename)
plt.show()
try:
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000, method='exact')
plot_only = 500
low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only, :])
labels = [reversed_dict[i] for i in xrange(plot_only)]
plot_with_labels(low_dim_embs, labels, os.path.join(gettempdir(), 'tsne.png'))
except ImportError as e:
print('please install sklearn, matplotlib & scipy')
print(e)
最终测试结果如下: