build-ml-proj-tf-zh-merge-1

34 阅读46分钟

使用 TensorFlow 构建机器学习项目中文版(二)

原文:Building Machine Learning Projects with TensorFlow

协议:CC BY-NC-SA 4.0

五、简单的前馈神经网络

神经网络确实是 Tensorflow 擅长的机器学习领域。 可以用它实现多种类型的架构和算法,以及结合了符号引擎的其他优点,这实际上将有助于训练更复杂的设置。

在本章中,我们开始利用高表现原语的功能来解决大量支持输入变量的日益复杂的问题。

在本章中,我们将介绍以下主题:

  • 神经网络的初步概念
  • 非线性一般函数回归的神经网络项目
  • 利用非线性回归预测汽车燃油效率的项目
  • 学习葡萄酒的分类和多分类

初步概念

为了将简单的框架构建到神经网络的组件和架构中,我们将对原始概念进行简单明了的构建,这些原始概念为当前,复杂而多样的神经网络格局铺平了道路。

人工神经元

人工神经元是一种数学函数,被视为真实生物神经元的模型。

它的主要特征是它接收一个或多个输入(训练数据),并对它们求和以产生输出。 此外,通常对总和进行加权(权重和偏差),然后将总和传递给非线性函数(激活函数或传递函数)。

原始示例 -- 感知器

感知器是实现人工神经元的最简单方法之一,并且它的算法可以追溯到 1950 年代,最早是在 1960 年代实现的。

从本质上讲,它是一种学习二分类函数的算法,该算法将一个实数映射为两个数:

Original example - the Perceptron

下图显示了单层感知器

感知机算法

感知器的简化算法为:

  1. 用随机分布初始化权重(通常为低值)

  2. 选择一个输入向量并将其呈现给网络,

  3. 为指定的输入向量和权重值计算网络的输出y'

  4. 用于感知机的函数是:

    Perceptron algorithm

  5. 如果y' ≠ y,则通过添加更改Δw = yx[i]修改所有连接w[i]

  6. 返回步骤 2。

神经网络层

单层感知器可以推广到彼此连接的多个层,但是仍然存在问题;表示函数是输入的线性组合,并且感知器只是一种线性分类器,不可能正确拟合非线性函数。

Neural network layers

神经网络激活函数

仅靠单变量线性分类器,神经网络的学习表现就不会那么好。 甚至机器学习中的一些轻微复杂的问题都涉及多个非线性变量,因此开发了许多变体来替代感知器的传递函数。

为了表示非线性模型,可以在激活函数中使用许多不同的非线性函数。 这意味着神经元将对输入变量的变化做出反应的方式发生变化。 实际上,最常用的激活函数是:

  • Sigmoid: 规范的激活函数,对于计算分类属性的概率具有很好的属性。

    Neural Network activation functions

  • Tanh: 与 Sigmoid 非常相似,但其值范围是[-1,1]而不是[0,1]

    Neural Network activation functions

  • Relu: 这称为整流线性单元,其主要优点之一是它不受“梯度消失”问题的影响,该问题通常存在于网络的第一层,趋向于 0 或很小的epsilon值:

    Neural Network activation functions

梯度和反向传播算法

当我们描述感知器的学习阶段时,我们描述了根据权重对最终误差的“责任”来按比例调整权重的阶段。

在这个复杂的神经元网络中,误差的责任将分布在整个架构中应用于数据的所有函数之间。

因此,一旦我们计算了总误差,并且将整个函数应用于原始数据,我们现在就必须尝试调整方程式中的所有变量以将其最小化。

正如最优化领域所研究的那样,我们需要知道的是能够使该误差最小化的是损失函数的梯度。

鉴于数据要经过许多权重和传递函数,因此必须通过链式法则来解决合成函数的梯度。

最小化损失函数:梯度下降

让我们看下图以了解损失函数:

Minimizing loss function: Gradient descent

神经网络问题的选择 -- 分类与回归

神经网络可用于回归问题和分类问题。 架构上的共同差异在于输出层:为了能够带来实数为基础的结果,不应应用诸如 Sigmoid 之类的标准化函数,这样我们就不会将变量的结果更改为许多可能的类别值之一,获得了可能的连续结果。

有用的库和方法

在本章中,我们将使用 TensorFlow 和工具库中的一些新工具,这些是最重要的工具:

TensorFlow 激活函数

TensorFlow 导航中最常用的函数:

  • tf.sigmoid(x):标准 Sigmoid 函数
  • tf.tanh(x):双曲正切
  • tf.nn.relu(features):Relu 传递函数

TensorFlow 导航的其他函数:

  • tf.nn.elu(features):计算指数线性:如果< 0则为exp(features) - 1,否则为features
  • tf.nn.softsign(features):计算 softsign:features / (abs(features) + 1)
  • tf.nn.bias_add(value, bias):为值增加偏差

TensorFlow 损失优化方法

TensorFlow 损失优化方法如下所述:

  • tf.train.GradientDescentOptimizer(learning_rate, use_locking, name):这是原始的梯度下降方法,仅具有学习率参数
  • tf.train.AdagradOptimizer(learning_rate, initial_accumulator_value, use_locking, name):此方法使学习率适应参数的频率,从而提高了最小搜索稀疏参数的效率
  • tf.train.AdadeltaOptimizer(learning_rate, rho, epsilon, use_locking, name):这是改良的 AdaGrad,它将限制频繁参数的累积到最大窗口,因此它考虑了一定数量的步骤,而不是整个参数历史记录。
  • tf.train.AdamOptimizer tf.train.AdamOptimizer.__init__(learning_rate, beta1, beta2, epsilon, use_locking, name):此方法在计算梯度时会添加一个因子,该因子对应于过去梯度的平均值,等同于动量因子。 因此,来自自适应矩估计的亚当这个名字。

Sklearn 预处理工具

让我们看一下以下 Sklearn 预处理工具:

  • preprocessing.StandardScaler():数据集的正则化是许多机器学习估计器的普遍要求,因此,为了使收敛更加直接,数据集将必须更像是标准正态分布,即具有零均值和单位方差的高斯曲线。 在实践中,我们通常会忽略分布的形状,而只是通过删除每个特征的平均值来变换数据以使其居中,然后通过将非恒定特征除以它们的标准偏差来缩放它。 对于此任务,我们使用StandardScaler,它实现了前面提到的任务。 它还存储转换,以便能够将其重新应用于测试集。
  • StandardScalerfit_transform():简单地将数据调整为所需格式。 StandardScaler对象将保存转换变量,因此您将能够取回非规格化数据。
  • cross_validation.train_test_split:此方法将数据集分为训练和测试段,我们只需要提供分配给每个阶段的数据集的百分比即可。

第一个项目 -- 非线性一般函数回归

人工神经网络示例通常包含绝大多数分类问题,但实际上有大量应用可以表示为回归。

用于回归的网络架构与用于分类问题的网络架构没有很大不同:它们可以采用多变量输入,也可以使用线性和非线性激活函数。

在某些情况下,唯一必要的情况是仅在层的末尾删除类似于 Sigmoid 的函数,以允许出现所有选项。

在第一个示例中,我们将对一个简单的,有噪声的二次函数进行建模,并将尝试通过单个隐藏层网络对其进行回归,并查看我们可以多么接近地预测从测试总体中得出的值。

数据集说明和加载

在这种情况下,我们将使用生成的数据集,该数据集与第 3 章的线性回归中的数据集非常相似。

我们将使用常见的 Numpy 方法生成二次函数,然后添加随机噪声,这将有助于我们了解线性回归如何推广。

核心样本创建例程如下:

import numpy as np 
trainsamples = 200 
testsamples = 60 
dsX = np.linspace(-1, 1, trainsamples + testsamples).transpose() 
dsY = 0.4* pow(dsX,2) +2 * dsX + np.random.randn(*dsX.shape) * 0.22 + 0.8  

数据集预处理

该数据集在生成时不需要进行预处理,并且具有良好的属性,例如居中并具有-1, 1的样本分布。

模型架构 -- 损失函数描述

此设置的损耗将简单地用均方根误差表示,如下所示:

cost = tf.pow(py_x-Y, 2)/(2)  

损失函数优化器

在这种情况下,我们将使用梯度下降成本优化器,可以通过以下代码调用该优化器:

train_op = tf.train.AdamOptimizer(0.5).minimize(cost)  

准确率和收敛性测试

predict_op = tf.argmax(py_x, 1)

cost1 += sess.run(cost, feed_dict={X: [[x1]], Y: y1}) / testsamples 

示例代码

让我们看一下下面显示的示例代码:

import tensorflow as tf
import numpy as np
from sklearn.utils import shuffle
%matplotlib inline
import matplotlib.pyplot as plt
trainsamples = 200
testsamples = 60
#Here we will represent the model, a simple imput, a hidden layer of sigmoid activation
def model(X, hidden_weights1, hidden_bias1, ow):
    hidden_layer =  tf.nn.sigmoid(tf.matmul(X, hidden_weights1)+ b)
    return tf.matmul(hidden_layer, ow)  
dsX = np.linspace(-1, 1, trainsamples + testsamples).transpose()
dsY = 0.4* pow(dsX,2) +2 * dsX + np.random.randn(*dsX.shape) * 0.22 + 0.8
plt.figure() # Create a new figure
plt.title('Original data')
plt.scatter(dsX,dsY) #Plot a scatter draw of the datapoints

Example code

X = tf.placeholder("float")
Y = tf.placeholder("float")
# Create first hidden layer
hw1 = tf.Variable(tf.random_normal([1, 10], stddev=0.1))
# Create output connection
ow = tf.Variable(tf.random_normal([10, 1], stddev=0.0))
# Create bias
b = tf.Variable(tf.random_normal([10], stddev=0.1))
model_y = model(X, hw1, b, ow)
# Cost function
cost = tf.pow(model_y-Y, 2)/(2)
# construct an optimizer
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(cost)
# Launch the graph in a session
with tf.Session() as sess:
    tf.initialize_all_variables().run() #Initialize all variables
    for i in range(1,100):
        dsX, dsY = shuffle (dsX.transpose(), dsY) #We randomize the samples to mplement a better training
        trainX, trainY =dsX[0:trainsamples], dsY[0:trainsamples]
        for x1,y1 in zip (trainX, trainY):
            sess.run(train_op, feed_dict={X: [[x1]], Y: y1})
        testX, testY = dsX[trainsamples:trainsamples + testsamples], dsY[0:trainsamples:trainsamples+testsamples]
        cost1=0.
        for x1,y1 in zip (testX, testY):
            cost1 += sess.run(cost, feed_dict={X: [[x1]], Y: y1}) / testsamples      
        if (i%10 == 0):
            print "Average cost for epoch " + str (i) + ":" + str(cost1)

结果描述

这是不同周期的结果的副本。请注意,由于这是一个非常简单的函数,因此即使第一次迭代也具有非常好的结果:

Average cost for epoch 1:[[ 0.00753353]]
Average cost for epoch 2:[[ 0.00381996]]
Average cost for epoch 3:[[ 0.00134867]]
Average cost for epoch 4:[[ 0.01020064]]
Average cost for epoch 5:[[ 0.00240157]]
Average cost for epoch 6:[[ 0.01248318]]
Average cost for epoch 7:[[ 0.05143405]]
Average cost for epoch 8:[[ 0.00621457]]
Average cost for epoch 9:[[ 0.0007379]]

第二个项目 -- 非线性回归和汽车的燃油效率建模

在此示例中,我们将进入一个区域,其中神经网络可提供大部分附加价值; 解决非线性问题。 为了开始这一旅程,我们将基于几个变量对几种汽车模型的燃油效率建模一个回归模型,该变量可以更好地用非线性函数表示。

数据集说明和加载

对于这个问题,我们将分析一个非常著名的,标准的,格式正确的数据集,该数据集将使我们能够分析一个多变量问题:根据离散和连续的一些相关变量来猜测汽车的 MPG。

这可以被认为是一个玩具,并且有些过时了,但是它将为更复杂的问题铺平道路,并且具有已经被众多书目分析的优势。

属性信息

该数据集具有以下数据列:

  • mpg:连续
  • cylinders:多值离散
  • displacement:连续
  • horsepower:连续
  • weight:连续
  • acceleration:连续
  • model year:多值离散
  • origin:多值离散
  • car name:字符串(将不使用)

我们将不对数据进行详细的分析,但是我们可以非正式地推断出所有连续变量都与增加或减少目标变量相关:

Dataset description and loading

数据集预处理

对于此任务,我们将使用来自 sklearn 的上述缩放器对象:

  • scaler = preprocessing.StandardScaler()
  • X_train = scaler.fit_transform(X_train)

模型架构

我们将要构建的是一个前馈神经网络,具有多变量输入和简单输出:

Modeling architecture

收敛性测试

score = metrics.mean_squared_error(regressor.predict(scaler.transform(X_test)), y_test)
print('MSE: {0:f}'.format(score))

结果描述

Step #99, avg. train loss: 182.33624
Step #199, avg. train loss: 25.09151
Step #300, epoch #1, avg. train loss: 11.92343
Step #400, epoch #1, avg. train loss: 11.20414
Step #500, epoch #1, avg. train loss: 5.14056
Total Mean Squared Error: 15.0792258911
%matplotlib inline  
import matplotlib.pyplot as plt 
import pandas as pd 

from sklearn import datasets, cross_validation, metrics 
from sklearn import preprocessing 
from tensorflow.contrib import skflow 

