Java多線程通關——基礎知識挑戰_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

等掌握了基礎知識之後,才有資格說基礎知識沒用這樣的話。否則就老老實實的開始吧。

 

 

對象的監視器

每一個Java對象都有一個監視器。並且規定,每個對象的監視器每次只能被一個線程擁有,只有擁有它的線程把它釋放之後,這個監視器才會被其它線程擁有。

其實就是說,對象的監視器對於多線程來說是互斥的,即一個線程從拿到它之後到釋放它之前這段時間內,其它線程是絕對不可能再拿到它的。這是由JVM保證的。

這樣一來,對象的監視器就可以用來保護那種每次只允許一個線程執行的方法或代碼片段,就是我們常說的同步方法或同步代碼塊。

Java包括兩種範疇的對象(當然,這樣講可能不準確,主要用於幫助理解),一種就是普通的對象,比如new Object()。一種就是描述類型信息的對象,即Class<?>類型的對象。

這兩類都是Java對象,這毋庸置疑,所以它們都有監視器。但這兩類對象又有明顯的不同,所以它們的監視器對外表現的行為也是不同的。

請看下面表達式:

 

Object o1 = new Object();Object o2 = new Object();o1 == o2; //false

 

o1和o2是分別new出來的兩個對象,它們肯定不相同。又因為監視器是和對象關聯的,所以o1的監視器和o2的監視器也是不同的,且它們沒有任何關係。

所以必須是同一個對象的監視器才行,不同對象的監視器達不到預期的效果,這一點要切記。

再看下面的表達式:

 

o1.getClass() == o2.getClass(); //trueo1.getClass() == Object.class; //true

 

但是o1的類型信息對象(o1.getClass())和o2的類型信息對象(o2.getClass())是同一個,且和Object類的類型信息對象(Object.class)也是同一個。這不廢話嘛,o1和o2都是從Object類new出來的。哈哈。

類型信息對象本身的類型是Class<?>,在類加載器(ClassLoader)加載一個類后,就會生成一個和該類相關的Class<?>類型的對象,該對象會被緩存起來,所以類型信息對象是全局(同一個JVM同一個類加載器)唯一的。

這也就說明了,為什麼同一個類new出來的多個對象是不同的,但是它們的類型信息對象卻是同一個,且可以使用“類.class”直接獲取到它。

Java語言規定,使用synchronized關鍵字可以獲取對象的監視器。下面分別來看這兩類對象的監視器用法。

普遍對象的監視器:

 

class SyncA { //方法A public synchronized void methodA() { //同步方法,當前對象的監視器 } //方法B public void methodB() { synchronized(this) { //同步代碼塊,當前對象的監視器 } }}
class SyncB { //對象 private SyncA syncA; public SyncB(SyncA syncA) { this.syncA = syncA; } //方法C public void methodC() { synchronized(syncA) { //同步代碼塊,syncA對象的監視器 } }}
//new一個對象SyncA syncA = new SyncA();//把該對象傳進去SyncB syncB = new SyncB(syncA);//A、B、C這三個方法都要擁有syncA這個對象的監視器才能執行new Thread(syncA::methodA).start();new Thread(syncA::methodB).start();new Thread(syncB::methodC).start();

 

這三個線程都去獲取同一個對象(即syncA)的監視器,因為一個對象的監視器一次只能被一個線程擁有,所以這三個線程是逐次獲取到的,因此這三個方法也是逐次執行的。

這個示例告訴我們,利用對象的監視器可以做到的,並不只是同一個方法不能同時被多個線程執行,多個不同的方法也可以不能同時被多個線程執行,只要它們用到的是同一個對象的監視器。

類型信息對象的監視器:

 

class SyncC { //靜態方法A public static synchronized void methodA() { //同步方法,類型信息對象的監視器 } //靜態方法B public static void methodB() { synchronized(SyncC.class) { //同步代碼塊,類型信息對象的監視器 } }}
class SyncD { //類型信息對象 private Class<SyncC> syncClass; public SyncD(Class<SyncC> syncClass) { this.syncClass = syncClass; } //方法C public void methodC() { synchronized(syncClass) { //同步代碼塊,SyncC類的類型信息對象的監視器 } } //方法D public void methodD() { synchronized(syncClass) { //同步代碼塊,SyncC類的類型信息對象的監視器 } }}
//A、B、C、D這四個方法都要擁有SyncC類的類型信息對象的監視器才能執行new Thread(SyncC::methodA).start();new Thread(SyncC::methodB).start();new Thread(new SyncD(SyncC.class)::methodC).start();new Thread(new SyncD((Class<SyncC>)new SyncC().getClass())::methodD).start();

 

因為一個類的類型信息對象只有一個,所以這四個線程其實是在競爭同一個對象的監視器,因此這四個方法也是逐次執行的。

通過這個示例,再次強調一下,不管是方法還是代碼塊,不管是靜態的還是實例的,也不管是屬於同一個類的還是多個類的,只要它們共用同一個對象的監視器,那麼這些方法或代碼塊在多線程下是無法併發運行的,只能逐個運行,因為同一個對象的監視器每次只能被一個線程所擁有,其它線程此時只能被阻塞着。

注:在實際使用中,一定要確保是同一個對象,尤其是使用字符串類型或数字類型的對象時,一定要注意

幾個重要的方法

首先是Object類的wait/notify/notifyAll方法,因為Java中的所有類最終都繼承自Object類,所以,可以使用任何Java對象來調用這三個方法。

不過Java規定,要在某個對象上調用這三個方法,必須先獲取那個對象的監視器才行。再次提醒,監視器是和對象關聯的,不同的對象監視器也是不同的。

請看下面的用法:

 

//new一個對象Object obj = new Object();//獲取對象的監視器synchronized(obj) { //在對象上調用wait方法 obj.wait();}

 

很多人首次接觸這一部分的時候一般都會比較懵,主要是因為搞不清人物關係。

這裏的wait方法雖然是在對象(即obj)上調用的,但卻不是讓這個對象等待的。而是讓執行這行代碼(即obj.wati())的線程(即Thread)在這個對象(即obj)上等待的。

這裏的線程是等待的“主體”,對象是等待的“位置”。比如學校開運動會時,會在操場上為每班劃定一個位置,並插上一個牌子,寫上班級名稱

這個牌子就相當於對象obj,它表示一個位置信息。當學生看到本班牌子之後,就會自動去牌子後面排隊等待。

學生就相當於線程,當學生看到牌子就相當於當線程執行到obj.wait(),學生去牌子後面排隊等待就相當於線程在對象obj上等待。

線程執行完obj.wait()后,就會釋放掉對象obj的監視器,轉而進入對象obj的等待集合中進行等待,線程由運行狀態變為等待(WAITING)狀態。此後這個線程將不再被線程調度器調度

(說明一點,當多個線程去競爭同一個對象的監視器而沒有競爭上時,線程會變為阻塞(BLOCKED)狀態,而非等待狀態。)

線程選擇等待的原因大多都是因為需要的資源暫時得不到,那什麼時候資源能就位讓線程再次執行呢?其實是不太好確定的,那乾脆就到資源OK時通知它一聲吧。

請看下面的方法:

 

//獲取對象(還是上面那個)的監視器synchronized(obj) { //在對象上調用notify方法 obj.notify();}

 

有了上面的基礎,現在就好理解多了。代碼的意思就是通知在對象obj上等待的線程,把其中一個喚醒。即把這個線程從對象obj的等待集合中移除。此後這個線程就又可以被線程調度器調度了。可能有一部分人覺得現在被喚醒的那個線程就可以執行了,其實不然

當前線程執行完notify方法后,必須要釋放掉對象obj的監視器,這樣被喚醒的那個線程才能重新獲取對象obj的監視器,這樣才可以繼續執行。

就是當一個線程想要通過wait進入等待時,需要獲取對象的監視器。當別的線程通過notify喚醒這個線程時,這個線程想要繼續執行,還需要獲取對象的監視器

notifyAll方法的用法和notify是一樣的,只是含義不同,表示通知對象obj上所有等待的線程,把它們全部都喚醒。雖然是全部喚醒,但也只能有一個線程可以運行,因為每次只有一個線程能獲取到對象obj的監視器。

還有一種wait方法是帶有超時時間的,它表示線程進入等待的時間達到超時時間后還沒有被喚醒時,它會自動醒來(也可以認為是被系統喚醒的)。

這種情況下沒有超時異常拋出,雖然線程是自動醒來,但想要繼續執行的話,同樣需要先獲取對象obj的監視器才行

注:線程通過wait進入等待時,只會釋放和這個wait相關的那個對象的監視器。如果此時線程還擁有其它對象的監視器,並不會去釋放它們,而是在等待期間一直擁有。這塊一定要注意,避免死鎖

使用須知:

處在等待狀態的線程,可能會被意外喚醒,即此時條件並不滿足,但是卻被喚醒了。當然,這種情況在實踐中很少發生。但是我們還是要做一些措施來應對,那就是再次檢測條件是否滿足,不滿足的話再次進入等待

可見這是一個具有重複性的邏輯,因此把它放到一個循環里是最合適的,如下這樣:

 

//獲取對象的監視器synchronized(obj) { //判斷條件是否滿足 while(condition is not satisfied) { //在對象上調用wait方法 obj.wait(); }}

 

這樣一來,即使被意外喚醒,還會再次進入等待。直到條件滿足后,才會退出while循環,執行後面的邏輯

多線程的話題怎麼能少了主角呢,下面有請主角上場,哈哈,就是Thread類啦。關於線程,我在上一篇文章中已經談過,這裏再贅述一遍,希望加深一下印象。

線程是可以獨立運行的“個體”,這就導致我們對它的“控制能力”變弱了。當我們想讓一個線程暫停或停止時,如果強制去執行,會產生兩方面的問題,一是使正在執行的業務中斷,導致業務出現不一致性。二是使正在使用的資源得不到釋放,導致內存泄漏或死鎖。可見,強制這種方式不可取。(看看Thread類的那些廢棄方法便知)

所以,只能採取柔和的方式,就是你對一個線程說,“大哥,停下來歇會吧”,或者是,“大哥,停止吧,不用再執行了”。雖然聽着是噁心了點,但意思就是這樣的。那麼當線程接收到這個“話語”時,它必須要做出反應,自己讓自己停止,當然,線程也可以根據自己的需要,選擇不停止而繼續執行

這才是和線程交互最安全的方式,就像一個高速行駛的汽車,只有自己慢慢停下來才是最好的方式,直接通過外力干預,很大概率是車毀人亡。

這種柔和的處理方式,在計算機里有個專用名詞,叫中斷這是一種交互方式,你對別人發送一个中斷,別人要響應這个中斷並做出相應的處理。如果別人不響應你的這个中斷,那隻能是“熱臉貼冷屁股”,完全沒了面子可見,參与中斷的雙方必須要提前約定好,你怎麼發送,別人怎麼處理,否則只能是雞同鴨講

Thread類和中斷相關的方法有三個:

實例方法,void interrupt(),表示中斷線程,要中斷哪個線程就在哪個線程的對象上調用該方法。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

 

Thread t = new Thread(() -> {doSomething();});t.start();t.interrupt();

 

new一個線程,啟動它,然後中斷它

當一個線程被其它線程中斷後,這個線程必須要能檢測到自己被中斷了才行,於是就有了下面這個方法。

實例方法,boolean isInterrupted(),返回一個線程是否被中斷。常用於一個線程檢測自己是否被中斷。

 

if(Thread.currentThread().isInterrupted()) { doSomething(); return;}

 

如果線程發現自己被中斷,做一些事情,然後退出。該方法只會去讀取線程的中斷狀態,而不會去修改它,所以多次調用返回同樣的結果

線程在處理中斷前,需要將中斷狀態清除一下,即將它設置成false。否則下次檢測時還是true,以為又中斷了呢,實則不是。

靜態方法,static boolean interrupted(),該方法有兩個作用,一是返回線程是否被中斷,二是如果中斷則清除中斷狀態

 

Thread.interrupted();

 

由於這個方法是靜態方法,所以只能用於當前線程,即線程自己清除自己的中斷狀態

由於這個方法會清除中斷狀態,所以,如果第一次調用返回true的話,緊接着再調用一次應該返回false,除非在兩次調用之間線程真的又被中斷了

還有一種特殊情況就是,在你中斷一個線程時,這個線程恰巧沒有在運行,它可能是因為競爭對象的監視器“失敗”(即沒有爭取上)而處於阻塞狀態,可能是因為條件不滿足而處於等待狀態,可能是因為在睡眠中。總之,線程目前沒有在執行代碼

由於線程目前沒有在執行代碼,所以根本就無法去檢測這个中斷狀態,也就是無法響應中斷了,這樣肯定是不行的。所以設計者們此時選擇了拋異常

因此,不管是由於阻塞/等待/睡眠,只要一個線程處於“停止”(即沒有在運行)時,此時去中斷它,線程會被喚醒,接着同樣要去再次獲取監視器,然後就收到了InterruptedException異常了,我們可以捕獲這個異常並處理它,使線程可以繼續正常運行。此時既然已經收到異常了,所以中斷狀態也就同時給清除了,因為中斷異常已經足夠表示中斷了

仔細想想這種設計其實頗具人性化。就好比一個人,在他醒着的時候,跟他說話,他一定會回應你。當他睡着時,跟他說話,其實他是聽不到的,自然無法回應你。此時應該採取稍微暴力一點的手段,比如把他搖晃醒

所以,一個線程正在運行時,去中斷它,是不會拋異常的,只是設置中斷狀態。此時中斷狀態就表示了中斷。一個線程在沒有運行時(阻塞/等待/睡眠),去中斷它,會拋出中斷異常,同時清除中斷狀態。此時中斷異常就表示了中斷

然後就是sleep方法,表示線程臨時停止執行一段時間,這裏只有一個要點,就是在睡眠期間,線程擁有的所有對象的監視器都不會被釋放

 

Thread.sleep(1000);

 

由於sleep是靜態方法,所以,一個線程只能讓自己睡眠,而沒有辦法讓別的線程睡眠,這是完全正確的,符合我們一直在闡述的思想。一個線程的行為應該由自己掌控,別的線程頂多可以給你一个中斷而已,而且你還可以選擇處理它或忽略它

最後一個方法是join,它是一個實例方法,所以需要在一個線程對象上調用它,如下:

 

Thread t = new Thread(() -> {doSomething();});t.start();t.join();

 

表示當前線程執行完t.join()代碼后,就會進入等待,直到線程t死亡后,當前線程才會重新恢復執行。我在上一篇文章中把它比喻為插隊,線程t插到了當前線程的前面,所以必須等線程t執行完后,當前線程才會接着執行

這裏主要是想說下它的源碼實現join方法標有synchronized關鍵字,所以是同步方法,而且在方法體內調用了從Object類繼承來的wait方法

所以join方法可以這樣來解釋,當前線程獲取到線程對象t的監視器,然後執行t.wait(),使當前線程在線程對象t上等待,當前線程從運行狀態進入到等待狀態。由於對象t是一個線程,這是非常特殊的,因為線程執行完是會終止的,且在終止時會自動調用notifyAll方法進行通知

有句話是這樣講的,“鳥之將死,其鳴也哀;人之將死,其言也善”。因此,一個線程都快要死了,是不是應該通知在自己身上等待的其它所有線程,把大夥都喚醒。總不能讓所有人都給自己“陪葬”吧,哈哈。

因此,在線程t執行結束后,會自動執行t.notifyAll()來通知所有在t上等待的線程,並把它們全部喚醒。所以當前線程會繼續接着執行。

為什麼說notifyAll()是自動執行的呢?因為源碼中並沒有去調用它,而實際卻執行了,所以只能是系統自動調用了

所以,從宏觀上看,就是當前線程在等待線程t的死亡

任何Java對象都有監視器,所以線程對象也有監視器,但線程對象確實比較特殊,所以它的wait/notify方法也會有特殊的地方,因此官方建議我們不要隨意去玩Thread類的這些方法

 

完整示例源碼:

https://github.com/coding-new-talking/java-code-demo.git

 

如果以上內容閣下全部都知道,而且理解到位,那已經很厲害了,請等待下篇多線的文章吧。

 

 

(END)

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號的二維碼,歡迎關注!

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

國際原子能機構核安全監管高官會在線召開_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

2020-09-25 來源:生態環境部

2020-09-25
來源:生態環境部 分享到:
[打印]
字號:[大] [中] [小]

