电竞比分网-中国电竞赛事及体育赛事平台

分享

【騰訊Bugly干貨分享】人人都可以做深度學(xué)習(xí)應(yīng)用:入門篇

 星星點(diǎn)點(diǎn)燈 2017-03-03

導(dǎo)語

2016年,繼虛擬現(xiàn)實(shí)(VR)之后,人工智能(AI)的概念全面進(jìn)入大眾的視野。谷歌,微軟,IBM等科技巨頭紛紛重點(diǎn)布局,AI 貌似將成為互聯(lián)網(wǎng)的下一個(gè)風(fēng)口。

很多開發(fā)同學(xué),對人工智能非常感興趣,確不知從何入手進(jìn)行學(xué)習(xí),精神哥也同樣被這個(gè)問題困擾。直至看見漢彬同學(xué)的這篇文章,豁然開朗,讓我堅(jiān)定地邁出了成為“AI 工程師”的第一步!

本文作者:騰訊QQ會員技術(shù)團(tuán)隊(duì)-徐漢彬
微信公眾號:小時(shí)光茶社

一、人工智能和新科技革命

2017年,圍棋界發(fā)生了一件比較重要的事,Master(AlphaGo)以60連勝橫掃天下,擊敗各路世界冠軍,人工智能以氣勢如虹的姿態(tài)出現(xiàn)在我們?nèi)祟惖拿媲啊逶?jīng)一度被稱為“人類智慧的堡壘”,如今,這座堡壘也隨之成為過去。

從2016年三月份AlphaGo擊敗李世石開始,AI全面進(jìn)入我們大眾的視野,對于它的討論變得更為火熱起來,整個(gè)業(yè)界普遍認(rèn)為,它很可能帶來下一次科技革命,并且,在未來可預(yù)見的十多年里,深刻的改變我們的生活。

其實(shí),AI除了可以做我們熟知的人臉識別、語音識別之外,還可以做其他蠻多有趣的事情。

例如,讓AI學(xué)習(xí)大量古詩之后寫古詩,并且可以寫出質(zhì)量非常不錯(cuò)的古詩,如下圖這兩首:

又或者,將兩部設(shè)計(jì)造型不同的汽車進(jìn)行融合,形成全新一種設(shè)計(jì)風(fēng)格的汽車造型。

還有,之前大家在朋友圈里可能看過的,將相片轉(zhuǎn)換成對應(yīng)的藝術(shù)風(fēng)格的畫作。

當(dāng)前,人工智能已經(jīng)在圖像、語音等多個(gè)領(lǐng)域的技術(shù)上,取得了全面的突破。與此同時(shí),另外一個(gè)問題隨之而來,如果這一輪的AI浪潮真的將會掀起新的科技革命,那么在可預(yù)見的未來,我們整個(gè)互聯(lián)網(wǎng)都將發(fā)生翻天覆地的變化,深刻影響我們的生活。

那么作為普通業(yè)務(wù)開發(fā)工程師的我,又應(yīng)該以何種態(tài)度和方式應(yīng)對這場時(shí)代洪流的沖擊呢?

在回答這個(gè)問題之前,我們先一起看看上一輪由計(jì)算機(jī)信息技術(shù)引領(lǐng)的科技革命中,過去30多年中國程序員的角色變化:

通過上圖可以簡總結(jié):編程技術(shù)在不斷地發(fā)展并且走向普及,從最開始掌握在科學(xué)家和專家學(xué)者手中的技能,逐漸發(fā)展為一門大眾技能。換而言之,我們公司內(nèi)很多資深的工程師,如果帶著今天對編程和計(jì)算機(jī)的理解和理念回到1980年,那么他無疑就是那個(gè)時(shí)代的計(jì)算機(jī)專家。

如果這一輪AI浪潮真的會帶來新的一輪科技革命,那么我們相信,它也會遵循類似的發(fā)展軌跡,逐步發(fā)展和走向普及。如果基于這個(gè)理解,或許,我們可以通過積極學(xué)習(xí),爭取成為第一代“AI工程師”。

二、深度學(xué)習(xí)技術(shù)

這一輪AI的技術(shù)突破,主要源于深度學(xué)習(xí)技術(shù),而關(guān)于AI和深度學(xué)習(xí)的發(fā)展歷史我們這里不再重復(fù)講述,大家可自行查閱。

我用了一個(gè)多月的業(yè)務(wù)時(shí)間,去了解和學(xué)習(xí)了深度學(xué)習(xí)技術(shù),在這里,我嘗試以一名業(yè)務(wù)開發(fā)工程師的視角,以盡量容易讓大家理解的方式一起探討下深度學(xué)習(xí)的原理,盡管,受限于我個(gè)人的技術(shù)水平和掌握程度,未必完全準(zhǔn)確。

1. 人類智能和神經(jīng)元

人類智能最重要的部分是大腦,大腦雖然復(fù)雜,它的組成單元卻是相對簡單的,大腦皮層以及整個(gè)神經(jīng)系統(tǒng),是由神經(jīng)元細(xì)胞組成的。而一個(gè)神經(jīng)元細(xì)胞,由樹突和軸突組成,它們分別代表輸入和輸出。連在細(xì)胞膜上的分叉結(jié)構(gòu)叫樹突,是輸入,那根長長的“尾巴”叫軸突,是輸出。神經(jīng)元輸出的有電信號和化學(xué)信號,最主要的是沿著軸突細(xì)胞膜表面?zhèn)鞑サ囊粋€(gè)電脈沖。

忽略掉各種細(xì)節(jié),神經(jīng)元,就是一個(gè)積累了足夠的輸入,就產(chǎn)生一次輸出(興奮)的相對簡單的裝置。

樹突和軸突都有大量的分支,軸突的末端通常連接到其他細(xì)胞的樹突上,連接點(diǎn)上是一個(gè)叫“突觸”的結(jié)構(gòu)。一個(gè)神經(jīng)元的輸出通過突觸傳遞給成千上萬個(gè)下游的神經(jīng)元,神經(jīng)元可以調(diào)整突觸的結(jié)合強(qiáng)度,并且,有的突觸是促進(jìn)下游細(xì)胞的興奮,有的是則是抑制。一個(gè)神經(jīng)元有成千上萬個(gè)上游神經(jīng)元,積累它們的輸入,產(chǎn)生輸出。

