本文擬通過使用 Tensorflow 實現線性支持向量機(LinearSVM)的形式來作為 Tensorflow 的“應用式入門教程”。雖說用 mnist 做入門教程項目幾乎是約定俗成的事了,但總感覺照搬這么個東西過來當專欄有些水……所以還是自己親手寫了個 LinearSVM ( σ'ω')σ
在實現之前,先簡要介紹一下 LinearSVM 算法(詳細介紹可以參見這里):
以及介紹一下 Tensorflow 的若干思想:
Tensorflow 的核心在于它能構建出一張“運算圖(Graph)”,我們需要做的是往這張 Graph 里加入元素
基本的元素有如下三種:常量(constant)、可訓練的變量(Variable)和不可訓練的變量(Variable(trainable=False))
由于機器學習算法常常可以轉化為最小化損失函數,Tensorflow 利用這一點、將“最小化損失”這一步進行了很好的封裝。具體而言,你只需要在 Graph 里面將損失表達出來后再調用相應的函數、即可完成所有可訓練的變量的更新
其中第三點我們會在實現 LinearSVM 時進行相應說明,這里則會把重點放在第二點上。首先來看一下應該如何定義三種基本元素以及相應的加、減、乘、除(值得一提的是,在 Tensorflow 里面、我們常常稱處于 Graph 之中的 Tensorflow 變量為“Tensor”,于是 Tensorflow 就可以理解為“Tensor 的流動”)(注:Tensor 這玩意兒叫張量,數學上是挺有來頭的東西;然而個人認為如果不是做研究的話就完全可以不管它數學內涵是啥、把它當成高維數組就好 ( σ'ω')σ):
import tensorflow as tf
# 定義常量、同時把數據類型定義為能夠進行 GPU 計算的 tf.float32 類型
x = tf.constant(1, dtype=tf.float32)
# 定義可訓練的變量
y = tf.Variable(2, dtype=tf.float32)
# 定義不可訓練的變量
z = tf.Variable(3, dtype=tf.float32, trainable=False)
x_add_y = x + y
y_sub_z = y – z
x_times_z = x * z
z_div_x = z / x
此外,Tensorflow 基本支持所有 Numpy 中的方法、不過它留給我們的接口可能會稍微有些不一樣。以“求和”操作為例:
# 用 Numpy 數組進行 Tensor 的初始化
x = tf.constant(np.array([[1, 2], [3, 4]]))
# Tensorflow 中對應于 np.sum 的方法
axis0 = tf.reduce_sum(x, axis=0) # 將會得到值為 [ 4 6 ] 的 Tensor
axis1 = tf.reduce_sum(x, axis=1) # 將會得到值為 [ 3 7 ] 的 Tensor
更多的操作方法可以參見這里(https://zhuanlan.zhihu.com/p/26657869)
最后要特別指出的是,為了將 Graph 中的 Tensor 的值“提取”出來、我們需要定義一個 Session 來做相應的工作。可以這樣理解 Graph 和 Session 的關系(注:該理解可能有誤!如果我確實在瞎扯的話,歡迎觀眾老爺們指出 ( σ'ω')σ):
Graph 中定義的是一套“運算規則”
Session 則會“啟動”這一套由 Graph 定義的運算規則,而在啟動的過程中、Session 可能會額外做三件事:
從運算規則中提取出想要的中間結果
更新所有可訓練的變量(如果啟動的運算規則包括“更新參數”這一步的話)
賦予“運算規則”中一些“占位符”以具體的值
其中“更新參數”和“占位符”的相關說明會放在后文進行,這里我們只說明“提取中間結果”是什么意思。比如現在 Graph 中有這么一套運算規則:,而我只想要運算規則被啟動之后、y 的運算結果。該需求的代碼實現如下:
x = tf.constant(1)
y = x + 1
z = y + 1
print(tf.Session().run(y)) # 將會輸出2
如果我想同時獲得 y 和 z 的運算結果的話,只需將第 4 行改為如下代碼即可:
print(tf.Session().run([y, z])) # 將會輸出 [2, 3]
最后想要特別指出一個非常容易犯錯的地方:當我們使用了 Variable 時,必須要先調用初始化的方法之后、才能利用 Session 將相應的值從 Graph 里面提取出來。比如說,下面這段代碼是會報錯的:
x = tf.Variable(1)
print(tf.Session().run(x)) # 報錯!
應該改為:
x = tf.Variable(1)
with tf.Session().as_default() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(x))
其中 tf.global_variables_initializer() 的作用可由其名字直接得知:初始化所有 Variable
接下來就是 LinearSVM 的實現了,由前文的討論可知,關鍵只在于把損失函數的形式表達出來(利用到了 ClassifierBase(https://link.zhihu.com/?target=https%3A//github.com/carefree0910/MachineLearning/blob/master/Util/Bases.py%23L196);同時為了簡潔,我們設置C=1):
import tensorflow as tf
from Util.Bases import ClassifierBase
class TFLinearSVM(ClassifierBase):
def __init__(self):
super(TFLinearSVM, self).__init__()
self._w = self._b = None
# 使用 self._sess 屬性來存儲一個 Session 以方便調用
self._sess = tf.Session()
def fit(self, x, y, sample_weight=None, lr=0.001, epoch=10 ** 4, tol=1e-3):
# 將 sample_weight(樣本權重)轉換為 constant Tensor
if sample_weight is None:
sample_weight = tf.constant(
np.ones(len(y)), dtype=tf.float32, name="sample_weight")
else:
sample_weight = tf.constant(
np.array(sample_weight) * len(y), dtype=tf.float32, name="sample_weight")
# 將輸入數據轉換為 constant Tensor
x, y = tf.constant(x, dtype=tf.float32), tf.constant(y, dtype=tf.float32)
# 將需要訓練的 w、b 定義為可訓練 Variable
self._w = tf.Variable(np.zeros(x.shape[1]), dtype=tf.float32, name="w")
self._b = tf.Variable(0., dtype=tf.float32, name="b")
# ========== 接下來的步驟很重要!!! ==========
# 調用相應方法獲得當前模型預測值
y_pred = self.predict(x, True, False)
# 利用相應函數計算出總損失:
# cost = ∑_(i=1)^N max?(1-y_i?(w?x_i+b),0)+1/2 + 0.5 * ‖w‖^2
cost = tf.reduce_sum(tf.maximum(
1 - y * y_pred, 0) * sample_weight) + tf.nn.l2_loss(self._w)
# 利用 Tensorflow 封裝好的優化器定義“更新參數”步驟
# 該步驟會調用相應算法、以減少上述總損失為目的來進行參數的更新
train_step = tf.train.AdamOptimizer(learning_rate=lr).minimize(cost)
# 初始化所有 Variable
self._sess.run(tf.global_variables_initializer())
# 不斷調用“更新參數”步驟;如果期間發現誤差小于閾值的話就提前終止迭代
for _ in range(epoch):
# 這種寫法是比較偷懶的寫法,得到的 cost 將不太精確
if self._sess.run([cost, train_step])[0] < tol:
break
然后就要定義獲取模型預測值的方法——self.predict 了:
def predict(self, x, get_raw_results=False, out_of_sess=True):
# 利用 reduce_sum 方法算出預測向量
rs = tf.reduce_sum(self._w * x, axis=1) + self._b
if not get_raw_results:
rs = tf.sign(rs)
# 如果 out_of_sess 參數為 True、就要利用 Session 把具體數值算出來
if out_of_sess:
rs = self._sess.run(rs)
# 否則、直接把 Tensor 返回即可
return rs
之所以要額外用一個 out_of_sess 參數控制輸出的原因如下:
Tensorflow 在內部進行 Graph 運算時是無需把具體數值算出來的、不如說使用原生態的 Tensor 進行運算反而會快很多
當模型訓練完畢后,在測試階段我們希望得到的當然是具體數值而非 Tensor、此時就需要 Session 幫我們把中間結果提取出來了
以上就是 LinearSVM 的完整實現,可以看到還是相當簡潔的
這里特別指出這么一點:利用 Session 來提取中間結果這個過程并非是沒有損耗的;事實上,當 Graph 運算本身的計算量不大時,開啟、關閉 Session 所造成的開銷反而會占整體開銷中的絕大部分。因此在我們編寫 Tensorflow 程序時、要注意避免由于貪圖方便而隨意開啟 Session
在本文的最后,我們來看一下 Tensorflow 里面 Placeholder 這個東西的應用。目前實現的 LinearSVM 雖說能用,但其實存在著內存方面的隱患。為了解決這個隱患,一個常見的做法是分 Batch 訓練,這將會導致“更新參數”步驟每次接受的數據都是“不固定”的數據——原數據的一個小 Batch。為了描述這個“不固定”的數據、我們就需要利用到 Tensorflow 中的“占位符(Placeholder)”,其用法非常直觀:
# 定義一個數據類型為 tf.float32、“長”未知、“寬”為 2 的矩陣
Placeholder x = tf.placeholder(tf.float32, [None, 2])
# 定義一個 numpy 數組:[ [ 1 2 ], [ 3 4 ], [ 5 6 ] ]
y = np.array([[1, 2], [3, 4], [5, 6]])
# 定義 x + 1 對應的 Tensor
z = x + 1
# 利用 Session 及其 feed_dict 參數、將 y 的值賦予給 x、同時輸出 z 的值 print(tf.Session().run(z, feed_dict={x: y}))
# 將會輸出 [ [ 2 3 ], [ 4 5 ], [ 6 7 ] ]
于是分 Batch 運算的實現步驟就很清晰了:
把計算損失所涉及的所有 x、y 定義為占位符
每次訓練時,通過 feed_dict 參數、將原數據的一個小 Batch 賦予給 x、y
占位符還有許多其它有趣的應用手段,它們的思想都是相通的:將未能確定的信息以 Placeholder 的形式進行定義、在確實調用到的時候再賦予具體的數值
事實上,基本所有 Tensorflow 模型都要用到 Placeholder。雖然我們上面實現的 TFLinearSVM 沒有用到,但正因如此、它是存在巨大缺陷的(比如說,如果在同一段代碼中不斷地調用參數 out_of_sess 為 True 的 predict 方法的話,會發現它的速度越來越慢。觀眾老爺們可以思考一下這是為什么 ( σ'ω')σ)
以上就是 Tensorflow 的一個簡要教程,雖然我是抱著“即使從來沒用過 Tensorflow 也能看懂”的心去寫的,但可能還是會有地方說得不夠詳細;若果真如此,還愿不吝指出 ( σ'ω')σ
-
人工智能
+關注
關注
1793文章
47595瀏覽量
239504 -
tensorflow
+關注
關注
13文章
329瀏覽量
60583
原文標題:從零開始學人工智能(26)--Tensorflow · LinearSVM
文章出處:【微信號:AI_shequ,微信公眾號:人工智能愛好者社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論