  9月24日,國際原子能機構核安全監管高官會召開,深入討論疫情給全球核安全帶來的挑戰。生態環境部副部長、國家核安全局局長劉華出席會議併發言。

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

  中方分享了疫情期間核安全監管和輻射安全監管方面的措施。中國努力確保核電廠安全運行,國家核安全局對核電廠持續實行駐廠現場監督,保障了重要的現場監督活動;通過優化放射源審批及監管措施,確保疫情期間核技術的安全應用。最後,中方就核應急準備與響應議題開展交流,與各方分享了核電廠場內核應急工作的監管經驗。

  本次核安全監管高官會以視頻形式召開,來自國際原子能機構、各國核安全監管部門150多名高級別官員出席會議。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

還買什麼捷達!7萬起的國產車實力又強顏值又高_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

但是卻依然不能影響銳3在我國的研發。作為一輛緊湊型車型,5。68萬的起售價是它最大的亮點,性價比相當的高,2620mm的軸距能夠滿足多數家庭用戶的需求。而且全系標配的电子轉向助力也讓它在轉向手感上非常精準以及輕便。

前言

對於汽車市場來說,消費者最為了解的都是那些大品牌的產品,因為它們有着豐厚的資源去進行線下的推廣,而一些比較小的品牌就只能“看運氣了”,酒香也怕巷子深。所以一些小的品牌或者是推廣就比較差的即使產品實力較強也無法獲得我們的注意,而今天筆者要介紹的就是這些競爭實力不低但卻比較少人知道的車型。

傳祺G3S是傳祺較早時期所製造的緊湊型車型,在推廣上是比較弱的。但是這輛緊湊型車型卻是有着較高的實力,是吸收消化阿爾法羅密歐166車型平台後的產物,在底盤方面表現出色,而且在設計上表現出意大利那種獨特的設計風格,內飾即使在現在來看都是非常前衛。發動機方面有着1.6L自然吸氣發動機以及1.3T渦輪增壓發動機可供選擇,

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

變速箱方面是5MT手動變速箱或者是4AT自動變速箱,和對手相比優勢不大。建議選擇的是1.3T的手動豪華ESp版。

東風裕隆-銳3

裕隆是近幾年進入我國的台灣品牌,所以知名度並不高,而且在網點上也是比較少。但是卻依然不能影響銳3在我國的研發。作為一輛緊湊型車型,5.68萬的起售價是它最大的亮點,性價比相當的高,2620mm的軸距能夠滿足多數家庭用戶的需求。而且全系標配的电子轉向助力也讓它在轉向手感上非常精準以及輕便。1.6L自然吸氣發動機配搭5MT手動變速箱或者是CVT變速箱的組合,在這個級別是處於主流水平。

榮威可謂是不溫不火的品牌,背靠着通用這座大山讓它有着不俗的開發能力,但卻因為網點的數量相對較少導致了車型銷售一直不高。但榮威360在技術上確實是處於中國品牌的前列,雖然1.5L自然吸氣發動機表現一般,甚至在自動擋上還是4AT變速箱的“過時玩意”,但作為高配的1.4T渦輪增壓配搭7速雙離合變速箱的組合卻是“誠意滿滿”,技術上在同價位緊湊型車上可謂是領先水平,這套動力系統還在科魯茲上使用,所以在無論是質量還是性能上都是值得信賴的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

Apple 新專利把 MacBook 變成手機與手錶的無線充電板_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

對現代人來說,手機沒電這件事情恐怕比筆電沒電還讓人焦慮, 近日國外媒體發現,Apple 剛獲得兩項美國的新專利,把 MacBook 變成能夠時時為行動裝置充電的無線充電板,也就是將反向充電技術移植到筆電上面,不過專利就只是專利,實用性跟技術上的問題當然還是需要多多思考。

Apple 新專利把 MacBook 變成手機與手錶的無線充電板

iPhone 12 系列相較於過去得機型多了便於用再 MagSafe 充電的磁鐵。從 Apple 的專利圖中可以看到,在 MacBook 觸控板以及旁邊左右兩側的手托區域裝置了無線充電線圈,用於在筆電關閉時對其它行動裝置,像是 iPhone、iPad 或是 Apple Watch 充電,而在其中也會加設磁鐵,以便正確地與其他裝置的無線充電線圈對齊。

除了上面提到的方式外,在闔上 MacBook 蓋子時你還可以把 iPad、iPhone 與 Apple Watch 放在蓋子上進行充電。支援反向充電也意味著設備可以用來接收與傳輸電力,就像 Google Pixel 5 一樣,而 Apple 也解釋道,內建的反向無線充電可以解決外出時必須為各種裝置攜帶多個充電器,或是所需用於充電的插座不足的問題,在 COVID-19 疫情結束人們恢復旅行後應可大有助益。

但正如 The Verge 所述,Apple 現行的 MacBook 皆採用鋁合金外殼,自然是無法適用於無線充電所需要的條件(只有玻璃與塑膠可用),以 Google Pixel 5 為例,該手機在鋁合金背蓋上為無線充電線圈挖了一個洞並以生物樹脂或塑膠填補來解決這個問題。另外還要思考的是發熱的問題,一般來說無論是有線或無線充電難免都會有升溫的情況,如何抑制溫度以確保供電與接收電力裝置兩方的運作,都是需要想辦法來克服的。我忽然想到胎死腹中的 AirPower。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

◎資料來源:The Verge、9to5Google

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

用TensorFlow搭建一個萬能的神經網絡框架(持續更新),tensorflow常用函數_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

博客作者:凌逆戰

博客地址:https://www.cnblogs.com/LXP-Never/p/12774058.html

文章代碼:https://github.com/LXP-Never/blog_data/tree/master/tensorflow_model

  我一直覺得TensorFlow的深度神經網絡代碼非常困難且繁瑣,對TensorFlow搭建模型也十分困惑,所以我近期閱讀了大量的神經網絡代碼,終於找到了搭建神經網絡的規律,各位要是覺得我的文章對你有幫助不妨點個,點個關注吧。

  我個人把深度學習分為以下步驟:數據處理 –> 模型搭建 –> 構建損失 –> 模型訓練 –> 模型評估

我先把代碼放出來,然後一點一點來講

# Author:凌逆戰
# -*- encoding:utf-8 -*-
# 修改時間:2020年5月31日
import time
from tensorflow.examples.tutorials.mnist import input_data
from nets.my_alex import alexNet
from ops import *

tf.flags.DEFINE_integer('batch_size', 50, 'batch size, default: 1')
tf.flags.DEFINE_integer('class_num', 10, 'batch size, default: 1')
tf.flags.DEFINE_integer('epochs', 10, 'batch size, default: 1')
tf.flags.DEFINE_float('learning_rate', 1e-4, '初始學習率, 默認: 0.0002')
tf.flags.DEFINE_string('checkpoints_dir', "checkpoints", '保存檢查點的地址')
FLAGS = tf.flags.FLAGS

# 從MNIST_data/中讀取MNIST數據。當數據不存在時,會自動執行下載
mnist = input_data.read_data_sets('./data', one_hot=True, reshape=False)
# reshape=False  (None, 28,28,1)    # 用於第一層是卷積層
# reshape=False  (None, 784)        # 用於第一層是全連接層

# 我們看一下數據的shape
print(mnist.train.images.shape)  # 訓練數據圖片(55000, 28, 28, 1)
print(mnist.train.labels.shape)  # 訓練數據標籤(55000, 10)
print(mnist.test.images.shape)  # 測試數據圖片(10000, 28, 28, 1)
print(mnist.test.labels.shape)  # 測試數據圖片(10000, 10)
print(mnist.validation.images.shape)  # 驗證數據圖片(5000, 28, 28, 1)
print(mnist.validation.labels.shape)  # 驗證數據圖片(5000, 784)


def train():
    batch_size = FLAGS.batch_size  # 一個batch訓練多少個樣本
    batch_nums = mnist.train.images.shape[0] // batch_size  # 一個epoch中應該包含多少batch數據
    class_num = FLAGS.class_num  # 分類類別數
    epochs = FLAGS.epochs  # 訓練周期數
    learning_rate = FLAGS.learning_rate  # 初始學習率

    ############    保存檢查點的地址   ############
    checkpoints_dir = FLAGS.checkpoints_dir  # checkpoints
    # 如果檢查點不存在,則創建
    if not os.path.exists(checkpoints_dir):
        os.makedirs(FLAGS.checkpoints_dir)

    ######################################################
    #                    創建圖                          #
    ######################################################
    graph = tf.Graph()  # 自定義圖
    # 在自己的圖中定義數據和操作
    with graph.as_default():
        inputs = tf.placeholder(dtype="float", shape=[None, 28, 28, 1], name='inputs')
        labels = tf.placeholder(dtype="float", shape=[None, class_num], name='labels')
        # 看個人喜歡,有的人在初始化定義中就定義了learning_rate,有的人喜歡通過feed傳learning_rate
        learning_rate = tf.placeholder("float", None, name='learning_rate')
        # 如果網絡結構有dropout層,需要定義keep_probn,如果沒有則不需要
        # 訓練的時候需要,測試的時候需要設置成1
        keep_prob = tf.placeholder(dtype="float", name='keep_prob')
        ############    搭建模型   ############
        logits = alexNet(inputs, class_num, keep_prob=keep_prob)  # 使用placeholder搭建模型
        ############    損失函數   ############
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits))
        tf.add_to_collection('losses', loss)
        total_loss = tf.add_n(tf.get_collection("losses"))  # total_loss=模型損失+權重正則化損失
        ############    模型精度   ############
        predict = tf.argmax(logits, 1)  # 模型預測結果
        accuracy = tf.reduce_mean(tf.cast(tf.equal(predict, tf.argmax(labels, 1)), tf.float32))
        ############    優化器   ############
        variable_to_train = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)  # 可訓練變量列表
        # 創建優化器,更新網絡參數,最小化loss,
        global_step = tf.Variable(0, trainable=False)
        learning_rate = tf.train.exponential_decay(learning_rate=learning_rate,  # 初始學習率
                                                   global_step=global_step,
                                                   decay_steps=batch_nums,  # 多少步衰減一次
                                                   decay_rate=0.1,  # 衰減率
                                                   staircase=True)  # 以階梯的形式衰減
        # 移動平均值更新參數
        # train_op = moving_average(loss, learning_rate, global_step)
        # adam優化器,adam算法好像會自動衰減學習率,
        train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss=total_loss,
                                                                  global_step=global_step,
                                                                  var_list=variable_to_train)
        ############    TensorBoard可視化 summary  ############
        summary_writer = tf.summary.FileWriter(logdir="./logs", graph=graph)  # 創建事件文件
        tf.summary.scalar(name="losses", tensor=total_loss)  # 收集損失值變量
        tf.summary.scalar(name="acc", tensor=accuracy)  # 收集精度值變量
        tf.summary.scalar(name='learning_rate', tensor=learning_rate)
        merged_summary_op = tf.summary.merge_all()  # 將所有的summary合併為一個op
        ############    模型保存和恢復 Saver   ############
        saver = tf.train.Saver(max_to_keep=5)

    ######################################################
    #                   創建會話                          #
    ######################################################
    max_acc = 0.
    config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)
    with tf.Session(config=config, graph=graph) as sess:
        # 加載模型,如果模型存在返回 是否加載成功和訓練步數
        could_load, checkpoint_step = load_model(sess, saver, FLAGS.checkpoints_dir)
        if could_load:
            print(" [*] 模型加載成功")
        else:
            print(" [!] 模型加載失敗")
            try:
                tf.global_variables_initializer().run()
            except:
                tf.initialize_all_variables().run()

        for epoch in range(epochs):
            for i in range(batch_nums):
                start_time = time.time()
                # batch_images = data_X[i * batch_size:(i + 1) * batch_size]
                # batch_labels = data_y[i * batch_size:(i + 1) * batch_size]
                train_batch_x, train_batch_y = mnist.train.next_batch(batch_size)

                # 使用真實數據填充placeholder,運行訓練模型和合併變量操作
                _, summary, loss, step = sess.run([train_op, merged_summary_op, total_loss, global_step],
                                                  feed_dict={inputs: train_batch_x,
                                                             labels: train_batch_y,
                                                             keep_prob: 0.5})
                if step % 100 == 0:
                    summary_writer.add_summary(summary, step)  # 將每次迭代后的變量寫入事件文件
                    summary_writer.flush()  # 強制summary_writer將緩存中的數據寫入到日誌文件中(可選)

                    ############    可視化打印   ############
                    print("Epoch:[%2d] [%4d/%4d] time:%4.4f,loss:%.8f" % (
                        epoch, i, batch_nums, time.time() - start_time, loss))

                # 打印一些可視化的數據,損失...
                if step % 100 == 0:
                    acc = sess.run(accuracy, feed_dict={inputs: mnist.validation.images,
                                                        labels: mnist.validation.labels,
                                                        keep_prob: 1.0})
                    print("Epoch:[%2d] [%4d/%4d] accuracy:%.8f" % (epoch, i, batch_nums, acc))
                    ############    保存模型   ############
                    if acc > max_acc:
                        max_acc = acc
                        save_path = saver.save(sess,
                                               save_path=os.path.join(checkpoints_dir, "model.ckpt"),
                                               global_step=step)
                        tf.logging.info("模型保存在: %s" % save_path)
        print("優化完成!")


def main(argv=None):
    train()


if __name__ == '__main__':
    # logging.basicConfig(level=logging.INFO)
    tf.logging.set_verbosity(tf.logging.INFO)
    tf.app.run()

main(global_step)

# Author:凌逆戰
# -*- encoding:utf-8 -*-
# 修改時間:2020年5月31日
import time
from tensorflow.examples.tutorials.mnist import input_data
from nets.my_vgg import VGG16Net
from ops import *