人腦有1000億個(gè)神經(jīng)元,1000萬億個(gè)突觸,它們組成人腦中龐大的神經(jīng)網(wǎng)絡(luò),最終產(chǎn)生的結(jié)果即是人類智能。

2. 人工神經(jīng)元和神經(jīng)網(wǎng)絡(luò)

一個(gè)神經(jīng)元的結(jié)構(gòu)相對來說是比較簡單的,于是,科學(xué)家們就思考,我們的AI是否可以從中獲得借鑒?神經(jīng)元接受激勵(lì),輸出一個(gè)響應(yīng)的方式,同計(jì)算機(jī)中的輸入輸出非常類似,看起來簡直就是量身定做的,剛好可以用一個(gè)函數(shù)來模擬。

通過借鑒和參考神經(jīng)元的機(jī)制,科學(xué)家們模擬出了人工神經(jīng)元和人工神經(jīng)網(wǎng)絡(luò)。當(dāng)然,通過上述這個(gè)抽象的描述和圖,比較難讓大家理解它的機(jī)制和原理。我們以“房屋價(jià)格測算”作為例子,一起來看看:

一套房子的價(jià)格,會受到很多因素的影響,例如地段、朝向、房齡、面積、銀行利率等等,這些因素如果細(xì)分,可能會有幾十個(gè)。一般在深度學(xué)習(xí)模型里,這些影響結(jié)果的因素我們稱之為特征。我們先假設(shè)一種極端的場景,比如影響價(jià)格的特征只有一種,就是房子面積。然后我們收集一批相關(guān)的數(shù)據(jù),例如,50平米50萬、93平米95萬等一系列樣本數(shù)據(jù),如果將這些樣本數(shù)據(jù)放到二維坐標(biāo)里看,則如下圖:

然后,正如我們前面所說的,我們嘗試用一個(gè)“函數(shù)”去擬合這個(gè)輸入(面積x)和輸出(價(jià)格y),簡而言之,我們就是要通過一條直線或者曲線將這些點(diǎn)“擬合”起來。

假設(shè)情況也比較極端,這些點(diǎn)剛好可以用一條“直線”擬合(真實(shí)情況通常不會是直線),如下圖:

那么我們的函數(shù)是一個(gè)一次元方程f(x) = ax +b,當(dāng)然,如果是曲線的話,我們得到的將是多次元方程。我們獲得這個(gè)f(x) = ax +b的函數(shù)之后,接下來就可以做房價(jià)“預(yù)測”,例如,我們可以計(jì)算一個(gè)我們從未看見的面積案例81.5平方米,它究竟是多少錢?

這個(gè)新的樣本案例,可以通過直線找到對應(yīng)的點(diǎn)(黃色的點(diǎn)),如圖下:

粗略的理解,上面就是AI的概括性的運(yùn)作方式。這一切似乎顯得過于簡單了?當(dāng)然不會,因?yàn)?,我們前面提到,影響房價(jià)其實(shí)遠(yuǎn)不止一個(gè)特征,而是有幾十個(gè),這樣問題就比較復(fù)雜了,接下來,這里則要繼續(xù)介紹深度學(xué)習(xí)模型的訓(xùn)練方式。

這部分內(nèi)容相對復(fù)雜一點(diǎn),我盡量以業(yè)務(wù)工程師的視角來做一個(gè)粗略而簡單的闡述。

3. 深度學(xué)習(xí)模型的訓(xùn)練方式

當(dāng)有好幾十個(gè)特征共同影響價(jià)格的時(shí)候,自然就會涉及權(quán)重分配的問題,例如有一些對房價(jià)是主要正權(quán)重的,例如地段、面積等,也有一些是負(fù)權(quán)重的,例如房齡等。

(1)初始化權(quán)重計(jì)算
那么,第一個(gè)步其實(shí)是給這些特征加一個(gè)權(quán)重值,但是,最開始我們根本不知道這些權(quán)重值是多少?怎么辦呢?不管那么多了,先給它們隨機(jī)賦值吧。隨機(jī)賦值,最終計(jì)算出來的估算房價(jià)肯定是不準(zhǔn)確的,例如,它可能將價(jià)值100萬的房子,計(jì)算成了10萬。

(2)損失函數(shù)
因?yàn)楝F(xiàn)在模型的估值和實(shí)際估值差距比較大,于是,我們需要引入一個(gè)評估“不準(zhǔn)確”程度的衡量角色,也就是損失(loss)函數(shù),它是衡量模型估算值和真實(shí)值差距的標(biāo)準(zhǔn),損失函數(shù)越小,則模型的估算值和真實(shí)值的察覺越小,而我們的根本目的,就是降低這個(gè)損失函數(shù)。讓剛剛的房子特征的模型估算值,逼近100萬的估算結(jié)果。

(3)模型調(diào)整
通過梯度下降和反向傳播,計(jì)算出朝著降低損失函數(shù)的方向調(diào)整權(quán)重參數(shù)。舉一個(gè)不恰當(dāng)?shù)谋扔?,我們給面積增加一些權(quán)重,然后給房子朝向減少一些權(quán)重(實(shí)際計(jì)算方式,并非針對單個(gè)個(gè)例特征的調(diào)整),然后損失函數(shù)就變小了。

(4)循環(huán)迭代
調(diào)整了模型的權(quán)重之后,就可以又重新取一批新的樣本數(shù)據(jù),重復(fù)前面的步驟,經(jīng)過幾十萬次甚至更多的訓(xùn)練次數(shù),最終估算模型的估算值逼近了真實(shí)值結(jié)果,這個(gè)模型的則是我們要的“函數(shù)”。

為了讓大家更容易理解和直觀,采用的例子比較粗略,并且講述深度學(xué)習(xí)模型的訓(xùn)練過程,中間省略了比較多的細(xì)節(jié)。

講完了原理,那么我們就開始講講如何學(xué)習(xí)和搭建demo。

三、深度學(xué)習(xí)環(huán)境搭建

在2個(gè)月前,人工智能對我來說,只是一個(gè)高大上的概念。但是,經(jīng)過一個(gè)多月的業(yè)余時(shí)間的認(rèn)真學(xué)習(xí),我發(fā)現(xiàn)還是能夠?qū)W到一些東西,并且跑一些demo和應(yīng)用出來的。