# Read the original dataset 
df = pd.read_csv("data/mpg.csv", header=0) 
# Convert the displacement column as float 
df['displacement']=df['displacement'].astype(float) 
# We get data columns from the dataset 
# First and last (mpg and car names) are ignored for X 
X = df[df.columns[1:8]] 
y = df['mpg'] 

plt.figure() # Create a new figure 

for i in range (1,8): 
    number = 420 + i 
    ax1.locator_params(nbins=3) 
    ax1 = plt.subplot(number) 
    plt.title(list(df)[i]) 
    ax1.scatter(df[df.columns[i]],y) #Plot a scatter draw of the  datapoints 
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0) 
# Split the datasets 

X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, 
test_size=0.25) 

# Scale the data for convergency optimization 
scaler = preprocessing.StandardScaler() 

# Set the transform parameters 
X_train = scaler.fit_transform(X_train) 

# Build a 2 layer fully connected DNN with 10 and 5 units respectively 
regressor = skflow.TensorFlowDNNRegressor(hidden_units=[10, 5], 
steps=500, learning_rate=0.051, batch_size=1) 

# Fit the regressor 
regressor.fit(X_train, y_train) 

# Get some metrics based on the X and Y test data 
score = metrics.mean_squared_error(regressor.predict(scaler.transform(X_test)), y_test) 

print(" Total Mean Squared Error: " + str(score)) 

第三个项目 -- 葡萄酒分类:多类分类的学习

在本节中,我们将使用更复杂的数据集,尝试根据产地对葡萄酒进行分类。

数据集说明和加载

该数据包含对来自意大利同一地区但来自三个不同品种的葡萄酒进行化学分析的结果。 分析确定了三种葡萄酒中每种所含 13 种成分的数量。

数据变量:

  • 苹果酸
  • 灰的碱度
  • 总酚
  • 黄酮
  • 非类黄酮酚
  • 花青素
  • 色彩强度
  • 色调
  • 稀释酒的 OD280/OD315
  • 脯氨酸

要读取数据集,我们将仅使用提供的 CSV 文件和熊猫:

df = pd.read_csv("./wine.csv", header=0)

Dataset description and loading

数据集预处理

随着 csv 上的值从 1 开始,我们将归一化带有偏差的值:

y = df['Wine'].values-1 

对于结果,我们将这些选项表示为一个数组的热门列表:

Y = tf.one_hot(indices = y, depth=3, on_value = 1., off_value = 0., axis = 1 , name = "a").eval() 

我们还将预先洗净值:

X, Y = shuffle (X, Y) 
scaler = preprocessing.StandardScaler() 
X = scaler.fit_transform(X) 

模型架构

这个特定的模型将由一个单层,全连接的神经网络组成:

  • x = tf.placeholder(tf.float32, [None, 12])
  • W = tf.Variable(tf.zeros([12, 3]))
  • b = tf.Variable(tf.zeros([3]))
  • y = tf.nn.softmax(tf.matmul(x, W) + b)

损失函数说明

我们将使用交叉熵函数来衡量损失:

y_ = tf.placeholder(tf.float32, [None, 3]) 
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) 

损失函数优化器

同样,将使用“梯度下降”方法来减少损失函数:

train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy) 

收敛测试

在收敛性测试中,我们将每个良好的回归均转换为 1,将每个错误的回归均转换为 0,然后获取值的平均值来衡量模型的准确率:

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 
print(accuracy.eval({x: Xt, y_: Yt})) 

结果描述

如我们所见,随着历时的发展,我们具有可变精度,但是它总是优于 90% 的精度,具有 30% 的随机基数(如果我们生成 0 到 3 之间的随机数来猜测结果)。

0.973684
0.921053
0.921053
0.947368
0.921053

完整源代码

让我们看一下完整的源代码:

sess = tf.InteractiveSession() 
import pandas as pd 
# Import data 
from tensorflow.examples.tlutorials.mnist import input_data 
from sklearn.utils import shuffle 
import tensorflow as tf 

from sklearn import preprocessing 

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

df = pd.read_csv("./wine.csv", header=0) 
print (df.describe()) 
#df['displacement']=df['displacement'].astype(float) 
X = df[df.columns[1:13]].values 
y = df['Wine'].values-1 
Y = tf.one_hot(indices = y, depth=3, on_value = 1., off_value = 0., axis = 1 , name = "a").eval() 
X, Y = shuffle (X, Y) 

scaler = preprocessing.StandardScaler() 
X = scaler.fit_transform(X) 

# Create the model 
x = tf.placeholder(tf.float32, [None, 12]) 
W = tf.Variable(tf.zeros([12, 3])) 
b = tf.Variable(tf.zeros([3])) 
y = tf.nn.softmax(tf.matmul(x, W) + b) 

# Define loss and optimizer 
y_ = tf.placeholder(tf.float32, [None, 3]) 
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) 
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy) 
# Train 
tf.initialize_all_variables().run() 
for i in range(100): 
X,Y =shuffle (X, Y, random_state=1) 

Xtr=X[0:140,:] 
Ytr=Y[0:140,:] 

Xt=X[140:178,:] 
Yt=Y[140:178,:] 
Xtr, Ytr = shuffle (Xtr, Ytr, random_state=0) 
#batch_xs, batch_ys = mnist.train.next_batch(100) 
batch_xs, batch_ys = Xtr , Ytr 
train_step.run({x: batch_xs, y_: batch_ys}) 
cost = sess.run (cross_entropy, feed_dict={x: batch_xs, y_: batch_ys}) 
# Test trained model 
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 
print(accuracy.eval({x: Xt, y_: Yt})) 

总结

在本章中,我们已经开始着手实现 TensorFlow 能力的真正替代物:神经网络模型。

我们还看到了在回归和分类任务中使用简单神经网络,简单生成模型和实验模型的情况。

在下一章中,我们将以卷积神经网络的形式提高新架构的知识以及将神经网络范式应用于其他知识领域(例如计算机视觉)的方式。

六、卷积神经网络

卷积神经网络是当前使用的许多最高级模型的一部分。 它们被用于许多领域,但是主要的应用领域是图像分类和特征检测领域。

我们将在本章中介绍的主题如下:

  • 了解卷积函数和卷积网络如何工作以及构建它们的主要操作类型
  • 将卷积运算应用于图像数据并学习一些应用于图像的预处理技术,以提高方法的准确率
  • 使用 CNN 的简单设置对 MNIST 数据集的数字进行分类
  • 使用应用于彩色图像的 CNN 模型对 CIFAR 数据集的真实图像进行分类

卷积神经网络的起源

新认知加速器是福岛教授在 1980 年发表的论文中介绍的卷积网络的前身,并且是一种能容忍位移和变形的自组织神经网络。

这个想法在 1986 年再次出现在原始反向传播论文的书本中,并在 1988 年被用于语音识别中的时间信号。

最初的设计后来在 1998 年通过 LeCun 的论文将基于梯度的学习应用于文档识别中进行了审查和改进,该论文提出了 LeNet-5 网络,该网络能够对手写数字进行分类。 与其他现有模型相比,该模型显示出更高的表现,尤其是在 SVM 的几种变体上,SVM 是出版年份中表现最高的操作之一。

然后在 2003 年对该论文进行了概括,论文为图像解释的层次神经网络。 但是,总的来说,我们将使用 LeCun 的 LeNet 论文架构的近似表示。

卷积入门

为了理解在这些类型的操作中应用于信息的操作,我们将从研究卷积函数的起源开始,然后我们将解释如何将此概念应用于信息。

为了开始跟踪操作的历史发展,我们将开始研究连续域中的卷积。

连续卷积

此操作的最初使用来自 18 世纪,并且可以在原始应用上下文中表示为将两个按时出现的特征混合在一起的操作。

从数学上讲,它可以定义如下:

Continuous convolution

当我们尝试将此操作概念化为算法时,可以在以下步骤中解释前面的方程式:

  1. 翻转信号:这是变量的(-τ)部分。
  2. 移动它:这是由g(τ)t求和因子给出的。
  3. 乘以:这是fg的乘积。
  4. 积分结果曲线:这是较不直观的部分,因为每个瞬时值都是积分的结果。

Continuous convolution

离散卷积

卷积可以转换为离散域,并以离散项描述离散函数:

Discrete convolution

卷积核

在离散域中应用卷积的概念时,经常会使用内核。

内核可以定义为nxm维矩阵,通常是在所有维上长的几个元素,通常是m = n

卷积运算包括将对应的像素与内核相乘,一次一个像素,然后将这些值相加,以便将该值分配给中央像素。

然后将应用相同的操作,将卷积矩阵向左移动,直到访问了所有可能的像素。

在以下示例中,我们有一个包含许多像素的图像和一个大小为3x3的内核,这在图像处理中特别常见:

Kernels and convolutions

卷积运算的解释

回顾了连续场和离散场的卷积运算的主要特征之后,现在让我们看一下该运算在机器学习中的用途。

卷积核突出或隐藏模式。 根据受过训练的(或在示例中,手动设置)参数,我们可以开始发现参数,例如不同尺寸的方向和边缘。 我们也可能通过诸如模糊内核之类的方法覆盖一些不必要的细节或离群值。

正如 LeCun 在他的基础论文中所述:

卷积网络可以看作是合成自己的特征提取器。

卷积神经网络的这一特性是相对于以前的数据处理技术的主要优势。 我们可以非常灵活地确定已确定数据集的主要组成部分,并通过这些基本构件的组合来表示其他样本。

在 TensorFlow 中应用卷积

TensorFlow 提供了多种卷积方法。 规范形式通过conv2d操作应用。 让我们看一下此操作的用法:

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu, data_format, name=None)

我们使用的参数如下:

  • input:这是将对其应用操作的原始张量。 它具有四个维度的确定格式,默认维度顺序如下所示。

  • [batch, in_height, in_width, in_channels]:批量是允许您拥有图像集合的维度。 顺序称为NHWC。 另一个选项是NCWH

    例如,单个100x100 像素彩色图像将具有以下形状:

            [1,100,100,3]
    
  • filter:这是代表kernelfilter的张量。 它有一个非常通用的方法:

        [filter_height, filter_width, in_channels, out_channels]
  • strides:这是四个int张量数据类型的列表,这些数据类型指示每个维度的滑动窗口。
  • Padding:可以是SAMEVALIDSAME将尝试保留初始张量尺寸,但VALID将允许其增长,以防计算输出大小和填充。
  • use_cudnn_on_gpu:这指示是否使用CUDA GPU CNN库来加速计算。
  • data_format:这指定数据的组织顺序(NHWCNCWH)。

其他卷积运算

TensorFlow 提供了多种应用卷积的方法,如下所示:

  • tf.nn.conv2d_transpose:这适用于conv2d的转置(梯度),并用于反卷积网络中
  • tf.nn.conv1d:给定 3D 输入和filter张量,这将执行 1D 卷积
  • tf.nn.conv3d:给定 5D 输入和filter张量,这将执行 3D 卷积

示例代码 -- 将卷积应用于灰度图像

在此示例代码中,我们将读取 GIF 格式的灰度图像,该图像将生成一个三通道张量,但每个像素具有相同的 RGB 值。 然后,我们将张量转换为真实的灰度矩阵,应用kernel,并在 JPEG 格式的输出图像中检索结果。

注意

请注意,您可以调整kernel变量中的参数以观察图像变化的影响。

以下是示例代码:

import tensorflow as tf 

#Generate the filename queue, and read the gif files contents 
filename_queue = tf.train.string_input_producer(tf.train.match_filenames_once("data/test.gif")) 
reader = tf.WholeFileReader() 
key, value = reader.read(filename_queue) 
image=tf.image.decode_gif(value) 

#Define the kernel parameters 
kernel=tf.constant( 
[ 
[[[-1.]],[[-1.]],[[-1.]]], 
[[[-1.]],[[8.]],[[-1.]]], 
[[[-1.]],[[-1.]],[[-1.]]] 
]             
) 

#Define the train coordinator 
coord = tf.train.Coordinator() 

with tf.Session() as sess: 
tf.initialize_all_variables().run() 
threads = tf.train.start_queue_runners(coord=coord) 
#Get first image 
image_tensor = tf.image.rgb_to_grayscale(sess.run([image])[0]) 
#apply convolution, preserving the image size 
imagen_convoluted_tensor=tf.nn.conv2d(tf.cast(image_tensor, tf.float32),kernel,[1,1,1,1],"SAME") 
#Prepare to save the convolution option 
file=open ("blur2.jpg", "wb+") 
#Cast to uint8 (0..255), previous scalation, because the convolution could alter the scale of the final image 
out=tf.image.encode_jpeg(tf.reshape(tf.cast(imagen_convoluted_tensor/tf.reduce_max(imagen_convoluted_tensor)*255.,tf.uint8), tf.shape(imagen_convoluted_tensor.eval()[0]).eval())) 
file.close() 
coord.request_stop() 
coord.join(threads) 

示例核的结果

在下图中,您可以观察到参数的变化如何影响图像的结果。 第一张图片是原始图片。

滤镜类型为从左到右,从上到下模糊,底部 Sobel(从上到下搜索边的一种滤镜),浮雕(突出显示拐角边)和轮廓(概述图像的外部边界)。

Sample kernels results

二次采样操作 -- 池化

在 TensorFlow 中通过称为池化的操作执行二次采样操作。 这个想法是应用一个(大小不一的)内核并提取内核覆盖的元素之一,其中max_poolavg_pool是最著名的一些元素,它们仅获得最大和平均值。 应用内核的元素。