tf.flags.DEFINE_integer('batch_size', 100, 'batch size, default: 1')
tf.flags.DEFINE_integer('class_num', 10, 'batch size, default: 1')
tf.flags.DEFINE_integer('epochs', 10, 'batch size, default: 1')
tf.flags.DEFINE_float('learning_rate', 2e-4, '初始學習率, 默認: 0.0001')
tf.flags.DEFINE_string('checkpoints_dir', "checkpoint", '保存檢查點的地址')
FLAGS = tf.flags.FLAGS

# 從MNIST_data/中讀取MNIST數據。當數據不存在時,會自動執行下載
mnist = input_data.read_data_sets('./MNIST_data', one_hot=True, reshape=False)
# reshape=False  (None, 28,28,1)    # 用於第一層是卷積層
# reshape=False  (None, 784)        # 用於第一層是全連接層

# 我們看一下數據的shape
print(mnist.train.images.shape)  # 訓練數據圖片(55000, 28, 28, 1)
print(mnist.train.labels.shape)  # 訓練數據標籤(55000, 10)
print(mnist.test.images.shape)  # 測試數據圖片(10000, 28, 28, 1)
print(mnist.test.labels.shape)  # 測試數據圖片(10000, 10)
print(mnist.validation.images.shape)  # 驗證數據圖片(5000, 28, 28, 1)
print(mnist.validation.labels.shape)  # 驗證數據圖片(5000, 784)


def train():
    batch_size = FLAGS.batch_size
    batch_nums = mnist.train.images.shape[0] // batch_size  # 一個epoch中應該包含多少batch數據
    class_num = FLAGS.class_num
    epochs = FLAGS.epochs
    learning_rate = FLAGS.learning_rate

    ############    保存檢查點的地址   ############
    checkpoints_dir = FLAGS.checkpoints_dir  # checkpoints
    # 如果檢查點不存在,則創建
    if not os.path.exists(checkpoints_dir):
        os.makedirs(FLAGS.checkpoints_dir)

    ######################################################
    #                    創建圖                          #
    ######################################################
    graph = tf.Graph()  # 自定義圖
    # 在自己的圖中定義數據和操作
    with graph.as_default():
        inputs = tf.placeholder(dtype="float", shape=[None, 28, 28, 1], name='inputs')
        labels = tf.placeholder(dtype="float", shape=[None, class_num], name='labels')
        ############    搭建模型   ############
        logits = VGG16Net(inputs, class_num)  # 使用placeholder搭建模型
        ############    損失函數   ############
        # 計算預測值和真實值之間的誤差
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits))
        tf.add_to_collection('losses', loss)
        total_loss = tf.add_n(tf.get_collection("losses"))  # total_loss=模型損失+權重正則化損失
        ############    模型精度   ############
        predict = tf.argmax(logits, axis=1)
        accuracy = tf.reduce_mean(tf.cast(tf.equal(predict, tf.argmax(labels, axis=1)), tf.float32))
        ############    優化器   ############
        variable_to_train = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)  # 可訓練變量列表
        # 創建優化器,更新網絡參數,最小化loss,
        train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss=total_loss,
                                                                  var_list=variable_to_train)
        ############    TensorBoard可視化 summary  ############
        summary_writer = tf.summary.FileWriter("./logs", graph=graph)  # 創建事件文件
        tf.summary.scalar(name="loss", tensor=total_loss)  # 收集損失值變量
        tf.summary.scalar(name='accuracy', tensor=accuracy)  # 收集精度值變量
        tf.summary.scalar(name='learning_rate', tensor=learning_rate)
        merged_summary_op = tf.summary.merge_all()  # 將所有的summary合併為一個op
        ############    模型保存和恢復 Saver   ############
        saver = tf.train.Saver(max_to_keep=5)

    ######################################################
    #                   創建會話                          #
    ######################################################
    max_acc = 0.
    config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)
    with tf.Session(config=config, graph=graph) as sess:
        # 加載模型,如果模型存在返回 是否加載成功和訓練步數
        could_load, checkpoint_step = load_model(sess, saver, FLAGS.checkpoints_dir)
        if could_load:
            step = checkpoint_step
            print(" [*] 模型加載成功")
        else:
            print(" [!] 模型加載失敗")
            try:
                tf.global_variables_initializer().run()
            except:
                tf.initialize_all_variables().run()
            step = 0

        for epoch in range(epochs):
            for i in range(batch_nums):
                start_time = time.time()    # 記錄一下開始訓練的時間
                # batch_images = data_X[i * batch_size:(i + 1) * batch_size]
                # batch_labels = data_y[i * batch_size:(i + 1) * batch_size]
                train_batch_x, train_batch_y = mnist.train.next_batch(batch_size)

                # 使用真實數據填充placeholder,運行訓練模型和合併變量操作
                _, summary, loss = sess.run([train_op, merged_summary_op, total_loss],
                                            feed_dict={inputs: train_batch_x,
                                                       labels: train_batch_y})
                if step % 100 == 0:
                    summary_writer.add_summary(summary, step)  # 將每次迭代后的變量寫入事件文件
                    summary_writer.flush()  # 強制summary_writer將緩存中的數據寫入到日誌文件中(可選)

                ############    可視化打印   ############
                print("Epoch:[%2d] [%4d/%4d] time:%4.4f,loss:%.8f" % (
                    epoch, i, batch_nums, time.time() - start_time, loss))

                # 打印一些可視化的數據,損失...
                # if np.mod(step, 100) == 1
                if step % 100 == 0:
                    acc = sess.run(accuracy, {inputs: mnist.validation.images,
                                              labels: mnist.validation.labels})
                    print("Epoch:[%2d] [%4d/%4d],acc:%.8f" % (epoch, i, batch_nums, acc))
                    ############    保存模型   ############
                    if acc > max_acc:
                        max_acc = acc
                        save_path = saver.save(sess,
                                               save_path=os.path.join(checkpoints_dir, "model.ckpt"),
                                               global_step=step)
                        # logging.info("模型保存在: %s" % save_path)
                        tf.logging.info("模型保存在: %s" % save_path)
                step += 1
            print("優化完成!")


def main(argv=None):
    train()


if __name__ == '__main__':
    # logging.basicConfig(level=logging.INFO)
    tf.logging.set_verbosity(tf.logging.INFO)
    tf.app.run()

main(step)

數據處理

  數據處理因為每個專業領域的原因各不相同,而這不同點也是各位論文創新點的新方向。不同的我沒法講,但我總結了幾點相同的地方——batch數據生成。因為深度學習模型需要一個batch一個batch的喂數據進行訓練,所以我們的數據必須是batch的形式,這裏衍生了三點問題

  1. 通過代碼批量讀取數據,
  2. 如何生成batch數據:由於篇幅過長,實在有很多地方要介紹和詳述,我把這一塊內容移到了這篇文章《TensorFlow讀取數據的三種方法》中
  3. 數據的shape:我舉兩個例子讓大家理解:圖片數據為4維 (batch_size, height,width, channels),序列數據為3維 (batch_size, time_steps, input_size),
    • 不同的shape處理方法不同,選擇神經網絡模型單元也不同。我會在後面細講

模型搭建

  閱讀這一節我默認大家已經學會了數據的batch讀取了。

  模型搭建這一步很像我們小時候玩的搭積木,我這裏以經典神經網絡模型VGG、Alex、ResNet、Google Inception Net為例講解,大家看代碼看多了也會很簡單的就找到,當然我是有一點私心的,我想把這些經典的網絡在這篇文章做一個tensorflow實現匯總,我細講第一個,大家可能看一個例子就懂了,看懂了就直接往下看,看不懂就多看幾個。

LeNet5模型

論文:1998_LeNet_Gradient-Based Learning Applied to Document Recognition

  下面我們定義一個LeNet5模型,我們先定義需要用到的神經網絡單元,相同的代碼盡量封裝成函數的形式以節省代碼量和簡潔代碼

def conv(input, kernel_size, output_size, stride, init_bias=0.0, padding="SAME", name=None, wd=None):
    input_size = input.shape[-1]
    conv_weights = tf.get_variable(name='weights',
                                   shape=[kernel_size, kernel_size, input_size, output_size],
                                   initializer=tf.truncated_normal_initializer(stddev=0.1),
                                   dtype=tf.float32)
    conv_biases = tf.get_variable(name='biases',
                                  shape=[output_size],
                                  initializer=tf.constant_initializer(init_bias),
                                  dtype=tf.float32)

    if wd is not None:
        # wd 0.004
        # tf.nn.l2_loss(var)=sum(t**2)/2
        weight_decay = tf.multiply(tf.nn.l2_loss(conv_weights), wd, name='weight_loss')
        tf.add_to_collection('losses', weight_decay)

    conv_layer = tf.nn.conv2d(input, conv_weights, [1, stride, stride, 1], padding=padding, name=name)  # 卷積操作
    conv_layer = tf.nn.bias_add(conv_layer, conv_biases)  # 加上偏置項
    conv_layer = tf.nn.relu(conv_layer)  # relu激活函數

    return conv_layer


def fc(input, output_size, init_bias=0.0, activeation_func=True, wd=None):
    input_shape = input.get_shape().as_list()
    # 創建 全連接權重 變量
    fc_weights = tf.get_variable(name="weights",
                                 shape=[input_shape[-1], output_size],
                                 initializer=tf.truncated_normal_initializer(stddev=0.1),
                                 dtype=tf.float32)
    if wd is not None:
        # wd 0.004
        # tf.nn.l2_loss(var)=sum(t**2)/2
        weight_decay = tf.multiply(tf.nn.l2_loss(fc_weights), wd, name='weight_loss')
        tf.add_to_collection('losses', weight_decay)
    # 創建 全連接偏置 變量
    fc_biases = tf.get_variable(name="biases",
                                shape=[output_size],
                                initializer=tf.constant_initializer(init_bias),
                                dtype=tf.float32)

    fc_layer = tf.matmul(input, fc_weights)  # 全連接計算
    fc_layer = tf.nn.bias_add(fc_layer, fc_biases)  # 加上偏置項
    if activeation_func:
        fc_layer = tf.nn.relu(fc_layer)  # rele激活函數
    return fc_layer

View Code

  然後利用我們搭建的神經網絡單元,搭建LeNet5神經網絡模型

# 訓練時:keep_prob=0.5
# 測試時:keep_prob=1.0
def leNet(inputs, class_num, keep_prob=0.5):
    # 第一層 卷積層 conv1
    with tf.variable_scope('layer1-conv1'):
        conv1 = conv(input=inputs, kernel_size=5, output_size=32, stride=1, init_bias=0.0, name="layer1-conv1",
                     padding="SAME")
    # 第二層 池化層
    with tf.name_scope('layer2-pool1'):
        pool1 = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
    # 第三層 卷積層 conv2
    with tf.variable_scope('layer3-conv2'):
        conv2 = conv(input=pool1, kernel_size=5, output_size=64, stride=1, init_bias=0.0, name="layer3-conv2",
                     padding="SAME")
    # 第四層 池化層
    with tf.name_scope('layer4-pool2'):
        pool2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

    # 後面要做全連接,因此要把數據變成2維
    # pool_shape = pool2.get_shape().as_list()
    pool_shape = pool2.shape
    flatten = tf.reshape(pool2, [-1, pool_shape[1] * pool_shape[2] * pool_shape[3]])
    with tf.variable_scope('layer5-fcl'):
        fc1 = fc(input=flatten, output_size=512, init_bias=0.1, activeation_func=tf.nn.relu, wd=None)
        fc1 = tf.nn.dropout(fc1, keep_prob=keep_prob, name="dropout1")
    with tf.variable_scope('layer6-fc2'):
        logit = fc(input=fc1, output_size=class_num, init_bias=0.1, activeation_func=False, wd=None)
    return logit

Alex模型

論文:2012_Alex_ImageNet Classification with Deep Convolutional Neural Networks

  下面我們定義一個Alex模型,我們先定義需要用到的神經網絡單元,相同的代碼盡量封裝成函數的形式以節省代碼量和簡潔代碼

def conv(input, kernel_size, output_size, stride, init_bias=0.0, padding="SAME", name=None, wd=None):
    input_size = input.shape[-1]
    conv_weights = tf.get_variable(name='weights',
                                   shape=[kernel_size, kernel_size, input_size, output_size],
                                   initializer=tf.random_normal_initializer(mean=0, stddev=0.01),
                                   dtype=tf.float32)
    if wd is not None:
        # wd 0.004
        # tf.nn.l2_loss(var)=sum(t**2)/2
        weight_decay = tf.multiply(tf.nn.l2_loss(conv_weights), wd, name='weight_loss')
        tf.add_to_collection('losses', weight_decay)

    conv_biases = tf.get_variable(name='biases',
                                  shape=[output_size],
                                  initializer=tf.constant_initializer(init_bias),
                                  dtype=tf.float32)
    conv_layer = tf.nn.conv2d(input, conv_weights, [1, stride, stride, 1], padding=padding, name=name)  # 卷積操作
    conv_layer = tf.nn.bias_add(conv_layer, conv_biases)  # 加上偏置項
    conv_layer = tf.nn.relu(conv_layer)  # relu激活函數

    return conv_layer

conv函數

def fc(input, output_size, init_bias=0.0, activeation_func=True, wd=None):
    input_shape = input.get_shape().as_list()
    # 創建 全連接權重 變量
    fc_weights = tf.get_variable(name="weights",
                                 shape=[input_shape[-1], output_size],
                                 initializer=tf.random_normal_initializer(mean=0.0, stddev=0.01),
                                 dtype=tf.float32)
    if wd is not None:
        # wd 0.004
        # tf.nn.l2_loss(var)=sum(t**2)/2
        weight_decay = tf.multiply(tf.nn.l2_loss(fc_weights), wd, name='weight_loss')
        tf.add_to_collection('losses', weight_decay)

    # 創建 全連接偏置 變量
    fc_biases = tf.get_variable(name="biases",
                                shape=[output_size],
                                initializer=tf.constant_initializer(init_bias),
                                dtype=tf.float32)

    fc_layer = tf.matmul(input, fc_weights)  # 全連接計算
    fc_layer = tf.nn.bias_add(fc_layer, fc_biases)  # 加上偏置項
    if activeation_func:
        fc_layer = tf.nn.relu(fc_layer)  # rele激活函數
    return fc_layer

fc函數

def LRN(input, depth_radius=2, alpha=0.0001, beta=0.75, bias=1.0):
    """Local Response Normalization 局部響應歸一化"""
    return tf.nn.local_response_normalization(input, depth_radius=depth_radius, alpha=alpha,
                                              beta=beta, bias=bias)

LRN函數

  然後利用我們搭建的神經網絡單元,搭建Alex神經網絡模型

