基於tensorflow實現稀疏自編碼和在推薦中的應用

稀疏自編碼

自編碼器(Auto-Encoder)顧名思義,即可以利用自身的高階特徵編碼自己。自編碼器也是一種神經網絡,他的輸入和輸出是一致的,他藉助稀疏編碼的思想,目標是使用稀疏的一些高階特徵重新組合來重構自己。

因此他的特徵十分明顯:

  • 期望輸入與輸出一致
  • 希望使用高階特徵來重構自己,而不只是複製像素點

自編碼器的輸入節點和輸出節點的數量是一致的,但如果只是單純的逐個複製輸入節點則沒有意義,像前面提到的,自編碼器通常希望使用少量稀疏的高維特徵來重構輸入,所以加入幾種限制:

  • (1)中間隱含層節點的數量。如果中間隱含節點數量小於輸入/輸出的數量,則爲一個降維的過程。此時不可能出現複製所有節點的情況,只能學習數據中最重要的特徵,將不太相關的特徵去除。如果再加一個L1正則,則可以根據懲罰係數控制隱含節點的稀疏程度,懲罰係數越大,學到特徵越稀疏
  • (2)給數據加入噪聲,變成了Denoising AutoEncoder(去噪自編碼器),將從噪聲中學習出數據的特徵。此時只有學習數據頻繁出現的模式和結構,將噪聲去除,纔可以復原數據。

去噪自編碼器中最常使用的噪聲有:

  • 加性高斯噪聲(Additive Gaussian Noise,AGN)
  • 用Masking Noise,即有隨機遮擋的噪聲
  • Variational AutoEncoder(VAE)

Xavier initialization

特點:根據某一層網絡的輸入、輸出節點數量自動調整最合適的分佈。Xavier讓權重滿足0均值,同時方差爲2nin+nout\frac{2}{n_{in} + n_{out}},分佈可以是均勻分佈或者高斯分佈。

tf.random_uniform 創造一個 (6nin+nout,6nin+nout)(- \sqrt{\frac{6}{n_{in} + n_{out}}},\sqrt{\frac{6}{n_{in} + n_{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 )

稀疏自編碼可以被解釋爲

  • 如果當神經元的輸出接近於1的時候我們認爲它被激活,而輸出接近於0的時候認爲它被抑制,那麼使得神經元大部分的時間都是被抑制的限制則被稱作稀疏性限制。這裏我們假設的神經元的激活函數是sigmoid函數。如果你使用tanh作爲激活函數的話,當神經元輸出爲-1的時候,我們認爲神經元是被抑制的。

只有一個隱藏層的稀疏自編碼結構如下圖
一個隱藏層的稀疏自編碼

這時候隱藏層則是原始特徵的另一種表達形式。

tf實現

直接上源碼,有註釋說明

import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 定義一個 Xavier初始化器,讓權重不大不小,正好合適
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))
    weight = tf.random_uniform( (fan_in, fan_out), minval=low, maxval=high, dtype=tf.float32)
    return weight

class AdditiveGaussianNoiseAutoEncoder(object):
    # 構造函數
    def __init__(self, n_input, n_hidden, transfer_function = tf.nn.softplus, optiminzer = tf.train.AdamOptimizer(), scale = 0.1):
        # 輸入變量數
        self.n_input = n_input
        # 隱含層節點數
        self.n_hidden = n_hidden
        # 隱含層激活函數
        self.transfer_func = transfer_function
        # 優化器,默認使用Adma
        self.optimizer = optiminzer
        # 高斯噪聲係數,默認使用0.1
        self.scale = tf.placeholder(tf.float32)
        self.training_scale = scale
        # 初始化神經網絡參數
        network_weights = self._initialize_weights()
        # 獲取神經網絡參數
        self.weights = network_weights
        # 初始化輸入的數據, 數據的維度爲 n_input 列,行數未知
        self.x = tf.placeholder(tf.float32, [None, self.n_input])
        # 計算隱藏層值,輸入數據爲融入噪聲的數據,然後與w1權重相乘,再加上偏置
        self.hidden = self.transfer_func(
            tf.add(
                tf.matmul(
                    self.x + scale * tf.random_normal((self.n_input,)), self.weights["w1"]
                ),
                self.weights["b1"]
            )
        )
        # 計算預測結果值,將隱藏層的輸出結果與w2相乘,再加上偏置
        self.reconstruction = tf.add(
            tf.matmul(
                self.hidden, self.weights["w2"]
            ),
            self.weights["b2"]
        )
        # 計算平方損失函數(Squared Error) subtract: 計算差值
        self.cost = 0.5 * tf.reduce_mean(
            tf.pow(
                tf.subtract(
                    self.reconstruction, self.x
                ),
                2.0
            )
        )
        # 定義優化方法,這裏默認使用的是Adma
        self.optimizer = optiminzer.minimize(self.cost)
        init = tf.global_variables_initializer()
        self.sess = tf.Session()
        self.sess.run(init)

    # 權值初始化函數
    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 = tf.float32) )
        all_weights["b2"] = tf.Variable( tf.zeros([self.n_input], dtype = tf.float32) )
        return all_weights

    # 計算損失函數和優化器
    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

    # 計算損失函數
    def calc_total_cost(self, X):
        return self.sess.run(
            self.cost, feed_dict= {self.x: X, self.scale: self.training_scale}
        )

    # 輸出自編碼器隱含層的輸出結果,用來提取高階特徵,是三層(輸入層,隱含層,輸出層)的前半部分
    def transform(self, X):
        return self.sess.run(
            self.hidden, feed_dict= {self.x: X, self.scale: self.training_scale}
        )

    # 將隱含層的輸出作爲結果,復原原數據,是整體拆分的後半部分
    def generate(self, hidden = None):
        if hidden is None:
            hidden = np.random.normal(size= self.weights["b1"])
        return self.sess.run(
            self.reconstruction, feed_dict= {self.hidden: hidden}
        )

    # 構建整個流程,包括: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(self.weights['w1'])

    # 獲取偏值b1
    def getBiases(self):
        return self.sess.run(self.weights['b1'])