在下图中,您可以看到将2x2内核应用于单通道16x16矩阵的操作。 它只是保持其覆盖的内部区域的最大值。

Subsampling operation - pooling

可以进行的合并操作的类型也有所不同。 例如,在 LeCun 的论文中,应用于原始像素的运算必须将它们乘以一个可训练的参数,并添加一个额外的可训练bias

下采样层的属性

二次采样层的主要目的与卷积层的目的大致相同。 减少信息的数量和复杂性,同时保留最重要的信息元素。 它们构建了基础信息的紧凑表示。

不变性

下采样层还允许将信息的重要部分从数据的详细表示转换为更简单的表示。 通过在图像上滑动滤镜,我们将检测到的特征转换为更重要的图像部分,最终达到 1 像素的图像,该特征由该像素值表示。 相反,此属性也可能导致模型丢失特征检测的局部性。

下采样层实现的表现

下采样层的实现要快得多,因为未使用的数据元素的消除标准非常简单。 通常,它只需要进行几个比较。

在 TensorFlow 中应用池化操作

首先,我们将分析最常用的pool操作max_pool。 它具有以下签名:

tf.nn.max_pool(value, ksize, strides, padding, data_format, name)

此方法类似于conv2d,参数如下:

  • value:这是float32元素和形状(批量长度,高度,宽度,通道)的 4D 张量。
  • ksize:这是一个整数列表,代表每个维度上的窗口大小
  • strides:这是在每个尺寸上移动窗口的步骤
  • data_format:设置数据尺寸
  • orderingNHWCNCHW
  • paddingVALIDSAME

其他池化操作

  • tf.nn.avg_pool:这将返回每个窗口的平均值的缩减张量
  • tf.nn.max_pool_with_argmax:这将返回max_pool张量和具有max_value的平展索引的张量
  • tf.nn.avg_pool3d:此操作使用类似立方的窗口执行avg_pool操作; 输入有额外的深度
  • tf.nn.max_pool3d:执行与(...)相同的操作,但应用max操作

示例代码

在以下示例代码中,我们将采用原始格式:

import tensorflow as tf 

#Generate the filename queue, and read the gif files contents 
filename_queue = tf.train.string_input_producer(tf.train.match_filenames_once("data/test.gif")) 
reader = tf.WholeFileReader() 
key, value = reader.read(filename_queue) 
image=tf.image.decode_gif(value) 

#Define the  coordinator 
coord = tf.train.Coordinator() 

def normalize_and_encode (img_tensor): 
    image_dimensions = tf.shape(img_tensor.eval()[0]).eval() 
    return tf.image.encode_jpeg(tf.reshape(tf.cast(img_tensor, tf.uint8), image_dimensions)) 

with tf.Session() as sess: 
    maxfile=open ("maxpool.jpg", "wb+") 
    avgfile=open ("avgpool.jpg", "wb+") 
    tf.initialize_all_variables().run() 
    threads = tf.train.start_queue_runners(coord=coord) 

    image_tensor = tf.image.rgb_to_grayscale(sess.run([image])[0]) 

    maxed_tensor=tf.nn.avg_pool(tf.cast(image_tensor, tf.float32),[1,2,2,1],[1,2,2,1],"SAME") 
    averaged_tensor=tf.nn.avg_pool(tf.cast(image_tensor, tf.float32),[1,2,2,1],[1,2,2,1],"SAME") 

    maxfile.write(normalize_and_encode(maxed_tensor).eval()) 
    avgfile.write(normalize_and_encode(averaged_tensor).eval()) 
    coord.request_stop() 
    maxfile.close() 
    avgfile.close() 
coord.join(threads) 

在下图中,我们首先看到原始图像和缩小尺寸的图像,然后是max_pool,然后是avg_pool。 如您所见,这两个图像看起来是相等的,但是如果我们绘制它们之间的图像差异,我们会发现,如果取最大值而不是均值(始终小于或等于均值),则会有细微的差异。

Sample code

提高效率 - 丢弃操作

在大型神经网络训练过程中观察到的主要优点之一是过拟合,即为训练数据生成非常好的近似值,但为单点之间的区域发出噪声。

在过拟合的情况下,该模型专门针对训练数据集进行了调整,因此对于一般化将无用。 因此,尽管它在训练集上表现良好,但是由于缺乏通用性,因此它在测试数据集和后续测试中的表现很差。

因此,引入了丢弃操作。 此操作将某些随机选择的权重的值减小为零,从而使后续层为零。

这种方法的主要优点是,它避免了一层中的所有神经元同步优化其权重。 随机分组进行的这种适应避免了所有神经元都收敛到相同的目标,从而使适应的权重解相关。

在丢弃应用中发现的第二个属性是隐藏单元的激活变得稀疏,这也是理想的特性。

在下图中,我们表示了原始的完全连接的多层神经网络以及具有链接的丢弃的关联网络:

Improving efficiency - dropout operation

在 TensorFlow 中应用丢弃操作

为了应用dropout操作,TensorFlows 实现了tf.nn.dropout方法,其工作方式如下:

tf.nn.dropout (x, keep_prob, noise_shape, seed, name)

参数如下:

  • x:这是原始张量
  • keep_prob:这是保留神经元的概率以及乘以其余节点的因子
  • noise_shape:这是一个四元素列表,用于确定尺寸是否将独立应用归零
示例代码

在此样本中,我们将对样本向量应用丢弃操作。 丢弃还可以将丢弃传输到所有与架构相关的单元。

在下面的示例中,您可以看到将丢弃应用于x变量的结果,其归零概率为 0.5,并且在未发生这种情况的情况下,值加倍(乘以1 / 1.5,丢弃概率):

Sample code

显然,大约一半的输入已被清零(选择此示例是为了显示概率不会总是给出预期的四个零)。

可能使您感到惊讶的一个因素是应用于非放置元素的比例因子。 这项技术用于维护相同的网络,并在训练时将keep_prob设为 1,将其恢复到原始架构。

卷积层的构建方法

为了构建卷积神经网络层,存在一些通用的实践和方法,可以在构建深度神经网络的方式中将其视为准规范。

为了促进卷积层的构建,我们将看一些简单的实用函数。

卷积层

这是卷积层的一个示例,它连接一个卷积,添加一个bias参数总和,最后返回我们为整个层选择的激活函数(在这种情况下,relu操作很常见)。

def conv_layer(x_in, weights, bias, strides=1): 
x = tf.nn.conv2d(x, weights, strides=[1, strides, strides, 1],                                                                      padding='SAME') 
x = tf.nn.bias_add(x_in, bias) 
return tf.nn.relu(x) 

下采样层

通常可以通过维持层的初始参数,通过max_pool操作来表示下采样层:

def maxpool2d(x, k=2): 
return tf.nn.max_pool(x, ksize=[1, k, k, 1], strides=[1, k, k, 1], 
padding='SAME') 

示例 1 -- MNIST 数字分类

在本节中,我们将首次使用最知名的模式识别数据集中的一个。 它最初是为了训练神经网络来对支票上的手写数字进行字符识别而开发的。

原始数据集有 60,000 个不同的数字用于训练和 10,000 个用于测试,并且在使用时是原始使用的数据集的子集。

在下图中,我们显示了 LeNet-5 架构,这是有关该问题发布的第一个著名的卷积架构。

在这里,您可以看到层的尺寸和最后的结果表示:

Example 1 - MNIST digit classification

数据集说明和加载

MNIST 是易于理解和阅读但难以掌握的数据集。 当前,有很多好的算法可以解决这个问题。 在我们的案例中,我们将寻求建立一个足够好的模型,以使其与 10% 的随机结果相去甚远。

为了访问 MNIST 数据集,我们将使用为 TensorFlow 的 MNIST 教程开发的一些实用工具类。

这两条线是我们拥有完整的 MNIST 数据集所需的全部工作。

在下图中,我们可以看到数据集对象的数据结构的近似值:

Dataset description and loading

通过此代码,我们将打开并探索 MNIST 数据集:

Dataset description and loading

要打印字符(在 Jupyter 笔记本中),我们将重塑表示图像的线性方式,形成28x28的方矩阵,分配灰度色图,并使用以下行绘制所得的数据结构:

plt.imshow(mnist.train.images[0].reshape((28, 28), order='C'), cmap='Greys', interpolation='nearest')

下图显示了此行应用于不同数据集元素的结果:

Dataset description and loading

数据集预处理

在此示例中,我们将不进行任何预处理; 我们只会提到,仅通过使用线性变换的现有样本(例如平移,旋转和倾斜的样本)扩展数据集示例,就可以实现更好的分类评分。

模型架构

在这里,我们将研究为该特定架构选择的不同层。

它开始生成带有名称的权重字典:

'wc1': tf.Variable(tf.random_normal([5, 5, 1, 32])), 
'wc2': tf.Variable(tf.random_normal([5, 5, 32, 64])), 
'wd1': tf.Variable(tf.random_normal([7*7*64, 1024])), 
'out': tf.Variable(tf.random_normal([1024, n_classes])) 

对于每个权重,还将添加一个bias以说明常数。

然后我们定义连接的层,一层又一层地集成:

conv_layer_1 = conv2d(x_in, weights['wc1'], biases['bc1']) 

conv_layer_1 = subsampling(conv_layer_1, k=2) 

conv_layer_2 = conv2d(conv_layer_1, weights['wc2'], biases['bc2']) 

conv_layer_2 = subsampling(conv_layer_2, k=2) 

fully_connected_layer = tf.reshape(conv_layer_2, [-1, weights['wd1'].get_shape().as_list()[0]]) 
fully_connected_layer = tf.add(tf.matmul(fully_connected_layer, weights['wd1']), biases['bd1']) 
fully_connected_layer = tf.nn.relu(fully_connected_layer) 

fully_connected_layer = tf.nn.dropout(fully_connected_layer, dropout) 

prediction_output = tf.add(tf.matmul(fully_connected_layer, weights['out']), biases['out']) 

损失函数说明

损失函数将是交叉熵误差函数的平均值,该函数通常是用于分类的 softmax 函数。

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y)) 

损失函数优化器

对于此示例,我们将使用改进的AdamOptimizer,其学习率可配置,我们将其定义为 0.001。

optimizer = tf.train.AdamOptimizer
           (learning_rate=learning_rate).minimize(cost)

准确率测试

准确率测试计算标签和结果之间比较的平均值,以获得01之间的值。

correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)) 

结果说明

此示例的结果简洁明了,并且假设我们仅训练 10,000 个样本,则准确率不是一流的,但与十分之一的随机采样结果明显分开:

Optimization Finished! 
Testing Accuracy: 0.382812 

完整源代码

以下是源代码:

import tensorflow as tf 
%matplotlib inline 
import matplotlib.pyplot as plt  
# Import MINST data 
from tensorflow.examples.tutorials.mnist import input_data 
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True) 
# Parameters 
learning_rate = 0.001 
training_iters = 2000 
batch_size = 128 
display_step = 10 

# Network Parameters 
n_input = 784 # MNIST data input (img shape: 28*28) 
n_classes = 10 # MNIST total classes (0-9 digits) 
dropout = 0.75 # Dropout, probability to keep units 

# tf Graph input 
x = tf.placeholder(tf.float32, [None, n_input]) 
y = tf.placeholder(tf.float32, [None, n_classes]) 
keep_prob = tf.placeholder(tf.float32) #dropout (keep probability) 

#plt.imshow(X_train[1202].reshape((20, 20), order='F'), cmap='Greys',  interpolation='nearest') 

# Create some wrappers for simplicity 
def conv2d(x, W, b, strides=1): 
    # Conv2D wrapper, with bias and relu activation 
    x = tf.nn.conv2d(x, W, strides=[1, strides, strides, 1], padding='SAME') 
    x = tf.nn.bias_add(x, b) 
    return tf.nn.relu(x) 
def maxpool2d(x, k=2): 
    # MaxPool2D wrapper 
    return tf.nn.max_pool(x, ksize=[1, k, k, 1], strides=[1, k, k, 1], 
                          padding='SAME') 
# Create model 
def conv_net(x, weights, biases, dropout): 
    # Reshape input picture 
    x = tf.reshape(x, shape=[-1, 28, 28, 1]) 

    # Convolution Layer 
    conv1 = conv2d(x, weights['wc1'], biases['bc1']) 
    # Max Pooling (down-sampling) 
    conv1 = maxpool2d(conv1, k=2) 

    # Convolution Layer 
    conv2 = conv2d(conv1, weights['wc2'], biases['bc2']) 
    # Max Pooling (down-sampling) 
    conv2 = maxpool2d(conv2, k=2) 

    # Fully connected layer 
    # Reshape conv2 output to fit fully connected layer input 
    fc1 = tf.reshape(conv2, [-1, weights['wd1'].get_shape().as_list()[0]]) 
    fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1']) 
    fc1 = tf.nn.relu(fc1) 
    # Apply Dropout 
    fc1 = tf.nn.dropout(fc1, dropout) 

    # Output, class prediction 
    out = tf.add(tf.matmul(fc1, weights['out']), biases['out']) 
    return out 
# Store layers weight & bias 
weights = { 
# 5x5 conv, 1 input, 32 outputs 
'wc1': tf.Variable(tf.random_normal([5, 5, 1, 32])), 
# 5x5 conv, 32 inputs, 64 outputs 
'wc2': tf.Variable(tf.random_normal([5, 5, 32, 64])), 
# fully connected, 7*7*64 inputs, 1024 outputs 
'wd1': tf.Variable(tf.random_normal([7*7*64, 1024])), 
# 1024 inputs, 10 outputs (class prediction) 
'out': tf.Variable(tf.random_normal([1024, n_classes])) 
} 