1. 學(xué)習(xí)的提前準(zhǔn)備

(1)部分?jǐn)?shù)學(xué)內(nèi)容的復(fù)習(xí),高中數(shù)學(xué)、概率、線性代數(shù)等部分內(nèi)容。(累計(jì)花費(fèi)了10個(gè)小時(shí),挑了關(guān)鍵的點(diǎn)看了下,其實(shí)還是不太夠,只能讓自己看公式的時(shí)候,相對沒有那么懵)
**(2)**Python基礎(chǔ)語法學(xué)習(xí)。(花費(fèi)了3個(gè)小時(shí)左右,我以前從未寫過Python,因?yàn)楹竺鍳oogle的TensorFlow框架的使用是基于Python的)
**(3)**Google的TensorFlow深度學(xué)習(xí)開源框架。(花費(fèi)了10多個(gè)小時(shí)去看)

數(shù)學(xué)基礎(chǔ)好或者前期先不關(guān)注原理的同學(xué),數(shù)學(xué)部分不看也可以開始做,全憑個(gè)人選擇。

2. Google的TensorFlow開源深度學(xué)習(xí)框架

深度學(xué)習(xí)框架,我們可以粗略的理解為是一個(gè)“數(shù)學(xué)函數(shù)”集合和AI訓(xùn)練學(xué)習(xí)的執(zhí)行框架。通過它,我們能夠更好的將AI的模型運(yùn)行和維護(hù)起來。

深度學(xué)習(xí)的框架有各種各樣的版本(Caffe、Torch、Theano等等),我只接觸了Google的TensorFlow,因此,后面的內(nèi)容都是基于TensorFlow展開的,它的詳細(xì)介紹這里不展開講述,建議直接進(jìn)入官網(wǎng)查看。非常令人慶幸的是TensorFlow比較早就有中文社區(qū)了,盡管里面的內(nèi)容有一點(diǎn)老,搭建環(huán)境方面有一些坑,但是已經(jīng)屬于為數(shù)不多的中文文檔了,大家且看且珍惜。

TensorFlow的中文社區(qū):
http://www./
TensorFlow的英文社區(qū):
https://www./

3. TensorFlow環(huán)境搭建

環(huán)境搭建本身并不復(fù)雜,主要解決相關(guān)的依賴。但是,基礎(chǔ)庫的依賴可以帶來很多問題,因此,建議盡量一步到位,會簡單很多。

(1)操作系統(tǒng)
我搭建環(huán)境使用的機(jī)器是騰訊云上的機(jī)器,軟件環(huán)境如下:
操作系統(tǒng):CentOS 7.2 64位(GCC 4.8.5)

因?yàn)檫@個(gè)框架依賴于python2.7和glibc 2.17。比較舊的版本的CentOS一般都是python2.6以及版本比較低的glibc,會產(chǎn)生比較的多基礎(chǔ)庫依賴問題。而且,glibc作為Linux的底層庫,牽一發(fā)動全身,直接對它升級是比較復(fù)雜,很可能會帶來更多的環(huán)境異常問題。

(2)軟件環(huán)境
我目前安裝的Python版本是python-2.7.5,建議可以采用yum install python的方式安裝相關(guān)的原來軟件。然后,再安裝 python內(nèi)的組件包管理器pip,安裝好pip之后,接下來的其他軟件的安裝就相對比較簡單了。

例如安裝TensorFlow,可通過如下一句命令完成(它會自動幫忙解決一些庫依賴問題):
pip install -U tensorflow

這里需要特別注意的是,不要按照TensorFlow的中文社區(qū)的指引去安裝,因?yàn)樗鼤惭b一個(gè)非常老的版本(0.5.0),用這個(gè)版本跑很多demo都會遇到問題的。而實(shí)際上,目前通過上述提供的命令安裝,是tensorflow (1.0.0)的版本了。

Python(2.7.5)下的其他需要安裝的關(guān)鍵組件:

  • tensorflow (0.12.1),深度學(xué)習(xí)的核心框架
  • image (1.5.5),圖像處理相關(guān),部分例子會用到
  • PIL (1.1.7),圖像處理相關(guān),部分例子會用到

除此之后,當(dāng)然還有另外的一些依賴組件,通過pip list命令可以查看我們安裝的python組件:

  • appdirs (1.4.0)
  • backports.ssl-match-hostname (3.4.0.2)
  • chardet (2.2.1)
  • configobj (4.7.2)
  • decorator (3.4.0)
  • Django (1.10.4)
  • funcsigs (1.0.2)
  • image (1.5.5)
  • iniparse (0.4)
  • kitchen (1.1.1)
  • langtable (0.0.31)
  • mock (2.0.0)
  • numpy (1.12.0)
  • packaging (16.8)
  • pbr (1.10.0)
  • perf (0.1)
  • PIL (1.1.7)
  • Pillow (3.4.2)
  • pip (9.0.1)
  • protobuf (3.2.0)
  • pycurl (7.19.0)
  • pygobject (3.14.0)
  • pygpgme (0.3)
  • pyliblzma (0.5.3)
  • pyparsing (2.1.10)
  • python-augeas (0.5.0)
  • python-dmidecode (3.10.13)
  • pyudev (0.15)
  • pyxattr (0.5.1)
  • setuptools (34.2.0)
  • six (1.10.0)
  • slip (0.4.0)
  • slip.dbus (0.4.0)
  • tensorflow (1.0.0)
  • urlgrabber (3.10)
  • wheel (0.29.0)
  • yum-langpacks (0.4.2)
  • yum-metadata-parser (1.1.4)

按照上述提供的來搭建系統(tǒng),可以規(guī)避不少的環(huán)境問題。

搭建環(huán)境的過程中,我遇到不少問題。

例如:在跑官方的例子時(shí)的某個(gè)報(bào)錯(cuò),AttributeError: ‘module’ object has no attribute ‘gfile’,就是因?yàn)榘惭b的TensorFlow的版本比較老,缺少gfile模塊導(dǎo)致的。而且,還有各種各樣的。(不要問我是怎么知道的,說多了都是淚啊~)

更詳細(xì)的安裝說明:
https://www./install/install_linux

