1.TensorFlow介绍
- TensorFlow计算模型——计算图
- TensorFlow数据模型——张量
- TensorFlow运行模型——会话
- TensorFlow的训练过程
2.TensorFlow实现样例
- TensorFlow实现线性回归demo
- TensorFlow实现word2vec (Skip-gram)demo
深度学习框架也就像tensorflow、caffe、Keras等这些是深度学习的工具,简单来说就是库,编程时需要import 。打个比方,一套深度学习框架就是这个品牌的一套积木,各个组件就是某个模型或算法的一部分,你可以自己设计如何使用积木去堆砌符合你数据集的积木。好处是你不必重复造轮子。
1.TensorFlow介绍
- TensorFlow计算模型——计算图
TensorFlow的名字已经说明了它最重要的两个概念——Tensor 和 Flow。如果说TensorFlow的第一个单词Tensor表明了它的数据结构,那么Flow则体现了它的计算模型。Flow翻译成中文就是“流”,它直观地表达了张量之间通过计算相互转换的过程。
TensorFlow是一个通过计算图的形式来表述计算的编程系统。TensorFlow中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。
- TensorFlow数据模型——张量
TensorFlow程序一般可以分为两个阶段。在第一个阶段需要定义图中所有的计算,我们将在此部分以上图为例介绍;第二个阶段是执行计算,我们将在下一部分1.3中介绍。
以上图为例,有以下几个节点定义:
变量(Variables):其中W和b为变量。课程中一直强调变量就是我们模型中要训练的一些参数。变量保留多次执行过程中(训练)的当前值,可以存储,方便模型的迁移和复现。
在TensorFlow中代码实现如下:
import tensorflow as tf
b=tf.Variable(tf.zeros((100,)))#定义全0的变量
W=tf.Variable(tf.random_uniform((784,100),-1,1))#服从均匀分布
占位符(Placeholders):其中X是为占位符。占位符是在执行时才会接受值的节点,比如说神经网络中的输入,这种不依赖任何其他节点。对于占位符我们不初始化任何值,我们只定义它的数据类型和张量大小。
在TensorFlow中代码实现如下:
import tensorflow as tf
X=tf.placeholder(tf.float32,(100,784))
数学操作(Mathematical Operations):其中MatMul、Add和ReLU为数学操作节点。其实就是定义一些数学操作来操作其他节点。
在TensorFlow中代码实现如下:
import tensorflow as tf
h=tf.nn.relu(tf.multiply(x,W)+b)
- TensorFlow运行模型——会话
到现在为止,我们实际上并没有操作任何数据,只是介绍了TensorFlow是如何组织数据和运算的,而并没有任何数据在我们的计算图中流动。此部分介绍如何使用TensorFlow中的会话session来执行定义好的运算,会话拥有并管理TensorFlow程序运行时的所有资源。仍然以此博客中的例子为例,session实现代码如下:
sess=tf.Session()#定义Session变量
sess.run(tf.initialize_all_variables())#初始化所有变量
sess.run(h,{x:np.random.rand(100,784)})#输入x是100*784的二维数组
- TensorFlow的训练过程
我们先是通过变量和占位符定义了计算图;然后我们给计算图定义了session会话,也就是运行环境;接下来我们就来看看如何训练模型代码过程:
prediction=tf.nn.softmax(...)#神经网络的输出
label=tf.placeholder(tf.float32,[100,10])#100是样本数,10是类别数
cross_entropy=-tf.reduce_sum(label*tf.log(prediction),axis=1)#定义损失
train_step=tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)#计算梯度和优化操作
2.TensorFlow实现样例
- TensorFlow实现线性回归demo
根据课程中实现了TensorFlow版的线性回归(Github)
import tensorflow as tf
import numpy as np
from matplotlib import pyplot as plt
#产生训练数据
def create_data():
#y=x*2+0.3*random(符合正态分布的采样)
x_batch=np.linspace(-1,1,100)#将-1到1之间分成100段,组成输入数组
y_batch=2*x_batch+np.random.randn(*x_batch.shape)*0.3#根据x产生y的真实值
return x_batch,y_batch
#定义次模型的计算图
def liner_regression():
x=tf.placeholder(tf.float32,shape=(None,),name='x')#定义输入
y=tf.placeholder(tf.float32,shape=(None,),name='y')#定义实际输出
w=tf.Variable(np.random.normal(),name='w')#定义需训练的权重参数
y_pred=tf.multiply(w,x)#定义预测输出
loss=tf.reduce_mean(tf.square(y-y_pred))#计算loss
return x,y,y_pred,loss
#模型运行函数session
def run():
x_batch,y_batch=create_data()#返回输入x和真实的y值
x, y, y_pred, loss=liner_regression()#得到计算图中的节点
optimizer=tf.train.GradientDescentOptimizer(0.1).minimize(loss)#定义优化器
init=tf.global_variables_initializer()#定义初始化参数操作
with tf.Session() as session:#采用with定义session,因此会自动关闭session
session.run(init)#初始化参数
feed_dict={x:x_batch,y:y_batch}#定义计算图的输入字典
for _ in range(50):#迭代次数
loss_val=session.run(loss,feed_dict)#计算loss
session.run(optimizer,feed_dict)#更新参数
# loss_val,_=session.run([loss,optimizer],feed_dict)#也可以同时计算loss,更新参数
print(loss_val)
y_pred_batch=session.run(y_pred,feed_dict)#通过输入数据字典,获取计算图中计算出预测值
plt.figure('liner_regression效果图')
plt.scatter(x_batch,y_batch)#画散点图
plt.plot(x_batch,y_pred_batch)#画连续图
plt.show()
if __name__=='__main__':
run()
- TensorFlow实现word2vec (Skip-gram)demo
将TensorFlow实现word2vec的basic版过了一遍,写了详细的备注,并实现了一个小例子(Github)。
语料采用的金庸大师的《倚天屠龙记》,《停用词表》采用的哈工大发布的语料。
效果和代码如下:
由于文本有限,效果肯定也就有限啦~
from numpy import random
import numpy as np
import collections
import math
import tensorflow as tf
import jieba
from collections import Counter
# 此函数作用是对初始语料进行分词处理
def cut_txt(old_file):
cut_file = old_file + '_cut.txt' # 分词之后保存的文件名
fi = open(old_file, 'r', encoding='utf-8') #注意操作前要把文件转化成utf-8文件
text = fi.read() # 获取文本内容
new_text = jieba.cut(text, cut_all=False) # 采用精确模式切词
str_out = ' '.join(new_text)
#去除停用词
stopwords = [line.strip() for line in open('DataSet/中文停用词.txt', 'r',encoding='utf-8').readlines()]
for stopword in stopwords:
str_out=str_out.replace(' '+stopword+' ',' ')
fo = open(cut_file, 'w', encoding='utf-8')
fo.write(str_out)
ret_list=str_out.split()#训练语料
ret_set=list(set(ret_list))#字典
list_len=len(ret_list)
set_len=len(set(ret_list))
print('总字数 总词数 :')
print(list_len,set_len) #总字数 总词数
print('词频字典 :')
print(dict(Counter(ret_list)))# 词频字典
return ret_list,ret_set
# 预处理切词后的数据
def build_dataset(words, n_words):
count = [['UNK', -1]] #存放词频做大的n_words个单词,第一个元素为单词,第二个元素为词频。UNK为其他单词
count.extend(collections.Counter(words).most_common(n_words - 1))#获取词频做大的n_words-1个单词(因为有了UNK,所以少一个)
dictionary = dict() #建立单词的索引字典
for word, _ in count:
dictionary[word] = len(dictionary) #建立单词的索引字典,key为单词,value为索引值
data = list()# 建立存放训练语料对应索引号的list,也就是说将训练的语料换成每个单词对应的索引
unk_count = 0 #计数UNK的个数
for word in words:
if word in dictionary:
index = dictionary[word]#获取单词在字典中的索引
else:
index = 0 #字典中不存在的单词(UNK),在字典中的索引是0
unk_count += 1 #每次遇到字典中不存在的单词 UNK计数器加1
data.append(index)#将训练语料对应的索引号存入data中
count[0][1] = unk_count
reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))#将单词的索引字典反转,即将key和value对换
return data, count, dictionary, reversed_dictionary#训练语料的索引;top大的单词词频;字典{单词:索引值};字典{索引值:单词}
# 为 skip-gram model 产生bathch训练样本.
#从文本总体的第skip_window+1个单词开始,每个单词依次作为输入,它的输出可以是上下文范围内的单词中的任何一个单词。一般不是取全部而是随机取其中的几组,以增加随机性。
def generate_batch(batch_size, num_skips, skip_window):#batch_size 就是每次训练用多少数据,skip_window是确定取一个词周边多远的词来训练,num_skips是对于一个输入数据,随机取其窗口内几个单词作为上下文(即输出标签)。
global data_index
assert batch_size % num_skips == 0#保证batch_size是 num_skips的整倍数,控制下面的循环次数
assert num_skips <= 2 * skip_window #保证num_skips不会超过当前输入的的上下文的总个数
batch = np.ndarray(shape=(batch_size), dtype=np.int32) #存储训练语料中心词的索引
labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)#存储训练语料中心词对应的上下文的索引
span = 2 * skip_window + 1 # [ skip_window target skip_window ]
#这个很重要,最大长度是span,后面如果数据超过这个长度,前面的会被挤掉,这样使得buffer里面永远是data_index周围的span歌数据,
#而buffer的第skip_window个数据永远是当前处理循环里的输入数据
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):#一个batch一共需要batch个训练单词对,每个span中随机选取num_skips个单词对,所以要循环batch_size // num_skips次
target = skip_window # 中心词索引在buffer中的位置
targets_to_avoid = [skip_window] #自己肯定要排除掉,不能自己作为自己的上下文
for j in range(num_skips):#采样num_skips次
while target in targets_to_avoid:
target = random.randint(0, span - 1) #随机取一个,增强随机性,减少训练时进入局部最优解
targets_to_avoid.append(target)#采样到某一个上下文单词后,下一次将不会再采样
batch[i * num_skips + j] = buffer[skip_window] #这里保存的是训练的输入序列
labels[i * num_skips + j, 0] = buffer[target] #这里保存的是训练时的输出序列,也就是标签
if data_index == len(data): #超长时回到开始
buffer.extend(data[0:span])
data_index = span
else:
buffer.append(data[data_index]) #append时会把queue的开始的一个挤掉
data_index += 1 #此处是控制模型窗口是一步步往后移动的
data_index = (data_index + len(data) - span) % len(data)# 倒回一个span,防止遗漏最后的一些单词
return batch, labels#返回 输入序列 输出序列
############ 第一步:对初始语料进行分词处理 ############
train_data,dict_data=cut_txt('DataSet/倚天屠龙记.txt')#切词
vocabulary_size =10000#字典的大小,只取词频top10000的单词
############ 第二步:预处理切词后的数据 ############
data, count, dictionary, reverse_dictionary = build_dataset(train_data,vocabulary_size)#预处理数据
print()
print(data)
print()
print(count)
print()
print(dictionary)
print()
print(reverse_dictionary)
print('Most common words (+UNK)', count[:5])#词频最高的前5个单词
print('Sample data', data[:10], [reverse_dictionary[i] for i in data[:10]])#前10个训练数据索引及其具体单词
############ 第三步:为 skip-gram model 产生bathch训练样本. ############
data_index = 0#控制窗口滑动的
batch, labels = generate_batch(batch_size=128, num_skips=8, skip_window=5)#产生一个batch的训练数据。batch大小128;从上下文中随机抽取8个单词作为输出标签;窗口大小5(即一个窗口下11个单词,1个人中心词,10个上下文单词);
for i in range(10):#输出一下一个batch中的前10个训练数据对(即10个训练样本)
print(batch[i], reverse_dictionary[batch[i]],'->', labels[i, 0], reverse_dictionary[labels[i, 0]])
############ 第四步: 构造一个训练skip-gram 的模型 ############
batch_size = 128 #一次更新参数所需的单词对
embedding_size = 128 # 训练后词向量的维度
skip_window = 5 #窗口的大小
num_skips = 8 # 一个完整的窗口(span)下,随机取num_skips个单词对(训练样本)
# 构造验证集的超参数
valid_size = 16 # 随机选取valid_size个单词,并计算与其最相似的单词
valid_window = 100 # 从词频最大的valid_window个单词中选取valid_size个单词
valid_examples = np.random.choice(valid_window, valid_size, replace=False)#选取验证集的单词索引
num_sampled = 64 #负采样的数目
graph = tf.Graph()
with graph.as_default():
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) #验证集
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))#定义单词的embedding
embed = tf.nn.embedding_lookup(embeddings, train_inputs)#窗口查询中心词对应的embedding
# 为 NCE loss构造变量
nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size],stddev=1.0 / math.sqrt(embedding_size)))#权重
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))#偏差
# 对于一个batch,计算其平均的 NEC loss
# 采用负采样优化训练过程
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=vocabulary_size))
#采用随机梯度下降优化损失函数,学习率采用1.0
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()
############ 第五步:开始训练 ############
num_steps = 100001 #迭代次数
with tf.Session(graph=graph) as session:
init.run()
print('开始训练')
average_loss = 0
for step in range(num_steps):
batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)#产生一个batch
feed_dict = {train_inputs: batch_inputs, train_labels: batch_labels}#tensor的输入
_, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)#得到一个batch的损失值
average_loss += loss_val #损失值累加
if step % 2000 == 0:#每迭代2000次,就计算一次平均损失,并输出
if step > 0:
average_loss /= 2000
print('Average loss at step ', step, ': ', average_loss)
average_loss = 0 #每2000次迭代后,将累加的损失值归零
if step % 10000 == 0:#每迭代10000次 就计算一次与验证集最相似的单词,由于计算量很大,所以尽量少计算相似度
sim = similarity.eval()
for i in range(valid_size):
valid_word = reverse_dictionary[valid_examples[i]]#得到需验证的单词
top_k = 10 # 和验证集最相似的top_k个单词
nearest = (-sim[i, :]).argsort()[1:top_k + 1]#最邻近的单词的索引,[1:top_k + 1]从1开始,是跳过了本身
log_str = 'Nearest to %s:' % valid_word
for k in range(top_k):
close_word = reverse_dictionary[nearest[k]]#获得第k个最近的单词
log_str = '%s %s,' % (log_str, close_word) #拼接要输出的字符串
print(log_str)
final_embeddings = normalized_embeddings.eval()