biases = { 
'bc1': tf.Variable(tf.random_normal([32])), 
'bc2': tf.Variable(tf.random_normal([64])), 
'bd1': tf.Variable(tf.random_normal([1024])), 
'out': tf.Variable(tf.random_normal([n_classes])) 
} 

# Construct model 
pred = conv_net(x, weights, biases, keep_prob) 

# Define loss and optimizer 
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y)) 
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) 

# Evaluate model 
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)) 

# Initializing the variables 
init = tf.initialize_all_variables() 

# Launch the graph 
with tf.Session() as sess: 
    sess.run(init) 
    step = 1 
    # Keep training until reach max iterations 
    while step * batch_size < training_iters: 
        batch_x, batch_y = mnist.train.next_batch(batch_size) 
        test = batch_x[0] 
        fig = plt.figure() 
        plt.imshow(test.reshape((28, 28), order='C'), cmap='Greys', 
        interpolation='nearest') 
        print (weights['wc1'].eval()[0]) 
        plt.imshow(weights['wc1'].eval()[0][0].reshape(4, 8), cmap='Greys',  interpolation='nearest') 
        # Run optimization op (backprop) 
        sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, 
                                       keep_prob: dropout}) 
        if step % display_step == 0: 
            # Calculate batch loss and accuracy 
            loss, acc = sess.run([cost, accuracy], feed_dict={x: batch_x, 
                                                              y: batch_y, 
                                                            keep_prob: 1.})
            print "Iter " + str(step*batch_size) + ", Minibatch Loss= " + \ 
                  "{:.6f}".format(loss) + ", Training Accuracy= " + \ 
                  "{:.5f}".format(acc) 
        step += 1 
    print "Optimization Finished!" 

    # Calculate accuracy for 256 mnist test images 
    print "Testing Accuracy:", \ 
        sess.run(accuracy, feed_dict={x: mnist.test.images[:256],
                                      y: mnist.test.labels[:256],
                                      keep_prob: 1.}) 

示例 2 -- CIFAR10 数据集和图像分类

在此示例中,我们将研究图像理解中使用最广泛的数据集之一,该数据集用作简单但通用的基准。 在此示例中,我们将构建一个简单的 CNN 模型,以了解解决此类分类问题所需的一般计算结构。

数据集说明和加载

该数据集包含 40,000 个32x32像素的图像,代表以下类别:飞机,汽车,鸟类,猫,鹿,狗,青蛙,马,船和卡车。 在此示例中,我们将只处理 10,000 个图像包中的第一个。

以下是您可以在数据集中找到的一些图像示例:

Dataset description and loading

数据集预处理

我们必须对原始数据集进行一些数据结构调整,首先将其转换为[10000, 3, 32, 32]多维数组,然后将通道维移动到最后一个顺序。

datadir='data/cifar-10-batches-bin/' 
plt.ion() 
G = glob.glob (datadir + '*.bin') 
A = np.fromfile(G[0],dtype=np.uint8).reshape([10000,3073]) 
labels = A [:,0] 
images = A [:,1:].reshape([10000,3,32,32]).transpose (0,2,3,1) 
plt.imshow(images[14]) 
print labels[11] 
images_unroll = A [:,1:] 

模型架构

在这里,我们将定义我们的建模函数,该函数是一系列卷积和池化操作,并使用最终的平坦层和逻辑回归来确定当前样本的分类概率。

def conv_model (X, y): 
X= tf. reshape(X, [-1, 32, 32, 3]) 
    with tf.variable_scope('conv_layer1'): 
        h_conv1=tf.contrib.layers.conv2d(X, num_outputs=16,  kernel_size=[5,5],  activation_fn=tf.nn.relu)#print (h_conv1) 
        h_pool1=max_pool_2x2(h_conv1)#print (h_pool1) 
with tf.variable_scope('conv_layer2'): 
        h_conv2=tf.contrib.layers.conv2d(h_pool1, num_outputs=16, kernel_size=[5,5], activation_fn=tf.nn.relu) 
    #print (h_conv2) 
    h_pool2=max_pool_2x2(h_conv2) 
    h_pool2_flat = tf.reshape(h_pool2,  [-1,8*8*16 ]) 
    h_fc1 = tf.contrib.layers.stack(h_pool2_flat, tf.contrib.layers.fully_connected ,[96,48], activation_fn=tf.nn.relu ) 

return skflow.models.logistic_regression(h_fc1,y) 

损失函数说明和优化器

以下是函数:

classifier = skflow.TensorFlowEstimator(model_fn=conv_model, n_classes=10, batch_size=100, steps=2000, learning_rate=0.01)

训练和准确率测试

使用以下两个命令,我们开始使用图像集对模型进行拟合并生成训练后模型的评分:

%time classifier.fit(images, labels, logdir='/tmp/cnn_train/')
%time score =metrics.accuracy_score(labels, classifier.predict(images))

结果描述

结果如下:

参数结果 1结果 2
CPU 时间用户 35 分钟 6 秒用户 39.8 秒
系统1 分钟 50 秒7.19 秒
总时间36 分钟 57 秒47 秒
墙上时间25 分钟 3 秒32.5 秒
准确率0.612200

完整源代码

以下是完整的源代码:

import glob 
import numpy as np 
import matplotlib.pyplot as plt 
import tensorflow as tf 
import tensorflow.contrib.learn as skflow 
from sklearn import metrics 
from tensorflow.contrib import learn 

datadir='data/cifar-10-batches-bin/' 

plt.ion() 
G = glob.glob (datadir + '*.bin') 
A = np.fromfile(G[0],dtype=np.uint8).reshape([10000,3073]) 
labels = A [:,0] 
images = A [:,1:].reshape([10000,3,32,32]).transpose (0,2,3,1) 
plt.imshow(images[15]) 
print labels[11] 
images_unroll = A [:,1:] 
def max_pool_2x2(tensor_in): 
    return tf.nn.max_pool(tensor_in,  ksize= [1,2,2,1], strides= [1,2,2,1], padding='SAME') 

def conv_model (X, y): 
    X= tf. reshape(X, [-1, 32, 32, 3]) 
    with tf.variable_scope('conv_layer1'): 
        h_conv1=tf.contrib.layers.conv2d(X, num_outputs=16,  kernel_size=[5,5],  activation_fn=tf.nn.relu)#print (h_conv1) 
        h_pool1=max_pool_2x2(h_conv1)#print (h_pool1) 
    with tf.variable_scope('conv_layer2'): 
        h_conv2=tf.contrib.layers.conv2d(h_pool1, num_outputs=16, kernel_size=[5,5], activation_fn=tf.nn.relu) 
    #print (h_conv2) 
    h_pool2=max_pool_2x2(h_conv2) 
    h_pool2_flat = tf.reshape(h_pool2,  [-1,8*8*16 ]) 
    h_fc1 = tf.contrib.layers.stack(h_pool2_flat, tf.contrib.layers.fully_connected ,[96,48], activation_fn=tf.nn.relu ) 
    return skflow.models.logistic_regression(h_fc1,y) 

images = np.array(images,dtype=np.float32) 
classifier = skflow.TensorFlowEstimator(model_fn=conv_model, n_classes=10, batch_size=100, steps=2000, learning_rate=0.01) 

%time classifier.fit(images, labels, logdir='/tmp/cnn_train/') 
%time score =metrics.accuracy_score(labels, classifier.predict(images)) 

总结

在本章中,我们了解了最先进的神经网络架构的组成部分之一:卷积神经网络。 使用此新工具,我们可以处理更复杂的数据集和概念抽象,因此我们将能够了解最新的模型。

在下一章中,我们将使用另一种新形式的神经网络以及更新的神经网络架构的一部分:循环神经网络。

七、循环神经网络和 LSTM

回顾我们对更传统的神经网络模型的了解后,我们发现训练阶段和预测阶段通常以静态方式表示,其中输入作为输入,而我们得到输出,但我们不仅考虑了事件发生的顺序。与到目前为止回顾的预测模型不同,循环神经网络的预测取决于当前的输入向量以及先前的输入向量。

我们将在本章中介绍的主题如下:

  • 了解循环神经网络的工作原理以及构建它们的主要操作类型
  • 解释在更高级的模型(例如 LSTM)中实现的想法
  • 在 TensorFlow 中应用 LSTM 模型来预测能耗周期
  • 撰写新音乐,从 J.S Bach 的一系列研究开始

循环神经网络

知识通常不会从虚无中出现。 许多新的想法是先前知识的结合而诞生的,因此这是一种有用的模仿行为。 传统的神经网络不包含任何将先前看到的元素转换为当前状态的机制。

为了实现这一概念,我们有循环神经网络,即 RNN。 可以将循环神经网络定义为神经网络的顺序模型,该模型具有重用已给定信息的特性。 他们的主要假设之一是,当前信息依赖于先前的数据。 在下图中,我们观察到称为单元的 RNN 基本元素的简化图:

Recurrent neural networks

单元的主要信息元素是输入(Xt),状态和输出(ht)。 但是正如我们之前所说,单元没有独立的状态,因此它还存储状态信息。 在下图中,我们将显示一个“展开”的 RNN 单元,显示其从初始状态到输出最终h[n]值的过程,中间有一些中间状态。

Recurrent neural networks

一旦我们定义了单元的动态性,下一个目标就是研究制造或定义 RNN 单元的内容。 在标准 RNN 的最常见情况下,仅存在一个神经网络层,该神经网络层将输入和先前状态作为输入,应用 tanh 操作,并输出新状态h(t+1).

Recurrent neural networks

这种简单的设置能够随着周期的过去而对信息进行汇总,但是进一步的实验表明,对于复杂的知识而言,序列距离使得难以关联某些上下文(例如,建筑师知道设计漂亮的建筑物)似乎是一种简单的结构, 请记住,但是将它们关联所需的上下文需要增加顺序才能将两个概念关联起来。 这也带来了爆炸和消失梯度的相关问题。

梯度爆炸和消失

循环神经网络的主要问题之一发生在反向传播阶段,鉴于其循环性质,误差反向传播所具有的步骤数与一个非常深的网络相对应。 梯度计算的这种级联可能在最后阶段导致非常不重要的值,或者相​​反,导致不断增加且不受限制的参数。 这些现象被称为消失和爆炸梯度。 这是创建 LSTM 架构的原因之一。

LSTM 神经网络

长短期内存(LSTM)是一种特定的 RNN 架构,其特殊的架构使它们可以表示长期依赖性。 而且,它们是专门为记住长时间的信息模式和信息而设计的。

门操作 -- 基本组件

为了更好地理解 lstm 单元内部的构造块,我们将描述 LSTM 的主要操作块:门操作。

此操作基本上有一个多元输入,在此块中,我们决定让一些输入通过,将其他输入阻塞。 我们可以将其视为信息过滤器,并且主要有助于获取和记住所需的信息元素。

为了实现此操作,我们采用了一个多元控制向量(标有箭头),该向量与具有 Sigmoid 激活函数的神经网络层相连。 应用控制向量并通过 Sigmoid 函数,我们将得到一个类似于二元的向量。

我们将用许多开关符号来表示此操作:

The gate operation - a fundamental component

定义了二元向量后,我们将输入函数与向量相乘,以便对其进行过滤,仅让部分信息通过。 我们将用一个三角形来表示此操作,该三角形指向信息行进的方向。

The gate operation - a fundamental component

LSTM 单元格的一般结构

在下面的图片中,我们代表了 LSTM 单元的一般结构。 它主要由上述三个门操作组成,以保护和控制单元状态。

此操作将允许丢弃(希望不重要)低状态数据,并且将新数据(希望重要)合并到状态中。

The gate operation - a fundamental component

上一个图试图显示一个 LSTM 单元的运行中发生的所有概念。

作为输入,我们有:

  • 单元格状态将存储长期信息,因为它从一开始就从单元格训练的起点进行优化的权重,并且
  • 短期状态h(t),将在每次迭代中直接与当前输入结合使用,因此,其状态将受输入的最新值的影响更大

作为输出,我们得到了结合所有门操作的结果。

操作步骤

在本节中,我们将描述信息将对其操作的每个循环步骤执行的所有不同子步骤的概括。

第 1 部分 -- 设置要忘记的值(输入门)

在本节中,我们将采用来自短期的值,再加上输入本身,并且这些值将由多元 Sigmoid 表示的二元函数的值设置。 根据输入和短期记忆值,Sigmoid 输出将允许或限制一些先前的知识或单元状态中包含的权重。

Part 1 - set values to forget (input gate)

第 2 部分 -- 设置要保留的值,更改状态

然后是时候设置过滤器了,该过滤器将允许或拒绝将新的和短期的内存合并到单元半永久状态。

因此,在此阶段,我们将确定将多少新信息和半新信息合并到新单元状态中。 此外,我们最终将通过我们一直在配置的信息过滤器,因此,我们将获得更新的长期状态。

为了规范新的和短期的信息,我们通过具有tanh激活的神经网络传递新的和短期的信息,这将允许在正则化(-1,1)范围内提供新信息。

Part 2 - set values to keep, change state

第 3 部分 -- 输出已过滤的单元状态

现在轮到短期状态了。 它还将使用新的和先前的短期状态来允许新信息通过,但是输入将是长期状态,点乘以 tanh 函数,再一次将输入标准化为(-1,1)范围。