(3)TensorFlow環(huán)境測試運(yùn)行
測試是否安裝成功,可以采用官方的提供的一個(gè)短小的例子,demo生成了一些三維數(shù)據(jù), 然后用一個(gè)平面擬合它們(官網(wǎng)的例子采用的初始化變量的函數(shù)是initialize_all_variables,該函數(shù)在新版本里已經(jīng)被廢棄了):

#!/usr/bin/python#coding=utf-8import tensorflow as tfimport numpy as np# 使用 NumPy 生成假數(shù)據(jù)(phony data), 總共 100 個(gè)點(diǎn).x_data = np.float32(np.random.rand(2, 100)) # 隨機(jī)輸入y_data = np.dot([0.100, 0.200], x_data) + 0.300# 構(gòu)造一個(gè)線性模型# b = tf.Variable(tf.zeros([1]))W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))y = tf.matmul(W, x_data) + b# 最小化方差loss = tf.reduce_mean(tf.square(y - y_data))optimizer = tf.train.GradientDescentOptimizer(0.5)train = optimizer.minimize(loss)# 初始化變量,舊函數(shù)(initialize_all_variables)已經(jīng)被廢棄,替換為新函數(shù)init = tf.global_variables_initializer()# 啟動圖 (graph)sess = tf.Session()sess.run(init)# 擬合平面for step in xrange(0, 201): sess.run(train) if step % 20 == 0: print step, sess.run(W), sess.run(b)# 得到最佳擬合結(jié)果 W: [[0.100 0.200]], b: [0.300]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

運(yùn)行的結(jié)果類似如下:

經(jīng)過200次的訓(xùn)練,模型的參數(shù)逐漸逼近最佳擬合的結(jié)果(W: [[0.100 0.200]], b: [0.300]),另外,我們也可以從代碼的“風(fēng)格”中,了解到框架樣本訓(xùn)練的基本運(yùn)行方式。雖然,官方的教程后續(xù)會涉及越來越多更復(fù)雜的例子,但從整體上看,也是類似的模式。

步驟劃分:

  • 準(zhǔn)備數(shù)據(jù):獲得有標(biāo)簽的樣本數(shù)據(jù)(帶標(biāo)簽的訓(xùn)練數(shù)據(jù)稱為有監(jiān)督學(xué)習(xí));

  • 設(shè)置模型:先構(gòu)建好需要使用的訓(xùn)練模型,可供選擇的機(jī)器學(xué)習(xí)方法其實(shí)也挺多的,換而言之就是一堆數(shù)學(xué)函數(shù)的集合;

  • 損失函數(shù)和優(yōu)化方式:衡量模型計(jì)算結(jié)果和真實(shí)標(biāo)簽值的差距;

  • 真實(shí)訓(xùn)練運(yùn)算:訓(xùn)練之前構(gòu)造好的模型,讓程序通過循環(huán)訓(xùn)練和學(xué)習(xí),獲得最終我們需要的結(jié)果“參數(shù)”;

  • 驗(yàn)證結(jié)果:采用之前模型沒有訓(xùn)練過的測試集數(shù)據(jù),去驗(yàn)證模型的準(zhǔn)確率。

其中,TensorFlow為了基于python實(shí)現(xiàn)高效的數(shù)學(xué)計(jì)算,通常會使用到一些基礎(chǔ)的函數(shù)庫,例如Numpy(采用外部底層語言實(shí)現(xiàn)),但是,從外部計(jì)算切回到python也是存在開銷的,尤其是在幾萬幾十萬次的訓(xùn)練過程。因此,Tensorflow不單獨(dú)地運(yùn)行單一的函數(shù)計(jì)算,而是先用圖描述一系列可交互的計(jì)算操作流程,然后全部一次性提交到外部運(yùn)行(在其他機(jī)器學(xué)習(xí)的庫里,也是類似的實(shí)現(xiàn))。所以,上述流程圖中,藍(lán)色部分都只是設(shè)置了“計(jì)算操作流程”,而綠色部分開始才是真正的提交數(shù)據(jù)給到底層庫進(jìn)行實(shí)際運(yùn)算,而且,每次訓(xùn)練一般是批量執(zhí)行一批數(shù)據(jù)的。

四、經(jīng)典入門demo:識別手寫數(shù)字(MNIST)

常規(guī)的編程入門有“Hello world”程序,而深度學(xué)習(xí)的入門程序則是MNIST,一個(gè)識別28*28像素的圖片中的手寫數(shù)字的程序。

MNIST的數(shù)據(jù)和官網(wǎng):
http://yann./exdb/mnist/

深度學(xué)習(xí)的內(nèi)容,其背后會涉及比較多的數(shù)學(xué)原理,作為一個(gè)初學(xué)者,受限于我個(gè)人的數(shù)學(xué)和技術(shù)水平,也許并不足以準(zhǔn)確講述相關(guān)的數(shù)學(xué)原理,因此,本文會更多的關(guān)注“應(yīng)用層面”,不對背后的數(shù)學(xué)原理進(jìn)行展開,感謝諒解。

1. 加載數(shù)據(jù)

程序執(zhí)行的第一步當(dāng)然是加載數(shù)據(jù),根據(jù)我們之前獲得的數(shù)據(jù)集主要包括兩部分:60000的訓(xùn)練數(shù)據(jù)集(mnist.train)和10000的測試數(shù)據(jù)集(mnist.test)。里面每一行,是一個(gè)28*28=784的數(shù)組,數(shù)組的本質(zhì)就是將28*28像素的圖片,轉(zhuǎn)化成對應(yīng)的像素點(diǎn)陣。

例如手寫字1的圖片轉(zhuǎn)換出來的對應(yīng)矩陣表示如下:

之前我們經(jīng)常聽說,圖片方面的深度學(xué)習(xí)需要大量的計(jì)算能力,甚至需要采用昂貴、專業(yè)的GPU(Nvidia的GPU),從上述轉(zhuǎn)化的案例我們就已經(jīng)可以獲得一些答案了。一張784像素的圖片,對學(xué)習(xí)模型來說,就有784個(gè)特征,而我們實(shí)際的相片和圖片動輒幾十萬、百萬級別,則對應(yīng)的基礎(chǔ)特征數(shù)也是這個(gè)數(shù)量級,基于這樣數(shù)量級的數(shù)組進(jìn)行大規(guī)模運(yùn)算,沒有強(qiáng)大的計(jì)算能力支持,確實(shí)寸步難行。當(dāng)然,這個(gè)入門的MNIST的demo還是可以比較快速的跑完。

