import numpy as np
import tensorflow as tf
# 使用Xavier初始化器进行权重初始化 它的特点是根据某一层网络的输入,输出节点数量自动调整最合适的分布
# 从数学的角度分析 Xavier就是让权重满足均值为0, 同时方差为(2/(n_input + n_output))
# 分布可以使用均匀分布或者高斯分布
# 如下代码所示,通过tf.random_uniform创建一个(-sqrt(6/(n_in + n_out)), +sqrt(6/(n_in + n_out)))的均匀分布
# 其期望为 (minval + maxval) / 2 = 0
# 其方差为 (maxval - minval)^2 / 12 = 2/(n_in + n_out)
# 因此该初始化器满足均匀分布的Xavier初始化器
# 其中fan_in是输入节点的数量,fan_out是输出节点的数量
def xavier_init(fan_in, fan_out, constant = 1):
low = -constant * np.sqrt(6.0 / (fan_in + fan_out))
high = constant * np.sqrt(6.0 / (fan_in + fan_out))
return tf.random_uniform((fan_in, fan_out), minval=low, maxval=high, dtype=tf.float32)
# 定义一个去噪自编码器
# 定义无噪声自编码器只要去掉噪声,并保证隐含层节点小于输入层节点
# Masking Noise的自编码器只需要将高斯噪声改为随机遮挡噪声
class AdditiveGaussianNoiseAutoencoder(object):
def __init__(self, n_input, n_hidden, transfer_function=tf.nn.softplus,
optimizer=tf.train.AdamOptimizer(), scale=0.1):
'''
n_input: 输入变量数
n_hidden: 隐藏层节点数
transfer_function: 隐藏层激活函数,默认为softplus
softplus: y = log(1 + exp(x))
optimizer: 优化器,默认为Adam,默认学习率为0.001,可在构造时修改
scal: 高斯噪声系数,默认为0.1
其中class内的scale参数做成了一个placeholder,
参数初始化使用了后面定义的_initialize_weights函数
'''
self.n_input = n_input
self.n_hidden = n_hidden
self.transfer = transfer_function
self.scale = tf.placeholder(tf.float32)
self.training_scale = scale
network_weights = self._initialize_weights()
self.weights = network_weights
self.x = tf.placeholder(tf.float32, [None, self.n_input])
# 将输入x加上噪声
self.x_noise = self.x + scale * tf.random_normal((n_input,))
# 计算前向传播
self.z = tf.add(tf.matmul(self.x_noise, self.weights['w1']),
self.weights['b1'])
# 激活
self.hidden = self.transfer(self.z)
# 经过隐藏层处理后,数据复原,重建操作, 这里不需要激活函数
# 其实就是个全连接
self.reconstruction = tf.add(tf.matmul(self.hidden,
self.weights['w2']), self.weights['b2'])
# 定义自编码的损失函数 均方误差
self.cost = 0.5 * tf.reduce_sum(tf.pow(tf.subtract(self.reconstruction, self.x), 2.0))
# 使用优化器self.optimizer对损失函数进行优化
# 注意学习率在optimizer中内置了,构造的时候即指定了学习率大小
self.optimizer = optimizer.minimize(self.cost)
# 创建session,初始化自编码器的全部模型参数
self.sess = tf.Session()
init = tf.global_variables_initializer()
self.sess.run(init)
# w1使用前面定义的xavier_init函数初始化,直接传入输入节点数和隐藏层节点数
# 然后xavier即可返回一个比较适合与softplus等激活函数的权重初始分布
# 而偏置b1只需要使用tf.zeros全部置为0即可
# 对于输出层self.reconstruction,因为没有使用激活函数
# 这里w2,b2全部初始化为0即可
def _initialize_weights(self):
all_weights = dict()
all_weights['w1'] = tf.Variable(xavier_init(self.n_input, self.n_hidden))
all_weights['b1'] = tf.Variable(tf.zeros([self.n_hidden], dtype=tf.float32))
all_weights['w2'] = tf.Variable(tf.zeros([self.n_hidden, self.n_input], dtype=dtypes.float32))
all_weights['b2'] = tf.Variable(tf.zeros([self.n_input], dtype=dtypes.float32))
return all_weights
# 定义计算cost及执行一部训练的函数partial_fit
# 函数里只需要让sesion执行两个计算图的节点,
# 分别是损失cost和训练optimizer,
# 输入的feed_dict包括输入数x,以及噪声的系数scale
# 函数partial_fit做的就是用一个batch数据进行训练并返回当前损失cost 相当于train
def partial_fit(self, X):
cost, opt = self.sess.run((self.cost, self.optimizer), feed_dict = {
self.x: X, self.scale: self.training_scale
})
return cost
# 再定义一个只求损失cost的函数calc_total_cost
# 这里只让session执行一个计算图节点self.const
# 传入的参数和前面的partial_fit一致
# 这个函数是在自编码器训练完毕后在测试集上对模型新能进行评测时用的 相当于test
# 它不会像partial_fit那样触发训练操作(optimizer)
def calc_total_cost(self, X):
return self.sess.run(self.cost, feed_dict={
self.x: X, self.scale: self.training_scale
})
# 定义一个transform函数,返回自编码隐藏层的输出结果
# 它的目的是提供一个接口来获取抽象后的特征
# 自编码器的隐藏层的最主要功能就是学习出数据中的高阶特征
def transform(self, X):
return self.sess.run(self.hidden, feed_dict={
self.x: X, self.scale: self.training_scale
})
# 定义generate函数,它将隐藏层的输出结果作为输入,
# 通过之后的重建层将提取到的高阶特征复原为原数据
# 这个接口和前面的transform正好将整个自编码器拆分为两部分
# 这里的generate接口是后半部分,将高阶特征复原为原始数据的步骤
def generate(self, hidden=None):
if hidden is None:
hidden = np.random.normal(size = sef.weights['b1'])
return self.sess.run(self.reconstruction, feed_dict={
self.hidden: hidden
})
# 定义reconstruct函数,它整体运行一遍复原过程,
# 包括提取高阶特征和通过高阶特征复原数据
# 即包括transform和generate两块
# 输入数据是原数据,输出数据是复原后的数据
def reconstruct(self, X):
return self.sess.run(self.reconstruction, feed_dict={
self.x: X, self.scale: self.training_scale
})
# 获取隐藏层权重w1
def getWeights(self):
return self.sess.run(sel.weights['w1'])
# 获取隐藏层偏置项b1
def getBiases(self):
return self.sess.run(self.weights['b1'])