GitChat 做者:李嘉璇
原文:從零開始,如何閱讀一篇人工智能論文,及構建論文與代碼的實現
關注公衆號:GitChat 技術雜談,一本正經的講技術html
首先講解如何從零基礎開始閱讀一篇機器學習方向的論文,以及對待論文中的數學問題。隨後,從一篇經典論文入手,講解如何快速梳理和理解一個深度學習框架及模型。python
最近人工智能和機器學習方向的論文很是多,那麼一個有工程背景、學術經驗較少或者有必定經驗的工程師,如何閱讀一篇人工智能相關的論文呢?git
在剛開始的學術探索中,我傾向於全文精讀,尤爲是深度學習領域的經典論文,但發現這種方式花費時間太多,以致於擠壓了個人真正目的——工程實現和工程結合。而且,由於想抓住的東西太多,反而沒有抓住一篇文章的核心,致使很容易忘記,好比昨天讀的文章就像喝了水同樣忘掉了。github
我將和你們從兩個方面探討。算法
這裏的從零開始,指的是咱們要從零瞭解這篇文章作了什麼事情、使用了什麼方法、獲得什麼結果,這樣的方法和結果對我有沒有什麼借鑑。編程
而不是說,接觸到一個全新的領域,從讀論文開始入手。對於沒有過接觸的陌生領域。個人方法是,先看中文綜述,中文博士論文,然後是英文綜述。經過中文綜述,能夠首先了解這個領域的基本名詞、實驗經常使用方法。數組
不然直接從論文入手的話,做者站的高度和咱們的水平不一致,很容易想固然的理解或者根本看不下去。所以,在閱讀這篇文章以前,對於這篇文章中涉及到的基礎知識,對應中文基礎都理解透徹。bash
這時,迴歸到從零開始理解這篇文章的狀態。服務器
對一篇文章的閱讀每每有3個遞增的層次:網絡
層次1. 讀懂這篇文章的概要信息(5-10分鐘)
認真讀懂標題、摘要、簡介(title, abstract, and introduction)。
只讀各個部分和子部分(section and sub-section)的標題,跳過具體內容。
讀懂結論和討論(做者一般會在這裏論述本研究的不足和缺失,爲將來的研究提供建議,指明方向)。
瀏覽參考文獻,記下哪些文獻是你已經讀過的。
所以,在第一層次事後,應該能回答出如下5個問題:
文章分類:關於實現方法的文章?對於已有系統的分析文章?對於研究理論的描述文章?
內容:有沒有對應的相關paper?這篇文章是基於什麼樣的基礎理論?(theoretical bases)
文章的假設(assumptions)是真的正確麼?
貢獻:這篇文章是在效果上(state of art)有了明顯進步?仍是方法上有了創新?仍是完善了基礎理論?
清晰度:是一篇描述清晰的文章麼?
第一個層次完成你就能夠以爲是否要深刻第二個層次,它足夠作你的某天想用到時的知識儲備,而不是如今馬上入手。
層次2. 抓住文章的內容,忽略文章細節(1個小時)
第二個層次須要認真讀,抓住重點:
對圖、表的含義以及他們支持的結論弄懂。
記下參考文獻中你認爲重要的未讀文獻,它能讓你對這篇文章的背景有深入理解。
完成第2個層次,要達到知道文章用了哪些證據,如何證實了一個什麼樣的結論。
尤爲在這個層次中,若是遇到讀不懂(緣由有不少:公式太多、對術語不理解、對實驗手段不熟悉、參考文獻的文獻過多)。說明咱們尚未和做者在一個基礎上,建議先從幾篇重要的參考文獻入手,補充背景知識。
層次3. 深刻細節理解文章(5-6小時)
若是這個文章是你想應用到目前工程中的,則須要第3個層次。目標是可以在相同的假設條件下,重現(re-implement)論文。
同時,要注重論文在GitHub上的對應代碼,跳到程序中能加速理解。
比較你重現的結果和原論文,就能真正理解一篇文章的創新點,以及它的隱含前提或假設。而且你能從重現過程當中獲得一些你將來工做的方向。
作這三個層次的好處就是,可以讓你對讀一篇文章的時間有合理的估計,甚至能夠根據時間和你的工做須要調整掌握一篇文章的深度。
這個在不少AI論文中很廣泛,因此通常來說,在第一個層次中,不須要理解一個公式的全部步驟。儘可能的跳過公式,讀文字描述,讀實驗結果,讀結論。
隨着你平時工做中數學的積累,在第二個層次中,你也許能直接經過看公式來真正理解做者的目的和步驟。
若是非要進入第三個層次,可能須要跟着文章作一些推導。可是實際上,若是有現成的代碼實現,可讓你從工程的角度更好的理解數學過程。
最後,建議你們用這種方式,嘗試把這128篇論文中本身感興趣的領域根據本身的須要,調整閱讀層次地讀完。(筆者剛剛讀完啦,歡迎一塊兒來交流哦)
下面結合TensorFlow的架構和體系設計論文《TensorFlow:
Large-Scale Machine Learning on Heterogeneous Distributed Systems》來說解如下兩點:
####TensorFlow的編程模型和基本概念
結合不到20行代碼講解靜態圖模型。
TensorFlow的運行方式分以下4步:
加載數據及定義超參數;
構建網絡;
訓練模型;
評估模型和進行預測。
下面咱們以一個神經網絡爲例,講解TensorFlow的運行方式。在這個例子中,咱們構造一個知足一元二次函數y = ax2+b的原始數據,而後構建一個最簡單的神經網絡,僅包含一個輸入層、一個隱藏層和一個輸出層。經過TensorFlow將隱藏層和輸出層的weights和biases的值學習出來,看看隨着訓練次數的增長,損失值是否是不斷在減少。
生成及加載數據
首先來生成輸入數據。咱們假設最後要學習的方程爲y = x2 − 0.5,咱們來構造知足這個方程的一堆x和y,同時加入一些不知足方程的噪聲點。
import tensorflow as tf
import numpy as np
# 編造知足一元二次方程的函數
x_data = np.linspace(-1,1,300)[:, np.newaxis] # 爲了使點更密一些,咱們構建了300個點,分佈在-1到1區間,直接採用np生成等差數列的方法,並將結果爲300個點的一維數組,轉換爲300×1的二維數組
noise = np.random.normal(0, 0.05, x_data.shape) # 加入一些噪聲點,使它與x_data的維度一致,而且擬合爲均值爲0、方差爲0.05的正態分佈
y_data = np.square(x_data) - 0.5 + noise # y = x^2 – 0.5 + 噪聲複製代碼
接下來定義x和y的佔位符來做爲將要輸入神經網絡的變量:
xs = tf.placeholder(tf.float32, [None, 1])
ys = tf.placeholder(tf.float32, [None, 1])複製代碼
構建網絡模型
這裏咱們須要構建一個隱藏層和一個輸出層。做爲神經網絡中的層,輸入參數應該有4個變量:輸入數據、輸入數據的維度、輸出數據的維度和激活函數。每一層通過向量化(y = weights×x + biases)的處理,而且通過激活函數的非線性化處理後,最終獲得輸出數據。
下面來定義隱藏層和輸出層,示例代碼以下:
def add_layer(inputs, in_size, out_size, activation_function=None):
# 構建權重:in_size×out_size大小的矩陣
weights = tf.Variable(tf.random_normal([in_size, out_size]))
# 構建偏置:1×out_size的矩陣
biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
# 矩陣相乘
Wx_plus_b = tf.matmul(inputs, weights) + biases
if activation_function is None:
outputs = Wx_plus_b
else:
outputs = activation_function(Wx_plus_b)
return outputs # 獲得輸出數據
# 構建隱藏層,假設隱藏層有10個神經元
h1 = add_layer(xs, 1, 20, activation_function=tf.nn.relu)
# 構建輸出層,假設輸出層和輸入層同樣,有1個神經元
prediction = add_layer(h1, 20, 1, activation_function=None)複製代碼
接下來須要構建損失函數:計算輸出層的預測值和真實值間的偏差,對兩者差的平方求和再取平均,獲得損失函數。運用梯度降低法,以0.1的效率最小化損失:
# 計算預測值和真實值間的偏差
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction),
reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)複製代碼
訓練模型
咱們讓TensorFlow訓練1000次,每50次輸出訓練的損失值:
init = tf.global_variables_initializer() # 初始化全部變量
sess = tf.Session()
sess.run(init)
for i in range(1000): # 訓練1000次
sess.run(train_step, feed_dict={xs: x_data, ys: y_data})
if i % 50 == 0: # 每50次打印出一次損失值
print(sess.run(loss, feed_dict={xs: x_data, ys: y_data}))複製代碼
####TensorFlow的基本實現
包括:設備、分佈式運行機制、跨設備間通訊、梯度計算。
TensorFlow的分佈式有兩種模式,數據並行和模型並行,咱們最經常使用的就是數據並行。數據並行的原理很簡單,如圖所示。其中CPU主要負責梯度平均和參數更新,而GPU1和GPU2主要負責訓練模型副本(model replica)。這裏稱做「模型副本」是由於它們都是基於訓練樣例的子集訓練獲得的,模型之間具備必定的獨立性。
具體的訓練步驟以下。
在GPU1和GPU2上分別定義模型網絡結構。
對於單個GPU,分別從數據管道讀取不一樣的數據塊,而後進行前向傳播,計算出損失,再計算當前變量的梯度。
把全部GPU輸出的梯度數據轉移到CPU上,先進行梯度求平均操做,而後進行模型變量的更新。
重複第1步至第3步,直到模型變量收斂爲止。
數據並行的目的主要是提升SGD的效率。例如,假如每次SGD的mini-batch大小是1000個樣本,那麼若是切成10份,每份100個,而後將模型複製10份,就能夠在10個模型上同時計算。
可是,由於10個模型的計算速度多是不一致的,有的快有的慢,那麼在CPU更新變量的時候,是應該等待這一mini-batch所有計算完成,而後求和取平均來更新呢,仍是讓一部分先計算完的就先更新,後計算完的將前面的覆蓋呢?
這就引出了同步更新和異步更新的問題。
分佈式隨機梯度降低法是指,模型參數能夠分佈式地存儲在不一樣的參數服務器上,工做節點能夠並行地訓練數據而且可以和參數服務器通訊獲取模型參數。更新參數也分爲同步和異步兩種方式,即爲異步隨機梯度降低法(Async-SGD)和同步隨機梯度降低法(Sync-SGD)。如圖:
同步隨機梯度降低法(也稱同步更新、同步訓練)的含義是在進行訓練時,每一個節點上的工做任務須要讀入共享參數,執行並行的梯度計算,同步須要等待全部工做節點把局部的梯度算好,而後將全部共享參數進行合併、累加,再一次性更新到模型的參數;下一個批次中,全部工做節點拿到模型更新後的參數再進行訓練。
這種方案的優點是,每一個訓練批次都考慮了全部工做節點的訓練狀況,損失降低比較穩定;劣勢是,性能瓶頸在於最慢的工做節點上。在異構設備中,工做節點性能經常不一樣,這個劣勢很是明顯。
異步隨機梯度降低法(也稱異步更新、異步訓練)的含義是每一個工做節點上的任務獨立計算局部梯度,並異步更新到模型的參數中,不須要執行協調和等待操做。
這種方案的優點優點是,性能不存在瓶頸;劣勢是,每一個工做節點計算的梯度值發送回參數服務器會有參數更新的衝突,必定程度上會影響算法的收斂速度,在損失降低過程當中抖動較大。
同步更新和異步更新如何選擇?有沒有優化方式呢?
同步更新和異步更新的實現區別主要在於更新參數服務器的參數的策略。在數據量小,各個節點的計算能力比較均衡的狀況下,推薦使用同步模式;在數據量很大,各個機器的計算性能良莠不齊的狀況下,推薦使用異步模式。具體使用哪種還能夠看實驗結果,通常數據量足夠大的狀況下異步更新效果會更好。
下面展現將如何建立一個TensorFlow服務器集羣,以及如何在該集羣中分佈式計算一個靜態圖。
TensorFlow分佈式集羣的全部節點執行的代碼都是相同的。分佈式任務代碼具備固定的結構:
# 第1步:命令行參數解析,獲取集羣的信息ps_hosts和worker_hosts,
以及當前節點的角色信息job_name和task_index。例如:
tf.app.flags.DEFINE_string("ps_hosts", "", "Comma-separated list of hostname:port pairs")
tf.app.flags.DEFINE_string("worker_hosts", "", "Comma-separated list of hostname:port pairs")
tf.app.flags.DEFINE_string("job_name", "", "One of 'ps', 'worker'")
tf.app.flags.DEFINE_integer("task_index", 0, "Index of task within the job")
FLAGS = tf.app.flags.FLAGS
ps_hosts = FLAGS.ps_hosts.split(",")
worker_hosts = FLAGS.worker_hosts(",")
# 第2步:建立當前任務節點的服務器
cluster = tf.train.ClusterSpec({"ps": ps_hosts, "worker": worker_hosts})
server = tf.train.Server(cluster, job_name=FLAGS.job_name, task_index=FLAGS.task_index)
# 第3步:若是當前節點是參數服務器,則調用server.join()無休止等待;若是是工做節點,則執行第4步
if FLAGS.job_name == "ps":
server.join()
# 第4步:構建要訓練的模型,構建計算圖
elif FLAGS.job_name == "worker":
# build tensorflow graph model
# 第5步:建立tf.train.Supervisor來管理模型的訓練過程
# 建立一個supervisor來監督訓練過程
sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0), logdir="/tmp/train_logs")
# supervisor負責會話初始化和從檢查點恢復模型
sess = sv.prepare_or_wait_for_session(server.target)
# 開始循環,直到supervisor中止
while not sv.should_stop()
# 訓練模型複製代碼
採起上面的代碼框架,對MNIST數據集進行分佈式訓練,代碼見:
本次Chat的第二部分: 講解如何將你手上的需求轉化爲論文的描述並實現出來。
以推薦系統爲例:參考文章:
www.textkernel.com/building-la…
咱們以招聘的招聘推薦系統中知識庫的構建爲例,講解在哪裏以及如何引入NLP和知識圖譜的方法。
(1)爲何建設知識庫?以下就是一個基於知識庫的搜索:
也就是咱們但願把對應的職位描述,結構化爲知識圖譜:
咱們知道知識圖譜包括實體和實體關係,那麼結合招聘的場景,實體庫中就應該包含:職位庫、職業庫、簡歷庫、實體詞庫。而實體關係可能有歸屬關係、層級關係、關聯關係。
咱們來對職位描述作結構化的抽取,來設計實體關係的標籤體系,以下:
具體如何來抽取呢?
尋找定位詞和標點符號,切分紅短句。
職位內容:
(營業員/學徒):負責
吧檯,跟隨師傅調製 飲料,切配果盤,小吃;待遇:
正式員工底薪3000- 3500元/月+獎金+五險一金,公司包吃住工做地點:
公司根據員工住所安排最近的上班地點
從短句中基於特徵字/詞定位核心內容
(營業員/學徒)
吧檯,跟隨師傅調製飲料,切配
果盤,小吃
正式員工底薪3000-3500
元/月+獎金+五
險一
金
公司根據員工住所安排最近
的上班地點
核心詞抽取
營業員 學徒
吧檯 調製飲料 切配果盤 小吃
底薪3000-3500元/月 獎金 五險一金
最近的上班地點
那麼在這個過程當中,如何來尋找定位詞呢?通常分爲3步:
(1)定位詞->種子詞->定位詞。例如:
(2)基於詞性標註。例如:
對文本分詞,進行詞性標註, 查找裏面的動詞、數詞、量詞
,做爲定位詞
(3)基於語法。例如:
動詞後面連續的名詞、簡稱
詞組聯合共現頻率高。動詞+形容詞,動詞+副詞的組合
關於詞性標註,詳見漢語詞性標註集:
最終創建起一個招聘知識庫:
最後,但願你們可以多讀論文,而且總結和溫習讀過的文章,結合GitHub上的開源實現,在TensorFlow上多多練習。在一個領域多的論文積累量,能發現不少存在的問題和機會。