Demo中的關(guān)鍵代碼(讀取并且加載數(shù)據(jù)到數(shù)組對象中,方便后面使用):

2. 構(gòu)建模型

MNIST的每一張圖片都表示一個(gè)數(shù)字,從0到9。而模型最終期望獲得的是:給定一張圖片,獲得代表每個(gè)數(shù)字的概率。比如說,模型可能推測一張數(shù)字9的圖片代表數(shù)字9的概率是80%但是判斷它是8的概率是5%(因?yàn)?和9都有上半部分的小圓),然后給予它代表其他數(shù)字的概率更小的值。

MNIST的入門例子,采用的是softmax回歸(softmax regression),softmax模型可以用來給不同的對象分配概率。

為了得到一張給定圖片屬于某個(gè)特定數(shù)字類的證據(jù)(evidence),我們對圖片的784個(gè)特征(點(diǎn)陣?yán)锏母鱾€(gè)像素值)進(jìn)行加權(quán)求和。如果某個(gè)特征(像素值)具有很強(qiáng)的證據(jù)說明這張圖片不屬于該類,那么相應(yīng)的權(quán)重值為負(fù)數(shù),相反如果某個(gè)特征(像素值)擁有有利的證據(jù)支持這張圖片屬于這個(gè)類,那么權(quán)重值是正數(shù)。類似前面提到的房價(jià)估算例子,對每一個(gè)像素點(diǎn)作出了一個(gè)權(quán)重分配。

假設(shè)我們獲得一張圖片,需要計(jì)算它是8的概率,轉(zhuǎn)化成數(shù)學(xué)公式則如下:

公式中的i代表需要預(yù)測的數(shù)字(8),代表預(yù)測數(shù)字為8的情況下,784個(gè)特征的不同權(quán)重值,代表8的偏置量(bias),X則是該圖片784個(gè)特征的值。通過上述計(jì)算,我們則可以獲得證明該圖片是8的證據(jù)(evidence)的總和,softmax函數(shù)可以把這些證據(jù)轉(zhuǎn)換成概率 y。(softmax的數(shù)學(xué)原理,辛苦各位查詢相關(guān)資料哈)

將前面的過程概括成一張圖(來自官方)則如下:

不同的特征x和對應(yīng)不同數(shù)字的權(quán)重進(jìn)行相乘和求和,則獲得在各個(gè)數(shù)字的分布概率,取概率最大的值,則認(rèn)為是我們的圖片預(yù)測結(jié)果。

將上述過程寫成一個(gè)等式,則如下:

該等式在矩陣乘法里可以非常簡單地表示,則等價(jià)為:

不展開里面的具體數(shù)值,則可以簡化為:

如果我們對線性代數(shù)中矩陣相關(guān)內(nèi)容有適當(dāng)學(xué)習(xí),其實(shí),就會明白矩陣表達(dá)在一些問題上,更易于理解。如果對矩陣內(nèi)容不太記得了,也沒有關(guān)系,后面我會附加上線性代數(shù)的視頻。

雖然前面講述了這么多,其實(shí)關(guān)鍵代碼就四行:

上述代碼都是類似變量占位符,先設(shè)置好模型計(jì)算方式,在真實(shí)訓(xùn)練流程中,需要批量讀取源數(shù)據(jù),不斷給它們填充數(shù)據(jù),模型計(jì)算才會真實(shí)跑起來。tf.zeros則表示,先給它們統(tǒng)一賦值為0占位。X數(shù)據(jù)是從數(shù)據(jù)文件中讀取的,而w、b是在訓(xùn)練過程中不斷變化和更新的,y則是基于前面的數(shù)據(jù)進(jìn)行計(jì)算得到。

3. 損失函數(shù)和優(yōu)化設(shè)置

為了訓(xùn)練我們的模型,我們首先需要定義一個(gè)指標(biāo)來衡量這個(gè)模型是好還是壞。這個(gè)指標(biāo)稱為成本(cost)或損失(loss),然后盡量最小化這個(gè)指標(biāo)。簡單的說,就是我們需要最小化loss的值,loss的值越小,則我們的模型越逼近標(biāo)簽的真實(shí)結(jié)果。

Demo中使用的損失函數(shù)是“交叉熵”(cross-entropy),它的公式如下:

y 是我們預(yù)測的概率分布, y’ 是實(shí)際的分布(我們輸入的),交叉熵是用來衡量我們的預(yù)測結(jié)果的不準(zhǔn)確性。TensorFlow擁有一張描述各個(gè)計(jì)算單元的圖,也就是整個(gè)模型的計(jì)算流程,它可以自動地使用反向傳播算法(backpropagation algorithm),來確定我們的權(quán)重等變量是如何影響我們想要最小化的那個(gè)loss值的。然后,TensorFlow會用我們設(shè)定好的優(yōu)化算法來不斷修改變量以降低loss值。

其中,demo采用梯度下降算法(gradient descent algorithm)以0.01的學(xué)習(xí)速率最小化交叉熵。梯度下降算法是一個(gè)簡單的學(xué)習(xí)過程,TensorFlow只需將每個(gè)變量一點(diǎn)點(diǎn)地往使loss值不斷降低的方向更新。
對應(yīng)的關(guān)鍵代碼如下:

備注內(nèi)容:
交叉熵:http://colah./posts/2015-09-Visual-Information/
反向傳播:http://colah./posts/2015-08-Backprop/

在代碼中會看見one-hot vector的概念和變量名,其實(shí)這個(gè)是個(gè)非常簡單的東西,就是設(shè)置一個(gè)10個(gè)元素的數(shù)組,其中只有一個(gè)是1,其他都是0,以此表示數(shù)字的標(biāo)簽結(jié)果。

例如表示數(shù)字3的標(biāo)簽值:

[0,0,0,1,0,0,0,0,0,0]
  • 1

4. 訓(xùn)練運(yùn)算和模型準(zhǔn)確度測試

通過前面的實(shí)現(xiàn),我們已經(jīng)設(shè)置好了整個(gè)模型的計(jì)算“流程圖”,它們都成為TensorFlow框架的一部分。于是,我們就可以啟動我們的訓(xùn)練程序,下面的代碼的含義是,循環(huán)訓(xùn)練我們的模型500次,每次批量取50個(gè)訓(xùn)練樣本。