def alexNet(inputs, class_num, keep_prob=0.5):
    # 第一層卷積層 conv1
    with tf.variable_scope("conv1"):
        conv1 = conv(input=inputs, kernel_size=7, output_size=96, stride=3, init_bias=0.0, name="conv1", padding="SAME")
        conv1 = LRN(conv1)
        conv1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name="pool1")
    # 第二層卷積層 conv2
    with tf.variable_scope("conv2"):
        conv2 = conv(input=conv1, kernel_size=7, output_size=96, stride=3, init_bias=1.0, name="conv2", padding="SAME")
        conv2 = LRN(conv2)
        conv2 = tf.nn.max_pool(conv2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name="pool2")
    # 第三層卷積層 conv3
    with tf.variable_scope("conv3"):
        conv3 = conv(input=conv2, kernel_size=7, output_size=96, stride=3, init_bias=0.0, name="conv3", padding="SAME")
    # 第四層卷積層 conv4
    with tf.variable_scope("conv4"):
        conv4 = conv(input=conv3, kernel_size=7, output_size=96, stride=3, init_bias=1.0, name="conv4", padding="SAME")
    # 第五層卷積層 conv5
    with tf.variable_scope("conv5"):
        conv5 = conv(input=conv4, kernel_size=3, output_size=256, stride=1, init_bias=1.0, name="conv5")
        conv5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name="pool5")
    conv5_shape = conv5.shape  # 後面做全連接,所以要把shape改成2維
    # shape=[batch, dim]
    flatten = tf.reshape(conv5, [-1, conv5_shape[1] * conv5_shape[2] * conv5_shape[3]])
    # 第一層全連接層 fc1
    with tf.variable_scope("fc1"):
        fc1 = fc(input=flatten, output_size=4096, init_bias=1.0, activeation_func=tf.nn.relu, wd=None)
        fc1 = tf.nn.dropout(fc1, keep_prob=keep_prob, name="dropout1")
    # 第一層全連接層 fc2
    with tf.variable_scope("fc2"):
        fc2 = fc(input=fc1, output_size=4096, init_bias=1.0, activeation_func=tf.nn.relu, wd=None)
        fc2 = tf.nn.dropout(fc2, keep_prob=keep_prob, name="dropout1")
    # 第一層全連接層 fc3
    with tf.variable_scope("fc3"):
        logit = fc(input=fc2, output_size=class_num, init_bias=1.0, activeation_func=False, wd=None)

    return logit  # 模型輸出

VGG模型

論文:2014_VGG_Very Deep Convolutional Networks for Large-Scale Image Recognition

VGG有兩個比較有名的網絡:VGG16、VGG19,我在這裏搭建VGG16,有興趣的朋友可以按照上面的模型結構自己用TensorFlow搭建VGG19模型

  下面我們定義一個VGG16模型,和前面一樣,我們先定義需要用到的神經網絡單元,相同的代碼盡量封裝成函數的形式以節省代碼量和簡潔代碼

  因為模型中同一個變量域中包含多個卷積操作,因此在卷積函數中套一層變量域

def conv(inputs, scope_name, kernel_size, output_size, stride, init_bias=0.0, padding="SAME", wd=None):
    input_size = int(inputs.get_shape()[-1])
    with tf.variable_scope(scope_name):
        conv_weights = tf.get_variable(name='weights',
                                       shape=[kernel_size, kernel_size, input_size, output_size],
                                       dtype=tf.float32,
                                       initializer=tf.truncated_normal_initializer(mean=0.0, stddev=1e-1))
        if wd is not None:
            # tf.nn.l2_loss(var)=sum(t**2)/2
            weight_decay = tf.multiply(tf.nn.l2_loss(conv_weights), wd, name='weight_loss')
            tf.add_to_collection('losses', weight_decay)

        conv_biases = tf.get_variable(name='biases',
                                      shape=[output_size],
                                      dtype=tf.float32,
                                      initializer=tf.constant_initializer(init_bias))
        conv_layer = tf.nn.conv2d(inputs, conv_weights, [1, stride, stride, 1], padding=padding, name=scope_name)
        conv_layer = tf.nn.bias_add(conv_layer, conv_biases)
        conv_layer = tf.nn.relu(conv_layer)
    return conv_layer

conv函數

def fc(inputs, scope_name, output_size, init_bias=0.0, activeation_func=True, wd=None):
    input_shape = inputs.get_shape().as_list()
    with tf.variable_scope(scope_name):
        # 創建 全連接權重 變量
        fc_weights = tf.get_variable(name="weights",
                                     shape=[input_shape[-1], output_size],
                                     dtype=tf.float32,
                                     initializer=tf.truncated_normal_initializer(mean=0.0, stddev=1e-1))
        if wd is not None:
            # wd 0.004
            # tf.nn.l2_loss(var)=sum(t**2)/2
            weight_decay = tf.multiply(tf.nn.l2_loss(fc_weights), wd, name='weight_loss')
            tf.add_to_collection('losses', weight_decay)
        
        # 創建 全連接偏置 變量
        fc_biases = tf.get_variable(name="biases",
                                    shape=[output_size],
                                    dtype=tf.float32,
                                    initializer=tf.constant_initializer(init_bias),
                                    trainable=True)

        fc_layer = tf.matmul(inputs, fc_weights)  # 全連接計算
        fc_layer = tf.nn.bias_add(fc_layer, fc_biases)  # 加上偏置項
        if activeation_func:
            fc_layer = tf.nn.relu(fc_layer)  # rele激活函數
    return fc_layer

fc函數

  然後利用我們搭建的神經網絡單元,搭建VGG16神經網絡模型

