1. 程式人生 > >從零開始,如何閱讀一篇人工智慧論文,及構建論文與程式碼的實現

從零開始,如何閱讀一篇人工智慧論文,及構建論文與程式碼的實現

本次 Chat 的第一部分:

首先講解如何從零基礎開始閱讀一篇機器學習方向的論文,以及對待論文中的數學問題。隨後,從一篇經典論文入手,講解如何快速梳理和理解一個深度學習框架及模型。

最近人工智慧和機器學習方向的論文非常多,那麼一個有工程背景、學術經驗較少或者有一定經驗的工程師,如何閱讀一篇人工智慧相關的論文呢?

在剛開始的學術探索中,我傾向於全文精讀,尤其是深度學習領域的經典論文,但發現這種方式花費時間太多,以至於擠壓了我的真正目的——工程實現和工程結合。並且,因為想抓住的東西太多,反而沒有抓住一篇文章的核心,導致很容易忘記,比如昨天讀的文章就像喝了水一樣忘掉了。

我將和大家從兩個方面探討。

一、從零開始,閱讀一篇論文的層次

這裡的從零開始,指的是我們要從零瞭解這篇文章做了什麼事情、使用了什麼方法、得到什麼結果,這樣的方法和結果對我有沒有什麼借鑑。

而不是說,接觸到一個全新的領域,從讀論文開始入手。對於沒有過接觸的陌生領域。我的方法是,先看中文綜述,中文博士論文,而後是英文綜述。通過中文綜述,可以首先了解這個領域的基本名詞、實驗常用方法。

否則直接從論文入手的話,作者站的高度和我們的水平不一致,很容易想當然的理解或者根本看不下去。因此,在閱讀這篇文章之前,對於這篇文章中涉及到的基礎知識,對應中文基礎都理解透徹。

這時,迴歸到從零開始理解這篇文章的狀態。

對一篇文章的閱讀往往有3個遞增的層次:

層次1. 讀懂這篇文章的概要資訊(5-10分鐘)

  • 認真讀懂標題、摘要、簡介(title, abstract, and introduction)。

  • 只讀各個部分和子部分(section and sub-section)的標題,跳過具體內容。

  • 讀懂結論和討論(作者通常會在這裡論述本研究的不足和缺失,為未來的研究提供建議,指明方向)。

  • 瀏覽參考文獻,記下哪些文獻是你已經讀過的。

因此,在第一層次過後,應該能回答出以下5個問題:

  1. 文章分類:關於實現方法的文章?對於已有系統的分析文章?對於研究理論的描述文章?

  2. 內容:有沒有對應的相關paper?這篇文章是基於什麼樣的基礎理論?(theoretical bases)

  3. 文章的假設(assumptions)是真的正確麼?

  4. 貢獻:這篇文章是在效果上(state of art)有了明顯進步?還是方法上有了創新?還是完善了基礎理論?

  5. 清晰度:是一篇描述清晰的文章麼?

第一個層次完成你就可以覺得是否要深入第二個層次,它足夠做你的某天想用到時的知識儲備,而不是現在立刻入手。

層次2. 抓住文章的內容,忽略文章細節(1個小時)

第二個層次需要認真讀,抓住重點:

  1. 對圖、表的含義以及他們支援的結論弄懂。

  2. 記下參考文獻中你認為重要的未讀文獻,它能讓你對這篇文章的背景有深刻理解。

完成第2個層次,要達到知道文章用了哪些證據,如何證明了一個什麼樣的結論。

尤其在這個層次中,如果遇到讀不懂(原因有很多:公式太多、對術語不理解、對實驗手段不熟悉、參考文獻的文獻過多)。說明我們還沒有和作者在一個基礎上,建議先從幾篇重要的參考文獻入手,補充背景知識。

層次3. 深入細節理解文章(5-6小時)

如果這個文章是你想應用到目前工程中的,則需要第3個層次。目標是能夠在相同的假設條件下,重現(re-implement)論文。

同時,要注重論文在GitHub上的對應程式碼,跳到程式中能加速理解。

比較你重現的結果和原論文,就能真正理解一篇文章的創新點,以及它的隱含前提或假設。並且你能從重現過程中得到一些你未來工作的方向。

做這三個層次的好處就是,能夠讓你對讀一篇文章的時間有合理的估計,甚至可以根據時間和你的工作需要調整掌握一篇文章的深度。