其訓(xùn)練過程,其實(shí)就是TensorFlow框架的啟動訓(xùn)練過程,在這個(gè)過程中,python批量地將數(shù)據(jù)交給底層庫進(jìn)行處理。

我在官方的demo里追加了兩行代碼,每隔50次則額外計(jì)算一次當(dāng)前模型的識別準(zhǔn)確率。它并非必要的代碼,僅僅用于方便觀察整個(gè)模型的識別準(zhǔn)確率逐步變化的過程。

當(dāng)然,里面涉及的accuracy(預(yù)測準(zhǔn)確率)等變量,需要在前面的地方定義占位:

當(dāng)我們訓(xùn)練完畢,則到了驗(yàn)證我們的模型準(zhǔn)確率的時(shí)候,和前面相同:

我的demo跑出來的結(jié)果如下(softmax回歸的例子運(yùn)行速度還是比較快的),當(dāng)前的準(zhǔn)確率是0.9252:

5. 實(shí)時(shí)查看參數(shù)的數(shù)值的方法

剛開始跑官方的demo的時(shí)候,我們總想將相關(guān)變量的值打印出來看看,是怎樣一種格式和狀態(tài)。從demo的代碼中,我們可以看見很多的Tensor變量對象,而實(shí)際上這些變量對象都是無法直接輸出查看,粗略地理解,有些只是占位符,直接輸出的話,會獲得類似如下的一個(gè)對象:

Tensor('Equal:0', shape=(?,), dtype=bool)
  • 1

既然它是占位符,那么我們就必須喂一些數(shù)據(jù)給它,它才能將真實(shí)內(nèi)容展示出來。因此,正確的方法是,在打印時(shí)通常需要加上當(dāng)前的輸入數(shù)據(jù)給它。

例如,查看y的概率數(shù)據(jù):

print(sess.run(y, feed_dict={x: batch_xs, y_: batch_ys}))
  • 1

部分非占位符的變量還可以這樣輸出來:

print(W.eval())
  • 1

總的來說,92%的識別準(zhǔn)確率是比較令人失望,因此,官方的MNIST其實(shí)也有多種模型的不同版本,其中比較適合圖片處理的CNN(卷積神經(jīng)網(wǎng)絡(luò))的版本,可以獲得99%以上的準(zhǔn)確率,當(dāng)然,它的執(zhí)行耗時(shí)也是比較長的。

(備注:cnn_mnist.py就是卷積神經(jīng)網(wǎng)絡(luò)版本的,后面有附帶微云網(wǎng)盤的下載url)
前饋神經(jīng)網(wǎng)絡(luò)(feed-forward neural network)版本的MNIST,可達(dá)到97%:

分享在微云上的數(shù)據(jù)和源碼:
http:///44aZOpP
(備注:國外網(wǎng)站下載都比較慢,我這份下載相對會快一些,在環(huán)境已經(jīng)搭建完畢的情況下,執(zhí)行里面的run.py即可)

五、和業(yè)務(wù)場景結(jié)合的demo:預(yù)測用戶是否是超級會員身份

根據(jù)前面的內(nèi)容,我們對上述基于softmax只是三層(輸入、處理、輸出)的神經(jīng)網(wǎng)絡(luò)模型已經(jīng)比較熟悉,那么,這個(gè)模型是否可以應(yīng)用到我們具體的業(yè)務(wù)場景中,其中的難度大嗎?為了驗(yàn)證這一點(diǎn),我拿了一些現(xiàn)網(wǎng)的數(shù)據(jù)來做了這個(gè)試驗(yàn)。

1. 數(shù)據(jù)準(zhǔn)備

我將一個(gè)現(xiàn)網(wǎng)的電影票活動的用戶參與數(shù)據(jù),包括點(diǎn)擊過哪些按鈕、手機(jī)平臺、IP地址、參與時(shí)間等信息抓取了出來。其實(shí)這些數(shù)據(jù)當(dāng)中是隱含了用戶的身份信息的,例如,某些禮包的必須是超級會員身份才能領(lǐng)取,如果這個(gè)按鈕用戶點(diǎn)擊領(lǐng)取成功,則可以證明該用戶的身份肯定是超級會員身份。當(dāng)然,我只是將這些不知道相不相關(guān)的數(shù)據(jù)特征直觀的整理出來,作為我們的樣本數(shù)據(jù),然后對應(yīng)的標(biāo)簽為超級會員身份。

用于訓(xùn)練的樣本數(shù)據(jù)格式如下:

第一列是QQ號碼,只做認(rèn)知標(biāo)識的,第二列表示是否超級會員身份,作為訓(xùn)練的標(biāo)簽值,后面的就是IP地址,平臺標(biāo)志位以及參與活動的參與記錄(0是未成功參與,1表示成功參與)。則獲得一個(gè)擁有11個(gè)特征的數(shù)組(經(jīng)過一些轉(zhuǎn)化和映射,將特別大的數(shù)變?。?/p>

[0.9166666666666666, 0.4392156862745098, 0.984313725490196, 0.7411764705882353, 0.2196078431372549, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
  • 1

對應(yīng)的是否是超級數(shù)據(jù)格式如下,作為監(jiān)督學(xué)習(xí)的標(biāo)簽:

超級會員:[0, 1]非超級會員:[1, 0]
  • 1
  • 2

這里需要專門解釋下,在實(shí)際應(yīng)用中需要做數(shù)據(jù)轉(zhuǎn)換的原因。一方面,將這些數(shù)據(jù)做一個(gè)映射轉(zhuǎn)化,有助于簡化數(shù)據(jù)模型。另一方面,是為了規(guī)避NaN的問題,當(dāng)數(shù)值過大,在一些數(shù)學(xué)指數(shù)和除法的浮點(diǎn)數(shù)運(yùn)算中,有可能得到一個(gè)無窮大的數(shù)值,或者其他溢出的情形,在Python里會變?yōu)镹aN類型,這個(gè)類型會破壞掉后續(xù)全部計(jì)算結(jié)果,導(dǎo)致計(jì)算異常。

例如下圖,就是特征數(shù)值過大,在訓(xùn)練過程中,導(dǎo)致中間某些參數(shù)累計(jì)越來越大,最終導(dǎo)致產(chǎn)生NaN值,后續(xù)的計(jì)算結(jié)果全部被破壞掉:

而導(dǎo)致NaN的原因在復(fù)雜的數(shù)學(xué)計(jì)算里,會產(chǎn)生無窮大或者無窮小。例如,在我們的這個(gè)demo中,產(chǎn)生NaN的原因,主要是因?yàn)閟oftmax的計(jì)算導(dǎo)致。