Part 3 - output filtered cell state

其他 RNN 架构

通常,在本章中,假设 RNN 的领域更为广泛,我们将集中讨论 LSTM 类型的循环神经网络单元。 例如,还采用了 RNN 的其他变体,并为该领域增加了优势。

  • 具有窥孔的 LSTM:在此网络中,单元门连接到单元状态
  • 门控循环单元:这是一个更简单的模型,它结合了忘记门和输入门,合并了单元的状态和隐藏状态,因此大大简化了网络的训练

TensorFlow LSTM 有用的类和方法

在本节中,我们将回顾可用于构建 LSTM 层的主要类和方法,我们将在本书的示例中使用它们。

tf.nn.rnn_cell.BasicLSTMCell

此类基本的 LSTM 循环网络单元,具有遗忘偏差,并且没有其他相关类型(如窥孔)的奇特特性,即使在不应影响的阶段,它也可以使单元查看所得状态。

以下是主要参数:

  • num_units:整数,LSTM 单元的单元数
  • forget_bias:浮动,此偏差(默认为1)被添加到忘记门,以便允许第一次迭代以减少初始训练步骤的信息丢失。
  • activation:内部状态的激活函数(默认为标准tanh

MultiRNNCellRNNCell

在将用于此特定示例的架构中,我们将不会使用单个单元来考虑历史值。 在这种情况下,我们将使用一组连接的单元格。 因此,我们将实例化MultiRNNCell类。

MultiRNNCell(cells, state_is_tuple=False)

这是multiRNNCell的构造器,此方法的主要参数是单元格,它将是我们要堆叠的RNNCells的实例。

class MultiRNNCell(RNNCell)

learning.ops.split_squeeze(dim, num_split, tensor_in)

此函数将输入拆分为一个维度,然后压缩拆分后的张量所属的前一个维度。 它需要切割的尺寸,切割方式的数量,然后是张量的切割。 它返回相同的张量,但缩小一维。

示例 1 -- 能耗数据的单变量时间序列预测

在此示例中,我们将解决回归域的问题。 我们将要处理的数据集是一个周期内对一个家庭的许多功耗量度的汇总。 正如我们可以推断的那样,这种行为很容易遵循以下模式(当人们使用微波炉准备早餐时,这种行为会增加,醒来后的电脑数量会有所增加,下午可能会有所减少,而到了晚上,一切都会增加。 灯,从午夜开始直到下一个起床时间减少为零)。

因此,让我们尝试在一个示例案例中对此行为进行建模。

数据集说明和加载

在此示例中,我们将使用 Artur Trindade 的电力负荷图数据集。

这是原始数据集的描述:

数据集没有缺失值。 每 15 分钟以 kW 为单位的值。 要以 kWh 为单位转换值,必须将值除以 4。每一列代表一个客户端。 在 2011 年之后创建了一些客户。在这些情况下,消费被视为零。 所有时间标签均以葡萄牙语小时为单位。 但是,整天呈现 96 个小节(24 * 15)。 每年 3 月的时间更改日(只有 23 小时),所有时间点的凌晨 1:00 和 2:00 之间均为零。 每年 10 月的时间变更日(有 25 个小时),上午 1:00 和凌晨 2:00 之间的值合计消耗两个小时。

为了简化我们的模型描述,我们仅对一位客户进行了完整的测量,并将其格式转换为标准 CSV。 它位于本章代码文件夹的数据子文件夹中

使用以下代码行,我们将打开并表示客户的数据:

import pandas as pd 
from matplotlib import pyplot as plt
df = pd.read_csv("data/elec_load.csv", error_bad_lines=False)
plt.subplot()
plot_test, = plt.plot(df.values[:1500], label='Load')
plt.legend(handles=[plot_test])

Dataset description and loading

我看一下这种表示形式(我们看一下前 1500 个样本),我们看到了一个初始瞬态状态,可能是在进行测量时可能出现的状态,然后我们看到了一个清晰的高,低功耗水平的循环。

从简单的观察中,我们还可以看到冰柱或多或少是 100 个样本的,非常接近该数据集每天的 96 个样本。

数据集预处理

为了确保反向传播方法更好的收敛性,我们应该尝试对输入数据进行正则化。

因此,我们将应用经典的缩放和居中技术,减去平均值,然后按最大值的底数进行缩放。

为了获得所需的值,我们使用熊猫describe()方法。

                Load 
count  140256.000000 
mean      145.332503 
std        48.477976 
min         0.000000 
25%       106.850998 
50%       151.428571 
75%       177.557604 
max       338.218126 

Dataset preprocessing

模型架构

在这里,我们将简要描述将尝试对电力消耗变化进行建模的架构:

最终的架构基本上由 10 个成员连接的 LSTM 多单元组成,该单元的末尾具有线性回归或变量,对于给定的历史记录,它将线性单元数组输出的结果转换为最终的实数。 值(在这种情况下,我们必须输入最后 5 个值才能预测下一个)。

def lstm_model(time_steps, rnn_layers, dense_layers=None): 
    def lstm_cells(layers): 
        return [tf.nn.rnn_cell.BasicLSTMCell(layer['steps'],state_is_tuple=True) 
                for layer in layers] 

    def dnn_layers(input_layers, layers): 
            return input_layers 

    def _lstm_model(X, y): 
        stacked_lstm = tf.nn.rnn_cell.MultiRNNCell(lstm_cells(rnn_layers), state_is_tuple=True) 
        x_ = learn.ops.split_squeeze(1, time_steps, X) 
        output, layers = tf.nn.rnn(stacked_lstm, x_, dtype=dtypes.float32) 
        output = dnn_layers(output[-1], dense_layers) 
        return learn.models.linear_regression(output, y) 

    return _lstm_model 

下图显示了主要模块,随后由学习模块进行了补充,您可以在其中看到 RNN 阶段,优化器以及输出之前的最终线性回归。

Modelling architecture

在这张图片中,我们看了 RNN 阶段,在那里我们可以观察到各个 LSTM 单元的级联,输入的挤压以及该学习包所添加的所有互补操作。

Modelling architecture

然后,我们将使用回归器完成模型的定义:

regressor = learn.TensorFlowEstimator(model_fn=lstm_model( 
                                    TIMESTEPS, RNN_LAYERS, DENSE_LAYERS), n_classes=0, 
                                      verbose=2,  steps=TRAINING_STEPS, optimizer='Adagrad', 
                                      learning_rate=0.03, batch_size=BATCH_SIZE) 

损失函数说明

对于损失函数,经典回归参数均方误差将:

rmse = np.sqrt(((predicted - y['test']) ** 2).mean(axis=0))

收敛性测试

在这里,我们将为当前模型运行拟合函数:

regressor.fit(X['train'], y['train'], monitors=[validation_monitor], logdir=LOG_DIR) 

并将获得以下内容(很好)! 错误率。 我们可以做的一项工作是避免对数据进行标准化,并查看平均误差是否相同(注意:不是,差很多)

这是我们将获得的简单控制台输出:

MSE: 0.001139 

这是生成的损耗/均值图形,它告诉我们误差在每次迭代中如何衰减:

Convergency test

结果描述

现在我们可以得到真实测试值和预测值的图形,在图形中我们可以看到平均误差表明我们的循环模型具有很好的预测能力:

Results description

完整源代码

以下是完整的源代码:


import numpy as np 
import pandas as pd 
import tensorflow as tf 
from matplotlib import pyplot as plt 

from tensorflow.python.framework import dtypes 
from tensorflow.contrib import learn 

import logging 
logging.basicConfig(level=logging.INFO) 

from tensorflow.contrib import learn 
from sklearn.metrics import mean_squared_error 

LOG_DIR = './ops_logs' 
TIMESTEPS = 5 
RNN_LAYERS = [{'steps': TIMESTEPS}] 
DENSE_LAYERS = None 
TRAINING_STEPS = 10000 
BATCH_SIZE = 100 
PRINT_STEPS = TRAINING_STEPS / 100 

def lstm_model(time_steps, rnn_layers, dense_layers=None): 
    def lstm_cells(layers): 
        return [tf.nn.rnn_cell.BasicLSTMCell(layer['steps'],state_is_tuple=True) 
                for layer in layers] 

    def dnn_layers(input_layers, layers): 
            return input_layers 

    def _lstm_model(X, y): 
        stacked_lstm = tf.nn.rnn_cell.MultiRNNCell(lstm_cells(rnn_layers), state_is_tuple=True) 
        x_ = learn.ops.split_squeeze(1, time_steps, X) 
        output, layers = tf.nn.rnn(stacked_lstm, x_, dtype=dtypes.float32) 
        output = dnn_layers(output[-1], dense_layers) 
        return learn.models.linear_regression(output, y) 

    return _lstm_model 

regressor = learn.TensorFlowEstimator(model_fn=lstm_model(TIMESTEPS, RNN_LAYERS, DENSE_LAYERS), n_classes=0, 
                                      verbose=2,  steps=TRAINING_STEPS, optimizer='Adagrad', 
                                      learning_rate=0.03, batch_size=BATCH_SIZE) 

df = pd.read_csv("data/elec_load.csv", error_bad_lines=False) 
plt.subplot() 
plot_test, = plt.plot(df.values[:1500], label='Load') 
plt.legend(handles=[plot_test]) 

print df.describe() 
array=(df.values- 147.0) /339.0 
plt.subplot() 
plot_test, = plt.plot(array[:1500], label='Normalized Load') 
plt.legend(handles=[plot_test]) 

listX = [] 
listy = [] 
X={} 
y={} 

for i in range(0,len(array)-6): 
    listX.append(array[i:i+5].reshape([5,1])) 
    listy.append(array[i+6]) 

arrayX=np.array(listX) 
arrayy=np.array(listy) 

X['train']=arrayX[0:12000] 
X['test']=arrayX[12000:13000] 
X['val']=arrayX[13000:14000] 

y['train']=arrayy[0:12000] 
y['test']=arrayy[12000:13000] 
y['val']=arrayy[13000:14000] 

# print y['test'][0] 
# print y2['test'][0] 

#X1, y2 = generate_data(np.sin, np.linspace(0, 100, 10000), TIMESTEPS, seperate=False) 
# create a lstm instance and validation monitor 
validation_monitor = learn.monitors.ValidationMonitor(X['val'], y['val'], 
                                                      every_n_steps=PRINT_STEPS, 
                                                      early_stopping_rounds=1000) 

regressor.fit(X['train'], y['train'], monitors=[validation_monitor], logdir=LOG_DIR) 

predicted = regressor.predict(X['test']) 
rmse = np.sqrt(((predicted - y['test']) ** 2).mean(axis=0)) 
score = mean_squared_error(predicted, y['test']) 
print ("MSE: %f" % score) 

#plot_predicted, = plt.plot(array[:1000], label='predicted') 

plt.subplot() 
plot_predicted, = plt.plot(predicted, label='predicted') 

plot_test, = plt.plot(y['test'], label='test') 
plt.legend(handles=[plot_predicted, plot_test]) 

示例 2 -- 编写音乐 A La Bach

在此示例中,我们将使用专门针对字符序列或字符 RNN 模型的循环神经网络。

我们将使用一系列基于字符的格式表达的音乐,即巴赫·戈德堡变奏曲(Bach Goldberg Variations),馈入该神经网络,并根据所学的结构编写一首音乐样本。

注意

请注意,此示例归功于《可视化和理解循环网络》标题为“循环神经网络的不合理有效性”的文章,该文章提供了许多想法和概念。

字符级别模型

如我们先前所见,字符 RNN 模型可用于字符序列。 这类输入可以代表多种可能的语言。 以下是一些示例:

  • 代码
  • 不同的人类语言(某些作者的写作风格的建模)
  • 科学论文(tex)等

字符序列和概率表示

RNN 的输入内容需要一种清晰直接的表示方式。 因此,选择单热表示,可以方便地将其直接用于表征有限数量的可能结果(有限字符的数量是有限的并且以十为单位)的输出,并可以将其与 Softmax函数值。

因此,模型的输入是字符序列,模型的输出将是每个实例的数组序列。 数组的长度将与词汇表的大小相同,因此,给定先前输入的序列字符,每个数组位置将代表当前字符在此序列位置中的概率。

在下图中,我们观察到一个非常简化的设置模型,其中编码的输入单词和该模型预测单词TEST作为预期的输出:

Character sequences and probability representation

将音乐编码为字符 -- ABC 音乐格式

搜索表示输入数据的格式时,如果可能的话,选择一种更简单但结构上均一的格式很重要。

关于音乐表示,ABC 格式是一种合适的选择,因为它的结构非常简单,使用的字符数有限,并且是 ASCII 字符集的子集。

ABC 格式数据组织

ABC 格式页面主要包含两个组件:标头和注释。

  • Header:标头包含一些键:值行,例如X:[Reference number]T:[Title]M:[Meter]K:[Key]C[Composer]
  • 注释:注释从K标题键之后开始,并列出每个小节的不同注释,以|字符分隔。

还有其他元素,但是通过以下示例,即使没有音乐训练,您也将了解格式的工作原理:

原始样本如下:

X:1 
T:Notes 
M:C 
L:1/4 
K:C 
C, D, E, F,|G, A, B, C|D E F G|A B c d|e f g a|b c' d' e'|f' g' a' b'|] 

最终表示如下:

ABC format data organization

巴赫·戈德堡的变化:

巴赫·戈德堡(Bach Goldberg)变奏曲是一组原始的咏叹调,并基于该咏叹调创作了 30 部作品,以巴赫的门徒约翰·哥特利布·戈德堡(Johann Gottlieb Goldberg)的名字命名,他可能是其主要的解释者。

在下一个清单和图中,我们将表示变体Nr 1的第一部分,因此您对我们将尝试模仿的文档结构有所了解:

X:1  
T:Variation no. 1  
C:J.S.Bach  
M:3/4  
L:1/16  
Q:500  
V:2 bass  
K:G  
[V:1]GFG2- GDEF GAB^c |d^cd2- dABc defd |gfg2- gfed ^ceAG|  
[V:2]G,,2B,A, B,2G,2G,,2G,2 |F,,2F,E, F,2D,2F,,2D,2 |E,,2E,D, E,2G,2A,,2^C2|  
%  (More parts with V:1 and V:2) 

ABC format data organization

有用的库和方法

在本节中,我们将学习在此示例中将使用的新函数。

保存和还原变量和模型

对于现实世界的应用来说,一项非常重要的能力是能够保存和检索整个模型。 TensorFlow 通过tf.train.Saver对象提供此功能。

该对象的主要方法如下:

  • tf.train.Saver(args):这是构造器。 这是主要参数的列表:
    • var_list:这是一个列表,其中包含要保存的所有变量的列表。 例如,{firstvar: var1secondvar: var2}。 如果不存在,请保存所有对象。
    • max_to_keep:这表示要维护的最大检查点数。
    • write_version:这是文件格式版本,实际上只有 1 个有效。
  • tf.train.Saver.save:此方法运行由构造器添加的用于保存变量的操作。 这需要当前会​​话,并且所有变量都已初始化。 主要参数如下:
    • session:这是保存变量的会话
    • save_path:这是检查点文件名的路径
    • global_step:这是唯一的步骤标识符

此方法返回保存检查点的路径。

  • tf.train.Saver.restore:此方法恢复以前保存的变量。 主要参数如下:
    • session:会话是要还原变量的位置
    • save_path:这是先前由save方法,对last_checkpoint()的调用或提供的变量先前返回的变量

加载和保存的伪代码

在这里,我们将使用一些示例代码来构建用于保存和检索两个示例变量的最小结构。

变量保存

以下是创建变量的代码:

# Create some variables.simplevar = tf.Variable(..., name="simple")anothervar = tf.Variable(..., name="another")...# Add ops to save and restore all the variables.saver = tf.train.Saver()# Later, launch the model, initialize the variables, do some work, save the# variables to disk.with tf.Session() as sess:  sess.run(tf.initialize_all_variables())  # Do some work with the model.  ..  # Save the variables to disk.  save_path = saver.save(sess, "/tmp/model.ckpt")
变量还原

以下是用于还原变量的代码:

saver = tf.train.Saver()
# Later, launch the model, use the saver to restore variables from disk, and
# do some work with the model.
with tf.Session() as sess:
#Work with the restored model....

数据集说明和加载

对于此数据集,我们从 30 幅作品开始,然后生成其随机分布的1000个实例的列表:

import random 
input = open('input.txt', 'r').read().split('X:') 
for i in range (1,1000): 
    print "X:" + input[random.randint(1,30)] + "\n_____________________________________\n" 

网络训练

网络训练的原始材料将是 ABC 格式的30作品。

注意

请注意,原始 ABC 文件位于此链接

然后,我们使用这个小程序。

对于此数据集,我们从30作品开始,然后生成一个随机分布的1000实例列表:

import random 
input = open('original.txt', 'r').read().split('X:') 
for i in range (1,1000): 
    print "X:" + input[random.randint(1,30)] + "\n_____________________________________\n" 

然后我们执行以下命令来获取数据集:

python generate_dataset.py > input.txt 

数据集预处理

生成的数据集在有用之前需要一些信息。 首先,它需要词汇的定义。

词汇定义

该过程的第一步是找到可以在原始文本中找到的所有不同字符,以便以后能够确定尺寸并填充单热编码输入。

在下图中,我们表示以 ABC 音乐格式找到的不同字符。 在这里,您可以看到标准中包含普通和特殊标点符号的内容:

Vocabulary definition

模型架构

下面的行中描述了此 RNN 的模型,它是具有初始零状态的多层 LSTM:

        cell_fn = rnn_cell.BasicLSTMCell  
        cell = cell_fn(args.rnn_size, state_is_tuple=True) 
        self.cell = cell = rnn_cell.MultiRNNCell([cell] * args.num_layers, state_is_tuple=True) 
        self.input_data = tf.placeholder(tf.int32, [args.batch_size, args.seq_length]) 
        self.targets = tf.placeholder(tf.int32, [args.batch_size, args.seq_length]) 
        self.initial_state = cell.zero_state(args.batch_size, tf.float32) 
        with tf.variable_scope('rnnlm'): 
            softmax_w = tf.get_variable("softmax_w", [args.rnn_size, args.vocab_size])  
            softmax_b = tf.get_variable("softmax_b", [args.vocab_size])   
            with tf.device("/cpu:0"): 
                embedding = tf.get_variable("embedding", [args.vocab_size, args.rnn_size]) 
                inputs = tf.split(1, args.seq_length, tf.nn.embedding_lookup(embedding, self.input_data)) 
                inputs = [tf.squeeze(input_, [1]) for input_ in inputs] 
        def loop(prev, _): 
            prev = tf.matmul(prev, softmax_w) + softmax_b 
            prev_symbol = tf.stop_gradient(tf.argmax(prev, 1)) 
            return tf.nn.embedding_lookup(embedding, prev_symbol) 
        outputs, last_state = seq2seq.rnn_decoder(inputs, self.initial_state, cell, loop_function=loop if infer else None, scope='rnnlm') 
        output = tf.reshape(tf.concat(1, outputs), [-1, args.rnn_size]) 

损失函数说明

损失函数由losss_by_example函数定义。 这是基于一种称为“困惑性”的度量,该度量可测量概率分布预测样本的程度。 此度量在语言模型中广泛使用:

        self.logits = tf.matmul(output, softmax_w) + softmax_b 
        self.probs = tf.nn.softmax(self.logits) 
        loss = seq2seq.sequence_loss_by_example([self.logits], 
                [tf.reshape(self.targets, [-1])], 
                [tf.ones([args.batch_size * args.seq_length])], 
                args.vocab_size) 
        self.cost = tf.reduce_sum(loss) / args.batch_size / args.seq_length 

停止条件

程序将迭代直到达到周期数和批号为止。 这是条件块:

if (e==args.num_epochs-1 and b == data_loader.num_batches-1) 

结果描述

为了运行程序,首先使用以下代码运行训练脚本:

python train.py 

然后,使用以下代码运行示例程序:

python sample.py 

配置X:1\n的质数,这是一个可能的初始化字符序列,我们可以根据 RNN 的深度(建议 3)和长度(建议 512)获得几乎可以识别的完整构图。

根据现场诊断,获得了以下乐谱,将得到的字符序列复制到 drawthedots.com 并进行简单的字符校正:

Results description

完整源代码

以下是完整的源代码(train.py):

from __future__ import print_function 
import numpy as np 
import tensorflow as tf 

import argparse 
import time 
import os 
from six.moves import cPickle 
from utils import TextLoader 
from model import Model 
class arguments: 
    def __init__(self): 
        return 
def main(): 
    args = arguments()     
    train(args) 
def train(args): 
    args.data_dir='data/'; args.save_dir='save'; args.rnn_size =64; 
    args.num_layers=1;  args.batch_size=50;args.seq_length=50 
    args.num_epochs=5;args.save_every=1000; args.grad_clip=5\. 
    args.learning_rate=0.002; args.decay_rate=0.97 
    data_loader = TextLoader(args.data_dir, args.batch_size, args.seq_length) 
    args.vocab_size = data_loader.vocab_size 
    with open(os.path.join(args.save_dir, 'config.pkl'), 'wb') as f: 
        cPickle.dump(args, f) 
    with open(os.path.join(args.save_dir, 'chars_vocab.pkl'), 'wb') as f: 
        cPickle.dump((data_loader.chars, data_loader.vocab), f) 
    model = Model(args) 
    with tf.Session() as sess: 
        tf.initialize_all_variables().run() 
        saver = tf.train.Saver(tf.all_variables()) 
        for e in range(args.num_epochs): 
            sess.run(tf.assign(model.lr, args.learning_rate * (args.decay_rate ** e))) 
            data_loader.reset_batch_pointer() 
            state = sess.run(model.initial_state) 
            for b in range(data_loader.num_batches): 
                start = time.time() 
                x, y = data_loader.next_batch() 
                feed = {model.input_data: x, model.targets: y} 
                for i, (c, h) in enumerate(model.initial_state): 
                    feed[c] = state[i].c 
                    feed[h] = state[i].h 
                train_loss, state, _ = sess.run([model.cost, model.final_state, model.train_op], feed) 
                end = time.time() 
                print("{}/{} (epoch {}), train_loss = {:.3f}, time/batch = {:.3f}" \ 
                    .format(e * data_loader.num_batches + b, 
                            args.num_epochs * data_loader.num_batches, 
                            e, train_loss, end - start)) 
                if (e==args.num_epochs-1 and b == data_loader.num_batches-1): # save for the last result 
                    checkpoint_path = os.path.join(args.save_dir, 'model.ckpt') 
                    saver.save(sess, checkpoint_path, global_step = e * data_loader.num_batches + b) 
                    print("model saved to {}".format(checkpoint_path)) 

if __name__ == '__main__': 
    main() 

以下是完整的源代码(model.py):

import tensorflow as tf
from tensorflow.python.ops import rnn_cell
from tensorflow.python.ops import seq2seq
import numpy as np

class Model():
    def __init__(self, args, infer=False):
        self.args = args
        if infer: #When we sample, the batch and sequence lenght are = 1
            args.batch_size = 1
            args.seq_length = 1
        cell_fn = rnn_cell.BasicLSTMCell #Define the internal cell structure
        cell = cell_fn(args.rnn_size, state_is_tuple=True)
        self.cell = cell = rnn_cell.MultiRNNCell([cell] * args.num_layers, state_is_tuple=True)
        #Build the inputs and outputs placeholders, and start with a zero internal values
        self.input_data = tf.placeholder(tf.int32, [args.batch_size, args.seq_length])
        self.targets = tf.placeholder(tf.int32, [args.batch_size, args.seq_length])
        self.initial_state = cell.zero_state(args.batch_size, tf.float32)
        with tf.variable_scope('rnnlm'):
            softmax_w = tf.get_variable("softmax_w", [args.rnn_size, args.vocab_size]) #Final w
            softmax_b = tf.get_variable("softmax_b", [args.vocab_size]) #Final bias
            with tf.device("/cpu:0"):
                embedding = tf.get_variable("embedding", [args.vocab_size, args.rnn_size])
                inputs = tf.split(1, args.seq_length, tf.nn.embedding_lookup(embedding, self.input_data))
                inputs = [tf.squeeze(input_, [1]) for input_ in inputs]
        def loop(prev, _):
            prev = tf.matmul(prev, softmax_w) + softmax_b
            prev_symbol = tf.stop_gradient(tf.argmax(prev, 1))
            return tf.nn.embedding_lookup(embedding, prev_symbol)
        outputs, last_state = seq2seq.rnn_decoder(inputs, self.initial_state, cell, loop_function=loop if infer else None, scope='rnnlm')
        output = tf.reshape(tf.concat(1, outputs), [-1, args.rnn_size])
        self.logits = tf.matmul(output, softmax_w) + softmax_b
        self.probs = tf.nn.softmax(self.logits)
        loss = seq2seq.sequence_loss_by_example([self.logits],
            [tf.reshape(self.targets, [-1])],
            [tf.ones([args.batch_size * args.seq_length])],
            args.vocab_size)
        self.cost = tf.reduce_sum(loss) / args.batch_size / args.seq_length
        self.final_state = last_state
        self.lr = tf.Variable(0.0, trainable=False)
        tvars = tf.trainable_variables()
        grads, _ = tf.clip_by_global_norm(tf.gradients(self.cost,        tvars),
        args.grad_clip)
        optimizer = tf.train.AdamOptimizer(self.lr)
        self.train_op = optimizer.apply_gradients(zip(grads, tvars))
    def sample(self, sess, chars, vocab, num=200, prime='START', sampling_type=1):
        state = sess.run(self.cell.zero_state(1, tf.float32))
        for char in prime[:-1]:
            x = np.zeros((1, 1))
            x[0, 0] = vocab[char]
            feed = {self.input_data: x, self.initial_state:state}
            [state] = sess.run([self.final_state], feed)
        def weighted_pick(weights):
            t = np.cumsum(weights)
            s = np.sum(weights)
            return(int(np.searchsorted(t, np.random.rand(1)*s)))
        ret = prime
        char = prime[-1]
        for n in range(num):
            x = np.zeros((1, 1))
            x[0, 0] = vocab[char]
            feed = {self.input_data: x, self.initial_state:state}
            [probs, state] = sess.run([self.probs, self.final_state], feed)
            p = probs[0]
            sample = weighted_pick(p)
            pred = chars[sample]
            ret += pred
            char = pred
        return ret

以下是完整的源代码(sample.py):

from __future__ import print_function

import numpy as np
import tensorflow as tf
import time
import os
from six.moves import cPickle
from utils import TextLoader
from model import Model
from six import text_type

class arguments: #Generate the arguments class
    save_dir= 'save'
    n=1000
    prime='x:1\n'
    sample=1 

def main():
    args = arguments()
    sample(args)   #Pass the argument object

def sample(args):
    with open(os.path.join(args.save_dir, 'config.pkl'), 'rb') as f:
        saved_args = cPickle.load(f) #Load the config from the standard file
    with open(os.path.join(args.save_dir, 'chars_vocab.pkl'), 'rb') as f:

        chars, vocab = cPickle.load(f) #Load the vocabulary
    model = Model(saved_args, True) #Rebuild the model
    with tf.Session() as sess:
        tf.initialize_all_variables().run() 
        saver = tf.train.Saver(tf.all_variables())   
        ckpt = tf.train.get_checkpoint_state(args.save_dir) #Retrieve the chkpoint
        if ckpt and ckpt.model_checkpoint_path:
            saver.restore(sess, ckpt.model_checkpoint_path) #Restore the model
            print(model.sample(sess, chars, vocab, args.n, args.prime, args.sample))
            #Execute the model, generating a n char sequence
            #starting with the prime sequence
if __name__ == '__main__':
    main()

以下是完整的源代码(utils.py):

import codecs
import os
import collections
from six.moves import cPickle
import numpy as np

class TextLoader():
    def __init__(self, data_dir, batch_size, seq_length, encoding='utf-8'):
        self.data_dir = data_dir
        self.batch_size = batch_size
        self.seq_length = seq_length
        self.encoding = encoding

        input_file = os.path.join(data_dir, "input.txt")
        vocab_file = os.path.join(data_dir, "vocab.pkl")
        tensor_file = os.path.join(data_dir, "data.npy")

        if not (os.path.exists(vocab_file) and os.path.exists(tensor_file)):
            print("reading text file")
            self.preprocess(input_file, vocab_file, tensor_file)
        else:
            print("loading preprocessed files")
            self.load_preprocessed(vocab_file, tensor_file)
        self.create_batches()
        self.reset_batch_pointer()

    def preprocess(self, input_file, vocab_file, tensor_file):
        with codecs.open(input_file, "r", encoding=self.encoding) as f:
            data = f.read()
        counter = collections.Counter(data)
        count_pairs = sorted(counter.items(), key=lambda x: -x[1])
        self.chars, _ = zip(*count_pairs)
        self.vocab_size = len(self.chars)
        self.vocab = dict(zip(self.chars, range(len(self.chars))))
        with open(vocab_file, 'wb') as f:
            cPickle.dump(self.chars, f)
        self.tensor = np.array(list(map(self.vocab.get, data)))
        np.save(tensor_file, self.tensor)

    def load_preprocessed(self, vocab_file, tensor_file):
        with open(vocab_file, 'rb') as f:
            self.chars = cPickle.load(f)
        self.vocab_size = len(self.chars)
        self.vocab = dict(zip(self.chars, range(len(self.chars))))
        self.tensor = np.load(tensor_file)
        self.num_batches = int(self.tensor.size / (self.batch_size *
                                                   self.seq_length))

    def create_batches(self):
        self.num_batches = int(self.tensor.size / (self.batch_size *
                                                   self.seq_length))

        self.tensor = self.tensor[:self.num_batches * self.batch_size * self.seq_length]
        xdata = self.tensor
        ydata = np.copy(self.tensor)
        ydata[:-1] = xdata[1:]
        ydata[-1] = xdata[0]
        self.x_batches = np.split(xdata.reshape(self.batch_size, -1), self.num_batches, 1)
        self.y_batches = np.split(ydata.reshape(self.batch_size, -1), self.num_batches, 1)

    def next_batch(self):
        x, y = self.x_batches[self.pointer], self.y_batches[self.pointer]
        self.pointer += 1
        return x, y

    def reset_batch_pointer(self):
        self.pointer = 0

总结

在本章中,我们回顾了一种最新的神经网络架构,即循环神经网络,从而完善了机器学习领域主流方法的全景。

在下一章中,我们将研究在最先进的实现中出现的不同的神经网络层类型组合,并涵盖一些新的有趣的实验模型。

八、深度神经网络

在本章中,我们将回顾机器学习,深度神经网络中最先进的技术,也是研究最多的领域之一。

深度神经网络定义

这是一个新闻技术领域蓬勃发展的领域,每天我们都听到成功地将 DNN 用于解决新问题的实验,例如计算机视觉,自动驾驶,语音和文本理解等。

在前几章中,我们使用了与 DNN 相关的技术,尤其是在涉及卷积神经网络的技术中。

出于实际原因,我们将指深度学习和深度神经网络,即其中层数明显优于几个相似层的架构,我们将指代具有数十个层的神经网络架构,或者复杂结构的组合。

穿越时空的深度网络架构

在本节中,我们将回顾从 LeNet5 开始在整个深度学习历史中出现的里程碑架构。

LeNet 5

在 1980 年代和 1990 年代,神经网络领域一直保持沉默。 尽管付出了一些努力,但是架构非常简单,并且需要大的(通常是不可用的)机器力量来尝试更复杂的方法。

1998 年左右,在贝尔实验室中,在围绕手写校验数字分类的研究中,Ian LeCun 开始了一种新趋势,该趋势实现了所谓的“深度学习——卷积神经网络”的基础,我们已经在第 5 章,简单的前馈神经网络中对其进行了研究。

在那些年里,SVM 和其他更严格定义的技术被用来解决这类问题,但是有关 CNN 的基础论文表明,与当时的现有方法相比,神经网络的表现可以与之媲美或更好。

Alexnet

经过几年的中断(即使 LeCun 继续将其网络应用到其他任务,例如人脸和物体识别),可用结构化数据和原始处理能力的指数增长,使团队得以增长和调整模型, 在某种程度上被认为是不可能的,因此可以增加模型的复杂性,而无需等待数月的训练。

来自许多技术公司和大学的计算机研究团队开始竞争一些非常艰巨的任务,包括图像识别。 对于以下挑战之一,即 Imagenet 分类挑战,开发了 Alexnet 架构:

Alexnet

Alexnet 架构

主要功能

从其第一层具有卷积运算的意义上讲,Alexnet 可以看作是增强的 LeNet5。 但要添加未使用过的最大池化层,然后添加一系列密集的连接层,以建立最后的输出类别概率层。 视觉几何组(VGG)模型

图像分类挑战的其他主要竞争者之一是牛津大学的 VGG。

VGG 网络架构的主要特征是它们将卷积滤波器的大小减小到一个简单的3x3,并按顺序组合它们。

微小的卷积内核的想法破坏了 LeNet 及其后继者 Alexnet 的最初想法,后者最初使用的过滤器高达11x11过滤器,但复杂得多且表现低下。 过滤器大小的这种变化是当前趋势的开始:

Main features

VGG 中每层的参数编号摘要

然而,使用一系列小的卷积权重的积极变化,总的设置是相当数量的参数(数以百万计的数量级),因此它必须受到许多措施的限制。

原始的初始模型

在由 Alexnet 和 VGG 主导的两个主要研究周期之后,Google 凭借非常强大的架构 Inception 打破了挑战,该架构具有多次迭代。

这些迭代的第一个迭代是从其自己的基于卷积神经网络层的架构版本(称为 GoogLeNet)开始的,该架构的名称让人想起了始于网络的方法。

GoogLenet(InceptionV1)

GoogLenet (InceptionV1)

InceptionV1

GoogLeNet 是这项工作的第一个迭代,如下图所示,它具有非常深的架构,但是它具有九个链式初始模块的令人毛骨悚然的总和,几乎没有或根本没有修改:

GoogLenet (InceptionV1)

盗梦空间原始架构

与两年前发布的 Alexnet 相比,它是如此复杂,但它设法减少了所需的参数数量并提高了准确率。

但是,由于几乎所有结构都由相同原始结构层构建块的确定排列和重复组成,因此提高了此复杂架构的理解和可伸缩性。

批量归一化初始化(V2)

2015 年最先进的神经网络在提高迭代效率的同时,还存在训练不稳定的问题。

为了理解问题的构成,首先我们将记住在前面的示例中应用的简单正则化步骤。 它主要包括将这些值以零为中心,然后除以最大值或标准偏差,以便为反向传播的梯度提供良好的基线。

在训练非常大的数据集的过程中,发生的事情是,经过大量训练示例之后,不同的值振荡开始放大平均参数值,就像在共振现象中一样。 我们非常简单地描述的被称为协方差平移。

Batch normalized inception (V2)

有和没有批量归一化的表现比较

这是开发批归一化技术的主要原因。

再次简化了过程描述,它不仅包括对原始输入值进行归一化,还对每一层上的输出值进行了归一化,避免了在层之间出现不稳定性之前就开始影响或漂移这些值。

这是 Google 在 2015 年 2 月发布的改进版 GoogLeNet 实现中提供的主要功能,也称为 InceptionV2。

InceptionV3

快进到 2015 年 12 月,Inception 架构有了新的迭代。 两次发行之间月份的不同使我们对新迭代的开发速度有了一个想法。

此架构的基本修改如下:

  • 将卷积数减少到最大3x3
  • 增加网络的总体深度
  • 在每一层使用宽度扩展技术来改善特征组合

下图说明了如何解释改进的启动模块:

InceptionV3

InceptionV3 基本模块

这是整个 V3 架构的表示形式,其中包含通用构建模块的许多实例:

InceptionV3

InceptionV3 总体图

残差网络(ResNet)

残差网络架构于 2015 年 12 月出现(与 InceptionV3 几乎同时出现),它带来了一个简单而新颖的想法:不仅使用每个构成层的输出,还将该层的输出与原始输入结合。

在下图中,我们观察到 ResNet 模块之一的简化​​视图。 它清楚地显示了卷积层栈末尾的求和运算,以及最终的 relu 运算:

Residual Networks (ResNet)

ResNet 一般架构

模块的卷积部分包括将特征从 256 个值减少到 64 个值,一个保留特征数的3x3过滤层以及一个从 64 x 256 个值增加1x1层的特征。 在最近的发展中,ResNet 的使用深度还不到 30 层,分布广泛。

其他深度神经网络架构

最近开发了很多神经网络架构。 实际上,这个领域是如此活跃,以至于我们每年或多或少都有新的杰出架构外观。 最有前途的神经网络架构的列表是:

  • SqueezeNet:此架构旨在减少 Alexnet 的参数数量和复杂性,声称减少了 50 倍的参数数量
  • 高效神经网络(Enet):旨在构建更简单,低延迟的浮点运算数量,具有实时结果的神经网络
  • Fractalnet:它的主要特征是非常深的网络的实现,不需要残留的架构,将结构布局组织为截断的分形

示例 -- 风格绘画 -- VGG 风格迁移

在此示例中,我们将配合 Leon Gatys 的论文《艺术风格的神经算法》的实现。

注意

此练习的原始代码由 Anish Athalye 提供。

我们必须注意,此练习没有训练内容。 我们将仅加载由 VLFeat 提供的预训练系数矩阵,该矩阵是预训练模型的数据库,可用于处理模型,从而避免了通常需要大量计算的训练:

Example - painting with style - VGG style transfer

风格迁移主要概念

有用的库和方法

  • 使用scipy.io.loadmat加载参数文件
    • 我们将使用的第一个有用的库是scipy.io模块,用于加载系数数据,该数据另存为 matlab 的 MAT 格式。
  • 上一个参数的用法:
scipy.io.loadmat(file_name, mdict=None, appendmat=True, **kwargs) 

  • 返回前一个参数:

    mat_dict : dict :dictionary,变量名作为键,加载的矩阵作为值。 如果填充了mdict参数,则将结果分配给它。

数据集说明和加载

为了解决这个问题,我们将使用预训练的数据集,即 VGG 神经网络的再训练系数和 Imagenet 数据集。

Dataset description and loading

数据集预处理

假设系数是在加载的参数矩阵中给出的,那么关于初始数据集的工作就不多了。

模型架构

模型架构主要分为两部分:风格和内容。

为了生成最终图像,使用了没有最终完全连接层的 VGG 网络。

损失函数

该架构定义了两个不同的损失函数来优化最终图像的两个不同方面,一个用于内容,另一个用于风格。

内容损失函数

content_loss函数的代码如下:

 # content loss 
        content_loss = content_weight * (2 * tf.nn.l2_loss( 
                net[CONTENT_LAYER] - content_features[CONTENT_LAYER]) / 
                content_features[CONTENT_LAYER].size) 

风格损失函数

损失优化循环

损耗优化循环的代码如下:

        best_loss = float('inf') 
        best = None 
        with tf.Session() as sess: 
            sess.run(tf.initialize_all_variables()) 
            for i in range(iterations): 
                last_step = (i == iterations - 1) 
                print_progress(i, last=last_step) 
                train_step.run() 

                if (checkpoint_iterations and i % checkpoint_iterations == 0) or last_step: 
                    this_loss = loss.eval() 
                    if this_loss < best_loss: 
                        best_loss = this_loss 
                        best = image.eval() 
                    yield ( 
                        (None if last_step else i), 
                        vgg.unprocess(best.reshape(shape[1:]), mean_pixel) 
                    ) 

收敛性测试

在此示例中,我们将仅检查指示的迭代次数(迭代参数)。

程序执行

为了以良好的迭代次数(大约 1000 个)执行该程序,我们建议至少有 8GB 的 RAM 内存可用:

python neural_style.py --content examples/2-content.jpg --styles examples/2-style1.jpg  --checkpoint-iterations=100 --iterations=1000 --checkpoint-output=out%s.jpg --output=outfinal

前面命令的结果如下:

Program execution

风格迁移步骤

控制台输出如下:

Iteration 1/1000
Iteration 2/1000
Iteration 3/1000
Iteration 4/1000
...
Iteration 999/1000
Iteration 1000/1000
  content loss: 908786
    style loss: 261789
       tv loss: 25639.9
    total loss: 1.19621e+06

完整源代码

neural_style.py的代码如下:

import os 

import numpy as np 
import scipy.misc 

from stylize import stylize 

import math 
from argparse import ArgumentParser 

# default arguments 
CONTENT_WEIGHT = 5e0 
STYLE_WEIGHT = 1e2 
TV_WEIGHT = 1e2 
LEARNING_RATE = 1e1 
STYLE_SCALE = 1.0 
ITERATIONS = 100 
VGG_PATH = 'imagenet-vgg-verydeep-19.mat' 

def build_parser(): 
    parser = ArgumentParser() 
    parser.add_argument('--content', 
            dest='content', help='content image', 
            metavar='CONTENT', required=True) 
    parser.add_argument('--styles', 
            dest='styles', 
            nargs='+', help='one or more style images', 
            metavar='STYLE', required=True) 
    parser.add_argument('--output', 
            dest='output', help='output path', 
            metavar='OUTPUT', required=True) 
    parser.add_argument('--checkpoint-output', 
            dest='checkpoint_output', help='checkpoint output format', 
            metavar='OUTPUT') 
    parser.add_argument('--iterations', type=int, 
            dest='iterations', help='iterations (default %(default)s)', 
            metavar='ITERATIONS', default=ITERATIONS) 
    parser.add_argument('--width', type=int, 
            dest='width', help='output width', 
            metavar='WIDTH') 
    parser.add_argument('--style-scales', type=float, 
            dest='style_scales', 
            nargs='+', help='one or more style scales', 
            metavar='STYLE_SCALE') 
    parser.add_argument('--network', 
            dest='network', help='path to network parameters (default %(default)s)', 
            metavar='VGG_PATH', default=VGG_PATH) 
    parser.add_argument('--content-weight', type=float, 
            dest='content_weight', help='content weight (default %(default)s)', 
            metavar='CONTENT_WEIGHT', default=CONTENT_WEIGHT) 
    parser.add_argument('--style-weight', type=float, 
            dest='style_weight', help='style weight (default %(default)s)', 
            metavar='STYLE_WEIGHT', default=STYLE_WEIGHT) 
    parser.add_argument('--style-blend-weights', type=float, 
            dest='style_blend_weights', help='style blending weights', 
            nargs='+', metavar='STYLE_BLEND_WEIGHT') 
    parser.add_argument('--tv-weight', type=float, 
            dest='tv_weight', help='total variation regularization weight (default %(default)s)', 
            metavar='TV_WEIGHT', default=TV_WEIGHT) 
    parser.add_argument('--learning-rate', type=float, 
            dest='learning_rate', help='learning rate (default %(default)s)', 
            metavar='LEARNING_RATE', default=LEARNING_RATE) 
    parser.add_argument('--initial', 
            dest='initial', help='initial image', 
            metavar='INITIAL') 
    parser.add_argument('--print-iterations', type=int, 
            dest='print_iterations', help='statistics printing frequency', 
            metavar='PRINT_ITERATIONS') 
    parser.add_argument('--checkpoint-iterations', type=int, 
            dest='checkpoint_iterations', help='checkpoint frequency', 
            metavar='CHECKPOINT_ITERATIONS') 
    return parser 

def main(): 
    parser = build_parser() 
    options = parser.parse_args() 

    if not os.path.isfile(options.network): 
        parser.error("Network %s does not exist. (Did you forget to download it?)" % options.network) 

    content_image = imread(options.content) 
    style_images = [imread(style) for style in options.styles] 

    width = options.width 
    if width is not None: 
        new_shape = (int(math.floor(float(content_image.shape[0]) / 
                content_image.shape[1] * width)), width) 
        content_image = scipy.misc.imresize(content_image, new_shape) 
    target_shape = content_image.shape 
    for i in range(len(style_images)): 
        style_scale = STYLE_SCALE 
        if options.style_scales is not None: 
            style_scale = options.style_scales[i] 
        style_images[i] = scipy.misc.imresize(style_images[i], style_scale * 
                target_shape[1] / style_images[i].shape[1]) 

    style_blend_weights = options.style_blend_weights 
    if style_blend_weights is None: 
        # default is equal weights 
        style_blend_weights = [1.0/len(style_images) for _ in style_images] 
    else: 
        total_blend_weight = sum(style_blend_weights) 
        style_blend_weights = [weight/total_blend_weight 
                               for weight in style_blend_weights] 

    initial = options.initial 
    if initial is not None: 
        initial = scipy.misc.imresize(imread(initial), content_image.shape[:2]) 

    if options.checkpoint_output and "%s" not in options.checkpoint_output: 
        parser.error("To save intermediate images, the checkpoint output " 
                     "parameter must contain `%s` (e.g. `foo%s.jpg`)") 

    for iteration, image in stylize( 
        network=options.network, 
        initial=initial, 
        content=content_image, 
        styles=style_images, 
        iterations=options.iterations, 
        content_weight=options.content_weight, 
        style_weight=options.style_weight, 
        style_blend_weights=style_blend_weights, 
        tv_weight=options.tv_weight, 
        learning_rate=options.learning_rate, 
        print_iterations=options.print_iterations, 
        checkpoint_iterations=options.checkpoint_iterations 
    ): 
        output_file = None 
        if iteration is not None: 
            if options.checkpoint_output: 
                output_file = options.checkpoint_output % iteration 
        else: 
            output_file = options.output 
        if output_file: 
            imsave(output_file, image) 

def imread(path): 
    return scipy.misc.imread(path).astype(np.float) 

def imsave(path, img): 
    img = np.clip(img, 0, 255).astype(np.uint8) 
    scipy.misc.imsave(path, img) 

if __name__ == '__main__': 
    main() 

Stilize.py的代码如下:

import vgg 

import tensorflow as tf 
import numpy as np 

from sys import stderr 

CONTENT_LAYER = 'relu4_2' 
STYLE_LAYERS = ('relu1_1', 'relu2_1', 'relu3_1', 'relu4_1', 'relu5_1') 

try: 
    reduce 
except NameError: 
    from functools import reduce 

def stylize(network, initial, content, styles, iterations, 
        content_weight, style_weight, style_blend_weights, tv_weight, 
        learning_rate, print_iterations=None, checkpoint_iterations=None): 
    """ 
    Stylize images. 

    This function yields tuples (iteration, image); `iteration` is None 
    if this is the final image (the last iteration).  Other tuples are yielded 
    every `checkpoint_iterations` iterations. 

    :rtype: iterator[tuple[int|None,image]] 
    """ 
    shape = (1,) + content.shape 
    style_shapes = [(1,) + style.shape for style in styles] 
    content_features = {} 
    style_features = [{} for _ in styles] 

    # compute content features in feedforward mode 
    g = tf.Graph() 
    with g.as_default(), g.device('/cpu:0'), tf.Session() as sess: 
        image = tf.placeholder('float', shape=shape) 
        net, mean_pixel = vgg.net(network, image) 
        content_pre = np.array([vgg.preprocess(content, mean_pixel)]) 
        content_features[CONTENT_LAYER] = net[CONTENT_LAYER].eval( 
                feed_dict={image: content_pre}) 

    # compute style features in feedforward mode 
    for i in range(len(styles)): 
        g = tf.Graph() 
        with g.as_default(), g.device('/cpu:0'), tf.Session() as sess: 
            image = tf.placeholder('float', shape=style_shapes[i]) 
            net, _ = vgg.net(network, image) 
            style_pre = np.array([vgg.preprocess(styles[i], mean_pixel)]) 
            for layer in STYLE_LAYERS: 
                features = net[layer].eval(feed_dict={image: style_pre}) 
                features = np.reshape(features, (-1, features.shape[3])) 
                gram = np.matmul(features.T, features) / features.size 
                style_features[i][layer] = gram 

    # make stylized image using backpropogation 
    with tf.Graph().as_default(): 
        if initial is None: 
            noise = np.random.normal(size=shape, scale=np.std(content) * 0.1) 
            initial = tf.random_normal(shape) * 0.256 
        else: 
            initial = np.array([vgg.preprocess(initial, mean_pixel)]) 
            initial = initial.astype('float32') 
        image = tf.Variable(initial) 
        net, _ = vgg.net(network, image) 

        # content loss 
        content_loss = content_weight * (2 * tf.nn.l2_loss( 
                net[CONTENT_LAYER] - content_features[CONTENT_LAYER]) / 
                content_features[CONTENT_LAYER].size) 
        # style loss 
        style_loss = 0 
        for i in range(len(styles)): 
            style_losses = [] 
            for style_layer in STYLE_LAYERS: 
                layer = net[style_layer] 
                _, height, width, number = map(lambda i: i.value, layer.get_shape()) 
                size = height * width * number 
                feats = tf.reshape(layer, (-1, number)) 
                gram = tf.matmul(tf.transpose(feats), feats) / size 
                style_gram = style_features[i][style_layer] 
                style_losses.append(2 * tf.nn.l2_loss(gram - style_gram) / style_gram.size) 
            style_loss += style_weight * style_blend_weights[i] * reduce(tf.add, style_losses) 
        # total variation denoising 
        tv_y_size = _tensor_size(image[:,1:,:,:]) 
        tv_x_size = _tensor_size(image[:,:,1:,:]) 
        tv_loss = tv_weight * 2 * ( 
                (tf.nn.l2_loss(image[:,1:,:,:] - image[:,:shape[1]-1,:,:]) / 
                    tv_y_size) + 
                (tf.nn.l2_loss(image[:,:,1:,:] - image[:,:,:shape[2]-1,:]) / 
                    tv_x_size)) 
        # overall loss 
        loss = content_loss + style_loss + tv_loss 

        # optimizer setup 
        train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss) 

        def print_progress(i, last=False): 
            stderr.write('Iteration %d/%d\n' % (i + 1, iterations)) 
            if last or (print_iterations and i % print_iterations == 0): 
                stderr.write('  content loss: %g\n' % content_loss.eval()) 
                stderr.write('    style loss: %g\n' % style_loss.eval()) 
                stderr.write('       tv loss: %g\n' % tv_loss.eval()) 
                stderr.write('    total loss: %g\n' % loss.eval()) 

        # optimization 
        best_loss = float('inf') 
        best = None 
        with tf.Session() as sess: 
            sess.run(tf.initialize_all_variables()) 
            for i in range(iterations): 
                last_step = (i == iterations - 1) 
                print_progress(i, last=last_step) 
                train_step.run() 

                if (checkpoint_iterations and i % checkpoint_iterations == 0) or last_step: 
                    this_loss = loss.eval() 
                    if this_loss < best_loss: 
                        best_loss = this_loss 
                        best = image.eval() 
                    yield ( 
                        (None if last_step else i), 
                        vgg.unprocess(best.reshape(shape[1:]), mean_pixel) 
                    ) 