def VGG16Net(inputs, class_num):
    with tf.variable_scope("conv1"):
        # conv1_1 [conv3_64]
        conv1_1 = conv(inputs=inputs, scope_name="conv1_1", kernel_size=3, output_size=64, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv1_2 [conv3_64]
        conv1_2 = conv(inputs=conv1_1, scope_name="conv1_2", kernel_size=3, output_size=64, stride=1,
                       init_bias=0.0, padding="SAME")
    pool1 = tf.nn.max_pool(conv1_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool1')
    with tf.variable_scope("conv2"):
        # conv2_1
        conv2_1 = conv(inputs=pool1, scope_name="conv2_1", kernel_size=3, output_size=128, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv2_2
        conv2_2 = conv(inputs=conv2_1, scope_name="conv2_2", kernel_size=3, output_size=128, stride=1,
                       init_bias=0.0, padding="SAME")
    pool2 = tf.nn.max_pool(conv2_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool2')
    with tf.variable_scope("conv3"):
        # conv3_1
        conv3_1 = conv(inputs=pool2, scope_name="conv3_1", kernel_size=3, output_size=256, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv3_2
        conv3_2 = conv(inputs=conv3_1, scope_name="conv3_2", kernel_size=3, output_size=256, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv3_3
        conv3_3 = conv(inputs=conv3_2, scope_name="conv3_3", kernel_size=3, output_size=256, stride=1,
                       init_bias=0.0, padding="SAME")
    pool3 = tf.nn.max_pool(conv3_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool3')
    with tf.variable_scope("conv4"):
        # conv4_1
        conv4_1 = conv(inputs=pool3, scope_name="conv4_1", kernel_size=3, output_size=512, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv4_2
        conv4_2 = conv(inputs=conv4_1, scope_name="conv4_2", kernel_size=3, output_size=512, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv4_3
        conv4_3 = conv(inputs=conv4_2, scope_name="conv4_3", kernel_size=3, output_size=512, stride=1,
                       init_bias=0.0, padding="SAME")
    pool4 = tf.nn.max_pool(conv4_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool4')
    with tf.variable_scope("conv5"):
        # conv5_1
        conv5_1 = conv(inputs=pool4, scope_name="conv4_1", kernel_size=3, output_size=512, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv5_2
        conv5_2 = conv(inputs=conv5_1, scope_name="conv4_2", kernel_size=3, output_size=512, stride=1,
                       init_bias=0.0, padding="SAME")
        # conv5_3
        conv5_3 = conv(inputs=conv5_2, scope_name="conv4_3", kernel_size=3, output_size=512, stride=1,
                       init_bias=0.0, padding="SAME")
    pool5 = tf.nn.max_pool(conv5_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool4')
    input_shape = pool5.get_shape().as_list()  # 後面做全連接,所以要把shape改成2維
    # shape=[batch, dim]
    flatten = tf.reshape(pool5, [-1, input_shape[1] * input_shape[2] * input_shape[3]])
    fc1 = fc(inputs=flatten, scope_name="fc1", output_size=4096, init_bias=1.0, activeation_func=True)
    fc2 = fc(inputs=fc1, scope_name="fc2", output_size=4096, init_bias=1.0, activeation_func=True)
    fc3 = fc(inputs=fc2, scope_name="fc3", output_size=class_num, init_bias=1.0, activeation_func=True)

    return fc3

上圖中有一個softmax層,我們也可以定義出來

class_num = 1000
# placeholder 定義
inputs = tf.placeholder(dtype="float", shape=[None, 28, 28, 3], name='inputs')
labels = tf.placeholder(dtype="float", shape=[None, class_num], name='labels')
learning_rate = tf.placeholder("float", None, name='learning_rate')

logits = VGG16Net(inputs)
probs = tf.nn.softmax(logits)

ResNet模型

論文

  • 2016_ResNet_Deep Residual Learning for Image Recognition
  • 2016_ResNet_Identity Mappings in Deep Residual Networks

  ResNet的網絡結構如下圖所示

我們先定義需要用到的神經網絡單元

def batch_normalization(inputs, output_size):
    mean, variance = tf.nn.moments(inputs, axes=[0, 1, 2])  # 計算均值和方差
    beta = tf.get_variable('beta', output_size, tf.float32, initializer=tf.zeros_initializer)
    gamma = tf.get_variable('gamma', output_size, tf.float32, initializer=tf.ones_initializer)
    bn_layer = tf.nn.batch_normalization(inputs, mean, variance, beta, gamma, 0.001)

    return bn_layer

batch_normalization函數

def conv(input, kernel_size, output_size, stride, padding="SAME", wd=None):
    input_size = input.shape[-1]
    conv_weights = tf.get_variable(name='weights',
                                   shape=[kernel_size, kernel_size, input_size, output_size],
                                   dtype=tf.float32,
                                   initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.1),
                                   regularizer=tf.contrib.layers.l2_regularizer(0.00004)) # 正則損失衰減率0.000004

    conv_layer = tf.nn.conv2d(input, conv_weights, [1, stride, stride, 1], padding=padding)  # 卷積操作
    batch_norm = batch_normalization(conv_layer, output_size)
    conv_output = tf.nn.relu(batch_norm)  # relu激活函數
    return conv_output

conv函數

def fc(input, output_size, activeation_func=True):
    input_shape = input.shape[-1]
    # 創建 全連接權重 變量
    fc_weights = tf.get_variable(name="weights",
                                 shape=[input_shape, output_size],
                                 initializer=tf.truncated_normal_initializer(stddev=0.01),
                                 dtype=tf.float32,
                                 regularizer=tf.contrib.layers.l2_regularizer(0.01))
    # 創建 全連接偏置 變量
    fc_biases = tf.get_variable(name="biases",
                                shape=[output_size],
                                initializer=tf.zeros_initializer,
                                dtype=tf.float32)

    fc_layer = tf.matmul(input, fc_weights)  # 全連接計算
    fc_layer = tf.nn.bias_add(fc_layer, fc_biases)  # 加上偏置項
    if activeation_func:
        fc_layer = tf.nn.relu(fc_layer)  # rele激活函數
    return fc_layer

fc函數

def block(input, n, output_size, change_first_stride, bottleneck):
    if n == 0 and change_first_stride:
        stride = 2
    else:
        stride = 1
    if bottleneck:
        with tf.variable_scope('a'):
            conv_a = conv(input=input, kernel_size=1, output_size=output_size, stride=stride, padding="SAME")
            conv_a = batch_normalization(conv_a, output_size)
            conv_a = tf.nn.relu(conv_a)
        with tf.variable_scope('b'):
            conv_b = conv(input=conv_a, kernel_size=3, output_size=output_size, stride=1, padding="SAME")
            conv_b = batch_normalization(conv_b, output_size)
            conv_b = tf.nn.relu(conv_b)

        with tf.variable_scope('c'):
            conv_c = conv(input=conv_b, kernel_size=1, output_size=output_size * 4, stride=1, padding="SAME")
            output = batch_normalization(conv_c, output_size * 4)
    else:
        with tf.variable_scope('A'):
            conv_A = conv(input=input, kernel_size=3, output_size=output_size, stride=stride, padding="SAME")
            conv_A = batch_normalization(conv_A, output_size)
            conv_A = tf.nn.relu(conv_A)

        with tf.variable_scope('B'):
            conv_B = conv(input=conv_A, kernel_size=3, output_size=output_size, stride=1, padding="SAME")
            output = batch_normalization(conv_B, output_size)

    if input.shape == output.shape:
        with tf.variable_scope('shortcut'):
            shortcut = input  # shortcut
    else:
        with tf.variable_scope('shortcut'):
            shortcut = conv(input=input, kernel_size=1, output_size=output_size * 4, stride=1, padding="SAME")
            shortcut = batch_normalization(shortcut, output_size * 4)

    return tf.nn.relu(output + shortcut)

block函數

  然後我們定義神經網絡框架

def inference(inputs, class_num, num_blocks=[3, 4, 6, 3], bottleneck=True):
    # data[1, 224, 224, 3]

    # 我們嘗試搭建50層ResNet
    with tf.variable_scope('conv1'):
        conv1 = conv(input=inputs, kernel_size=7, output_size=64, stride=2, padding="SAME")
        conv1 = batch_normalization(inputs=conv1, output_size=64)
        conv1 = tf.nn.relu(conv1)

    with tf.variable_scope('conv2_x'):
        conv_output = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
        for n in range(num_blocks[0]):
            with tf.variable_scope('block%d' % (n + 1)):
                conv_output = block(conv_output, n, output_size=64, change_first_stride=False, bottleneck=bottleneck)

    with tf.variable_scope('conv3_x'):
        for n in range(num_blocks[1]):
            with tf.variable_scope('block%d' % (n + 1)):
                conv_output = block(conv_output, n, output_size=128, change_first_stride=True, bottleneck=bottleneck)

    with tf.variable_scope('conv4_x'):
        for n in range(num_blocks[2]):
            with tf.variable_scope('block%d' % (n + 1)):
                conv_output = block(conv_output, n, output_size=256, change_first_stride=True, bottleneck=bottleneck)

    with tf.variable_scope('conv5_x'):
        for n in range(num_blocks[3]):
            with tf.variable_scope('block%d' % (n + 1)):
                conv_output = block(conv_output, n, output_size=512, change_first_stride=True, bottleneck=bottleneck)

    output = tf.reduce_mean(conv_output, reduction_indices=[1, 2], name="avg_pool")
    with tf.variable_scope('fc'):
        output = fc(output, class_num, activeation_func=False)

    return output

Google Inception Net模型

  Inception Net模型 以後再更新吧,如果這篇文章對大家有用,歡迎大家催促我。

RNN模型

  Tensorflow中的CNN變數很少,而RNN卻豐富多彩,不僅在RNN Cell上有很多種、在實現上也有很多種,在用法上更是花樣百出。

五個基本的RNN CellRNNCellBasicRNNCellLSTMCellBasicLSTMCellGRUCell

RNN Cell的封裝和變形MultiRNNCell(多層RNN)、DropoutWrapperResidualWrapperDeviceWrapper

四種架構 (static+dynamic)*(單向+雙向)=4:static_rnn(靜態RNN)、dynamic_rnn(動態RNN)、static_bidirectional_rnn(靜態雙向RNN)、bidirectional_dynamic_rnn(動態雙向RNN)

五種手法 (one+many)*(one+many) +1=5:

  1. one to one(1 vs 1)輸入一個,輸出一個。其實和全連接神經網絡並沒有什麼區別,這一類別算不得是 RNN。
  2. one to many(1 vs N)輸入一個,輸出多個。圖像標註,輸入一個圖片,得到對圖片的語言描述
  3. many to one(N vs 1)輸入多個,輸出一個。序列分類,把序列壓縮成一個向量
  4. many to many(N vs N)輸入多個,輸出多個。兩者長度可以不一樣。翻譯任務
  5. many to many(N vs N)輸入多個,輸出多個。兩者長度一樣。char RNN

我們先定義需要用到的神經網絡單元

全連接層

def fc(input, output_size, activeation_func=tf.nn.relu):
    input_shape = input.shape[-1]
    # 創建 全連接權重 變量
    fc_weights = tf.get_variable(name="weights",
                                 shape=[input_shape, output_size],
                                 initializer=tf.truncated_normal_initializer(stddev=0.01),
                                 dtype=tf.float32,
                                 regularizer=tf.contrib.layers.l2_regularizer(0.01))
    # 創建 全連接偏置 變量
    fc_biases = tf.get_variable(name="biases",
                                shape=[output_size],
                                initializer=tf.zeros_initializer,
                                dtype=tf.float32)

    fc_layer = tf.matmul(input, fc_weights)  # 全連接計算
    fc_layer = tf.nn.bias_add(fc_layer, fc_biases)  # 加上偏置項
    if activeation_func:
        fc_layer = activeation_func(fc_layer)  # rele激活函數
    return fc_layer

View Code

單層 靜態/動態 LSTM/GRU

#######################################
#       單層 靜態/動態 LSTM/GRU        #
#######################################
# 單層靜態LSTM
def single_layer_static_lstm(input_x, time_steps, hidden_size):
    """
    :param input_x: 輸入張量 形狀為[batch_size, n_steps, input_size]
    :param n_steps: 時序總數
    :param n_hidden: LSTM單元輸出的節點個數 即隱藏層節點數
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x1 = tf.unstack(input_x, num=time_steps, axis=1)
    lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)  # 創建LSTM_cell
    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    output, states = tf.nn.static_rnn(cell=lstm_cell, inputs=input_x1, dtype=tf.float32)  # 通過cell類構建RNN

    return output, states


# 單層靜態gru
def single_layer_static_gru(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size, n_steps, input_size]
    :param n_steps: 時序總數
    :param n_hidden: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回靜態單層GRU單元的輸出,以及cell狀態
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x = tf.unstack(input, num=time_steps, axis=1)
    gru_cell = tf.nn.rnn_cell.GRUCell(num_units=hidden_size)  # 創建GRU_cell
    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    output, states = tf.nn.static_rnn(cell=gru_cell, inputs=input_x, dtype=tf.float32)  # 通過cell類構建RNN

    return output, states


# 單層動態LSTM
def single_layer_dynamic_lstm(input, time_steps, hidden_size):
    """
    :param input_x: 輸入張量 形狀為[batch_size, time_steps, input_size]
    :param time_steps: 時序總數
    :param hidden_size: LSTM單元輸出的節點個數 即隱藏層節點數
    :return: 返回動態單層LSTM單元的輸出,以及cell狀態
    """
    lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)  # 創建LSTM_cell
    # 動態rnn函數傳入的是一個三維張量,[batch_size,time_steps, input_size]  輸出也是這種形狀
    output, states = tf.nn.dynamic_rnn(cell=lstm_cell, inputs=input, dtype=tf.float32)  # 通過cell類構建RNN
    output = tf.transpose(output, [1, 0, 2])  # 注意這裏輸出需要轉置  轉換為時序優先的
    return output, states


# 單層動態gru
def single_layer_dynamic_gru(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size, time_steps, input_size]
    :param time_steps: 時序總數
    :param hidden_size: GRU單元輸出的節點個數 即隱藏層節點數
    :return: 返回動態單層GRU單元的輸出,以及cell狀態
    """
    gru_cell = tf.nn.rnn_cell.GRUCell(num_units=hidden_size)  # 創建GRU_cell
    # 動態rnn函數傳入的是一個三維張量,[batch_size,n_steps,input_size]  輸出也是這種形狀
    output, states = tf.nn.dynamic_rnn(cell=gru_cell, inputs=input, dtype=tf.float32)  # 通過cell類構建RNN
    output = tf.transpose(output, [1, 0, 2])  # 注意這裏輸出需要轉置  轉換為時序優先的
    return output, states

View Code

多層 靜態/動態 LSTM/GRU

#######################################
#       多層 靜態/動態 LSTM/GRU        #
#######################################
# 多層靜態LSTM網絡
def multi_layer_static_lstm(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,time_steps,input_size]
    :param time_steps: 時序總數
    :param n_hidden: LSTM單元輸出的節點個數 即隱藏層節點數
    :return: 返回靜態多層LSTM單元的輸出,以及cell狀態
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x1 = tf.unstack(input, num=time_steps, axis=1)

    # 多層RNN的實現 例如cells=[cell1,cell2,cell3],則表示一共有三層
    mcell = tf.nn.rnn_cell.MultiRNNCell(
        [tf.nn.rnn_cell.LSTMCell(num_units=hidden_size) for _ in range(3)])

    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    output, states = tf.nn.static_rnn(cell=mcell, inputs=input_x1, dtype=tf.float32)

    return output, states


# 多層靜態GRU
def multi_layer_static_gru(input, time_steps, hidden_size):
    """
    :param input_x: 輸入張量 形狀為[batch_size,n_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回靜態多層GRU單元的輸出,以及cell狀態
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x = tf.unstack(input, num=time_steps, axis=1)

    # 多層RNN的實現 例如cells=[cell1,cell2,cell3],則表示一共有三層
    mcell = tf.nn.rnn_cell.MultiRNNCell(
        [tf.nn.rnn_cell.GRUCell(num_units=hidden_size) for _ in range(3)])

    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    output, states = tf.nn.static_rnn(cell=mcell, inputs=input_x, dtype=tf.float32)

    return output, states


# 多層靜態GRU和LSTM 混合
def multi_layer_static_mix(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,n_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回靜態多層GRU和LSTM混合單元的輸出,以及cell狀態
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x = tf.unstack(input, num=time_steps, axis=1)

    # 可以看做2個隱藏層
    lstm_cell = tf.nn.rnn_cell.LSTMCell(num_units=hidden_size)
    gru_cell = tf.nn.rnn_cell.GRUCell(num_units=hidden_size * 2)

    # 多層RNN的實現 例如cells=[cell1,cell2],則表示一共有兩層,數據經過cell1后還要經過cells
    mcell = tf.nn.rnn_cell.MultiRNNCell(cells=[lstm_cell, gru_cell])

    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    output, states = tf.nn.static_rnn(cell=mcell, inputs=input_x, dtype=tf.float32)

    return output, states


# 多層動態LSTM
def multi_layer_dynamic_lstm(input, time_steps, hidden_size):
    """
    :param input: 輸入張量  形狀為[batch_size,n_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: LSTM單元輸出的節點個數 即隱藏層節點數
    :return: 返回動態多層LSTM單元的輸出,以及cell狀態
    """
    # 多層RNN的實現 例如cells=[cell1,cell2],則表示一共有兩層,數據經過cell1后還要經過cells
    mcell = tf.nn.rnn_cell.MultiRNNCell(
        [tf.nn.rnn_cell.LSTMCell(num_units=hidden_size) for _ in range(3)])

    # 動態rnn函數傳入的是一個三維張量,[batch_size,n_steps,input_size]  輸出也是這種形狀
    output, states = tf.nn.dynamic_rnn(cell=mcell, inputs=input, dtype=tf.float32)

    # 注意這裏輸出需要轉置  轉換為時序優先的
    output = tf.transpose(output, [1, 0, 2])
    return output, states


# 多層動態GRU
def multi_layer_dynamic_gru(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,n_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回動態多層GRU單元的輸出,以及cell狀態
    """
    # 多層RNN的實現 例如cells=[cell1,cell2],則表示一共有兩層,數據經過cell1后還要經過cells
    mcell = tf.nn.rnn_cell.MultiRNNCell(
        [tf.nn.rnn_cell.GRUCell(num_units=hidden_size) for _ in range(3)])

    # 動態rnn函數傳入的是一個三維張量,[batch_size,n_steps,input_size]  輸出也是這種形狀
    output, states = tf.nn.dynamic_rnn(cell=mcell, inputs=input, dtype=tf.float32)

    # 注意這裏輸出需要轉置  轉換為時序優先的
    output = tf.transpose(output, [1, 0, 2])
    return output, states


# 多層動態GRU和LSTM 混合
def multi_layer_dynamic_mix(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,n_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回動態多層GRU和LSTM混合單元的輸出,以及cell狀態
    """
    # 可以看做2個隱藏層
    gru_cell = tf.nn.rnn_cell.GRUCell(num_units=hidden_size * 2)
    lstm_cell = tf.nn.rnn_cell.LSTMCell(num_units=hidden_size)

    # 多層RNN的實現 例如cells=[cell1,cell2],則表示一共有兩層,數據經過cell1后還要經過cells
    mcell = tf.nn.rnn_cell.MultiRNNCell(cells=[lstm_cell, gru_cell])

    # 動態rnn函數傳入的是一個三維張量,[batch_size,n_steps,input_size]  輸出也是這種形狀
    output, states = tf.nn.dynamic_rnn(cell=mcell, inputs=input, dtype=tf.float32)

    # 注意這裏輸出需要轉置  轉換為時序優先的
    output = tf.transpose(output, [1, 0, 2])
    return output, states

View Code

單層/多層 雙向 靜態/動態 LSTM/GRU

#######################################
#   單層/多層 雙向 靜態/動態 LSTM/GRU   #
#######################################
# 單層靜態雙向LSTM
def single_layer_static_bi_lstm(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,time_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: LSTM單元輸出的節點個數 即隱藏層節點數
    :return: 返回單層靜態雙向LSTM單元的輸出,以及cell狀態
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x = tf.unstack(input, num=time_steps, axis=1)
    lstm_fw_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)  # 正向
    lstm_bw_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)  # 反向

    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    # 這裏的輸出output是一個list 每一個元素都是前向輸出,後向輸出的合併
    output, fw_state, bw_state = tf.nn.static_bidirectional_rnn(cell_fw=lstm_fw_cell,
                                                                cell_bw=lstm_bw_cell,
                                                                inputs=input_x,
                                                                dtype=tf.float32)
    print(type(output))  # <class 'list'>
    print(len(output))  # 28
    print(output[0].shape)  # (?, 256)

    return output, fw_state, bw_state


# 單層動態雙向LSTM
def single_layer_dynamic_bi_lstm(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,time_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回單層動態雙向LSTM單元的輸出,以及cell狀態
    """
    lstm_fw_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)  # 正向
    lstm_bw_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size)  # 反向

    # 動態rnn函數傳入的是一個三維張量,[batch_size,time_steps,input_size]  輸出是一個元組 每一個元素也是這種形狀
    output, state = tf.nn.bidirectional_dynamic_rnn(cell_fw=lstm_fw_cell,
                                                    cell_bw=lstm_bw_cell,
                                                    inputs=input,
                                                    dtype=tf.float32)
    print(type(output))  # <class 'tuple'>
    print(len(output))  # 2
    print(output[0].shape)  # (?, 28, 128)
    print(output[1].shape)  # (?, 28, 128)

    output = tf.concat(output, axis=2)  # 按axis=2合併 (?,28,128) (?,28,128)按最後一維合併(?,28,256)
    output = tf.transpose(output, [1, 0, 2])  # 注意這裏輸出需要轉置  轉換為時序優先的

    return output, state


# 多層靜態雙向LSTM
def multi_layer_static_bi_lstm(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,time_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: LSTM單元輸出的節點個數 即隱藏層節點數
    :return: 返回多層靜態雙向LSTM單元的輸出,以及cell狀態
    """
    # 把輸入input_x按列拆分,並返回一個有n_steps個張量組成的list
    # 如batch_sizex28x28的輸入拆成[(batch_size,28),((batch_size,28))....]
    # 如果是調用的是靜態rnn函數,需要這一步處理   即相當於把序列作為第一維度
    input_x = tf.unstack(input, num=time_steps, axis=1)

    stacked_fw_rnn = []
    stacked_bw_rnn = []
    for i in range(3):
        stacked_fw_rnn.append(tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size))  # 正向
        stacked_bw_rnn.append(tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size))  # 反向

    # 靜態rnn函數傳入的是一個張量list  每一個元素都是一個(batch_size,input_size)大小的張量
    # 這裏的輸出output是一個list 每一個元素都是前向輸出,後向輸出的合併
    output, fw_state, bw_state = tf.contrib.rnn.stack_bidirectional_rnn(stacked_fw_rnn,
                                                                        stacked_bw_rnn,
                                                                        inputs=input_x,
                                                                        dtype=tf.float32)
    print(type(output))  # <class 'list'>
    print(len(output))  # 28
    print(output[0].shape)  # (?, 256)

    return output, fw_state, bw_state


# 多層動態雙向LSTM
def multi_layer_dynamic_bi_lstm(input, time_steps, hidden_size):
    """
    :param input: 輸入張量 形狀為[batch_size,n_steps,input_size]
    :param time_steps: 時序總數
    :param hidden_size: gru單元輸出的節點個數 即隱藏層節點數
    :return: 返回多層動態雙向LSTM單元的輸出,以及cell狀態
    """
    stacked_fw_rnn = []
    stacked_bw_rnn = []
    for i in range(3):
        stacked_fw_rnn.append(tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size))  # 正向
        stacked_bw_rnn.append(tf.nn.rnn_cell.BasicLSTMCell(num_units=hidden_size))  # 反向

    # 動態rnn函數傳入的是一個三維張量,[batch_size,n_steps,input_size]  輸出也是這種形狀,
    # input_size變成了正向和反向合併之後的 即input_size*2
    output, fw_state, bw_state = tf.contrib.rnn.stack_bidirectional_dynamic_rnn(stacked_fw_rnn,
                                                                                stacked_bw_rnn,
                                                                                inputs=input,
                                                                                dtype=tf.float32)
    print(type(output))  # <class 'tensorflow.python.framework.ops.Tensor'>
    print(output.shape)  # (?, 28, 256)

    output = tf.transpose(output, [1, 0, 2])  # 注意這裏輸出需要轉置  轉換為時序優先的

    return output, fw_state, bw_state

View Code

然後我們定義神經網絡框架

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

def RNN_inference(inputs, class_num, time_steps, hidden_size):
    """
    :param inputs: [batch_size, n_steps, input_size]
    :param class_num: 類別數
    :param time_steps: 時序總數
    :param n_hidden: LSTM單元輸出的節點個數 即隱藏層節點數
    """
    #######################################
    #       單層 靜態/動態 LSTM/GRU        #
    #######################################
    # outputs, states = single_layer_static_lstm(inputs, time_steps, hidden_size)  # 單層靜態LSTM
    # outputs, states = single_layer_static_gru(inputs, time_steps, hidden_size)   # 單層靜態gru
    # outputs, states = single_layer_dynamic_lstm(inputs, time_steps, hidden_size)  # 單層動態LSTM
    # outputs, states = single_layer_dynamic_gru(inputs, time_steps, hidden_size)  # 單層動態gru
    #######################################
    #       多層 靜態/動態 LSTM/GRU        #
    #######################################
    # outputs, states = multi_layer_static_lstm(inputs, time_steps, hidden_size)  # 多層靜態LSTM網絡
    # outputs, states = multi_layer_static_gru(inputs, time_steps, hidden_size)  # 多層靜態GRU
    # outputs, states = multi_layer_static_mix(inputs, time_steps, hidden_size)  # 多層靜態GRU和LSTM 混合
    # outputs, states = multi_layer_dynamic_lstm(inputs, time_steps, hidden_size)  # 多層動態LSTM
    # outputs, states = multi_layer_dynamic_gru(inputs, time_steps, hidden_size)  # 多層動態GRU
    # outputs, states = multi_layer_dynamic_mix(inputs, time_steps, hidden_size)  # 多層動態GRU和LSTM 混合
    #######################################
    #   單層/多層 雙向 靜態/動態 LSTM/GRU  #
    #######################################
    # outputs, fw_state, bw_state = single_layer_static_bi_lstm(inputs, time_steps, hidden_size)  # 單層靜態雙向LSTM
    # outputs, state = single_layer_dynamic_bi_lstm(inputs, time_steps, hidden_size)  # 單層動態雙向LSTM
    # outputs, fw_state, bw_state = multi_layer_static_bi_lstm(inputs, time_steps, hidden_size)  # 多層靜態雙向LSTM
    outputs, fw_state, bw_state = multi_layer_dynamic_bi_lstm(inputs, time_steps, hidden_size)  # 多層動態雙向LSTM

    # output靜態是 time_step=28個(batch=128, output=128)組成的列表
    # output動態是 (time_step=28, batch=128, output=128)
    print('hidden:', outputs[-1].shape)  # 最後一個時序的shape(128,128)

    # 取LSTM最後一個時序的輸出,然後經過全連接網絡得到輸出值
    fc_output = fc(input=outputs[-1], output_size=class_num, activeation_func=tf.nn.relu)

    return fc_output

設置全局變量和超參數

  在模型訓練之前我們首先會定義一些超參數:batch_size、batch_nums、class_num、epochs、learning_rate

batch_size = FLAGS.batch_size
batch_nums = mnist.train.images.shape[0] // batch_size  # 一個epoch中應該包含多少batch數據
class_num = FLAGS.class_num
epochs = FLAGS.epochs
learning_rate = FLAGS.learning_rate

保存檢查點的地址

############    保存檢查點的地址   ############
checkpoints_dir = FLAGS.checkpoints_dir  # checkpoints
# 如果檢查點不存在,則創建
if not os.path.exists(checkpoints_dir):
    os.makedirs(FLAGS.checkpoints_dir)

創建圖

  這一步可以不設置,因為tensorflow有一個默認圖,我們定義的操作都是在默認圖上的,當然我們也可以定義自己的,方便管理。

######################################################
#                    創建圖                          #
######################################################
graph = tf.Graph()  # 自定義圖
# 在自己的圖中定義數據和操作
with graph.as_default():

佔位符

  一般我們會把input和label做成placeholder,方便我們使用把不同的batch數據傳入網絡,一些其他的超參數也可以做成placeholder,比如learning_rate、dorpout_keep_prob。一般在搭建模型的時候把placeholder的變量傳入模型,在訓練模型sess.run(train_op, feed_dict)的時候通過參數feed_dict={input:真實數據,label:真實標籤} 把真實的數據傳入神經網絡。

inputs = tf.placeholder(dtype="float", shape=[None, 28, 28, 1], name='inputs')
labels = tf.placeholder(dtype="float", shape=[None, class_num], name='labels')
# 看個人喜歡,有的人在初始化定義中就定義了learning_rate,有的人喜歡通過feed傳learning_rate
learning_rate = tf.placeholder("float", None, name='learning_rate')
# 如果網絡結構有dropout層,需要定義keep_probn,如果沒有則不需要
# 訓練的時候需要,測試的時候需要設置成1
keep_prob = tf.placeholder(dtype="float", name='keep_prob')  

搭建模型

  傳進入的都是placeholder數據,不是我們之前整理好的batch數據。

############    搭建模型   ############
logits = alexNet(inputs, class_num, keep_prob=keep_prob)  # 使用placeholder搭建模型

構建損失

  分類任務一般輸出的是每個類別的概率向量,因此模型輸出最後都要經過softmax轉換成概率。一般經過softmax的輸出損失函數都是交叉熵損失函數,tensorflow有將以上兩步合在一起的現成函數 tf.nn.softmax_cross_entropy_with_logits

############    損失函數   ############
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits))
tf.add_to_collection('losses', loss)
total_loss = tf.add_n(tf.get_collection("loss"))  # total_loss=模型損失+權重正則化損失

自定義損失

  以後更新,歡迎大家催我。

模型精度

  在測試數據集上的精度

############    模型精度   ############
predict = tf.argmax(logits, 1)      # 模型預測結果
accuracy = tf.reduce_mean(tf.cast(tf.equal(predict, tf.argmax(labels, 1)), tf.float32))

自定義度量

  以後更新,歡迎大家催我。

優化器

  創建優化器,更新網絡參數,最小化loss

  優化器的種類有很多種,但是用法都差不多,常用的優化器有:

  • tf.train.AdamOptimizer
  • tf.train.GradientDescentOptimizer

  • tf.train.RMSPropOptimizer

下面以Adam優化器為例

############    優化器   ############
variable_to_train = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)  # 可訓練變量列表
global_step = tf.Variable(0, trainable=False)    # 訓練step
# 設置學習率衰減
learning_rate = tf.train.exponential_decay(learning_rate=learning_rate,  # 初始學習率
                                           global_step=global_step,
                                           decay_steps=batch_nums,  # 多少步衰減一次
                                           decay_rate=0.1,  # 衰減率
                                           staircase=True)  # 以階梯的形式衰減
# 創建Adam優化器,更新模型參數,最小化損失函數
train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss=total_loss,  # 損失函數
                                                          global_step=global_step,
                                                          var_list=variable_to_train)  # 通過訓練需要更新的參數列表

講解

  • variable_to_train:上面的代碼定義了可訓練變量,我只是把列出了模型默認的可訓練變量,這一個步是tensorflow默認的,如果不設置也沒有關係。我寫出來的原因是,有的大牛會這麼寫,對不同的可訓練變量分別進行不同的優化,希望大家看到我的代碼,下次看到別人的不會覺得陌生。
  • global_step:大多數人會用step=0,然後在訓練的時候step+=1的方式更新step,但是本文介紹的是另一種方式,以tf.Variable的方式定義step,在模型訓練的時候傳入sess.run,global_step會自動+1更新
  • learning_rate:本文還設置了學習率衰減,大家也可以不設置,以固定的學習率訓練模型,但是對於大型項目,還是推薦設置。

移動平均值更新參數

採用移動平均值的方式更新損失值和模型參數

def train(total_loss, global_step):
    lr = tf.train.exponential_decay(0.01, global_step, decay_steps=350, decay_rate=0.1, staircase=True)
    # 採用滑動平均的方法更新損失值
    loss_averages = tf.train.ExponentialMovingAverage(decay=0.9, name='avg')
    losses = tf.get_collection('losses')  # losses的列表
    loss_averages_op = loss_averages.apply(losses + [total_loss])  # 計算損失值的影子變量op

    # 計算梯度
    with tf.control_dependencies([loss_averages_op]):  # 控制計算指定,只有執行了括號中的語句才能執行下面的語句
        opt = tf.train.GradientDescentOptimizer(lr)  # 創建優化器
        grads = opt.compute_gradients(total_loss)  # 計算梯度

    # 應用梯度
    apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

    # 採用滑動平均的方法更新參數
    variable_averages = tf.train.ExponentialMovingAverage(0.999, num_updates=global_step)
    variables_averages_op = variable_averages.apply(tf.trainable_variables())

    with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
        # tf.no_op()表示執行完apply_gradient_op, variable_averages_op操作之後什麼都不做
        train_op = tf.no_op(name='train')

    return train_op

View Code

TensorBoard可視化 summary

############    TensorBoard可視化 summary  ############
summary_writer = tf.summary.FileWriter(logdir="./logs", graph=graph)  # 創建事件文件
tf.summary.scalar(name="losses", tensor=total_loss)  # 收集損失值變量
tf.summary.scalar(name="acc", tensor=accuracy)  # 收集精度值變量
tf.summary.scalar(name='learning_rate', tensor=learning_rate)
merged_summary_op = tf.summary.merge_all()  # 將所有的summary合併為一個op

模型保存和恢復 Saver

saver = tf.train.Saver(max_to_keep=5)  # 保存最新的5個檢查點

創建會話

配置會話

  在創建會話之前我們一般都要配置會話,比如使用GPU還是CPU,用多少GPU等等。

我們一般使用 tf.ConfigProto()配置Session運行參數&&GPU設備指定

config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)
config.gpu_options.per_process_gpu_memory_fraction = 0.4  # 佔用40%顯存 sess = tf.Session(config=config)
# 或者
config = tf.ConfigProto()
config.allow_soft_placement = True
config.log_device_placement = True

with tf.Session(config=config) as sess:
# 或者
sess = tf.Session(config=config)

tf.ConfigProto(log_device_placement=True):記錄設備指派情況

  設置tf.ConfigProto()中參數log_device_placement = True,獲取 operations 和 Tensor 被指派到哪個設備(幾號CPU或幾號GPU)上運行,會在終端打印出各項操作是在哪個設備上運行的。

tf.ConfigProto(allow_soft_placement=True):自動選擇運行設備

  在TensorFlow中,通過命令 “with tf.device(‘/cpu:0’):“,允許手動設置操作運行的設備。如果手動設置的設備不存在或者不可用,就會導致tf程序等待或異常,為了防止這種情況,可以設置tf.ConfigProto()中參數allow_soft_placement=True,自動選擇一個存在並且可用的設備來運行操作。

config.gpu_options.allow_growth = True

當使用GPU時候,Tensorflow運行自動慢慢達到最大GPU的內存

tf.test.is_built_with_cuda():返回是否能夠使用GPU進行運算

  為了加快運行效率,TensorFlow在初始化時會嘗試分配所有可用的GPU顯存資源給自己,這在多人使用的服務器上工作就會導致GPU佔用,別人無法使用GPU工作的情況。這時我們需要限制GPU資源使用,詳細實現方法請參考我的另一篇博客 tensorflow常用函數 Ctrl+F搜索“限制GPU資源使用”

創建會話Session

  Session有兩種創建方式:

sess = tf.Session(config=config, graph=graph)
# 或通過with的方式創建Session
with tf.Session(config=config, graph=graph) as sess:

  如果我們之前自定義了graph,則在會話中也要配置graph,如果之前沒有自定義graph,使用的是tensorflow默認graph,則在會話不用自己去定義,tensorflow會自動找到默認圖。

  在訓練模型之前我們首先要設置一個高級一點的東西,那就是檢查是否有之前保存好的模型,如果有着接着前面的繼續訓練,如果沒有則從頭開始訓練模型。

恢復/重新訓練

  定義一個檢查模型是否存在的函數,為了美觀,可以把這個函數放在最上面,或者其他腳本中,通過import導入。

def load_model(sess, saver, checkpoint_dir):
    """加載模型,看看還能不能加一個功能,必須現在的檢查檢點是1000,但是我的train是100,要報錯
        還有就是讀取之前的模型繼續訓練的問題
        checkpoint_dir = checkpoint"""

    # 通過checkpoint找到模型文件名
    ckpt = tf.train.get_checkpoint_state(checkpoint_dir=checkpoint_dir)
    if ckpt and ckpt.model_checkpoint_path:
        ckpt_name = os.path.basename(ckpt.model_checkpoint_path)  # 返回最新的chechpoint文件名 model.ckpt-1000
        print("新的chechpoint文件名", ckpt_name)  # model.ckpt-2
        saver.restore(sess, os.path.join(checkpoint_dir, ckpt_name))
        # 現在不知道checkpoint文件名時怎樣的,因此不知道裏面如何運行
        counter = int(next(re.finditer("(\d+)(?!.*\d)", ckpt_name)).group(0))  # 2
        print(" [*] 成功模型 {}".format(ckpt_name))
        return True, counter
    else:
        print(" [*] 找不到checkpoint")
        return False, 0

View Code

  如果大家之前用的是global_step = tf.Variable(0, trainable=False),則使用下面diamante

# 加載模型,如果模型存在返回 是否加載成功和訓練步數
could_load, checkpoint_step = load_model(sess, saver, "./log")
if could_load:
    print(" [*] 加載成功")
else:
    print(" [!] 加載失敗")
    try:
        tf.global_variables_initializer().run()
    except:
        tf.initialize_all_variables().run()

  如果大家想使用step=0,step+=1,則可以使用下面代碼

# 加載模型,如果模型存在返回 是否加載成功和訓練步數
could_load, checkpoint_step = load_model(sess, saver, FLAGS.checkpoints_dir)
if could_load:
    step = checkpoint_step
    print(" [*] 模型加載成功")
else:
    print(" [!] 模型加載失敗")
    try:
        tf.global_variables_initializer().run()
    except:
        tf.initialize_all_variables().run()
    step = 0

開始訓練

for epoch in range(epochs):
    for i in range(batch_nums):
        start_time = time.time()
        # batch_images = data_X[i * batch_size:(i + 1) * batch_size]
        # batch_labels = data_y[i * batch_size:(i + 1) * batch_size]
        train_batch_x, train_batch_y = mnist.train.next_batch(batch_size)

        # 使用真實數據填充placeholder,運行訓練模型和合併變量操作
        _, summary, loss, step = sess.run([train_op, merged_summary_op, total_loss, global_step],
                                          feed_dict={inputs: train_batch_x,
                                                     labels: train_batch_y,
                                                     keep_prob: 0.5})
        if step % 100 == 0:
            summary_writer.add_summary(summary, step)  # 將每次迭代后的變量寫入事件文件
            summary_writer.flush()  # 強制summary_writer將緩存中的數據寫入到日誌文件中(可選)

            ############    可視化打印   ############
            print("Epoch:[%2d] [%4d/%4d] time:%4.4f,loss:%.8f" % (
                epoch, i, batch_nums, time.time() - start_time, loss))

        # 打印一些可視化的數據,損失...
        if step % 100 == 0:
            acc = sess.run(accuracy, feed_dict={inputs: mnist.validation.images,
                                                labels: mnist.validation.labels,
                                                keep_prob: 1.0})
            print("Epoch:[%2d] [%4d/%4d] accuracy:%.8f" % (epoch, i, batch_nums, acc))
            ############    保存模型   ############
            if acc > max_acc:
                max_acc = acc
                save_path = saver.save(sess,
                                       save_path=os.path.join(checkpoints_dir, "model.ckpt"),
                                       global_step=step)
                tf.logging.info("模型保存在: %s" % save_path)
print("優化完成!")

模型評估

eval.py

模型評估的代碼和模型訓練的代碼很像,只不過不需要對模型進行訓練而已。

from ops import *
import tensorflow as tf
from nets.my_alex import alexNet
from tensorflow.examples.tutorials.mnist import input_data

tf.flags.DEFINE_integer('batch_size', 50, 'batch size, default: 1')
tf.flags.DEFINE_integer('class_num', 10, 'batch size, default: 1')
tf.flags.DEFINE_integer('epochs', 10, 'batch size, default: 1')
tf.flags.DEFINE_string('checkpoints_dir', "checkpoints", '保存檢查點的地址')
FLAGS = tf.flags.FLAGS

# 從MNIST_data/中讀取MNIST數據。當數據不存在時,會自動執行下載
mnist = input_data.read_data_sets('./data', one_hot=True, reshape=False)

# 將數組張換成圖片形式
print(mnist.train.images.shape)  # 訓練數據圖片(55000, 28, 28, 1)
print(mnist.train.labels.shape)  # 訓練數據標籤(55000, 10)
print(mnist.test.images.shape)  # 測試數據圖片(10000, 28, 28, 1)
print(mnist.test.labels.shape)  # 測試數據圖片(10000, 10)
print(mnist.validation.images.shape)  # 驗證數據圖片(5000, 28, 28, 1)
print(mnist.validation.labels.shape)  # 驗證數據圖片(5000, 10)


def evaluate():
    batch_size = FLAGS.batch_size
    batch_nums = mnist.train.images.shape[0] // batch_size  # 一個epoch中應該包含多少batch數據
    class_num = FLAGS.class_num
    test_batch_size = 5000
    test_batch_num = mnist.test.images.shape[0] // test_batch_size

    ############    保存檢查點的地址   ############
    checkpoints_dir = FLAGS.checkpoints_dir  # checkpoints
    # 如果檢查點不存在,則創建
    if not os.path.exists(checkpoints_dir):
        print("模型文件不存在,無法進行評估")

    ######################################################
    #                    創建圖                          #
    ######################################################
    graph = tf.Graph()  # 自定義圖
    # 在自己的圖中定義數據和操作
    with graph.as_default():
        inputs = tf.placeholder(dtype="float", shape=[None, 28, 28, 1], name='inputs')
        labels = tf.placeholder(dtype="float", shape=[None, class_num], name='labels')
        ############    搭建模型   ############
        logits = alexNet(inputs, FLAGS.class_num, keep_prob=1)  # 使用placeholder搭建模型
        ############    模型精度   ############
        predict = tf.argmax(logits, 1)
        accuracy = tf.reduce_mean(tf.cast(tf.equal(predict, tf.argmax(labels, 1)), tf.float32))
        ############    模型保存和恢復 Saver   ############
        saver = tf.train.Saver(max_to_keep=5)

    ######################################################
    #                   創建會話                          #
    ######################################################
    config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)
    with tf.Session(config=config, graph=graph) as sess:
        # 加載模型,如果模型存在返回 是否加載成功和訓練步數
        could_load, checkpoint_step = load_model(sess, saver, FLAGS.checkpoints_dir)
        if could_load:
            print(" [*] 加載成功")
        else:
            print(" [!] 加載失敗")
            raise ValueError("模型文件不存在,無法進行評估")

        for i in range(test_batch_num):
            test_batch_x, test_batch_y = mnist.test.next_batch(test_batch_num)
            acc = sess.run(accuracy, feed_dict={inputs: test_batch_x,
                                                labels: test_batch_y})
            print("模型精度為:", acc)
        one_image = mnist.test.images[1].reshape(1, 28, 28, 1)
        predict_label = sess.run(predict, feed_dict={inputs: one_image})
        # print("123", tf.argmax(pre_yyy, 1).eval())  # [7]
        # print("123", tf.argmax(yyy, 1).eval())  # 7


def main(argv=None):
    evaluate()


if __name__ == '__main__':
    tf.app.run()

 

參考文獻

CSDN_AlexNet神經網絡結構

CSDN_【深度學習理論3】ALexNet模型的詳解

github搜索tensorflow AlexNet

github_finetune_alexnet_with_tensorflow

github_AlexNet_with_tensorflow

github tensorflow vgg

ResNet詳解與分析

tensorflow中使用tf.ConfigProto()配置Session運行參數&&GPU設備指定

比較完整且容易入門的MNIST案例

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

商人韓寒的資本路徑:從力盛賽車,到果麥文化和博納影業_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

如果今天果麥文化順利過會,再加上前段時間通過審核的博納影業,A股市場將同時迎來兩家“韓寒概念股”。

很多人不知道的是,韓寒作為賽車手效力的力盛賽車2017年成功上市,市場已經領教過一回韓寒的影響力。

這3家公司,代表了韓寒的三種身份,賽車手、作家、導演,背後也站着韓寒的三位“貴人”,夏青、路金波和於冬:賽車事業的領路人,將韓寒從青年作家包裝為公共知識分子的定義者,以及幫助韓寒出圈的幕後推手。

這個在中國輿論場活躍了20年的人物,正在褪去其少年時代的青澀感,通過創立亭東影業,以及投資果麥文化和博納影業,逐漸勾勒起一個商人的野心版圖。

賽車,為他人做嫁衣

上世紀90年代,溫州人夏青經歷7年銀行會計和3年鄉鎮公務員生活后,28歲那年來上海創業,在包裝印刷和房地產行業收益頗豐。2000年,他花100萬元收購了一支車隊,正式進軍賽車圈。

幾乎在同一時間,韓寒退學后通過《三重門》小賺了一筆,遊盪北京玩賽車,雖然也加入過幾支車隊,但純屬草台班子。

一個是半路出家的車隊老闆需要挖掘新鮮力量,一個是初出茅廬的車手需要職業化培養,雙方一拍即合,2004年見了一面,幾個月後就在電話中敲定了加盟的事。

沒過幾年,夏青的眼光得到證實,韓寒在賽車場上大放異彩,冠軍拿到手軟。

在夏青的操盤下,旗下公司力盛賽車(002858.SZ)以賽事運營、賽車隊經營、賽車場館經營、汽車活動推廣四大業務,衝擊A股上市,2017年初終於如願以償。

按照公司招股書的表述,韓寒的價值無處不在。當時的市場分析認為,韓寒的名氣,是力盛賽車最大的看點,也是最大的賣點。

上市公司實際控制人夏青持有過半股權,但當時公司的金字招牌韓寒,並不持有任何股份。

2015年6月,力盛賽車和韓寒合資成立上海桿位文化傳媒有限公司,雙方各持股50%,實收資本100萬元。2015年和2016年上半年,該公司分別虧損了52.48萬元和84.21萬元。

次年8月,上市公司收購了韓寒所持的50%股權。如果以凈資產價格收購,相當於韓寒投了50萬元,1年時間虧掉了十幾萬。

同在2015年6月,力盛賽車與韓寒等人合資成立上海荔盛艾甫奕投資管理有限公司,公司持股70%,韓寒持股7%,但這家公司5個月之後就直接註銷了。

總之,韓寒與力盛賽車的資本合作,最後都告吹了。此後幾年,連業務合作都減少了。

韓寒仍然留在力盛的上汽大眾333車隊,但同時也在CTCC和斯巴魯車隊效力。再之後,韓寒淡出賽車圈,這幾年連參加比賽的頻率都降低了很多。

2018年之後,力盛賽車也不在自己的年報中重點提及韓寒。公司的幾大業務日漸式微,業績下滑、市值暴跌。今年前三季度,公司營業收入、歸母凈利潤分別為1.22億元、-804.61萬元,分別同比下降55.96%和151.23%。

寫作,資本路崛起

韓寒最早被人所知,還是因為作家身份。

1999年以一篇《杯中窺人》獲得新概念作文大賽一等獎,一炮而紅,2000年的《三重門》、2001年的《零下一度》、2002年的《像少年啦飛馳》都成為當時的暢銷書。

韓寒早年出書,基本上都是隨便挑的出版社,飽受隱瞞印數與版稅的困擾,他多次寫文章加以嘲諷。

2003年,韓寒認識路金波,以5000塊的低價就把作品的漫畫改編權賣掉,開啟了首次合作。

2年後,韓寒在博客上發表聲明,宣布收回某出版社的《長安亂》版權,因為該出版社及其委託的發行人私自盜版了這部作品。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

韓寒跟出版社打官司的消息爆出來,當晚,路金波找上來,在上海以8萬元的低價簽下韓寒——不久之後路金波簽下安妮寶貝,花了200萬元。

2007年,路金波去賽車場找韓寒,沿路遇到好幾個女孩跟他打招呼,他覺得奇怪。後來對方告訴他,她們都是韓迷俱樂部的,都認識韓寒的出版人。

路金波這才意識到,作為一个中國頂級明星的出版商,他因此多了很多曝光率和知名度。

2012年路金波從萬榕書業離開,創立果麥文化,旗下作者包括韓寒、易中天、楊紅櫻、蔡崇達、張皓宸、嚴歌苓、馮唐等。

這一次,韓寒沒有錯過資本盛宴。

果麥文化成立之初,韓寒的母親周巧蓉就作為路金波唯一的合伙人出資,持股10%,經過多輪融資,周巧蓉套現數千萬元后,仍然持有果麥文化4.5278%的股權,進入前十大股東名單。

2017年-2019年,韓寒及其實際控制的上海有樹文化傳播有限公司均為果麥文化前十大版權採購商之一。

值得一提的是,近幾年韓寒很少自己寫書,上一部作品還是2014年與電影《後會無期》同步推出的配套作品《告白與告別》。韓寒依託閱讀類APP一個,打造了一個文學矩陣,相當於從單一的作者,演化為一個作者群,從自己寫書,到幫別人出書。

電影,三重身份完美結合

韓寒近年的事業重心,轉移到了電影上。

2014年前後韓寒籌拍《後會無期》,由自己的出版人路金波和製片人方勵投資,韓寒負責編劇和導演,方勵當製片人,路金波協調外聯和商務。

後來,他們還引入了一位出品人,博納影業於冬,博納負責影片的宣發事宜。

幾位大佬強強聯手相助,導演新手韓寒取得了不錯的成績,《後會無期》票房6.29億元,力壓同期上映的《小時代3》。

2016年韓寒的亭東影業成立,博納成為主要投資人之一,持股11.25%,為僅次於韓寒和阿里影業的第三大股東。另外,博納影業還投資了果麥文化,持股9.2541%,同樣是第三大股東。

路金波的母親孫妮,為亭東影業第五大股東,持股5.93%。啟信寶显示,孫妮還擔任公司董事。

那時候,正是博納影業從納斯達克退市,準備A股上市的關鍵時期。公司迫切需要新故事,韓寒正是這些故事的核心之一。

後來,博納影業引入戰略投資者,2017年初韓寒“突擊”入股,最終持股0.06%。

韓寒的亭東影業,與果麥文化及博納影業,結成利益共同體。在它們的聯合操盤之下,韓寒後來的兩部電影《乘風破浪》、《飛馳人生》表現不錯,分別斬獲票房10.49億元和17.03億元。

果麥文化在其IPO招股書中披露,公司分別斥資1720萬元和2800萬元投資韓寒電影,2017年-2019年分別獲得投資收益88.67萬元、1394.72萬元、817.87萬元。

在博納影業近幾年的片單中,除了《紅海行動》《中國機長》《湄公河行動》這幾部“大國題材”,就屬韓寒的幾部片子較為出彩。

至此,韓寒的名導身份高調定格。

韓寒的三重身份,其實並不分離。作家身份是導演身份的基礎,導演的電影也都與賽車相關。

當韓寒持股的果麥文化和博納影業上市在即,我們現在回過頭來看,《飛馳人生》中說盡車手與車隊的愛恨情仇,多少有點調侃力盛賽車的意味。

亭東影業會獨立上市,讓韓寒續寫資本之路的新章節嗎?

【本文作者范建,由合作夥伴微信公眾號:斑馬消費授權發布,文章版權歸原作者及原出處所有,轉載請聯繫原出處。文章系作者個人觀點,不代表立場。如內容、圖片有任何版權問題,請聯繫(editor@zero2ipo.com.cn)處理。】

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

OPPO Reno5 、Reno5 Pro 正式在台發表:全新升級 AI 錄影、獨家 Reno Glow 2.0 星鑽工藝打造精緻外觀_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

今(12)日稍早, OPPO 正式在台灣發表旗下最新 Reno5 系列新機 OPPO Reno5 和 Reno5 Pro ,除了延續前一代輕薄的機身設計,在機身質感處理、 AI 錄影拍攝全面升級,讓用戶錄製生活影片帶來更豐富的樂趣。另外,在 OPPO Reno5 和 Reno5 Pro 皆支持最高 90Hz 螢幕更新率和 65W SuperVOOC 2.0 超級閃充技術。

OPPO Reno5 、Reno5 Pro 正式在台發表:全新升級 AI 錄影、獨家 Reno Glow 2.0 星鑽工藝打造精緻外觀

外觀設計方面,除了 Reno5 機身保持在 7.9mm 、Reno5 Pro 更僅有 7.6mm 的極致輕薄設計,顏色方面這次推出「星夜黑」以及此次 OPPO 為 Reno5 採用更閃耀的「Reno Glow 2.0 星鑽工藝」技術的全新顏色「幻彩銀」。

幻彩銀配色的 Reno5 系列在機身膜片疊層工藝進行了創新,通過 Picasus 炫彩層、菲尼爾紋理層、反光層的疊加,在炫彩層 70mm 的光學反光膜,接著經過光線的穿透與折射,反光出上千種顏色。另外,兩層疊加進一步增強了色彩的亮度,因此在轉動機身時可呈現出上千種顏色的閃爍變化。

螢幕方面, OPPO Reno5 配備 6.43 吋 FHD+ 解析度 OLED 螢幕,支持 90Hz 螢幕更新率和 180Hz 觸控採樣率,局部峰值最高亮度為 750nit ; Reno5 Pro 則配備更大的 6.55 吋 FHD+ 解析度 OLED 曲面螢幕,支持 90Hz 螢幕更新率和 180Hz 觸控採樣率,局部峰值最高亮度則為 1100nit。

Reno5 和 Reno5 Pro 在相機規格方面, OPPO Reno5 系列搭載後置 6,400 萬像素人像四鏡頭主相機,分別為 6,400 萬像素標準鏡頭+800 萬像素超廣角鏡頭+200 萬像素微距鏡頭+200 萬像素人像黑白鏡頭。

前置相機則配備 3,200 萬像素自拍鏡頭。在相機功能方面, Reno5 系列在錄影部分升級 AI 錄影增強結合「超級動態夜景」和「Live HDR」功能,透過先進的 AI 演算法提升暗光畫面亮度和細節表現。

這次 Reno5 系列加入的「雙重錄影」讓使用者可同時開啟前後鏡頭進行影片錄製,並提供左右均分、上下舉行切割以及圓形三種模式可選擇,對於生活記錄也更加有趣。

美顏方面, Reno5 系列更支援 AI 錄影美顏,透過 AI 檢測可針對臉部標記多達 194 個特徵點,並提供八重 100 級的自訂美顏選項,包括摩陂、小臉、瘦臉、下巴、大眼、補妝、瘦鼻、立體等設定,打造全面的美顏優化效果。

AI 錄影美顏還能智慧識別天生特徵與後天瑕疵,將瑕疵部分處理掉保留像是美人痣等天生特徵。

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

Reno5 和 Reno5 Pro 在硬體規格方面略有差異, Reno5 搭載高通 Snapdragon 765G  5G 處理器、配備 8GB RAM 和 128GB ROM、內建等效 4300mAh 電池; Reno5 Pro 搭載聯發科天璣 1000+ 5G 處理器、配備 12GB RAM 和 256GB ROM、內建等效 4350mAh 電池。

OPPO Reno5 系列全面支援 65W SuperVOOC 2.0 超級閃充技術,只需充電 5 分鐘即可追劇 4 小時、充電 15 分鐘則最高可充電至 60% , Reno5 充電至 100% 只需要 35 分鐘、 Reno5 Pro 更只需要 30 分鐘即可充滿。

售價方面, OPPO Reno5 建議售價為 14,990 元;Reno5 Pro 建議售價則為 20,990 元:

OPPO Reno5 和 Reno5 Pro 於今(12)日起至 1 月 15 日 23:59 官方與電商通路開放預購、將於 1 月 16 日正式開賣, 1 月 25 日起則在各大電信通路全面開賣。

凡於指定通路預購 OPPO Reno5 、 Reno5 Pro 除可獲贈限量 Reno5 系列專屬禮盒,其中包含 OPPO Enco W31 無線藍牙耳機(市價 NT$1,899)和犀牛皮螢幕保護膜(市價 NT$890),另外在官網登錄再贈送 12 個月螢幕意外保障和 6 個月延長保固。

OPPO 也將於 1 月 16 日於以下指定通路舉辦首銷派對,現場排隊 VIP 交機限量加碼贈 7-Eleven 禮券,並可於現場抽獎還有機會免費獲得 OPPO Reno5(市價 NT$14,990)或 OPPO Watch Wi-Fi(市價 NT$8,990)。

OPPO Reno5|Reno5 Pro 官網預購頁面:點我前往

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

一個騷命令防止你的文件被誤刪除!_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

大家好,我是良許。

文件是我們在電腦里最珍貴的財富,我們經常工作了老半天,然後出來的成果就只是一個文件而已。特別是程序員,我們寫了半天的代碼,結果也就是一個個代碼文件而已。

但是,我們都有過這樣慘痛的經歷——工作了老半天,結果不小心把文件刪除了……

這時候,你的心情是怎樣的?痛苦?自責?無奈?

今天良許就給大家介紹一個命令,它將給你的重要文件加一把鎖,防止誤刪除或誤修改,為你的寶貝文件保駕護航!

chattr命令簡介

這裏我們需要使用到一個命令是:chattr ,這個命令在大部分的 Linux 發行版里都有,所以對於它的安裝就不贅述了。它的基本用法如下:

$ chattr 操作符 屬性 文件名

對於操作符,有以下三種:

  • + :給文件增加屬性
  • - :去除文件屬性
  • = :設置文件的僅有屬性

注意,這裏講的屬性不是文件對應的系統屬性,而是 chattr 給文件賦予的屬性。本文要講的屬性有以下兩個:

  • a – 允許給文件追加內容
  • i – 保護模式(不允許刪除或修改)

但是,它的屬性可以設置很多,有興趣的小夥伴可以去看下它的 man 手冊。

$ man chattr

防止文件被誤刪除或修改

假如我們現在有個國寶級重要文件 file.txt ,現在我們使用 chattr 對它進行保護。在這裏,我們給文件增加 +i 屬性:

$ sudo chattr +i file.txt

然後,我們可以使用 lsattr 命令查看它的屬性。類似的輸出如下:

$ lsattr file.txt
----i---------e---- file.txt

現在,我們來嘗試一下,手賤去刪除那個文件:

$ rm file.txt
rm: cannot remove 'file.txt': Operation not permitted

咦?不允許刪除?難道權限不夠?

那好,我 sudo 一下!

$ sudo rm file.txt
rm: cannot remove 'file.txt': Operation not permitted

我 X ,居然還是不能刪除?

我們再來試一下,修改文件的內容。

$ echo 'hello world!' >> file.txt
bash: file.txt: Operation not permitted

可以看出來,依然不能對文件進行修改了。

而且,即使你從 GUI 界面手動去刪除這個文件,也還是不能耐他幾何。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

所以,可以看出來,現在這個文件得到了很好的保護,既不能被刪除(各種方法都不行),也不能被修改。

那我們要怎麼去除這個保護呢?很簡單,只需加上 -i 這個選項。

$ sudo chattr -i file.txt

現在,文件又恢復原樣了,我們想修改就修改,想刪除就刪除。

$ echo 'Hello World!' >> file.txt
$ cat file.txt
Hello World!
$ rm file.txt

防止文件夾被誤刪除或修改

上面講到的是保護文件,那麼文件夾要如何保護呢?

其實也是一樣,使用 +i 這個選項。假如我們現在有個 dir1 目錄,裏面有個 file.txt 文件。我們來對這個文件夾進行保護。

$ sudo chattr -R +i dir1

在這裏,我們使用 -R 選項表示可以遞歸作用到目錄里所有的文件(包括子目錄)。

現在,我們同樣測試一下是否可以被刪除或修改。

$ rm -rf dir1
$ sudo rm -rf dir1
rm: cannot remove 'dir1/file.txt': Operation not permitted
$ echo 'hello world!' >> dir1/file.txt
bash: file.txt: Operation not permitted

所以與文件一樣,我們成功地對文件夾進行了保護。

防止文件/目錄被刪除,但允許追加內容

現在我們知道怎麼防止文件/目錄被誤刪除或修改了,但是,假如我們不想要文件已有內容被修改,但允許別人在文件末尾追加內容,要怎麼操作?

這時候我們就需要使用 +a 這個選項了。

對文件:

$ sudo chattr +a file.txt

對目錄:

$ sudo chattr -R +a dir1

現在,我們來確認一下,文件是否可以被追加內容。

$ echo 'Hello World!' >> file.txt
$ echo 'Hello World!' >> dir1/file.txt

我們再使用 cat 命令去查看一下內容:

$ cat file.txt
Hello World!
$ cat dir1/file.txt
Hello World!

可以看出來,文件都是可以被追加的。

但是,file.txt 還有 dir1/file.txt 依然不能被刪除。

如果你想去掉可追加的屬性,可以使用 -a 這個選項。

對文件:

$ sudo chattr -R -a file.txt

對目錄:

$ sudo chattr -R -a dir1/

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

這車開起來不但耐用省心,還超帶勁了!_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

8L自然吸氣版本,同時又能夠擁有低油耗的養車成本,1。2T渦輪增壓發動機的D-4T,再加上S-CVT無級變速器的平順性和燃油經濟性,可謂是“TS白金動力組合”,將油耗降到最低,同時還提供6擋手動變速器,比原1。6L車型更多的擋位數,能夠有效地降低油耗,百公里綜合油耗低至5。

對於我們普通百姓來說,買車時候只知道自己手頭有個8萬10萬的預算,買自然吸氣動力又肉,買外觀中庸的又怕被朋友嫌棄。確實,現在汽車產業發展飛快,人們對於如何選汽車變得越來越挑剔,車不僅要長得合心意,養起來還要夠省心,開起來的駕駛感受還要夠“有趣”,那麼開起來“有趣”,究竟是怎麼樣的感受,且聽慢慢為你敘說。

想成為人們心中有“駕趣”的車,需要什麼條件?

特接地氣的按一位購車消費者的說法:“,給我推薦一個好開,省油,耐用的10萬級代步車”。

無疑地,這是無數購車消費者買車時共同的心思,單獨地來解釋一下這6個字所涵蓋的意義,“好開”意味着擁有隨心所欲的行駛性能,“省油”意味着優越的燃油經濟性,而“耐用”意味着高品質的造車工藝。所以“駕趣”這詞被衍生開來,也就是說“好開省油耐用”即是大多數人所認為的“駕趣”。

“好開,省油,耐用的10萬級車”難選嗎?

其實並不難,相信大家都對豐田雷凌有着很深的印象,有着1.6L,1.8L自然吸氣版本和雙擎混合動力版本,其實這2個動力總成已經足夠讓許多人嚮往,而在今天,豐田最新的1.2T發動機技術也搭載到雷凌身上啦,又為廣大消費者造福利,以往雷凌1.6L原車型動力剛好夠用,現在搭載1.2T發動機的動力更強,最大功率85kw,峰值扭矩達到185牛米,強勁的初中段加速,動力持續提升,在1500轉既能夠達到最大扭矩,搭配8速S-CVT無級變速器,帶有獨特的柔性鎖止控制技術,動力傳輸更加直接,操控性大幅增加,這就是“好開”帶來的“駕趣”了。

低排量渦輪增壓發動機是以後代步級別的發展趨勢,既能夠保證足夠的動力輸出,扭矩足以超越1.6L和1.8L自然吸氣版本,

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

同時又能夠擁有低油耗的養車成本,1.2T渦輪增壓發動機的D-4T,再加上S-CVT無級變速器的平順性和燃油經濟性,可謂是“TS白金動力組合”,將油耗降到最低,同時還提供6擋手動變速器,比原1.6L車型更多的擋位數,能夠有效地降低油耗,百公里綜合油耗低至5.4L,不易積碳不燒機油,這就是“省油”帶來的“駕趣”了。

除了好開,省油之外,當然在質量可靠性上,並不希望買回來新車一兩年就成為修理廠的常客,故障率一定要低,豐田一直秉承着精益求精的造車理念,近乎苛刻的造車工藝,在2015年度“豐田出貨品質監查”結果中,廣汽豐田兩條生產線的整車品質都獲得了“零缺陷率”的評價,所以廣汽豐田旗下車型雷凌,也頗受市場的熱寵,這就是“耐用”帶來的“駕趣”了。

雷凌Turbo除了動力不同,還有什麼優勢賣點呢?

在外觀上,雷凌Turbo與現款沒有什麼太大的區別,尾部多了一個“D-4T”的標誌,這是豐田對於自己缸內直噴+渦輪增壓的技術命名,豪華版車型搭配LED日間行車燈,內飾上座椅多了黑色棕紅色的顏色搭配,車門扶手處加以紅色縫線,座椅靠背處還加了“LEVIN”的車型標識,讓這款加了“T”的雷凌更加具備運動風,種種現象告訴着你:我將已不在是以前那個我了!

雷凌Turbo擁有穩固紮實的底盤,採用前麥弗遜,后扭力梁式懸挂,對前後懸架減震器的阻尼進行了調教,彈簧剛度進行優化,能夠應付中國多種非鋪裝路面和不平整路面的特點。懸挂系統的振動過濾性能,彎道抗側傾性能均具有優異的表現。

配置升級!重要的事情只說一遍,新款雷凌Turbo和1.8L版本,全系標配智能節油啟停系統,VSC車身穩定控制系統和TRC牽引力控制系統,實實在在地將一輛車的安全配置落實到底,配置升級不僅如此,還有這HAC上坡啟動輔助控制系統,能夠在坡道上時維持剎車原地定住2秒防止車輛后溜,再也不怕上坡溜尾遭後車按喇叭啦。

雷凌作為廣汽豐田中小型車戰略中的首款中級車,目前全系月均銷量接近1.4萬台,累計銷 量已突破40萬台,隨着雷凌Turbo上市,雷凌家族形成涵蓋1.2T直噴渦輪增壓,1.8L自然吸氣發動機和混合動力的全擎動力矩陣,覆蓋10-16萬主流價格區間,滿足不同需求的用戶群體,Turbo版本上市,將會對市場低排量發動機市場帶來一波衝擊。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

本年度試過最深刻的SUV竟是一台國產車_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

路虎SVR作為一輛極其暴力的性能SUV,其5。0L V8發動機能爆發出550馬力的動力,而且輸出非常直接,只要你深踩油門,車子馬上就會發出炸裂整條街的聲浪,其暢快的感覺絕對讓你上癮。吉利近年推出的車型風評都非常不錯,博瑞被稱為最美中國車,而最美中國SUV則落到了博越身上。

上一期我們為大家送上了年度印象最深刻的性能車盤點,今天就為大家送上年度印象最深刻的SUV盤點。

奔馳GLA A45 AMG是一台性能SUV,同級對手就是已經在國外上市的RS Q3。可能不少人會覺得GLA A45 AMG就是一輛加高了的A45 AMG,開起來並無太多不同。實際上底盤高度加高了之後除了通過性提高了之外,整輛GLA A45 AMG的身板還是很硬挺的,過彎的時候你還是能很明顯感覺到它性能SUV的屬性,同時超過380馬力的動力輸出也能讓你享受一下推背感,當你遇到一些非鋪裝路面時,又能直接開過去,這種逆天的設定還真是有點吸引人呢。

Model X是特斯拉首款SUV,Model X p90D在狂暴模式下百公里加速僅為3.4秒,也是把特斯拉的超強加速能力繼承了下來。除此之後,其鷹翼式車門,可電動控制開關的車門,提供五座和七座車型,

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

加上一個回頭率極高的外觀,Model X絕對適合一些喜歡獨特又能接受新事物的車主。

雖然車底有一個大電機,增加了不少車重,但也讓Model X的重心壓低了,行駛過程的穩定性也提高了不少,對於想偶爾激烈駕駛一下的朋友來說,Model X也是能應付得到的。

路虎SVR作為一輛極其暴力的性能SUV,其5.0L V8發動機能爆發出550馬力的動力,而且輸出非常直接,只要你深踩油門,車子馬上就會發出炸裂整條街的聲浪,其暢快的感覺絕對讓你上癮。

吉利近年推出的車型風評都非常不錯,博瑞被稱為最美中國車,而最美中國SUV則落到了博越身上。博越的設計風格越趨成熟,加上2.0L/1.8T發動機和6AT變速箱的動力單元,其匹配程度非常不錯,果然自主品牌還是得在變速箱上下功夫。

而博越的行駛質感也對得起這個價位,雖然操控上來說不會太理想,但是作為SUV,博越還是提供了不錯的舒適性,如果平常有照顧家人的需要,那麼博越是值得考慮的一款自主SUV。

好了這期盤點就到這了,請期待下一期家用車盤點。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司