RuntimeWarning: divide by zero encountered in log

剛開始做實(shí)際的業(yè)務(wù)應(yīng)用,就發(fā)現(xiàn)經(jīng)常跑出極奇怪異的結(jié)果(遇到NaN問題,我發(fā)現(xiàn)程序也能繼續(xù)走下去),幾經(jīng)排查才發(fā)現(xiàn)是NAN值問題,是非常令人沮喪的。當(dāng)然,經(jīng)過仔細(xì)分析問題,發(fā)現(xiàn)也并非沒有排查的方式。因?yàn)?,NaN值是個(gè)奇特的類型,可以采用下述編碼方式NaN != NaN來檢測自己的訓(xùn)練過程中,是否出現(xiàn)的NaN。

關(guān)鍵程序代碼如下:

我采用上述方法,非常順利地找到自己的深度學(xué)習(xí)程序,在學(xué)習(xí)到哪一批數(shù)據(jù)時(shí)產(chǎn)生的NaN。因此,很多原始數(shù)據(jù)我們都會做一個(gè)除以某個(gè)值,讓數(shù)值變小的操作。例如官方的MNIST也是這樣做的,將256的像素顏色的數(shù)值統(tǒng)一除以255,讓它們都變成一個(gè)小于1的浮點(diǎn)數(shù)。
MNIST在處理原始圖片像素特征數(shù)據(jù)時(shí),也對特征數(shù)據(jù)進(jìn)行了變小處理:

NaN值問題一度深深地困擾著我(往事不堪回首-__-!!),特別放到這里,避免入門的同學(xué)踩坑。

2. 執(zhí)行結(jié)果

我準(zhǔn)備的訓(xùn)練集(6700)和測試集(1000)數(shù)據(jù)并不多,不過,超級會員身份的預(yù)測準(zhǔn)確率最終可以達(dá)到87%。雖然,預(yù)測準(zhǔn)確率是不高,這個(gè)可能和我的訓(xùn)練集數(shù)據(jù)比較少有關(guān)系,不過,整個(gè)模型也沒有花費(fèi)多少時(shí)間,從整理數(shù)據(jù)、編碼、訓(xùn)練到最終跑出結(jié)果,只用了2個(gè)晚上的時(shí)間。

下圖是兩個(gè)實(shí)際的測試?yán)樱?,該模型預(yù)測第一個(gè)QQ用戶有82%的概率是非超級會員用戶,17.9%的概率為超級會員用戶(該預(yù)測是準(zhǔn)確的)。

通過上面的這個(gè)例子,我們會發(fā)覺其實(shí)對于某些比較簡單的場景下應(yīng)用,我們是可以比較容易就實(shí)現(xiàn)的。

六、其他模型

1. CIFAR-10識別圖片分類的demo(官方)

CIFAR-10數(shù)據(jù)集的分類是機(jī)器學(xué)習(xí)中一個(gè)公開的基準(zhǔn)測試問題,它任務(wù)是對一組32x32RGB的圖像進(jìn)行分類,這些圖像涵蓋了10個(gè)類別:飛機(jī), 汽車, 鳥, 貓, 鹿, 狗, 青蛙, 馬, 船和卡車。

這也是官方的重要demo之一。

更詳細(xì)的介紹內(nèi)容:
http://www.cs./~kriz/cifar.html
http:///tfdoc/tutorials/deep_cnn.html

該例子執(zhí)行的過程比較長,需要耐心等待。

我在機(jī)器上的執(zhí)行過程和結(jié)果:

cifar10_train.py用于訓(xùn)練:

cifar10_eval.py用于檢驗(yàn)結(jié)果:

識別率不高是因?yàn)樵摴俜侥P偷淖R別率本來就不高:

另外,官方的例子我首次在1月5日跑的時(shí)候,還是有一些小問題的,無法跑起來(最新的官方可能已經(jīng)修正),建議可以直接使用我放到微云上的版本(代碼里面的log和讀取文件的路徑,需要調(diào)整一下)。

源碼下載:http:///44mRzBh

微云盤里,不含訓(xùn)練集和測試集的圖片數(shù)據(jù),但是,程序如果檢測到這些圖片不存在,會自行下載:

2. 是否大于5歲的測試demo

為了檢驗(yàn)softmax回歸模型是否能夠?qū)W習(xí)到一些我自己設(shè)定好的規(guī)則,我做了一個(gè)小demo來測試。我通過隨機(jī)數(shù)生成的方式構(gòu)造了一系列的數(shù)據(jù),讓前面的softmax回歸模型去學(xué)習(xí),最終看看模型能否通過訓(xùn)練集的學(xué)習(xí),最終100%預(yù)測這個(gè)樣本數(shù)據(jù)是否大于5歲。

模型和數(shù)據(jù)本身都比較簡單,構(gòu)造的數(shù)據(jù)的方式:
我隨機(jī)構(gòu)造一個(gè)只有2個(gè)特征緯度的樣本數(shù)據(jù),[year, 1],其中year隨機(jī)取值0-10,數(shù)字1是放進(jìn)去作為干擾。
如果year大于5歲,則標(biāo)簽設(shè)置為:[0, 0, 1];
否則,標(biāo)簽設(shè)置為:[0, 1, 0]。

生成了6000條假訓(xùn)練集去訓(xùn)練該模型,最終它能做到100%成功預(yù)測準(zhǔn)確:

微云下載(源碼下載):
http:///44mKFNK

3. 基于RNN的古詩學(xué)習(xí)