二、如何閱讀包含很多數學內容的機器學習論文

這個在很多AI論文中很普遍,所以一般來講,在第一個層次中,不需要理解一個公式的所有步驟。儘量的跳過公式,讀文字描述,讀實驗結果,讀結論。

隨著你平時工作中數學的積累,在第二個層次中,你也許能直接通過看公式來真正理解作者的目的和步驟。

如果非要進入第三個層次,可能需要跟著文章做一些推導。但是實際上,如果有現成的程式碼實現,可以讓你從工程的角度更好的理解數學過程。

最後,建議大家用這種方式,嘗試把這128篇論文中自己感興趣的領域根據自己的需要,調整閱讀層次地讀完。(筆者剛剛讀完啦,歡迎一起來交流哦)

下面結合TensorFlow的架構和體系設計論文《TensorFlow:
Large-Scale Machine Learning on Heterogeneous Distributed Systems》來講解以下兩點:

TensorFlow的程式設計模型和基本概念

結合不到20行程式碼講解靜態圖模型。

TensorFlow的執行方式分如下4步:

  1. 載入資料及定義超引數;

  2. 構建網路;

  3. 訓練模型;

  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)。這裡稱作“模型副本”是因為它們都是基於訓練樣例的子集訓練得到的,模型之間具有一定的獨立性。

enter image description here

具體的訓練步驟如下。

  1. 在GPU1和GPU2上分別定義模型網路結構。

  2. 對於單個GPU,分別從資料管道讀取不同的資料塊,然後進行前向傳播,計算出損失,再計算當前變數的梯度。

  3. 把所有GPU輸出的梯度資料轉移到CPU上,先進行梯度求平均操作,然後進行模型變數的更新。

  4. 重複第1步至第3步,直到模型變數收斂為止。

資料並行的目的主要是提高SGD的效率。例如,假如每次SGD的mini-batch大小是1000個樣本,那麼如果切成10份,每份100個,然後將模型複製10份,就可以在10個模型上同時計算。

但是,因為10個模型的計算速度可能是不一致的,有的快有的慢,那麼在CPU更新變數的時候,是應該等待這一mini-batch全部計算完成,然後求和取平均來更新呢,還是讓一部分先計算完的就先更新,後計算完的將前面的覆蓋呢?

這就引出了同步更新和非同步更新的問題。

分散式隨機梯度下降法是指,模型引數可以分散式地儲存在不同的引數伺服器上,工作節點可以並行地訓練資料並且能夠和引數伺服器通訊獲取模型引數。更新引數也分為同步和非同步兩種方式,即為非同步隨機梯度下降法(Async-SGD)和同步隨機梯度下降法(Sync-SGD)。如圖:

enter image description here

同步隨機梯度下降法(也稱同步更新、同步訓練)的含義是在進行訓練時,每個節點上的工作任務需要讀入共享引數,執行並行的梯度計算,同步需要等待所有工作節點把區域性的梯度算好,然後將所有共享引數進行合併、累加,再一次性更新到模型的引數;下一個批次中,所有工作節點拿到模型更新後的引數再進行訓練。

這種方案的優勢是,每個訓練批次都考慮了所有工作節點的訓練情況,損失下降比較穩定;劣勢是,效能瓶頸在於最慢的工作節點上。在異構裝置中,工作節點效能常常不同,這個劣勢非常明顯。

非同步隨機梯度下降法(也稱非同步更新、非同步訓練)的含義是每個工作節點上的任務獨立計算區域性梯度,並非同步更新到模型的引數中,不需要執行協調和等待操作。

這種方案的優勢優勢是,效能不存在瓶頸;劣勢是,每個工作節點計算的梯度值傳送回引數伺服器會有引數更新的衝突,一定程度上會影響演算法的收斂速度,在損失下降過程中抖動較大。

同步更新和非同步更新如何選擇?有沒有優化方式呢?

同步更新和非同步更新的實現區別主要在於更新引數伺服器的引數的策略。在資料量小,各個節點的計算能力比較均衡的情況下,推薦使用同步模式;在資料量很大,各個機器的計算效能參差不齊的情況下,推薦使用非同步模式。具體使用哪一種還可以看實驗結果,一般資料量足夠大的情況下非同步更新效果會更好。