def _tensor_size(tensor): 
    from operator import mul 
    return reduce(mul, (d.value for d in tensor.get_shape()), 1) 
 vgg.py 
import tensorflow as tf 
import numpy as np 
import scipy.io 

def net(data_path, input_image): 
    layers = ( 
        'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1', 

        'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2', 

        'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3', 
        'relu3_3', 'conv3_4', 'relu3_4', 'pool3', 

        'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'conv4_3', 
        'relu4_3', 'conv4_4', 'relu4_4', 'pool4', 

        'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'conv5_3', 
        'relu5_3', 'conv5_4', 'relu5_4' 
    ) 

    data = scipy.io.loadmat(data_path) 
    mean = data['normalization'][0][0][0] 
    mean_pixel = np.mean(mean, axis=(0, 1)) 
    weights = data['layers'][0] 

    net = {} 
    current = input_image 
    for i, name in enumerate(layers): 
        kind = name[:4] 
        if kind == 'conv': 
            kernels, bias = weights[i][0][0][0][0] 
            # matconvnet: weights are [width, height, in_channels, out_channels] 
            # tensorflow: weights are [height, width, in_channels, out_channels] 
            kernels = np.transpose(kernels, (1, 0, 2, 3)) 
            bias = bias.reshape(-1) 
            current = _conv_layer(current, kernels, bias) 
        elif kind == 'relu': 
            current = tf.nn.relu(current) 
        elif kind == 'pool': 
            current = _pool_layer(current) 
        net[name] = current 

    assert len(net) == len(layers) 
    return net, mean_pixel 

def _conv_layer(input, weights, bias): 
    conv = tf.nn.conv2d(input, tf.constant(weights), strides=(1, 1, 1, 1), 
            padding='SAME') 
    return tf.nn.bias_add(conv, bias) 

def _pool_layer(input): 
    return tf.nn.max_pool(input, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1), 
            padding='SAME') 

def preprocess(image, mean_pixel): 
    return image - mean_pixel 

def unprocess(image, mean_pixel): 
    return image + mean_pixel 

总结

在本章中,我们一直在学习不同的深度神经网络架构。

我们了解了如何构建近年来最著名的架构之一 VGG,以及如何使用它来生成可转换艺术风格的图像。

在下一章中,我们将使用机器学习中最有用的技术之一:图形处理单元。 我们将回顾安装具有 GPU 支持的 TensorFlow 所需的步骤并对其进行训练,并将执行时间与唯一运行的模型 CPU 进行比较。