最開頭的AI寫古詩,非常令人感到驚艷,那個(gè)demo是美國的一個(gè)研究者做出來的,能夠根據(jù)主題生成不能的古詩,而且古詩的質(zhì)量還比較高。于是,我也嘗試在自己的機(jī)器上也跑一個(gè)能夠?qū)懝旁姷哪P?,后來我找到的是一個(gè)基于RNN的模型。RNN循環(huán)神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Networks),是非常常用的深度學(xué)習(xí)模型之一。我基于一個(gè)外部的demo,進(jìn)行一些調(diào)整后跑起一個(gè)能夠?qū)W習(xí)古詩和寫古詩的比較簡單的程序。

執(zhí)行寫詩(讓它寫了十首):

  • 抑滴留居瀲罅斜,二川還羨五侯家。古劉稱士身相染,桃李栽林欲稱家?;厥锥啻眨f當(dāng)仙性盡甘無。如何羽馬嘶來淚,不信紅峰一寸西。

  • 廢寺松陰月似空,垂楊風(fēng)起晚光催。烏心不把嫌香徑,出定滄洲幾好清。蘭逐白頭鄰斧蝶,蒼蒼歸路自清埃。漁樵若欲斜陽羨,桂苑西河碧朔來。

  • 遙天花落甚巫山,鳳珮飛馳不騁莊。翠初才象飲毫勢,上月朱爐一重牛。香催戍渚同虛客,石勢填樓取蕊紅。佳句舊清箱畔意,剪顏相激菊花繁。

  • 江上蕭條第一取,名長經(jīng)起月還游。數(shù)尺溫皋云戰(zhàn)遠(yuǎn),放船鄉(xiāng)鬼蘸云多。相逢檻上西風(fēng)動,莫聽風(fēng)煙認(rèn)釣魚。堤費(fèi)禽雛應(yīng)昨夢,去朝從此滿玄塵。

  • 避命拋醺背暮時(shí),見川誰哭夢知年。卻隨筵里腥消極,不遇嘉唐兩帶春。大歲秘魔窺石稅,鶴成應(yīng)聽白云中。朝浮到岸鴟巇恨,不向青青聽徑長。

  • 楚田馀絕宇氤氳,細(xì)雨洲頭萬里涼。百葉長看如不盡,水東春夜足殘峰。湖頭風(fēng)浪斜暾鼓,北闕別罹初里村。山在四天三顧客,轆轤爭養(yǎng)抵丹墀。

  • 九日重門攜手時(shí),吟疑須渴辭金香。釣來猶繞結(jié)茶酒,衣上敬亭寧強(qiáng)燒。自明不肯疑恩日,琴館寒霖急暮霜。劃口濡于孤姹末,出謝空卿寄銀機(jī)。蓮龕不足厭絲屨,華騎敷砧出釣磯。

  • 為到席中逢舊木,容華道路不能休。時(shí)閑客后多時(shí)石,暗水天邊暖人說。風(fēng)弄霜花嗥明鏡,犀成磨逐乍牽腸。何勞相聽真行侍,石石班場古政蹄。

  • 聽巾邑外見朱蘭,雜時(shí)臨廂北滿香。門外玉壇花府古,香牌風(fēng)出即升登。陵橋翠黛銷仙妙,曉接紅樓疊影聞。敢把苦謠金字表,應(yīng)從科劍獨(dú)頻行。

  • 昨日榮枯桃李慶,紫騮堅(jiān)黠自何侵。險(xiǎn)知河在皆降月,漢縣煙波白發(fā)來。仍省封身明月閣,不知吹水洽誰非。更擬慚送風(fēng)痕去,只怕鯨雛是后仙。

另外,我抽取其中一些個(gè)人認(rèn)為寫得比較好的詩句(以前跑出來的,不在上圖中):

該模型比較簡單,寫詩的水平不如最前面我介紹的美國研究者demo,但是,所采用的基本方法應(yīng)該是類似的,只是他做的更為復(fù)雜。

另外,這是一個(gè)通用模型,可以學(xué)習(xí)不同的內(nèi)容(古詩、現(xiàn)代詩、宋詞或者英文詩等),就可以生成對應(yīng)的結(jié)果。

七、深度學(xué)習(xí)入門體會

  1. 人工智能和深度學(xué)習(xí)技術(shù)并不神秘,更像是一個(gè)新型的工具,通過喂數(shù)據(jù)給它,然后,它能發(fā)現(xiàn)這些數(shù)據(jù)背后的規(guī)律,并為我們所用。

  2. 數(shù)學(xué)基礎(chǔ)比較重要,這樣有助于理解模型背后的數(shù)學(xué)原理,不過,從純應(yīng)用角度來說,并不一定需要完全掌握數(shù)學(xué),也可以提前開始做一些嘗試和學(xué)習(xí)。

  3. 我深深地感到計(jì)算資源非常缺乏,每次調(diào)整程序的參數(shù)或訓(xùn)練數(shù)據(jù)后,跑完一次訓(xùn)練集經(jīng)常要很多個(gè)小時(shí),部分場景不跑多一些訓(xùn)練集數(shù)據(jù),看不出差別,例如寫詩的案例。個(gè)人感覺,這個(gè)是制約AI發(fā)展的重要問題,它直接讓程序的“調(diào)試”效率非常低下。

  4. 中文文檔比較少,英文文檔也不多,開源社區(qū)一直在快速更新,文檔的內(nèi)容過時(shí)也比較快。因此,入門學(xué)習(xí)時(shí)遇到的問題會比較多,并且缺乏成型的文檔。

八、小結(jié)

我不知道人工智能的時(shí)代是否真的會來臨,也不知道它將要走向何方,但是,毫無疑問,它是一種全新的技術(shù)思維模式。更好的探索和學(xué)習(xí)這種新技術(shù),然后在業(yè)務(wù)應(yīng)用場景尋求結(jié)合點(diǎn),最終達(dá)到幫助我們的業(yè)務(wù)獲得更好的成果,一直以來,就是我們工程師的核心宗旨。

另一方面,對發(fā)展有重大推動作用的新技術(shù),通常會快速的發(fā)展并且走向普及,就如同我們的編程一樣。

因此,人人都可以做深度學(xué)習(xí)應(yīng)用,并非只是一句噱頭。

參考文檔:
http://www./
https://www./

數(shù)學(xué)相關(guān)的內(nèi)容:
高中和大學(xué)數(shù)學(xué)部分內(nèi)容
http:///44r6LAQ
線性代數(shù)視頻:
http://open.163.com/special/opencourse/daishu.html

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多