class ModelTrain:
    def __init__(self, training_epochs = 20, batch_size = 128, display_step = 1):
        self.mnist = self.load_data()
        # 格式化訓練集和測試集
        self.x_train, self.x_test = self.standard_scale(self.mnist.train.images, self.mnist.test.images)
        # 總的訓練樣本數
        self.n_samples = int(self.mnist.train.num_examples)
        # 訓練次數
        self.training_epochs = training_epochs
        # 每次訓練的批大小
        self.batch_size = batch_size
        # 設置每多少輪顯示一次loss值
        self.display_step = display_step

    # 加載數據集
    def load_data(self):
        return input_data.read_data_sets("../MNIST_data", one_hot=True)

    # 數據標準化處理函數
    def standard_scale(self, x_train, x_test):
        # StandardScaler: z = (x - u) / s (u 均值, s 標準差)
        preprocessor = prep.StandardScaler().fit(x_train)
        x_train = preprocessor.transform(x_train)
        x_test = preprocessor.transform(x_test)
        return x_train, x_test

    # 最大限度不重複的獲取數據
    def get_random_block_from_data(self, data, batch_size):
        start_index = np.random.randint(0, len(data) - batch_size)
        return data[start_index:(start_index + batch_size)]

if __name__ == "__main__":
    autoencoder = AdditiveGaussianNoiseAutoEncoder(
        n_input=784,
        n_hidden= 200,
        transfer_function=tf.nn.softplus,
        optiminzer= tf.train.AdamOptimizer(learning_rate=0.01),
        scale= 0.01
    )
    modeltrain = ModelTrain(training_epochs= 20, batch_size= 128, display_step= 1)
    for epoch in range(modeltrain.training_epochs):
        avg_cost = 0
        # 一共計算多少次數據集
        total_bacth = int(modeltrain.n_samples / modeltrain.batch_size)
        for i in range(total_bacth):
            batch_x = modeltrain.get_random_block_from_data(modeltrain.x_train, modeltrain.batch_size)
            cost = autoencoder.partial_fit(batch_x)
            avg_cost += cost / modeltrain.n_samples * modeltrain.batch_size
        if epoch % modeltrain.display_step == 0:
            print("Epoch:", '%04d' % (epoch + 1), "cost=", "{:.9f}".format(avg_cost))

    print("Total cost: " + str(autoencoder.calc_total_cost(modeltrain.x_test)))

在推薦中的應用

一個大型推薦系統,物品的數量級爲千萬,用戶的數量級爲億。使用稀疏編碼進行數據降維後,用戶或者物品均可用一組低維基向量表徵,便於存儲計算,可供在線層實時調用。

在推薦實踐中,我們主要使用稀疏編碼的方法,輸入用戶點擊/收藏/購買數據,訓練出物品及用戶的特徵向量,具體構造自編碼網絡的方法如下:

輸入層:每首物品的輸入向量爲 (u1,u2,...,un)(u_1, u_2, ..., u_n),其中uiu_i表示用戶ii是否點擊/收藏/購買這個物品。輸入矩陣爲(m+1)n(m+1)*n維(包含一個截距項),mm爲用戶數量,nn爲物品數量。

輸出層:指定爲和輸出層一致(無截距項)。

隱藏層:強制指定神經元的數量爲k+1k+1個,此時隱藏層其實就是物品的低維特徵向量,矩陣爲k+1n,k+1(k+1)*n,k+1爲特徵維數(包含一個截距項1,之所以保留,是爲了可以重構出輸出層),nn爲物品數量。

隱藏層到輸出層的連接。一般的神經網絡中,往往會忽略隱藏層到輸出層的連接權重W(1,1),W(1,2),b1,b2W^{(1,1)},W^{(1,2)},b^1,b^2的意義,只是將其作爲一個輸出預測的分類器;但在自編碼網絡中,連接層是有實際意義的。這些權重作用是將物品特徵向量映射到用戶是否聽過/喜歡該物品,其實可就是用戶的低維特徵,所以該稀疏網絡同樣可以學習到用戶的特徵矩陣m(k+1)m*(k+1)。值得注意的是,當網絡結構爲3層時,其目標函數與svd基本一致,算法上是相通的。


ok,不知道你對稀疏自編碼是否有一定的瞭解,歡迎加微信交流!


【技術服務】,詳情點擊查看: https://mp.weixin.qq.com/s/PtX9ukKRBmazAWARprGIAg

掃一掃 關注微信公衆號!號主 專注於搜索和推薦系統,嘗試使用算法去更好的服務於用戶,包括但不侷限於機器學習,深度學習,強化學習,自然語言理解,知識圖譜,還不定時分享技術,資料,思考等文章!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章