下面展示將如何建立一個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的第二部分: 講解如何將你手上的需求轉化為論文的描述並實現出來。

以推薦系統為例:參考文章:

我們以招聘的招聘推薦系統中知識庫的構建為例,講解在哪裡以及如何引入NLP和知識圖譜的方法。

(1)為什麼建設知識庫?如下就是一個基於知識庫的搜尋:

enter image description here

也就是我們希望把對應的職位描述,結構化為知識圖譜:

enter image description here

我們知道知識圖譜包括實體和實體關係,那麼結合招聘的場景,實體庫中就應該包含:職位庫、職業庫、簡歷庫、實體詞庫。而實體關係可能有歸屬關係、層級關係、關聯關係。

我們來對職位描述做結構化的抽取,來設計實體關係的標籤體系,如下:

enter image description here

具體如何來抽取呢?

  1. 尋找定位詞和標點符號,切分成短句。

    職位內容:(營業員/學徒):負責吧檯,跟隨師傅調製 飲料,切配果盤,小吃;待遇:正式員工底薪3000- 3500元/月+獎金+五險一金,公司包吃住工作地點: 公司根據員工住所安排最近的上班地點

  2. 從短句中基於特徵字/詞定位核心內容

    (營業員/學徒)
    吧檯,跟隨師傅調製飲料,切配果盤,小吃
    正式員工底薪3000-3500元/月+獎金+
    公司根據員工住所安排最近的上班地點

  3. 核心詞抽取

    營業員 學徒
    吧檯 調製飲料 切配果盤 小吃
    底薪3000-3500元/月 獎金 五險一金
    最近的上班地點

那麼在這個過程中,如何來尋找定位詞呢?一般分為3步:

(1)定位詞->種子詞->定位詞。例如:

enter image description here

(2)基於詞性標註。例如:

對文字分詞,進行詞性標註, 查詢裡面的動詞、數詞、量詞,作為定位詞

enter image description here

(3)基於語法。例如:

動詞後面連續的名詞、簡稱

片語聯合共現頻率高。動詞+形容詞,動詞+副詞的組合

enter image description here

關於詞性標註,詳見漢語詞性標註集:

最終建立起一個招聘知識庫:

enter image description here

最後,希望大家能夠多讀論文,並且總結和溫習讀過的文章,結合GitHub上的開源實現,在TensorFlow上多多練習。在一個領域多的論文積累量,能發現很多存在的問題和機會。

彩蛋:

重磅 Chat 分享:

《高效學習,快速變現:不走彎路的五大學習策略》

分享人:
一名會在 B 站直播寫程式碼,會玩雜耍球、彈 Ukulele、極限健身、跑步、寫段子、畫畫、翻譯、寫作、演講、培訓的程式設計師。喜歡用程式設計實現自己的想法,在 Android 市場上賺過錢,有多次創業經歷。擅長學習,習慣養成,時間管理。身體力行地影響他人做出積極的改變!目前就職於 ThoughtWorks,致力於傳播快樂高效的程式設計理念。業餘創立軟體匠藝社群 CodingStyle.cn,組織超過30場技術活動。個

Chat簡介:
說到學習呀,真是頭大喲:
碎片化,沒有較長的連續時間來學習
難專注,捧起書,手機卻在召喚:來呀,快活呀~ 反正有,大把時光~
做不到,看了很多書,生活中卻做不到
然並卵,學了方法和工具,找不到使用場景
效率低,學習速度跟不上知識產生的速度
記不牢,學習速度趕不上遺忘速度
在這個知識氾濫、跨界競爭的年代,學習能力才是核心競爭力。你想想,過去一週,有沒有哪一件工作是不需要學習就能完成的?儘管如此重要,大部分人卻沒研究過學習這件事,以為上下班路上開啟「得到」聽本書,就是碎片時間終身學習者了。

我是程式設計師,諮詢師,培訓師,這幾個角色都要求我必須學得又快又好。本場 Chat 將分析學習的「趨勢,原則,策略」,幫你站在更高的視角看待學習,從「內容,動機,互動,收益,資源」五方面制定策略,解決學習痛點,助你成為高效學習者!

想要免費參與本場 Chat ?很簡單,「GitChat技術雜談」公眾號後臺回覆「高效學習」

這裡寫圖片描述