停止依賴中國稀土 美國防部提案擴大投資上限

摘錄自2020年5月19日自由時報報導

美國國防部已對國會提出一項提案,擴大對稀土的投資上限,以停止對中國的依賴程度,這些稀土可以用來製造飛彈和彈藥、極音速武器,及相關電子產品。如果美國可以重新生產稀土,中國打「稀土牌」的威脅程度將大幅降低。

根據《國防新聞》報導,美國國防部希望提高《國防生產法》的支出上限,在開採稀土上提升至最高17.5億美元(約新台幣523億),在微電子晶片上增至3.5億美元(約新台幣104億),當涉及到極音速武器時,將會沒有上限。據悉,此提案已於本月初提出,已納入國會正在起草的年度國防政策法案。

美國防部副部長洛德(Ellen Lord)去年8月曾表示,國防部正與澳洲進行談判,要求其為美軍提供稀土。澳洲Lynas公司擁有稀土礦,同時在馬來西亞也有精煉廠,可能是此計劃的核心。

環境經濟
循環經濟
國際新聞
美國
稀土
礦業

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

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

針對的都是首次購車的年輕人,這倆合資小型SUV該怎麼選?

但本田擅長的空間攫取能力則得以比較充分的體現,儘管XR-V的車身三圍尺寸並不算大,但在乘坐空間的表現上,好感度要比同級別的昂科拉更勝一籌。動力:非常經典的對決別克昂科拉與本田XR-V的對比也可以說是很多人十分在意的“渦輪增壓與自然吸氣的對比”,別克昂科拉採用1。

別克昂科拉 VS 本田XR-V?

當下汽車設計的主流也跟汽車消費的主流趨勢一樣,越來越趨於年輕化,別克昂科拉和東風本田XR-V則都是設計偏向年輕時尚的小型SUV,也是很多首次購車人群在合資小型SUV這個細分市場的首要考慮對象車型,那麼這兩款車型各自優勢在哪?又該如何選擇?

由於兩車在指導價格方面差距還是比較大,別克昂科拉明顯偏貴,而且別克昂科拉的細分車型當中有一款四驅配置車型,反觀XR-V全系沒有四驅標配,我們去除別克朗科拉頂配四驅旗艦型,採用兩驅都市精英型(次頂配),與XR-V 1.8L CVT豪華版(頂配)對比,兩者指導價格和配置更加接近。

東風本田-XR-V

1.8L VTi CVT豪華版

指導價格:16.28萬

別克昂科拉

18T 自動兩驅都市精英型

指導價格:16.99萬

外觀:敦實沉穩VS時尚運動

昂科拉的整體外觀變化並不大,主要的變化在於前臉,車標加上飛翼式鍍鉻綬帶的裝飾安置於直瀑式中網上,鍍鉻綬帶還與前大燈模塊內的日間行車燈融為一體,這讓昂科拉的前臉顯得更加具有辨識度;

整車還是維持了昂科拉一貫的敦實形象,這麼一款尺寸不大的小型SUV看上去會讓人有一種沉穩紮實的感覺,腰線和車尾線條飽滿,配合上原廠提供的較為具有活力的配色,昂科拉的造型也透露出一種時尚的動感。

XR-V使用了當下本田家族式的上下雙條幅鍍鉻裝飾作為前臉主體設計元素,配合上銳利的大燈和大面積的黑色塑料裝飾,本田XR-V前臉顯得運動感與攻擊性都更為明顯。

相較於昂科拉的“敦厚”,XR-V的整車線條則顯得有些“扁”,這種效果就會使得車身側面的視覺效果顯得更加修長,車尾設計線條層次感豐富,橫向線條多次運用但不會顯得雜亂和複雜,反而更提升了XR-V年輕的效果。

內飾:各家所長得以充分體現

別克家族近年來的內飾營造手段是體現在全系車型上的,昂科拉亦是如此,乍一眼看上去,別克昂科拉的內飾設計很容易給人以好感,而且在用料的選材和裝配上,別克昂克拉的內飾顯得比較高檔。

而XR-V的內飾設計感或許稍微欠缺,而且由於成本所限,拼接裝飾板材質較硬,在對比體驗上說或許溝通感有所減分。但本田擅長的空間攫取能力則得以比較充分的體現,儘管XR-V的車身三圍尺寸並不算大,但在乘坐空間的表現上,好感度要比同級別的昂科拉更勝一籌。

動力:非常經典的對決

別克昂科拉與本田XR-V的對比也可以說是很多人十分在意的“渦輪增壓與自然吸氣的對比”,別克昂科拉採用1.4T渦輪增壓發動機配合6速手自一體的動力總成。最大馬力143匹,峰值扭矩205牛米。

昂科拉的峰值扭矩平台在發動機達到1800轉的時候得以爆發,對於這個級別的小排量渦輪增壓車型來說中規中矩,兩驅版本的車重1.4噸,動力輸出也不會因此而感到拖沓,換擋效率較高的手自一體變速箱也賦予了昂科拉一定的駕駛樂趣。

本田XR-V所搭載的是一台1.8L自然吸氣發動機,最大馬力136匹,峰值扭矩169牛米,與之配合的是一台CVT無極變速箱,整體駕駛感受保留了一台CVT該有的平順特性,駕駛樂趣的話,小編覺得談不上,但踏實平穩才是一款CVT車型該有的基調。

而且由於XR-V整備質量不大,頂配CVT豪華版車重1.3噸,加之本田CVT變速箱一貫良好的加速性能,XR-V在城市裡通勤的表現也是非常靈活,駕駛起來沒有太大難度。

哪個更值得買?

如果看重的是整車所給予人的質感和高級感方面,別克昂科拉是比較好的選擇,畢竟在車內裝飾用料和整車NVH的控制上,別克昂科拉可以說做到了領先於同級別其他車型的水準,昂科拉所給予人的是更高檔的使用感受。

而如果更看重的是車內更實用的空間表現和駕駛起來的平順性的話,本田XR-V或許更值得考慮,畢竟這兩塊是本田這個品牌所擅長的領域,而且在XR-V上也得以較好的體現,無論是乘坐空間還是載物空間,本田依舊展現了較強的空間利用實力,在動力平順性上,本田的CVT變速箱和自然吸氣發動機配合出來的線性感受也不會使人失望。

全文總結:儘管2017款的昂科拉在12月6日才剛剛上市,但是外觀配置並沒有過多的變化,而且在售價上與2016款幾乎齊平;加之在終端優惠上,別克全系車型當下的終端優惠普遍不小,而反觀日系的XR-V,降價的幅度則比較有限,所以如果從終端優惠上看,或許選擇折扣更大的別克昂科拉也是個不錯的想法。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

一文看懂人臉識別算法技術發展脈絡

【摘要】我們從人臉識別技術的技術細節講起,帶你初步了解人臉識別技術的發展過程。通過平台實例的操作,帶你看看如何利用公有雲的計算資源,快速訓練一個可用的人臉識別模型。

前言

大家應該都看過布拉德.伯德執導、湯姆.克魯斯主演的《碟中諜4吧》?茫茫人海的火車站,只要一眨眼的功夫已經被計算機識別出來,隨即被特工盯梢;迎面相逢的美女是致命殺手,手機發出嘀嘀的報警聲,上面已經显示美女的姓名和信息。這就是本文想要介紹的人臉識別算法,以及如果使用公有雲AI平台訓練模型。

作為目前人工智能領域中成熟較早、落地較廣的技術之一,人臉識別的目的是要判斷圖片和視頻中人臉的身份。從平常手機的刷臉解鎖、刷臉支付,再到安防領域內的人臉識別布控,等等,人臉識別技術都有着廣泛的應用。人臉是每個人與生俱來的特徵,該特徵具有唯一性並且不易被複制,從而為身份鑒別提供了必要的前提。

人臉識別的研究始於20世紀60年代,隨着計算機技術和光學成像技術的發展不斷提高,以及近幾年神經網絡技術的再次興起,尤其是卷積神經網絡在圖像識別和檢測中取得的巨大成功,使得人臉識別系統的效果得到了極大的提升。本文,我們從人臉識別技術的技術細節講起,帶你初步了解人臉識別技術的發展過程,文章的後半篇,我們將會使用ModelArts平台的自定義鏡像,帶你看看如何利用公有雲的計算資源,快速訓練一個可用的人臉識別模型。

正文

不管是基於傳統圖像處理和機器學習技術,還是利用深度學習技術,其中的流程都是一樣的。如圖1所示,人臉識別系統都包括人臉檢測、對齊、編碼以及匹配四個基本環節組成。所以該部分首先通過對基於傳統圖像處理和機器學習算法的人臉識別系統進行概述,就可以看出整個深度學習算法在人臉識別領域內發展的脈絡。

人臉檢測流程

傳統機器學習算法

前面已經說過,人臉識別的目的就是要判斷圖像中的人臉身份是什麼,所以就首先需要先把圖像中的人臉檢測出來,其實這一步歸根結底就是一個目標檢測的問題。傳統的圖像目標檢測算法主要有三部分組成,建議框生成、特徵工程以及分類,包括著名的RCNN系列算法的優化思路也是基於這三部分進行的。

首先是建議框生成,該步驟最簡單的想法就是在圖片中crop出來一堆待檢測框,然後檢測該框內是否存在目標,如果存在,則該框在原圖中的位置即為目標檢測出的位置,因此在該步驟中對目標的覆蓋率越大,則建議框生成策略越好。常見的建議框生成策略有sliding window、Selective Search、Randomized Prim等等,生成大量的候選框,如下圖所示。

得到大量的候選框后,傳統的人臉檢測算法接下來最主要的部分就是特徵工程。特徵工程其實就是利用算法工程師的專家經驗對不同場景的人臉提取各種特徵,例如邊緣特徵、形狀形態學特徵、紋理特徵等等,具體的算法是技術有LBP、Gabor、Haar、SIFT等等特徵提取算法,將一張以二維矩陣表示的人臉圖片轉換成各種特徵向量的表示。

得到特徵向量之後,就可以通過傳統的機器學習分類器對特徵進行分類,得到是否是人臉的判斷,例如通過adaboost、cascade、SVM、隨機森林等等。通過傳統分類器分類之後就可以得到人臉的區域、特徵向量以及分類置信度等等。通過這些信息,我們就可以完成人臉對齊、特徵表示以及人臉匹配識別的工作。

以傳統方法中,經典的HAAR+AdaBoost的方法為例,在特徵提取階段,首先會利用haar特徵在圖片中提取出很多簡單的特徵。Haar特徵如下圖所示。為了滿足不同大小人臉的檢測,通常會利用高斯金字塔對不同分辨率的圖像進行Haar特徵的提取。

Haar特徵的計算方法是將白色區域內的像素和減去黑色區域,因此在人臉和非人臉的區域內,得到的值是不一樣的。一般在具體實現過程中,可以通過積分圖的方法快速實現。一般在歸一化到20*20的訓練圖片中,可供使用的Haar特徵數在一萬個左右,因此在這種特徵規模的情況下,可以利用機器學習的算法進行分類和識別。

得到Haar特徵后,可以利用Adaboost進行分類,Adaboost算法是一種將多個比較弱的分類方法合在一起,組合出新的強分類方法。根據該級聯分類器,和訓練好的各個特徵選擇閾值,就可以完成對人臉的檢測。

從上述方法可以看出,傳統的機器學習算法是基於特徵的算法,因此需要大量的算法工程師的專家經驗進行特徵工程和調參等工作,算法效果也不是很好。而且人工設計在無約束環境中對不同變化情況都魯棒很困難的。過去的圖像算法是工程師更多的是通過傳統的圖像處理方法,根據現實場景和專家經驗提取大量的特徵,然後對提取的特徵再進行統計學習的處理,這樣整體算法的性能就非常依賴於現實場景和專家經驗,對於人臉這種類別巨大,每類樣本不均衡情況嚴重的無約束場景效果並不是很好。因此,近幾年隨着深度學習在圖像處理中取得的巨大成功,人臉識別技術也都以深度學習為主,並且已經達到了非常好的效果。

深度學習在人臉識別領域的應用

在深度學習的人臉識別系統中,該問題被分成了一個目標檢測問題和一個分類問題,而目標檢測問題在深度學習中本質還是一個分類問題和回歸問題,因此隨着卷積神經網絡在圖片分類上的成功應用,人臉識別系統的效果得到了快速且巨大的提升,並以此誕生了大量的視覺算法公司,並將人臉識別應用在了社會生活的各個方面。

其實利用神經網絡來做人臉識別並不是什麼新思想,1997年就有研究者為人臉檢測、眼部定位和人臉識別提出了一種名為基於概率決策的神經網絡的方法。這種人臉識別 PDBNN 被分成了每一個訓練主體一個全連接子網絡,以降低隱藏單元的數量和避免過擬合。研究者使用密度和邊特徵分別訓練了兩個 PBDNN,然後將它們的輸出組合起來得到最終分類決定。但是受限於當時算力和數據的嚴重不足,算法相對簡單,因此該算法並沒有得到很好的效果。隨着僅今年反向傳播理論和算力框架等的日趨成熟,人臉識別算法的效果才開始得到巨大的提升。

在深度學習中,一個完整的人臉識別系統也包括圖1所示的四個步驟,其中第一步驟叫做人臉檢測算法,本質也是一個目標檢測算法。第二個步驟叫做人臉對齊,目前又基於關鍵點的幾何對齊和基於深度學習的人臉對齊。第三個步驟特徵表示,在深度學習中是通過分類網絡的思想,提取分類網絡中的一些feature層作為人臉的特徵表示,然後用相同的方式對標準人臉像進行處理,最後通過比對查詢的方式完成整體的人臉識別系統。下面主要對人臉檢測和人臉識別算法的發展進行簡單綜述。

人臉檢測

深度學習在圖像分類中的巨大成功后很快被用於人臉檢測的問題,起初解決該問題的思路大多是基於CNN網絡的尺度不變性,對圖片進行不同尺度的縮放,然後進行推理並直接對類別和位置信息進行預測。另外,由於對feature map中的每一個點直接進行位置回歸,得到的人臉框精度比較低,因此有人提出了基於多階段分類器由粗到細的檢測策略檢測人臉,例如主要方法有Cascade CNN、 DenseBox和MTCNN等等。

MTCNN是一個多任務的方法,第一次將人臉區域檢測和人臉關鍵點檢測放在了一起,與Cascade CNN一樣也是基於cascade的框架,但是整體思路更加的巧妙合理,MTCNN總體來說分為三個部分:PNet、RNet和ONet,網絡結構如下圖所示。

首先PNet網絡對輸入圖片resize到不同尺寸,作為輸入,直接經過兩層卷積后,回歸人臉分類和人臉檢測框,這部分稱之為粗檢測。將粗檢測得到的人臉從原圖中crop出來后,在輸入的R-Net,再進行一次人臉檢測。最後將得到的人臉最終輸入O-Net,得到的O-Net輸出結果為最終的人臉檢測結果。MTCNN整體流程相對比較簡單,能夠快速的進行部署和實現,但是MTCNN的缺點也很多。包括多階段任務訓練費時,大量中間結果的保存需要佔用大量的存儲空間。另外,由於改網絡直接對feature點進行bounding box的回歸,對於小目標人臉檢測的效果也不是很好。還有,該網絡在推理的過程中為了滿足不同大小人臉檢測需要,要將人臉圖片resize到不同尺寸內,嚴重影響了推理的速度。

隨着目標檢測領域的發展,越來越多的實驗證據證明目標檢測中更多的瓶頸在於底層網絡語義低但定位精度相對較高和高層網絡語義高但定位精度低的矛盾,目標檢測網絡也開始流行anchor-based的策略和跨層融合的策略,例如著名的Faster-rcnn、SSD和yolo系列等。因此,人臉檢測算法也越來越多的利用anchor和多路輸出來滿足不同大小人臉檢出的效果,其中最著名的算法就是SSH網絡結構。

從上圖中可以看出,SSH網絡已經有對不同網絡層輸出進行處理的方法,只需要一遍推理就能完成不同大小人臉的檢測過程,因此稱之為Single Stage。SSH的網絡也比較簡單,就是對VGG不同卷積層驚醒了分支計算並輸出。另外還對高層feature進行了上採樣,與底層feature做Eltwise Sum來完成底層與高層的特徵融合。另外SSH網絡還設計了detection module和context module,其中context module作為detection module的一部分,採用了inception的結構,獲取更多上下文信息以及更大的感受野。

SSH中的detection module模塊SSH中detection module里的context module模塊

SSH利用1×1卷積對輸出最終的回歸和分類的分支結果,並沒有利用全連接層,因此可以保證不同尺寸圖片的輸入都能得到輸出的結果,也是響應了當時全卷積設計方式的潮流。遺憾的是該網絡並沒有輸出landmark點,另外其實上下文結構也沒有用到比較流行的特徵金字塔結構,VGG16的backbone也相對較淺,隨着人臉優化技術的不斷進行,各種各樣的trick也都日趨成熟。因此,最後向大家介紹一下目前人臉檢測算法中應用比較廣的Retinaface網絡。

Retinaface由google提出,本質是基於RetinaNet的網絡結構,採用特徵金字塔技術,實現了多尺度信息的融合,對檢測小物體有重要的作用。網絡結構如下所示。

從上圖可以看出,Retinaface的backbone網絡為常見的卷積神經網絡,然後加入特徵金子塔結構和Context Module模塊,進一步融合上下文的信息,並完成包括分類、檢測、landmark點回歸以及圖像自增強的多種任務。

因為人臉檢測的本質是目標檢測任務,目標檢測未來的方向也適用於人臉的優化方向。目前在目標檢測中小目標、遮擋目標的檢測依舊很困難,另外大部份檢測網絡更多的開始部署在端側,因此基於端側的網絡模型壓縮和重構加速等等更加考驗算法工程師對與深度學習檢測算法的理解和應用。

人臉識別

人臉識別問題本質是一個分類問題,即每一個人作為一類進行分類檢測,但實際應用過程中會出現很多問題。第一,人臉類別很多,如果要識別一個城鎮的所有人,那麼分類類別就將近十萬以上的類別,另外每一個人之間可獲得的標註樣本很少,會出現很多長尾數據。根據上述問題,要對傳統的CNN分類網絡進行修改。

我們知道深度卷積網絡雖然作為一種黑盒模型,但是能夠通過數據訓練的方式去表徵圖片或者物體的特徵。因此人臉識別算法可以通過卷積網絡提取出大量的人臉特徵向量,然後根據相似度判斷與底庫比較完成人臉的識別過程,因此算法網絡能不能對不同的人臉生成不同的特徵,對同一人臉生成相似的特徵,將是這類embedding任務的重點,也就是怎麼樣能夠最大化類間距離以及最小化類內距離。

在人臉識別中,主幹網絡可以利用各種卷積神經網絡完成特徵提取的工作,例如resnet,inception等等經典的卷積神經網絡作為backbone,關鍵在於最後一層loss function的設計和實現。現在從兩個思路分析一下基於深度學習的人臉識別算法中各種損失函數。

思路1:metric learning,包括contrastive loss, triplet loss以及sampling method

思路2:margin based classification,包括softmax with center loss, sphereface, normface, AM-sofrmax(cosface) 和arcface。

1. Metric Larning

(1)Contrastive loss

深度學習中最先應用metric learning思想之一的便是DeepID2了。其中DeepID2最主要的改進是同一個網絡同時訓練verification和classification(有兩個監督信號)。其中在verification loss的特徵層中引入了contrastive loss。

Contrastive loss不僅考慮了相同類別的距離最小化,也同時考慮了不同類別的距離最大化,通過充分運用訓練樣本的label信息提升人臉識別的準確性。因此,該loss函數本質上使得同一個人的照片在特徵空間距離足夠近,不同人在特徵空間里相距足夠遠直到超過某個閾值。(聽起來和triplet loss有點像)。

Contrastive loss引入了兩個信號,並通過兩個信號對網絡進行訓練。其中識別信號的表達式如下:

驗證信號的表達式如下:

基於這樣的信號,DeepID2在訓練的時候就不是以一張圖片為單位了,而是以Image Pair為單位,每次輸入兩張圖片,為同一人則為1,如果不是同一人則為-1.

(2)Triplet loss from FaceNet

這篇15年來自Google的FaceNet同樣是人臉識別領域分水嶺性質的工作。它提出了一個絕大部分人臉問題的統一解決框架,即:識別、驗證、搜索等問題都可以放到特徵空間里做,需要專註解決的僅僅是如何將人臉更好的映射到特徵空間。

Google在DeepID2的基礎上,拋棄了分類層即Classification Loss,將Contrastive Loss改進為Triplet loss,只為了一個目的:學習到更好的feature。

直接貼出Triplet loss的損失函數,其輸入的不再是Image Pair,而是三張圖片(Triplet),分別為Anchor Face, Negative Face和Positive Face。Anchor與Positive Face為同一人,與Negative Face為不同的人。那麼Triplet loss的損失函數即可表示為:

該式子的直觀解釋為:在特徵空間里Anchor與Positive的距離要小於Anchor與Negative的距離並超過一個Margin Alpha。他與Contrastive loss的直觀區別由下圖所示。

(3)Metric learning的問題

上述的兩個loss function效果很不錯,而且也符合人的客觀認知,在實際項目中也有大量的應用,但該方法仍有一些不足之處。

  • 模型訓練依賴大量的數據,擬合過程很慢。由於contrastive loss和triplet loss都是基於pair或者triplet的,需要準備大量的正負樣本,,訓練很長時間都不可能完全遍歷所有可能的樣本間組合。網上有博客說10000人、500000張左右的亞洲數據集上花一個月才能完成擬合。
  • Sample方式影響模型的訓練。比如對於triplet loss來說,在訓練過程中要隨機的採樣anchor face, negative face以及positive face,好的樣本採樣能夠加快訓練速度和模型收斂,但是在隨機抽取的過程中很難做到非常好。
  • 缺少對hard triplets的挖掘,這也是大多數模型訓練的問題。比如說在人臉識別領域中,hard negatives表示相似但不同的人,而hard positive表示同一個人但完全不同的姿態、表情等等。而對hard example進行學習和特殊處理對於提高識別模型的精度至關重要。

2. 對於Metric Learning不足進行修正的各種trick

(1)Finetune

參考論文:Deep Face Recognition

在論文《Deep Face Recognition》中,為了加快triplet loss的訓練,坐着先用softmax訓練人臉識別模型,然後移除頂層的classification layer,然後用triplet loss對模型進行特徵層finetune,在加速訓練的同時也取得了很不錯的效果。該方法也是現在訓練triplet loss時最常用的方法。

(2)對Triplet loss的修改

參考論文:In Defense of the Triplet Loss for Person Re-Identification

該作者說出了Triplet loss的缺點。對於Triplet loss訓練所需要的一個三元組,anchor(a)、positive(p)、negative(n)來說,需要從訓練集中隨機挑選。由於loss function的驅動,很有可能挑選出來的是很簡單的樣本組合,即很像的正樣本以及很不像的負樣本,而讓網絡一直在簡單樣本上進行學習,會限制網絡的范化能力。因此坐着修改了triplet loss並添加了新的trick,大量實驗證明,這種改進版的方法效果非常好。

在Google提供的facenet triplet loss訓練時,一旦選定B triplets集合,數據就會按照順序排好的3個一組,那麼總共的組合就有3B種,但是這些3B個圖像實際上有多達種有效的triplets組合,僅僅使用3B種就很浪費。

在該片論文中,作者提出了一個TriHard loss,其核心思想是在triplet loss的基礎上加入對hard example的處理:對於每一個訓練的batch, 隨機挑選P個ID的行人,每個行人隨機挑選K張不同的圖片,即一個batch含有P×K張圖片。之後對於batch中的每一張圖片a,我們可以挑選一個最難的正樣本和一個最難的負樣本和a組成一個三元組。首先我們定義和a為相同ID的圖片集為A,剩下不同ID的圖片圖片集為B,則TriHard損失表示為:

其中是人為設定的閾值參數。TriHard loss會計算a和batch中的每一張圖片在特徵空間的歐氏距離,然後選出與a距離最遠(最不像)的正樣本p和距離最近(最像)的負樣本n來計算三元組損失。其中d表示歐式距離。損失函數的另一種寫法如下:

另外,作者在輪中也提出了幾個實驗得到的觀點:

  • 平方后的歐式距離不如開方后的真實歐氏距離(後續會簡單提一下原因)
  • 提出了Soft-Margin損失函數替代原始的Triplet loss表達式,soft-margin能夠使得損失函數更加平滑,避免函數收斂在bad local處,能夠一定程度上加速算法收斂。
  • 引進了Batch Hard Sampling

該方法考慮了hard example後效果比傳統的triplet loss好。

(3)對loss以及sample方法的修改

參考論文:Deep Metric Learning via Lifted Structured Feature Embedding

該論文首先提出了現有的三元組方法無法充分利用minibatch SGD training的training batches的優勢,創造性的將the vector of pairwise distances轉換成the matrix of pairwise distance,然後設計了一個新的結構化損失函數,取得了非常好的效果。如下圖所示,是contrastice embedding,triplet embedding以及lifted structured embedding三種方式的採樣示意圖。

直觀上看,lifted structured embedding涉及的分類模式更多,作者為了避免大量數據造成的訓練困難,作者在此基礎上給出了一個結構化的損失函數。如下圖所示。

其中P是正樣本集合,N是負樣本集合。可以看到對比上述的損失函數,該損失函數開始考慮一個樣本集合的問題。但是,並不是所有樣本對之間的negative edges都攜帶了有用的信息,也就是說隨機採樣的樣本對之間的negative edges攜帶了非常有限的信息,因此我們需要設計一種非隨機的採樣方法。

通過上述的結構化損失函數我們可以看到,在最終計算損失函數時,考慮了最像和最不像的hard pairs(也就是損失函數中max的用處),也就相當於在訓練過程中添加了difficult neighbors的信息了訓練mini-batch,通過這種方式訓練數據能夠大概率的搜尋到hard negatives和hard positives的樣本,而隨着訓練的不斷進行,對hard樣本的訓練也將實現最大化類間距離和最小化類內距離的目的。

如上圖所示,該文章在進行metric learning的時候並沒有隨機的選擇sample pairs,而是綜合了多類樣本之間較難區分者進行訓練。此外,文中還提到了以為的尋求max的過程或者尋求single hardest negative的過程會導致網絡收斂到一個bad local optimum,我猜想可能是因為max的截斷效應,使得梯度比較陡峭或者梯度間斷點過多。作者進一步改進了loss function,採用了smooth upper bound,即下式所示。

(4)對sample方式和對triplet loss的進一步修改

參考論文:Sampling Matters in Deep Embedding Learning

  • 對採樣方式的修改

文章指出hard negative樣本由於anchor的距離較小,這是如果有噪聲,那麼這種採樣方式就很容易受到噪聲的影響,從而造成訓練時的模型坍塌。FaceNet曾經提出一種semi-hard negative mining的方法,它提出的方法是讓採樣的樣本不是太hard。但是根據作者的分析認為,sample應該在樣本中進行均勻的採樣,因此最佳的採樣狀態應該是在分散均勻的負樣本中,既有hard,又有semi-hard,又有easy的樣本,因此作者提出了一種新的採樣方法Distance weighted sampling。

在現實狀態下,我們隊所有的樣本進行兩兩採樣,計算其距離,最終得到點對距離的分佈有着如下的關係:

那麼根據給定的距離,通過上述函數的反函數就可以得到其採樣概率,根據該概率決定每個距離需要採樣的比例。給定一個anchor,採樣負例的概率為下式:

由於訓練樣本與訓練梯度強相關,因此作者也繪製出了採樣距離、採樣方法與數據梯度方差的關係,如下圖所示。從圖中可以看出,hard negative mining方法採樣的樣本都處於高方差的區域,如果數據集中有噪聲的話,採樣很容易受到噪聲的影響,從而導致模型坍塌。隨機採樣的樣本容易集中在低方差的區域,從而使得loss很小,但此時模型實際上並沒有訓練好。Semi-hard negative mining採樣的範圍很小,這很可能導致模型在很早的時候就收斂,loss下降很慢,但實際上此時模型也還沒訓練好;而本文提出的方法,能夠實現在整個數據集上均勻採樣。

  • 對loss function的修改

作者在觀察constractive loss和triplet loss的時候發現一個問題,就是負樣本在非常hard的時候loss函數非常的平滑,那麼也就意味着梯度會很小,梯度小對於訓練來說就意味着非常hard的樣本不能充分訓練,網絡得不到hard樣本的有效信息,因此hard樣本的效果就會變差。所以如果在hard樣本周圍loss不是那麼平滑,也就是深度學習中經常用的導數為1(像relu一樣),那麼hard模式會不會就解決了梯度消失的問題。另外loss function還要實現triplet loss對正負樣本的兼顧,以及具備margin設計的功能,也就是自適應不同的數據分佈。損失函數如下:

我們稱anchor樣本與正例樣本之間的距離為正例對距離;稱anchor樣本與負例樣本之間的距離為負例對距離。公式中的參數beta定義了正例對距離與負例對距離之間的界限,如果正例對距離Dij大於beta,則損失加大;或者負例對距離Dij小於beta,損失加大。A控制樣本的分離間隔;當樣本為正例對時,yij為1,樣本為負例對時,yij為-1。下圖為損失函數曲線。

從上圖可以看出為什麼在非常hard的時候會出現梯度消失的情況,因為離0點近的時候藍色的線越來越平滑,梯度也就越來越小了。另外作者對的設置也進行了調優,加入了樣本偏置、類別偏置以及超參,對損失函數進一步優化,能夠根據訓練過程自動修改的值。

3. Margin Based Classification

Margin based classification不像在feature層直接計算損失的metric learning那樣對feature加直觀的強限制,是依然把人臉識別當 classification 任務進行訓練,通過對 softmax 公式的改造,間接實現了對 feature 層施加 margin 的限制,使網絡最後得到的 feature 更 discriminative。

(1)Center loss

參考論文:A Discriminative Feature Learning Approach for Deep Face Recognition

ECCV 2016的這篇文章主要是提出了一個新的Loss:Center Loss,用以輔助Softmax Loss進行人臉的訓練,為了讓同一個類別壓縮在一起,最終獲取更加discriminative的features。center loss意思即為:為每一個類別提供一個類別中心,最小化min-batch中每個樣本與對應類別中心的距離,這樣就可以達到縮小類內距離的目的。下圖為最小化樣本和類別中心距離的損失函數。

為每個batch中每個樣本對應的類別中心,和特徵的維度一樣,用歐式距離作為高維流形體距離表達。因此,在softmax的基礎上,center loss的損失函數為:

個人理解Center loss就如同在損失函數中加入了聚類的功能,隨着訓練的進行,樣本自覺地聚類在每一個batch的中心,進一步實現類間差異最大化。但是我覺得,對於高維特徵,歐氏距離並不能反映聚類的距離,因此這樣簡單的聚類並不能在高維上取得更好的效果。

(2)L-Softmax

原始的Softmax的目的是使得,將向量相乘的方式變換為向量的模與角度的關係,即,在這個基礎上,L-Softmax希望可以通過增加一個正整數變量m,可以看到:

使得產生的決策邊界可以更加嚴格地約束上述不等式,讓類內的間距更加的緊湊,讓類間的間距更加有區分性。所以基於上式和softmax的公式,可以得到L-softmax的公式為:

由於cos是減函數,所以乘以m會使得內積變小,最終隨着訓練,類本身之間的距離會增大。通過控制m的大小,可以看到類內和類間距離的變化,二維圖显示如下:

作者為了保障在反向傳播和推理過程中能夠滿足類別向量之間的角度都能夠滿足margin的過程,並保證單調遞減,因此構建了一種新的函數形式:

有人反饋L-Softmax調參難度較大,對m的調參需要反覆進行,才能達到更好的效果。

(3)Normface

參考論文:NormFace: L2 Hypersphere Embedding for Face Verification

這篇論文是一篇很有意思的文章,文章對於權重與特徵歸一化做了很多有意思的探討。文章提出,sphereface雖然好,但是它不優美。在測試階段,sphereface通過特徵間的餘弦值來衡量相似性,即以角度為相似性度量。但在訓練過程中也有一個問題,權重沒有歸一化,loss function在訓練過程中減小的同時,會使得權重的模越來越大,所以sphereface損失函數的優化方向並不是很嚴謹,其實優化的方向還有一部分去增大特徵的長度了。有博主做實驗發現,隨着m的增大,坐標的尺度也在不斷增大,如下圖所示。

因此作者在優化的過程中,對特徵做了歸一化處理。相應的損失函數也如下所示:

其中W和f都為歸一化的特徵,兩個點積就是角度餘弦值。參數s的引入是因為數學上的性質,保證了梯度大小的合理性,原文中有比較直觀的解釋,可以閱讀原論文,並不是重點。s既可以變成可學習的參數,也可以變成超參,論文作者給了很多推薦值,可以在論文中找到。其實,FaceNet中歸一化的歐氏距離,和餘弦距離是統一的。

4. AM-softmax/CosFace

參考論文:Additive Margin Softmax for Face Verification

CosFace: Large Margin Cosine Loss for Deep Face Recognition

看上面的論文,會發現少了一個東西,那就是margin,或者說是margin的意味少了一些,所以AM-softmax在歸一化的基礎上有引入了margin。損失函數如下:

直觀上來看,-m比更小,所以損失函數值比Normface里的更大,因此有了margin的感覺。m是一個超參數,控制懲罰,當m越大,懲罰越強。該方法好的一點是容易復現,而且沒有很多調參的tricks,效果也很好。

(1)ArcFace

與 AM-softmax 相比,區別在於 Arcface 引入 margin 的方式不同,損失函數:

乍一看是不是和 AM-softmax一樣?注意 m 是在餘弦裏面。文章指出基於上式優化得到的特徵間的 boundary 更為優越,具有更強的幾何解釋。

然而這樣引入 margin 是否會有問題?仔細想 cos(θ+m) 是否一定比 cos(θ) 小?

最後我們用文章中的圖來解釋這個問題,並且也由此做一個本章 Margin-based Classification 部分的總結。

這幅圖出自於 Arcface,橫坐標為 θ 為特徵與類中心的角度,縱坐標為損失函數分子指數部分的值(不考慮 s),其值越小損失函數越大。

看了這麼多基於分類的人臉識別論文,相信你也有種感覺,大家似乎都在損失函數上做文章,或者更具體一點,大家都是在討論如何設計上圖的 Target logit-θ 曲線。

這個曲線意味着你要如何優化偏離目標的樣本,或者說,根據偏離目標的程度,要給予多大的懲罰。兩點總結:

1. 太強的約束不容易泛化。例如 Sphereface 的損失函數在 m=3 或 4 的時候能滿足類內最大距離小於類間最小距離的要求。此時損失函數值很大,即 target logits 很小。但並不意味着能泛化到訓練集以外的樣本。施加太強的約束反而會降低模型性能,且訓練不易收斂。

2. 選擇優化什麼樣的樣本很重要。Arcface 文章中指出,給予 θ∈[60° , 90°] 的樣本過多懲罰可能會導致訓練不收斂。優化 θ ∈ [30° , 60°] 的樣本可能會提高模型準確率,而過分優化 θ∈[0° , 30°] 的樣本則不會帶來明顯提升。至於更大角度的樣本,偏離目標太遠,強行優化很有可能會降低模型性能。

這也回答了上一節留下的疑問,上圖曲線 Arcface 後面是上升的,這無關緊要甚至還有好處。因為優化大角度的 hard sample 可能沒有好處。這和 FaceNet 中對於樣本選擇的 semi-hard 策略是一個道理。

Margin based classification 延伸閱讀

1. A discriminative feature learning approach for deep face recognition [14]

提出了 center loss,加權整合進原始的 softmax loss。通過維護一個歐式空間類中心,縮小類內距離,增強特徵的 discriminative power。

2. Large-margin softmax loss for convolutional neural networks [10]

Sphereface 作者的前一篇文章,未歸一化權重,在 softmax loss 中引入了 margin。裏面也涉及到 Sphereface 的訓練細節。

使用ModelArts訓練人臉模型

人臉識別算法實現解釋

本文我們部署的人臉識別算法模型主要包括兩部分:

  1. 第一部分為人臉檢測算法模型,該模型將圖片中的人臉進行識別,返回人臉的位置信息;
  2. 第二部分為人臉特徵表示算法模型,也稱之為識別模型。這個部分將crop出的人臉圖像embedding到一個固定維度大小的向量,然後利用該向量與底庫進行比對,完成人臉識別的整體流程。

如下圖所示,整體算法實現的流程分為線下和線上兩個部分,在每次對不同的人進行識別之前首先利用訓練好的算法生成人臉標準底庫,將底庫數據保存在modelarts上。然後在每次推理的過程中,圖片輸入會經過人臉檢測模型和人臉識別模型得到人臉特徵,然後基於該特徵在底庫中搜索相似對最高的特徵,完成人臉識別的過程。

在實現過程中,我們採用了基於Retinaface+resnet50+arcface的算法完成人臉圖像的特徵提取,其中Retinaface作為檢測模型,resnet50+arcface作為特徵提取模型。

在鏡像中,運行訓練的腳本有兩個,分別對應人臉檢測的訓練和人臉識別的訓練。

  • 人臉檢測的訓練腳本為:
run_face_detection_train.sh

該腳本的啟動命令為

 sh run_face_detection_train.sh data_path model_output_path

其中model_output_path為模型輸出的路徑,data_path為人臉檢測訓練集的輸入路徑,輸入的圖片路徑結構如下:

 detection_train_data/
    train/
      images/
      label.txt
    val/
      images/
      label.txt
    test/
      images/
      label.txt
  • 人臉識別的訓練腳本為:
run_face_recognition_train.sh

該腳本的啟動命令為

 sh run_face_recognition_train.sh data_path model_output_path

其中model_output_path為模型輸出的路徑,data_path為人臉檢測訓練集的輸入路徑,輸入的圖片路徑結構如下:

 recognition_train_data/
cele.idx
cele.lst
cele.rec
property
  • 底庫生成的腳本:
run_generate_data_base.sh

該腳本的啟動命令為:

sh run_generate_data_base.sh data_path detect_model_path recognize_model_path db_output_path

其中data_path為底庫輸入路徑,detect_model_path為檢測模型輸入路徑,recognize_model_path為識別模型輸入路徑,db_output_path為底庫輸出路徑。

  • 底庫生成的腳本:
run_face_recognition.sh

該腳本的啟動命令為:

sh run_generate_data_base.sh data_path db_path detect_model_path recognize_model_path

其中data_path為測試圖片輸入路徑,db_path為底庫路徑,detect_model_path為檢測模型的輸入路徑,recognize_model_path為識別模型的輸入路徑

訓練過程

華為雲ModelArts有訓練作業的功能,可以用來作模型訓練以及對模型訓練的參數和版本進行管理。這個功能對於多版本迭代開發的開發者有一定的幫助。訓練作業中有預置的一些鏡像和算法,當前對於常用的框架均有預置鏡像(包括Caffe, MXNet, Pytorch, TensorFlow )和華為自己的昇騰芯片的引擎鏡像(Ascend-Powered-Engine)。

本文我們會基於ModelArts的自定義鏡像特性,上傳自己在本機調試完畢的完整鏡像,利用華為雲的GPU資源訓練模型。

我們是想在華為雲上的ModelArts基於網站上常見的明星的數據訓練完成一個人臉識別模型。在這個過程中,由於人臉識別網絡是工程師自己設計的網絡結構,所以需要通過自定義鏡像進行上傳。所以整個人臉訓練的過程分為以下九步:

  1. 構建本地Docker環境
  2. 從華為雲下載基礎鏡像
  3. 根據自己需求構建自定義鏡像環境
  4. 導入訓練數據到自定義鏡像
  5. 導入人臉識別底庫到自定義鏡像
  6. 導入預訓練模型到自定義鏡像
  7. 上傳自定義鏡像到SWR
  8. 使用華為雲訓練作業進行訓練
  9. 使用華為雲進行推理工作

構建本地Docker環境

Docker環境可以在本地計算機進行構建,也可以在華為雲上購買一台彈性雲服務器進行Docker環境構建。全過程參考Docker官方的文檔進行:

https://docs.docker.com/engine/install/binaries/#install-static-binaries

從華為雲下載基礎鏡像

官網說明網址:

https://support.huaweicloud.com/engineers-modelarts/modelarts_23_0085.html#modelarts_23_0085__section19397101102

我們訓練需要使用到的是MXNet的環境,首先需要從華為雲上下載相對應的自定義鏡像的基礎鏡像。官網給出的下載命令如下:

在訓練作業基礎鏡像的規範里,找到了這個命令的解釋。

https://support.huaweicloud.com/engineers-modelarts/modelarts_23_0217.html

根據我們的腳本要求,我使用的是cuda9的鏡像:

官方還給出了另一種方法,就是使用docker file的。​基礎鏡像的dockerfile也是在訓練作業基礎鏡像的規範里找到的。可以參考一下的dockerfile:

https://github.com/huaweicloud/ModelArts-Lab/tree/master/docs/custom_image/custom_base

根據自己需求構建自定義鏡像環境

由於比較懶,所以還是沒有使用Dockerfile的方式自己構建鏡像。我採用的是另一種方式!

因為我們的需求就是cuda 9 還有一些相關的python依賴包,假設官方的鏡像提供的是cuda 9的,我們大可以在訓練腳本中跟着這個教程加一個requirement.txt。簡單高效快捷就能解決需求!!!下面是教程~~~

https://support.huaweicloud.com/modelarts_faq/modelarts_05_0063.html

上傳自定義鏡像到SWR

官網教程:

  • https://support.huaweicloud.com/engineers-modelarts/modelarts_23_0085.html#modelarts_23_0085__section19397101102
  • https://support.huaweicloud.com/usermanual-swr/swr_01_0011.html

上傳鏡像的頁面寫着,文件解壓后不得超過2GB。但是官方提供的基礎鏡像就3.11GB,我們加上需要的預訓練的模型後鏡像是5+GB,所以不能使用頁面進行上傳的工作,必須使用客戶端。上傳鏡像首先要創建組織,

如果覺得產品文檔理解還是比較難,可以嘗試一下SWR頁面的pull/push鏡像體驗:

這裏後面引導了客戶如何將本地鏡像推上雲端,第一步是登陸倉庫:

第二步拉取鏡像,這個我們就用自己打的自定義鏡像代替,

第三步修改組織,使用根據產品文檔創建的組織名。在這一步需要將本地的一個鏡像重命名為雲上識別的鏡像命。具體看下圖解釋:

第四步推送鏡像,

當熟練掌握這四步技巧的時候,可以脫離這個教程,使用客戶端進行上傳。使用客戶端登陸然後上傳。客戶端登陸可以使用生成臨時docker loging指令。這個頁面在”我的鏡像“-> ”客戶端上傳“->”生成臨時docker login指令“中:

在本地docker環境中,使用這個生成的臨時docker login指令登陸后,使用下面的命令進行上傳鏡像:

使用華為雲訓練作業進行訓練

華為雲ModelArts提供訓練作業給用戶進行模型訓練。在訓練作業中有預置鏡像和可以選擇自定義鏡像。預置的鏡像包含市面上大部分框架,沒有特殊要求的時候,使用這些框架的鏡像進行訓練也是很方便的。本次測試還是使用的自定義鏡像。

自定義鏡像中不僅需要在鏡像中進行配置自己的環境,假如改變了訓練作業啟動的方式,還需要修改訓練的啟動腳本。從華為雲ModelArts官網拉取下來的官方鏡像的/home/work/路徑下有一個啟動腳本”run_train.sh”,自定義的啟動腳本需要基於這個腳本進行修改。主要是要注意 “dls_get_app”,這個是從OBS下載相關的命令。其他的部分根據自己的訓練腳本進行修改。

如果需要上傳訓練結果或者模型到OBS,需要參考”dls_get_app”加”dls_upload_model”的命令。在我們這次訓練中,上傳的腳本如下:

訓練作業進行調試的時候,當前可以使用免費提供的一小時V100。ModelArts的訓練作業一個比較好的地方是方便了我們版本管理。版本中會記錄所有通過運行參數傳入到訓練腳本里的所有參數,還可以使用版本對比進行參數對比。還有個比較方便的地方是可以基於某一個版本進行修改,減少了重新輸入所有參數這一步驟,比較方便調試。

在訓練作業中訓練完成后,還可以在ModelArts中進行模型部署上線。

後記

目前針對人臉識別算法的優化已經到達一個瓶頸期,但是在技術層面針對人臉面部結構的相似性、人臉的姿態、年齡變化、複雜環境的光照變化、人臉的飾物遮擋等還面臨這很多的問題,因此基於多種算法技術的融合解決人臉識別中的各種問題仍然在安防、互聯網中有着巨大的市場。另外,隨着人臉支付的逐漸完善,人臉識別系統也應用於銀行、公安系統、商場等等,因此人臉識別的安全問題和防攻擊問題也是一個亟待解決的問題,例如活體檢測、3D面部識別等等。

最後,人臉識別作為目前深度學習中應用比較成熟的項目,其發展還與深度學習本身技術發展息息相關,目前在很多優化上,深度學習最大的缺點是沒有相應的數學理論支撐,優化所提升的性能也很有限,因此對深度學習算法本身的研究也是未來的重點。

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

自己動手實現深度學習框架-7 RNN層–GRU, LSTM

目標

        這個階段會給cute-dl添加循環層,使之能夠支持RNN–循環神經網絡. 具體目標包括:

  1. 添加激活函數sigmoid, tanh.
  2. 添加GRU(Gate Recurrent Unit)實現.
  3. 添加LSTM(Long Short-term Memory)實現.
  4. 使用基於GRU和LSTM的RNN模型擬合一個正餘弦疊加函數.

RNN原理

原始的RNN

        RNN模型用來捕捉序列數據的特徵. 給定一個長度為T的輸入系列\(X=(x_1, x_2, .., X_T)\), RNN層輸出一個長度為T的序列\(H=(h_1, h_2, …, H_T)\), 對於任意時間步t, 可以表示為:

\[H_t = δ(X_tW_x + H_{t-1}W_h + b), \quad t = 2, 3, .., T \]

        函數δ是sigmoid函數:

\[δ = \frac{1}{1 + e^{-x}} \]

        \(H_t\)包含了前面第1到t-1步的所有信息。 和CNN層類似, CNN層在空間上共享參數, RNN層在時間步上共享參數\(W_x, W_h, b\).

        RNN層中隱藏層的數量為T-2, 如果T較大(超過10), 反向傳播是很容易出現梯度爆炸. GRU和LSTM就是為了解決這個問題而誕生, 這兩種模型,可以讓RNN能夠支持長度超過1000的輸入序列。

GRU

        GRU使用了不同功能的門控單元, 分別捕捉序列上不同時間跨度的的依賴關係。每個門控單元都會都有獨立的參數, 這些參數在時間步上共享。

        GRU的門控單元有:

        \(R_t = δ(X_tW^r_x + H_{t-1}W^r_h + b^r)\), 重置門用於捕捉短期依賴關係.

        \(U_t = δ(X_tW^u_x + H_{t-1}W^u_h + b^u)\), 更新門用於捕捉長期依賴關係

        \(\bar{H}_t = tanh(X_t\bar{W}_x + (R_t * H_{t-1})\bar{W}_h + \bar{b})\)

        除此之外, 還有一個輸出單元:

        \(H_t = U_t * H_{t-1} + (1-U_t)*\bar{H}_t\)

LSTM

        LSTM的設計思路和GRU類似, 同樣使用了多個門控單元:

        \(I_t = δ(X_tW^i_x + H_{t-1}W^i_h + b^i)\), 輸入門,過濾記憶門的輸出.

        \(F_t = δ(X_tW^f_x + H_{t-1}W^f_h + b^f)\), 遺忘門, 過濾前面時間步的記憶.

        \(O_t = δ(X_tW^o_x + H_{t-1}W^o_h + b^o)\), 輸出門, 過濾當前時間步的記憶.

        \(M_t = tanh(X_tW^m_x + H_{t-1}W^m_h + b^m)\), 記憶門.

        它還有自己獨有的記憶單元和輸出單元:

        \(\bar{M}_t = F_t * \bar{M}_{t-1} + I_t * M_t\)

        \(H_t = O_t * tanh(\bar{M}_t)\)

RNN實現

        設計要求:

  1. RNN層中的隱藏層的數量是基於序列長度的,輸入序列有多長, RNN層應生成對應數量的隱藏層。
  2. RNN層在時間步上共享參數, 從前面的描述可以看出, 只有門控單元有參數,因此門控單元應獨立實現。
  3. 任意一個時間步上的層都依賴上一個時間步的輸出,在正向傳播和反向傳播過程中都需要上一個時間步的輸出, 每個門控單元都使用棧保存上一個時間步的輸出.
  4. 默認情況下RNN層輸出所有時間步的輸出。但有時只需要最後一個時間步的輸出, 這種情況下使用過濾層, 只向下一層傳播最後一個時間步的輸出。
  5. 使用門控單元實現GRU和LSTM

RNN基礎類的實現

RNN類

        文件: cutedl/rnn_layers.py, 類名: RNN

        這個類是RNN層基類, 它主要功能是控制向前傳播和向後傳播的主流程.

        初始化參數:

  '''
  out_units 輸出單元數
  in_units 輸入單元數
  stateful 保留當前批次的最後一個時間步的狀態作為下一個批次的輸入狀態, 默認False不保留

  RNN 的輸入形狀是(m, t, in_units)
  m: batch_size
  t: 輸入系列的長度
  in_units: 輸入單元數頁是輸入向量的維數

  輸出形狀是(m, t, out_units)
  '''
  def __init__(self, out_units, in_units=None, stateful=False, activation='linear'):

        向前傳播

def forward(self, in_batch, training):
    m, T, n = in_batch.shape
    out_units = self.__out_units
    #所有時間步的輸出
    hstatus = np.zeros((m, T, out_units))
    #上一步的輸出
    pre_hs = self.__pre_hs
    if pre_hs is None:
        pre_hs = np.zeros((m, out_units))

    #隱藏層循環過程, 沿時間步執行
    for t in range(T):
        hstatus[:, t, :] = self.hiden_forward(in_batch[:,t,:], pre_hs, training)
        pre_hs = hstatus[:, t, :]

    self.__pre_hs = pre_hs
    #pdb.set_trace()
    if not self.stateful:
        self.__pre_hs = None

    return hstatus

        反向傳播

def backward(self, gradient):
      m, T, n = gradient.shape

      in_units = self.__in_units
      grad_x = np.zeros((m, T, in_units))
      #pdb.set_trace()
      #從最後一個梯度開始反向執行.
      for t in range(T-1, -1, -1):
          grad_x[:,t,:], grad_hs = self.hiden_backward(gradient[:,t,:])
          #pdb.set_trace()
          if t - 1 >= 0:
              gradient[:,t-1,:] = gradient[:,t-1,:] + grad_hs

      #pdb.set_trace()
      return grad_x

sigmoid和tanh激活函數

sigmoid及其導數

\[sigmoid = \frac{1}{1+e^{-x}} \]

\[\frac{d}{dx}sigmoid = sigmoid(1-sigmoid) \]

tanh及其導數

\[tanh = \frac{e^x – e^{-x}}{e^x + e^{-x}} \]

\[\frac{d}{dx}tanh = 1 – tanh^2 \]

門控單元實現

        文件: cutedl/rnn_layers.py, 類名: GateUint

        門控單元是RNN層基礎的參數單元. 和Dense層類似,它是Layer的子類,負責學習和使用參數。但在學習和使用參數的方式上有很大的不同:

  • Dense有兩個參數矩陣, GateUnit有3個參數矩陣.
  • Dense在一次反向傳播過程中只使用當前的梯度學習參數,而GateUnit會累積每個時間步的梯度。

        下面我們會主要看一下GateUnit特別之處的代碼.

        在__ init__方法中定義參數和棧:

    #3個參數
    self.__W = None #當前時間步in_batch權重參數
    self.__Wh = None #上一步輸出的權重參數
    self.__b = None #偏置量參數

    #輸入棧
    self.__hs = []  #上一步輸出
    self.__in_batchs = [] #當前時間步的in_batch

        正向傳播:

  def forward(self, in_batch, hs, training):
      W = self.__W.value
      b = self.__b.value
      Wh = self.__Wh.value

      out = in_batch @ W + hs @ Wh + b

      if training:
          #向前傳播訓練時把上一個時間步的輸出和當前時間步的in_batch壓棧
          self.__hs.append(hs)
          self.__in_batchs.append(in_batch)

          #確保反向傳播開始時參數的梯度為空
          self.__W.gradient = None
          self.__Wh.gradient = None
          self.__b.gradient = None

      return self.activation(out)

        反向傳播:

def backward(self, gradient):
    grad = self.activation.grad(gradient)

    W = self.__W.value
    Wh = self.__Wh.value
    pre_hs = self.__hs.pop()
    in_batch = self.__in_batchs.pop()

    grad_in_batch = grad @ W.T
    grad_W = in_batch.T @ grad
    grad_hs = grad @ Wh.T
    grad_Wh = pre_hs.T @ grad
    grad_b = grad.sum(axis=0)

    #反向傳播計算
    if self.__W.gradient is None:
        #當前批次第一次
        self.__W.gradient = grad_W
    else:
        #累積當前批次的所有梯度
        self.__W.gradient = self.__W.gradient + grad_W

    if self.__Wh.gradient is None:
        self.__Wh.gradient = grad_Wh
    else:
        self.__Wh.gradient = self.__Wh.gradient +  grad_Wh

    if self.__b.gradient is None:
        self.__b.gradient = grad_b
    else:
        self.__b.gradient = self.__b.gradient + grad_b

    return grad_in_batch, grad_hs

GRU實現

        文件: cutedl/rnn_layers.py, 類名: GRU

        隱藏單初始化:

def set_parent(self, parent):
    super().set_parent(parent)

    out_units = self.out_units
    in_units = self.in_units

    #pdb.set_trace()
    #重置門
    self.__g_reset = GateUnit(out_units, in_units)
    #更新門
    self.__g_update = GateUnit(out_units, in_units)
    #候選輸出門
    self.__g_cddout = GateUnit(out_units, in_units, activation='tanh')

    self.__g_reset.set_parent(self)
    self.__g_update.set_parent(self)
    self.__g_cddout.set_parent(self)

    #重置門乘法單元
    self.__u_gr = MultiplyUnit()
    #輸出單元
    self.__u_out = GRUOutUnit()

        向前傳播:

  def hiden_forward(self, in_batch, pre_hs, training):
      gr = self.__g_reset.forward(in_batch, pre_hs, training)
      gu = self.__g_update.forward(in_batch, pre_hs, training)
      ugr = self.__u_gr.forward(gr, pre_hs, training)
      cddo = self.__g_cddout.forward(in_batch, ugr, training)

      hs = self.__u_out.forward(gu, pre_hs, cddo, training)

      return hs

        反向傳播:

def hiden_backward(self, gradient):

    grad_gu, grad_pre_hs, grad_cddo = self.__u_out.backward(gradient)
    #pdb.set_trace()
    grad_in_batch, grad_ugr = self.__g_cddout.backward(grad_cddo)

    #計算梯度的過程中需要累積上一層輸出的梯度
    grad_gr, g_pre_hs = self.__u_gr.backward(grad_ugr)
    grad_pre_hs = grad_pre_hs + g_pre_hs

    g_in_batch, g_pre_hs = self.__g_update.backward(grad_gu)
    grad_in_batch = grad_in_batch + g_in_batch
    grad_pre_hs = grad_pre_hs + g_pre_hs

    g_in_batch, g_pre_hs = self.__g_reset.backward(grad_gr)
    grad_in_batch = grad_in_batch + g_in_batch
    grad_pre_hs = grad_pre_hs + g_pre_hs

    #pdb.set_trace()
    return grad_in_batch, grad_pre_hs    

LSTM實現

        文件: cutedl/rnn_layers.py, 類名: LSTM

        隱藏單元初始化:

def set_parent(self, layer):
    super().set_parent(layer)

    in_units = self.in_units
    out_units = self.out_units

    #輸入門
    self.__g_in = GateUnit(out_units, in_units)
    #遺忘門
    self.__g_forget = GateUnit(out_units, in_units)
    #輸出門
    self.__g_out = GateUnit(out_units, in_units)
    #記憶門
    self.__g_memory = GateUnit(out_units, in_units, activation='tanh')

    self.__g_in.set_parent(self)
    self.__g_forget.set_parent(self)
    self.__g_out.set_parent(self)
    self.__g_memory.set_parent(self)

    #記憶單元
    self.__memory_unit =LSTMMemoryUnit()
    #輸出單元
    self.__out_unit = LSTMOutUnit()

        向前傳播:

def hiden_forward(self, in_batch, hs, training):
    g_in = self.__g_in.forward(in_batch, hs, training)
    #pdb.set_trace()
    g_forget = self.__g_forget.forward(in_batch, hs, training)
    g_out = self.__g_out.forward(in_batch, hs, training)
    g_memory = self.__g_memory.forward(in_batch, hs, training)

    memory = self.__memory_unit.forward(g_forget, g_in, g_memory, training)
    cur_hs = self.__out_unit.forward(g_out, memory, training)

    return cur_hs

        反向傳播:

def hiden_backward(self, gradient):
    #pdb.set_trace()
    grad_out, grad_memory = self.__out_unit.backward(gradient)
    grad_forget, grad_in, grad_gm = self.__memory_unit.backward(grad_memory)

    grad_in_batch, grad_hs = self.__g_memory.backward(grad_gm)
    tmp1, tmp2 = self.__g_out.backward(grad_out)
    grad_in_batch += tmp1
    grad_hs += tmp2

    tmp1, tmp2 = self.__g_forget.backward(grad_forget)
    grad_in_batch += tmp1
    grad_hs += tmp2

    tmp1, tmp2 = self.__g_in.backward(grad_in)
    grad_in_batch += tmp1
    grad_hs += tmp2

    return grad_in_batch, grad_hs

驗證

        接下來, 驗證示例將會構建一個簡單的RNN模型, 使用該模型擬合一個正餘弦疊加函數:

#採樣函數
def sample_function(x):
    y = 3*np.sin(2 * x * np.pi) + np.cos(x * np.pi) + np.random.uniform(-0.05,0.05,len(x))
    return y

        訓練數據集和測試數據集在這個函數的不同定義域區間內樣. 訓練數據集的採樣區間為[1, 200.01), 測試數據集的採樣區間為[200.02, 240.002). 模型任務是預測這個函數值的序列.

        示例代碼在examples/rnn/fit_function.py文件中.

使用GRU構建的模型

def fit_gru():
    model = Model([
                rnn.GRU(32, 1),
                nn.Filter(),
                nn.Dense(32),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('gru', model)

訓練報告:

使用LSTM構建的模型

def fit_lstm():
    model = Model([
                rnn.LSTM(32, 1),
                nn.Filter(),
                nn.Dense(2),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('lstm', model)

訓練報告:

總結

        這個階段,框架新增了RNN的兩個最常見的實現:GRU和LSTM, 相應地增加了它需要的激活函數. cute-dl已經具備了構建最基礎RNN模型的能力。通過驗證發現, GRU模型和LSTM模型在簡單任務上都表現出了很好的性能。會添加嵌入層,使框架能夠構建文本分類任務的模型,然後在imdb-review(電影評價)數據集上進行驗證.

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

如何優雅地停止 Spring Boot 應用?

首先來介紹下什麼是優雅地停止,簡而言之,就是對應用進程發送停止指令之後,能保證正在執行的業務操作不受影響,可以繼續完成已有請求的處理,但是停止接受新請求

在 Spring Boot 2.3 中增加了新特性優雅停止,目前 Spring Boot 內置的四個嵌入式 Web 服務器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反應式和基於 Servlet 的 Web 應用程序都支持優雅停止。

下面,我們先用新版本嘗試下:

Spring Boot 2.3 優雅停止

首先創建一個 Spring Boot 的 Web 項目,版本選擇 2.3.0.RELEASE,Spring Boot 2.3.0.RELEASE 版本內置的 Tomcat 為 9.0.35

然後需要在 application.yml 中添加一些配置來啟用優雅停止的功能:

# 開啟優雅停止 Web 容器,默認為 IMMEDIATE:立即停止
server:
  shutdown: graceful

# 最大等待時間
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

其中,平滑關閉內置的 Web 容器(以 Tomcat 為例)的入口代碼在 org.springframework.boot.web.embedded.tomcatGracefulShutdown 里,大概邏輯就是先停止外部的所有新請求,然後再處理關閉前收到的請求,有興趣的可以自己去看下。

內嵌的 Tomcat 容器平滑關閉的配置已經完成了,那麼如何優雅關閉 Spring 容器了,就需要 Actuator 來實現 Spring 容器的關閉了。

然後加入 actuator 依賴,依賴如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然後接着再添加一些配置來暴露 actuator 的 shutdown 接口:

# 暴露 shutdown 接口
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

其中通過 Actuator 關閉 Spring 容器的入口代碼在 org.springframework.boot.actuate.context 包下 ShutdownEndpoint 類中,主要的就是執行 doClose() 方法關閉並銷毀 applicationContext,有興趣的可以自己去看下。

配置搞定后,然後在 controller 包下創建一個 WorkController 類,並有一個 work 方法,用來模擬複雜業務耗時處理流程,具體代碼如下:

@RestController
public class WorkController {

    @GetMapping("/work")
    public String work() throws InterruptedException {
        // 模擬複雜業務耗時處理流程
        Thread.sleep(10 * 1000L);
        return "success";
    }
}

然後,我們啟動項目,先用 Postman 請求 http://localhost:8080/work 處理業務:

然後在這個時候,調用 http://localhost:8080/actuator/shutdown 就可以執行優雅地停止,返回結果如下:

{
    "message": "Shutting down, bye..."
}

如果在這個時候,發起新的請求 http://localhost:8080/work,會沒有反應:

再回頭看第一個請求,返回了結果:success

其中有幾條服務日誌如下:

2020-05-20 23:05:15.163  INFO 102724 --- [     Thread-253] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2020-05-20 23:05:15.287  INFO 102724 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
2020-05-20 23:05:15.295  INFO 102724 --- [     Thread-253] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

從日誌中也可以看出來,當調用 shutdown 接口的時候,會先等待請求處理完畢后再優雅地停止。

到此為止,Spring Boot 2.3 的優雅關閉就講解完了,是不是很簡單呢?如果是在之前不支持優雅關閉的版本如何去做呢?

Spring Boot 舊版本優雅停止

在這裏介紹 GitHub 上 issue 里 Spring Boot 開發者提供的一種方案:

選取的 Spring Boot 版本為 2.2.6.RELEASE,首先要實現 TomcatConnectorCustomizer 接口,該接口是自定義 Connector 的回調接口:

@FunctionalInterface
public interface TomcatConnectorCustomizer {

	void customize(Connector connector);
}

除了定製 Connector 的行為,還要實現 ApplicationListener<ContextClosedEvent> 接口,因為要監聽 Spring 容器的關閉事件,即當前的 ApplicationContext 執行 close() 方法,這樣我們就可以在請求處理完畢後進行 Tomcat 線程池的關閉,具體的實現代碼如下:

@Bean
public GracefulShutdown gracefulShutdown() {
    return new GracefulShutdown();
}

private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
    private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);

    private volatile Connector connector;

    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        this.connector.pause();
        Executor executor = this.connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
            try {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
                    log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
                }
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

有了定製的 Connector 回調,還需要在啟動過程中添加到內嵌的 Tomcat 容器中,然後等待監聽到關閉指令時執行,addConnectorCustomizers 方法可以把定製的 Connector 行為添加到內嵌的 Tomcat 中,具體代碼如下:

@Bean
public ConfigurableServletWebServerFactory tomcatCustomizer() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addConnectorCustomizers(gracefulShutdown());
    return factory;
}

到此為止,內置的 Tomcat 容器平滑關閉的操作就完成了,Spring 容器優雅停止上面已經說過了,再次就不再贅述了。

通過測試,同樣可以達到上面那樣優雅停止的效果。

總結

本文主要講解了 Spring Boot 2.3 版本和舊版本的優雅停止,避免強制停止導致正在處理的業務邏輯會被中斷,進而導致產生業務異常的情形。

另外使用 Actuator 的同時要注意安全問題,比如可以通過引入 security 依賴,打開安全限制並進行身份驗證,設置單獨的 Actuator 管理端口並配置只對內網開放等。

本文的完整代碼在 https://github.com/wupeixuan/SpringBoot-Learngraceful-shutdown 目錄下。

最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。

參考

https://github.com/spring-projects/spring-boot/issues/4657

https://github.com/wupeixuan/SpringBoot-Learn

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

MySQL查詢優化利刃-EXPLAIN

有一個 ?

遇到這樣一個疑問:當where查詢中In一個索引字段作為條件,那麼在查詢中還會使用到索引嗎?

SELECT * FROM table_name WHERE column_index in (expr)

上面的sql語句檢索會使用到索引嗎?帶着這個問題,在網上查找了很多文章,但是有的說 in 會導致放棄索引,全表掃描;有的說Mysql5.5之前的版本不會走,之後的innodb版本會走索引…

越看越迷糊,那答案到底是怎樣的呢?

唯有實踐是檢驗真理的唯一方式!

拿出我們的利刃——EXPLAIN,去剖析 SELECT 語句,一探究竟!

EXPLAIN 的用法

在 SELECT 語句前加上 EXPLAIN 就可以了 ,例如:

EXPLAIN SELECT * FROM table_name [WHERE Clause]

EXPLAIN 的輸出

EXPLAIN 命令的輸出內容為一個表格形式,表的每一個字段含義如下:

列名 解釋
id SELECT 查詢的標識符. 每個 SELECT 都會自動分配一個唯一的標識符
select_type SELECT 查詢的類型
table 查詢的是哪個表
partitions 匹配的分區
type join 類型
possible_keys 此次查詢中可能選用的索引
key 此次查詢中確切使用到的索引
ref 哪個字段或常數與 key 一起被使用;與索引比較的列
rows 显示此查詢一共掃描了多少行, 這個是一個估計值
filtered 表示此查詢條件所過濾的數據的百分比
extra 額外的信息
select_type
查詢類型 解釋
SIMPLE 表示此查詢不包含 UNION 查詢或子查詢
PRIMARY 表示此查詢是最外層的查詢
UNION 表示此查詢是 UNION 的第二或隨後的查詢
DEPENDENT UNION UNION 中的第二個或後面的查詢語句, 取決於外面的查詢
UNION RESULT UNION 的結果
SUBQUERY 子查詢中的第一個 SELECT
DEPENDENT SUBQUERY 子查詢中的第一個 SELECT,取決於外面的查詢。子查詢依賴於外層查詢的結果
MATERIALIZED Materialized subquery
table

表示查詢涉及的表或衍生表 。 這也可以是以下值之一:

  • <unionM,N>:該行指的是具有和id值的行 的 M並集 N。
  • :該行是指用於與該行的派生表結果id的值 N。派生表可能來自FROM子句中的子查詢 。
  • :該行是指該行的物化子查詢的結果,其id 值為N。
partitions

查詢將匹配記錄的分區。該值適用NULL於未分區的表。

type

聯接類型。 提供了判斷查詢是否高效的重要依據依據。通過 type 字段,我們判斷此次查詢是全表掃描還是索引掃描等。 從最佳類型到最差類型:

  • system: 該表只有一行(=系統表)。這是const聯接類型的特例 。

  • const: 針對主鍵或唯一索引的等值查詢掃描,最多只返回一行數據。const 查詢速度非常快,因為它僅僅讀取一次即可 。

    SELECT * FROM tbl_name WHERE primary_key=1;
    
    SELECT * FROM tbl_name
      WHERE primary_key_part1=1 AND primary_key_part2=2;
    
  • eq_ref: 此類型通常出現在多表的 join 查詢,表示對於前表的每一個結果,都只能匹配到后表的一行結果。並且查詢的比較操作通常是 =,查詢效率較高

    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column=other_table.column;
    
    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column_part1=other_table.column
      AND ref_table.key_column_part2=1;
    
  • ref : 此類型通常出現在多表的 join 查詢,針對於非唯一或非主鍵索引,或者是使用了最左前綴規則索引的查詢。ref可以用於使用=或<=> 運算符進行比較的索引列。

    SELECT * FROM ref_table WHERE key_column=expr;
    
    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column=other_table.column;
    
    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column_part1=other_table.column
      AND ref_table.key_column_part2=1;
    
  • ref_or_null: 這種連接類型類似於 ref,但是除了MySQL會額外搜索包含NULL值的行。此聯接類型優化最常用於解析子查詢。

    SELECT * FROM ref_table
      WHERE key_column=expr OR key_column IS NULL;
    
  • unique_subquery: 只是一個索引查找函數,它完全替代了子查詢以提高效率。

    value IN (SELECT primary_key FROM single_table WHERE some_expr)
    
  • index_subquery:此連接類型類似於 unique_subquery。它代替IN子查詢,但適用於以下形式的子查詢中的非唯一索引。

  • range: 表示使用索引範圍查詢, 通過索引字段範圍獲取表中部分數據記錄。這個類型通常出現在 =,<>,>,>=,<,<=,IS NULL,<=>,BETWEEN,IN() 操作中。

    當 type 是 range 時,那麼 EXPLAIN 輸出的 ref 字段為 NULL,並且 key_len 字段是此次查詢中使用到的索引的最長的那個 。

    SELECT * FROM tbl_name
      WHERE key_column = 10;
    
    SELECT * FROM tbl_name
      WHERE key_column BETWEEN 10 and 20;
    
    SELECT * FROM tbl_name
      WHERE key_column IN (10,20,30);
    
    SELECT * FROM tbl_name
      WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
    
  • index: 表示全索引掃描(full index scan)和 ALL 類型類似,只不過 ALL 類型是全表掃描,而 index 類型則僅僅掃描所有的索引,而不掃描數據。

    index 類型通常出現在: 所要查詢的數據直接在索引樹中就可以獲取到,而不需要掃描數據。當是這種情況時,Extra 字段 會显示 Using index

  • ALL: 表示全表掃描,這個類型的查詢是性能最差的查詢之一。

    我們的查詢不應該出現 ALL 類型的查詢,因為這樣的查詢在數據量大的情況下,對數據庫的性能是巨大的災難。如一個查詢是 ALL 類型查詢,那麼一般來說可以對相應的字段添加索引來避免 。

possible_keys

表示 MySQL 在查詢時,能夠使用到的索引。

即使有些索引在 possible_keys 中出現,但是並不表示此索引會真正地被 MySQL 使用到。MySQL 在查詢時具體使用了哪些索引,由 key 字段決定。

key

是 MySQL 在當前查詢時所真正使用到的索引。

key_len

表示查詢優化器使用了索引的字節數。

這個字段可以評估組合索引是否完全被使用,或只有最左部分字段被使用到。key_len 的計算規則如下:

  • 字符串
    • char(n): n 字節長度
    • varchar(n): 如果是 utf8 編碼, 則是 3n + 2字節; 如果是 utf8mb4 編碼, 則是 4n + 2 字節
  • 數值類型
  • TINYINT: 1字節
  • SMALLINT: 2字節
  • MEDIUMINT: 3字節
  • INT: 4字節
  • BIGINT: 8字節
  • 時間類型
  • DATE: 3字節
  • TIMESTAMP: 4字節
  • DATETIME: 8字節
  • 字段屬性: NULL 屬性 佔用一個字節。如果一個字段是 NOT NULL 的, 則沒有此屬性
rows

查詢優化器根據統計信息,估算 SQL 要查找到結果集需要掃描讀取的數據行數。這個值非常直觀显示 SQL 的效率好壞,原則上 rows 越少越好。

這個 rows 就是 mysql 認為必須要逐行去檢查和判斷的記錄的條數。舉個例子來說,假如有一個語句 select * from t where column_a = 1 and column_b = 2; 全表假設有 100 條記錄,column_a 字段有索引(非聯合索引),column_b沒有索引。column_a = 1 的記錄有 20 條, column_a = 1 and column_b = 2 的記錄有 5 條。

Extra

EXplain 中的很多額外的信息會在 Extra 字段显示,常見的有以下幾種內容:

  • Using filesort:當 Extra 中有 Using filesort 時,表示 MySQL 需額外的排序操作,不能通過索引順序達到排序效果。一般有 Using filesort,都建議優化去掉,因為這樣的查詢 CPU 資源消耗大。
  • Using index:”覆蓋索引掃描”,表示查詢在索引樹中就可查找所需數據,不用掃描表數據文件,往往說明性能不錯
  • Using temporary:查詢有使用臨時表,一般出現於排序,分組和多表 join 的情況,查詢效率不高,建議優化
  • Using where: WHERE子句用於限制哪些行與下一個表匹配或發送給客戶端 。

得出結論

說到最後,那 WHERE column_index in (expr) 到底走不走索引呢? 答案是不確定的。

走不走索引是由 expr 來決定的,不是一概而論走還是不走。

SELECT * FROM a WHERE id in (1,23,456,7,8)
-- id 是主鍵,查詢是走索引的。type = range,key = PRIMARY
SELECT * FROM a WHERE id in (SELECT b.a_id FROM b WHERE some_expr)
-- id 是主鍵,如果 some_expr 是一個索引查詢,那麼 select a 將走索引;
-- some_expr 不是索引查詢,那麼 select a 將全表掃描;

上面是兩個通用案例,但到底對不對了,還是自己去實踐最好了,拿起EXPLAIN去剖析吧~

參考文章: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain

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

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

聊聊Asp.net過濾器Filter那一些事

  最近在整理優化.net代碼時,發現幾個很不友好的處理現象:登錄判斷、權限認證、日誌記錄、異常處理等通用操作,在項目中的action中到處都是。在代碼優化上,這一點是很重要着力點。這時.net中的過濾器、攔截器(Filter)就派上用場了。現在根據這幾天的實際工作,對其做了一個簡單的梳理,分享出來,以供大家參考交流,如有寫的不妥之處,多多指出,多多交流。

概述:

.net中的Filter中主要包括以下4大類:Authorize(授權),ActionFilter(自定義),HandleError(錯誤處理)。

過濾器

類名

實現接口

描述

授權

AuthorizeAttribute

IAuthorizationFilter

此類型(或過濾器)用於限制進入控制器或控制器的某個行為方法,比如:登錄、權限、訪問控制等等

異常

HandleErrorAttribute

IExceptionFilter

用於指定一個行為,這個被指定的行為處理某個行為方法或某個控制器裏面拋出的異常,比如:全局異常統一處理。

自定義

ActionFilterAttribute

IActionFilterIResultFilter

用於進入行為之前或之後的處理或返回結果的之前或之後的處理,比如:用戶請求日誌詳情日誌記錄

 

AuthorizeAttribute:認證授權

認證授權主要是對所有action的訪問第一入口認證,對用戶的訪問做第一道監管過濾攔截閘口。

實現方式:需要自定義一個類,繼承AuthorizeAttribute並重寫OnAuthorization,在OnAuthorization中能夠獲取到用戶請求的所有Request信息,其實我們做的所有認證攔截操作,其所有數據支撐都是來自Request中。

具體驗證流程設計:

IP白名單:這個主要針對的是API做IP限制,只有指定IP才可訪問,非指定IP直接返回

請求頻率控制:這個主要是控制用戶的訪問頻率,主要是針對API做,超出請求頻率直接返回。

登錄認證:登錄認證一般我們採用的是通過在請求的header中傳遞token的方式來進行驗證,這樣即使用與一般的MVC登錄認證,也使用與API接口的Auth認證,並且也不依賴於用戶前端js設置等。

授權認證:授權認證就簡單了,主要是驗證該用戶是否具有該權限,如果不具有,直接做下相應的返回處理。

MVC和API異同:

  命名空間:MVC:System.Web.Http.Filters;API:System.Web.Mvc

  注入方式:在注入方式上,主要包括:全局->控制器Controller->行為Action

  全局註冊:針對所有系統的所有Aciton都使用

  Controller:只針對該Controller下的Action起作用

  Action:只針對該Action起作用

其中全局註冊,針對MVC和API還有一些差異:

  MVC在 FilterConfig.cs中注入
    filters.Add(new XYHMVCAuthorizeAttribute());

  API 在 WebApiConfig.cs 中注入

     config.Filters.Add(new XYHAPIAuthorizeAttribute());

注意事項:在實際使用中,針對認證授權,我們一般都是添加全局認證,但是,有的action又不需要做認證,比如本來的登錄Action等等,那麼該如何排除呢?其實也很簡單,我們只需要在自定定義一個Attribute集成Attribute,或者系統的AllowAnonymousAttribute,在不需要驗證的action中只需要註冊上對於的Attribute,並在驗證前做一個過濾即可,比如:

    // 有 AllowAnonymous 屬性的接口直接開綠燈

            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())

            {

                return;

            }

API AuthFilterAttribute實例代碼

/// <summary>
    /// 授權認證過濾器
    /// </summary>
    public class XYHAPIAuthFilterAttribute : AuthorizationFilterAttribute
    {
        /// <summary>
        /// 認證授權驗證
        /// </summary>
        /// <param name="actionContext">請求上下文</param>
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            // 有 AllowAnonymous 屬性的接口直接開綠燈
            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
            {
                return;
            }

            // 在請求前做一層攔截,主要驗證token的有效性和驗簽
            HttpRequest httpRequest = HttpContext.Current.Request;

            // 獲取apikey
            var apikey = httpRequest.QueryString["apikey"];

            // 首先做IP白名單校驗 
            MBaseResult<string> result = new AuthCheckService().CheckIpWhitelist(FilterAttributeHelp.GetIPAddress(actionContext.Request), apikey);

            // 檢驗時間戳
            string timestamp = httpRequest.QueryString["Timestamp"];
            if (result.Code == MResultCodeEnum.successCode)
            {
                // 檢驗時間戳 
                result = new AuthCheckService().CheckTimestamp(timestamp);
            }

            if (result.Code == MResultCodeEnum.successCode)
            {
                // 做請求頻率驗證 
                string acitonName = actionContext.ActionDescriptor.ActionName;
                string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
                result = new AuthCheckService().CheckRequestFrequency(apikey, $"api/{controllerName.ToLower()}/{acitonName.ToLower()}");
            }

            if (result.Code == MResultCodeEnum.successCode)
            {
                // 簽名校驗

                // 獲取全部的請求參數
                Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();

                result = new AuthCheckService().SignCheck(queryParameters, apikey);

                if (result.Code == MResultCodeEnum.successCode)
                {
                    // 如果有NoChekokenFilterAttribute 標籤 那麼直接不做token認證
                    if (actionContext.ActionDescriptor.GetCustomAttributes<XYHAPINoChekokenFilterAttribute>().Any())
                    {
                        return;
                    }

                    // 校驗token的有效性
                    // 獲取一個 token
                    string token = httpRequest.Headers.GetValues("Token") == null ? string.Empty :
                        httpRequest.Headers.GetValues("Token")[0];

                    result = new AuthCheckService().CheckToken(token, apikey, httpRequest.FilePath);
                }
            }

            // 輸出
            if (result.Code != MResultCodeEnum.successCode)
            {
                // 一定要實例化一個response,是否最終還是會執行action中的代碼
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
                //需要自己指定輸出內容和類型
                HttpContext.Current.Response.ContentType = "text/html;charset=utf-8";
                HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result));
                HttpContext.Current.Response.End(); // 此處結束響應,就不會走路由系統
            }
        }
    }

 

 MVC AuthFilterAttribute實例代碼

 

/// <summary>
    /// MVC自定義授權
    /// 認證授權有兩個重寫方法
    /// 具體的認證邏輯實現:AuthorizeCore 這個裡面寫具體的認證邏輯,認證成功返回true,反之返回false
    /// 認證失敗處理邏輯:HandleUnauthorizedRequest 前一步返回 false時,就會執行到該方法中
    /// 但是,我平時在應用過程中,一般都是在AuthorizeCore根據不同的認證結果,直接做認證后的邏輯處理
    /// </summary>
    public class XYHMVCAuthorizeAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// 認證邏輯
        /// </summary>
        /// <param name="filterContext">過濾器上下文</param>
        public override void OnAuthorization(AuthorizationContext filterContext)
        {

            // 此處主要寫認證授權的相關驗證邏輯
            // 該部分的驗證一般包括兩個部分
            // 登錄權限校驗
            //   --我們的一般處理方式是,通過header中傳遞一個token來進行邏輯驗證
            //   --當然不同的系統在設計上也不盡相同,有的也會採用session等方式來驗證
            //   --所以最終還是根據其項目本身的實際情況來進行對應的邏輯操作

            // 具體的頁面權限校驗
            // --該部分的驗證是具體的到頁面權限驗證
            // --我看有得小夥伴沒有做到這一個程度,直接將這一步放在前端js來驗證,這樣不是很安全,但是可以攔住小白用戶
            // --當然有的系統根本就沒有做權限控制,那就更不需要這一個邏輯了。
            // --所以最終還是根據其項目本身的實際情況來進行對應的邏輯操作

            // 現在用一個粗暴的方式來簡單模擬實現過,用系統當前時間段秒廚藝3,取餘數
            // 當餘數為0:認證授權通過
            //         1:代表為登錄,調整至登錄頁面
            //         2:代表無訪問權限,調整至無權限提示頁面

            // 當然,在這也還可以做一些IP白名單,IP黑名單驗證  請求頻率驗證等等

            // 說到這而,還有一點需要注意,如果我們選擇的是全局註冊該過濾器,那麼如果有的頁面根本不需要權限認證,比如登錄頁面,那麼我們可以給不需要權限的認證的控制器或者action添加一個特殊的註解 AllowAnonymous ,來排除

            // 獲取Request的幾個關鍵信息
            HttpRequest httpRequest = HttpContext.Current.Request;
            string acitonName = filterContext.ActionDescriptor.ActionName;
            string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;

            // 注意:如果認證不通過,需要設置filterContext.Result的值,否則還是會執行action中的邏輯

            filterContext.Result = null;
            int thisSecond = System.DateTime.Now.Second;
            switch (thisSecond % 3)
            {
                case 0:
                    // 認證授權通過
                    break;
                case 1:
                    // 代表為登錄,調整至登錄頁面
                    // 只有設置了Result才會終結操作
                    filterContext.Result = new RedirectResult("/html/Login.html");
                    break;
                case 2:
                    // 代表無訪問權限,調整至無權限提示頁面
                    filterContext.Result = new RedirectResult("/html/NoAuth.html");
                    break;
            }
        }
    }

 

ActionFilter自定義過濾器

自定義過濾器,主要是監控action請求前後,處理結果返回前後的事件。其中API只有請求前後的兩個方法。

重新方法

方法功能描述

使用於

OnActionExecuting

一個請求在進入到aciton邏輯前執行

MVCAPI

OnActionExecuted

一個請求aciton邏輯執行后執行

MVCAPI

OnResultExecuting

對應的view視圖渲染前執行

MVC

OnResultExecuted

對應的view視圖渲染后執行

MVC

 

在這幾個方法中,我們一般主要用來記錄交互日誌,記錄每一個步驟的耗時情況,以便後續系統優化使用。具體的使用,根據自身的業務場景使用。

其中MVC和API的異同點,和上面說的認證授權的異同類似,不在詳細說明。

下面的一個實例代碼:

API定義過濾器實例DEMO代碼

 

/// <summary>
    /// Action過濾器
    /// </summary>
    public class XYHAPICustomActionFilterAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// Action執行開始
        /// </summary>
        /// <param name="actionContext"></param>
        public override void OnActionExecuting(HttpActionContext actionContext)
        {

        }

        /// <summary>
        /// action執行以後
        /// </summary>
        /// <param name="actionContext"></param>
        public override void OnActionExecuted(HttpActionExecutedContext actionContext)
        {
            try
            {
                // 構建一個日誌數據模型
                MApiRequestLogs apiRequestLogsM = new MApiRequestLogs();

                // API名稱
                apiRequestLogsM.API = actionContext.Request.RequestUri.AbsolutePath;

                // apiKey
                apiRequestLogsM.API_KEY = HttpContext.Current.Request.QueryString["ApiKey"];

                // IP地址
                apiRequestLogsM.IP = FilterAttributeHelp.GetIPAddress(actionContext.Request);

                // 獲取token
                string token = HttpContext.Current.Request.Headers.GetValues("Token") == null ? string.Empty :
                              HttpContext.Current.Request.Headers.GetValues("Token")[0];
                apiRequestLogsM.TOKEN = token;

                // URL
                apiRequestLogsM.URL = actionContext.Request.RequestUri.AbsoluteUri;

                // 返回信息
                var objectContent = actionContext.Response.Content as ObjectContent;
                var returnValue = objectContent.Value;
                apiRequestLogsM.RESPONSE_INFOR = returnValue.ToString();

                // 由於數據庫中最大隻能存儲4000字符串,所以對返回值做一個截取
                if (!string.IsNullOrEmpty(apiRequestLogsM.RESPONSE_INFOR) &&
                    apiRequestLogsM.RESPONSE_INFOR.Length > 4000)
                {
                    apiRequestLogsM.RESPONSE_INFOR = apiRequestLogsM.RESPONSE_INFOR.Substring(0, 2000);
                }

                // 請求參數
                apiRequestLogsM.REQUEST_INFOR = actionContext.Request.RequestUri.Query;

                // 定義一個異步委託 ,異步記錄日誌
                //  Func<MApiRequestLogs, string> action = AddApiRequestLogs;//聲明一個委託
                // IAsyncResult ret = action.BeginInvoke(apiRequestLogsM, null, null);

            }
            catch (Exception ex)
            {

            }
        }
    }

 

HandleError錯誤處理

異常處理對於我們來說很常用,很好的利用異常處理,可以很好的避免全篇的try/catch。異常處理箱單很簡單,值需要自定義集成:ExceptionFilterAttribute,並自定義實現:OnException方法即可。

在OnException我們可以根據自身需要,做一些相應的邏輯處理,比如記錄異常日誌,便於後續問題分析跟進。

OnException還有一個很重要的處理,那就是對異常結果的統一包裝,返回一個很友好的結果給用戶,避免把一些不必要的信息返回給用戶。比如:針對MVC,那麼跟進不同異常,統一調整至友好的提示頁面等等;針對API,那麼我們可以一個統一的返回幾個封裝,便於用戶統一處理結果。

MVC 的異常處理實例代碼:

 

   /// <summary>
    /// MVC自定義異常處理機制
    /// 說道異常處理,其實我們腦海中的第一反應,也該是try/cache操作
    /// 但是在實際開發中,很有可能地址錯誤根本就進入不到try中,又或者沒有被try處理到異常
    /// 該類就發揮了作用,能夠很好的未經捕獲的異常,並做相應的邏輯處理
    /// 自定義異常機制,主要集成HandleErrorAttribute 重寫其OnException方法
    /// </summary>
    public class XYHMVCHandleError : HandleErrorAttribute
    {
        /// <summary>
        /// 處理異常
        /// </summary>
        /// <param name="filterContext">異常上下文</param>
        public override void OnException(ExceptionContext filterContext)
        {
            // 我們在平時的項目中,異常處理一般有兩個作用
            // 1:記錄異常的詳細日誌,便於事後分析日誌
            // 2:對異常的統一友好處理,比如根據異常類型重定向到友好提示頁面

            // 在這裏面既能獲取到未經處理的異常信息,也能獲取到請求信息
            // 在此可以根據實際項目需要做相應的邏輯處理
            // 下面簡單的列舉了幾個關鍵信息獲取方式

            // 控制器名稱 注意,這樣獲取出來的是一個文件的全路徑 
            string contropath = filterContext.Controller.ToString();

            // 訪問目錄的相對路徑
            string filePath = filterContext.HttpContext.Request.FilePath;

            // url完整地址
            string url = (filterContext.HttpContext.Request.Url.AbsoluteUri).ExUrlDeCode();

            // 請求方式 post get
            string httpMethod = filterContext.HttpContext.Request.HttpMethod;

            // 請求IP地址
            string ip = filterContext.HttpContext.Request.GetIPAddress();

            // 獲取全部的請求參數
            HttpRequest httpRequest = HttpContext.Current.Request;
            Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();

            // 獲取異常對象
            Exception ex = filterContext.Exception;

            // 異常描述信息
            string exMessage = ex.Message;

            // 異常堆棧信息
            string stackTrace = ex.StackTrace;

            // 根據實際情況記錄日誌(文本日誌、數據庫日誌,建議具體步驟採用異步方式來完成)


            filterContext.ExceptionHandled = true;

            // 模擬根據不同的做對應的邏輯處理
            int statusCode = filterContext.HttpContext.Response.StatusCode;

            if (statusCode>=400 && statusCode<500)
            {
                filterContext.Result = new RedirectResult("/html/404.html");
            }
            else 
            {
                filterContext.Result = new RedirectResult("/html/500.html");
            }
        }
    }

 

API 的異常處理實例代碼:

 

 /// <summary>
    /// API自定義異常處理機制
    /// 說道異常處理,其實我們腦海中的第一反應,也該是try/cache操作
    /// 但是在實際開發中,很有可能地址錯誤根本就進入不到try中,又或者沒有被try處理到異常
    /// 該類就發揮了作用,能夠很好的未經捕獲的異常,並做相應的邏輯處理
    /// 自定義異常機制,主要集成ExceptionFilterAttribute 重寫其OnException方法
    /// </summary>
    public class XYHAPIHandleError : ExceptionFilterAttribute
    {
        /// <summary>
        /// 處理異常
        /// </summary>
        /// <param name="actionExecutedContext">異常上下文</param>
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            // 我們在平時的項目中,異常處理一般有兩個作用
            // 1:記錄異常的詳細日誌,便於事後分析日誌
            // 2:對異常的統一友好處理,比如根據異常類型重定向到友好提示頁面

            // 在這裏面既能獲取到未經處理的異常信息,也能獲取到請求信息
            // 在此可以根據實際項目需要做相應的邏輯處理
            // 下面簡單的列舉了幾個關鍵信息獲取方式

            // action名稱 
            string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;

            // 控制器名稱 
            string controllerName =actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName;

            // url完整地址
            string url = (actionExecutedContext.Request.RequestUri.AbsoluteUri).ExUrlDeCode();

            // 請求方式 post get
            string httpMethod = actionExecutedContext.Request.Method.Method;

            // 請求IP地址
            string ip = actionExecutedContext.Request.GetIPAddress();

            // 獲取全部的請求參數
            HttpRequest httpRequest = HttpContext.Current.Request;
            Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();

            // 獲取異常對象
            Exception ex = actionExecutedContext.Exception;

            // 異常描述信息
            string exMessage = ex.Message;

            // 異常堆棧信息
            string stackTrace = ex.StackTrace;

            // 根據實際情況記錄日誌(文本日誌、數據庫日誌,建議具體步驟採用異步方式來完成)
            // 自己的記錄日誌落地邏輯略 ......

            // 構建統一的內部異常處理機制,相當於對異常做一層統一包裝暴露
            MBaseResult<string> result = new MBaseResult<string>()
            {
                Code = MResultCodeEnum.systemErrorCode,
                Message = MResultCodeEnum.systemError
            };

            actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
            //需要自己指定輸出內容和類型
            HttpContext.Current.Response.ContentType = "text/html;charset=utf-8";
            HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result));
            HttpContext.Current.Response.End(); // 此處結束響應,就不會走路由系統
        }
    }

 

總結

.net過濾器,我個人的一句話理解就是:對action的各個階段進行統一的監控處理等操作。.net過濾器中,其中每一個種過濾器的執行先後順序為:Authorize(授權)–>ActionFilter自定義–>HandleError(錯誤處理)

好了,就先聊到這而,如果什麼地方說的不對之處,多多指點和多多包涵。我自己寫了一個練習DEMO,裏面會有每一種情況的處理說明。有興趣的可以取下載下來看一看,謝謝。

DEMO在GitHub地址為:https://github.com/xuyuanhong0902/XYH.FilterTest.git

 

END
為了更高的交流,歡迎大家關注我的公眾號,掃描下面二維碼即可關注,謝謝:

 

 

認證授權

時間戳

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

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

市值破2.4兆 155家跨國企業連署振興訴求 指名「科學為基礎」的零碳經濟方案

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

輕鬆解決Github連接緩慢、圖裂問題

1 簡介

  gayhub(誤)github作為全世界最大的開源代碼庫以及版本控制系統,是用來託管項目以及學習開源技術非常好的平台,是我心中最好的學習網站,我們公眾號(Python大數據分析)的眾多技術文章對應的數據和代碼也都一直託管在github上。

  但熟悉github的朋友應該都被其越來越慢的連接速度,以及“全員圖裂”所困擾:

圖1

  本文就將參考github倉庫(https://github.com/521xueweihan/GitHub520 ),教大家如何在不kexue上網的前提下,簡單幾步解決github訪問緩慢已經各種圖裂的問題。

2 通過修改本地hosts文件加速github

2.1 手動修改更新

  首先我們需要找到自己設備上的hosts文件,不同的平台其存放路徑各不相同,主要的平台hosts文件所在路徑如下:

  • Windows :C:\Windows\System32\drivers\etc\hosts
  • Linux:/etc/hosts
  • Mac:/etc/hosts

  以Windows為例,按照上面的說明,進入C:\Windows\System32\drivers\etc目錄,找到hosts文件:

圖2

  這是一個無拓展名的文件,我們可以使用記事本、notepad++等文本編輯器來打開它,將下面的內容(這部分內容在寫作本文的時候是有效的,如果你在嘗試時它們已經失效了,可以前往上文提到的github倉庫複製最新的,或者參考下文中的第2種方法)複製,並粘貼hosts文件的最後:

# GitHub520 Host Start
185.199.108.154                                   github.githubassets.com
199.232.68.133                                    camo.githubusercontent.com
199.232.68.133                                    github.map.fastly.net
199.232.69.194                                    github.global.ssl.fastly.net
140.82.112.3                                      github.com
140.82.113.5                                      api.github.com
199.232.68.133                                    raw.githubusercontent.com
199.232.68.133                                    user-images.githubusercontent.com
199.232.68.133                                    favicons.githubusercontent.com
199.232.68.133                                    avatars5.githubusercontent.com
199.232.68.133                                    avatars4.githubusercontent.com
199.232.68.133                                    avatars3.githubusercontent.com
199.232.68.133                                    avatars2.githubusercontent.com
199.232.68.133                                    avatars1.githubusercontent.com
199.232.68.133                                    avatars0.githubusercontent.com
# GitHub520 Host End

圖3

  如果保存時需要管理員權限,按照提示以管理員方式重新打開再保存即可,正常情況下在保存退出後會立即生效,如果依然加載不出圖,可以根據自己系統的不同來執行對應的命令刷新DNS重啟機器即可:

  • Windows:ipconfig /flushdns
  • Linux:sudo rcnscd restart
  • Mac:sudo killall -HUP mDNSResponder

  接下來我們來看看這種方法的效果如何,在遵循上述流程修改好hosts文件之後,重新打開圖1對應的README頁面:

圖4

  O(∩_∩)O哈哈~,這時我們成功地加載出了原本裂掉的圖,但這種方式麻煩的地方在於當你配置好hosts之後的確是可以正常訪問github的,但一旦你某天訪問github發現老毛病又出現了,就得重複一遍上述的過程,接下來我們來學習另一種能將上述過程自動化的方法。

2.2 利用SwitchHosts軟件自動更新hosts信息

  SwitchHosts是一個用於快速切換hosts文件的開源軟件(https://github.com/oldj/SwitchHosts ),我們可以通過其官方提供的百度雲盤地址(https://pan.baidu.com/s/1inED1 )下載適合自己系統的版本。

  下載后直接正常安裝,接着以管理員身份打開,點擊左下角+新建hosts,再按照圖5配置好,設置自動刷新時間間隔為你覺得合適的,我選的1小時刷新一次,這樣每隔一小時SwitchHosts就會自動訪問URL並更新hosts信息:

圖5

  點擊刷新按鈕刷新成功后,點擊OK創建完成。其中URL信息是碼雲同步可正常訪問版本(https://gitee.com/xueweihan/codes/6g793pm2k1hacwfbyesl464/raw?blob_name=GitHub520.yml ),因為原始倉庫中的URL為github源會連接失敗。

  創建完成后,把開關打開,讓SwitchHosts在後台靜靜的運行即可:

圖6

  完成后,保持軟件後台運行即可,之後訪問Github同樣解決了問題。

  以上就是本文的全部內容,如有疑問歡迎在評論區與我討論。

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

我們是如何做DevOps的?

一、DevOps的理解

DevOps的概念理解

DevOps 的概念在軟件開發行業中逐漸流行起來。越來越多的團隊希望實現產品的敏捷開發,DevOps 使一切成為可能。有了 DevOps ,團隊可以定期發布代碼、自動化部署、並將持續集成 / 持續交付作為發布過程的一部分。
一句話概括就是提高生產力,快速交付!

二、引入DevOps的背景

2.1 福祿技術棧介紹

  • 後端開發框架:基於C#的.netCore和Java的SpringCloud,少部分項目採用python和go開發

  • 前端開發框架:vue、react

  • 服務部署:前端站點基於ECS的nginx部署 ,後端服務統一部署在kubernetes上

  • 代碼倉庫:gitlab

  • 項目環境:目前有6套,開發、測試、壓測、集成、PRE和生產

2.2 後端服務的CICD現狀

                                                                                                 福祿後端CICD流程

CICD 流程說明

每一次的代碼push,根據創建的分支,根據在gitlab的CICD文件gitlab.yml定義構建步驟,觸發runner,從單元測試、通過dockerfile進行編譯和生成鏡像版本、將新鏡像部署到K8S生成pod,然後觸發接口自動化測試任務的執行

!!#00ffff 好像缺了點什麼 !!

  • 初次部署應用到kubernetes怎麼做的?

  • 服務的configmap在哪裡維護的?

  • 每個服務的gitlab.yml文件都不一樣,如何維護的?

  • 應用的域名解析怎麼做?

目前有6套環境進行管理,其中開發、測試、集成、壓測都是測試人員維護,預發布和生產運維人員維護;這也就要求每一個測試人員都必須對整個cicd流程和配置絕對掌握;所以當新人入職,需要掌握整個流程才能進入項目測試中,這是一個學習成本;

預發布和生產的kubernetes只有運維能夠操作,當有新的服務需要上線上述環境,或者configmap有變動,或者有時候排查問題需要查看容器日誌,我們只能通過運維的工單系統描述作業操作,中間文字描述可能存在理解差異,溝通成本和時間成本很大;

有的新應用我們去設置cicd的相關文件,比如dockerfile,我們發現應用的代碼目錄結構各種各樣,這樣往往就沒法套用一個模板快速配置完成

2.3 前端站點的CICD現狀

前端CICD流程說明

開發人員push代碼到gitlab,測試人員通過jenkins拉取最新的代碼到jenkins本地,然後通過jenkins與服務器之間的傳輸管道,將要部署的文件更新到目標服務器,並觸發UI自動化的job

完整的過程來看,也缺點內容

  • 一個新的站點部署,nginx需要做一些配置初始化工作,比如域名、路徑的配置
  • 前端的配置文件是如何管理的

跟後端應用一樣,前端的PRE和生產環境也是運維處理,所以當一個新的應用上線我們也需要發工單,描述具體操作,然後運維執行工單;配置文件一般不會變更,所以我們在jenkins推送更新文件到目標服務器的時候,將配置文件做了過濾處理。後續需要變更通過工單執行

2.3 痛點你看到了嗎

2.3.1 安全管控缺位
  • 代碼安全:CICD的起點在gitlab裏面,所以大家都有gitlab的賬號,代碼安全管控缺位
  • 線上安全:線上項目部署也是通過gitlab的cicd直接觸發,審批流程缺失
2.3.2 管理成本
  • 維護賬號多:gitlab賬號、jenkins賬號、kubernetes賬號(本地和阿里雲),每一個人員都需要上述賬號,運維管理麻煩,大家每個平台維護自己的賬號也麻煩
  • 工單溝通:工單編寫、溝通過程花費時間較多
  • 代碼規範:項目組多,微服務也多,代碼框架各自發揮,無論是流程維護還是問題排查都增加了難度

三、研發管理平台(RDMS)應運而生

3.1 如何理解這個平台

!!#ff0000 工具鏈到平台的轉變 !!

當前的cicd是對工具鏈進行了打通,但需要大家登錄各個工具平台操作,我們希望對工具集進行功能整合,打造一個系統平台,並且將CICD的技術細節進行屏蔽,開發人員能夠專心進行業務需求的開發,測試人員能夠專註到需求測試任務中,而運維人員能夠解放繁重的工單內容,投入到服務高可用的建設上!

3.2 業務功能設計

                                                                                                                                  福祿研發管理平台功能結構圖

3.2.1 功能說明
  • 項目管理:項目的創建和維護,默認提供了.netcore的api和控制台,java的api和前端站點的應用初始化代碼框架,開發人員開發新的應用直接根據應用類型選擇對應的模板就可以在git默認創建代碼倉庫和初始化框架代碼,並自動生成應用的http和https的域名
  • 構建記錄:獲取gitlab的pipeline,展示所有分支的構建記錄信息,可以一鍵跳轉到git倉庫
  • 部署管理:部署構建的鏡像到指定的環境,提供實時部署和定時部署功能
  • 容器管理:提供容器的查看功能,可以看到容器的存活狀態和容器實時日誌
  • 配置字段權限申請:針對PRE和生產環境查看配置,需要先走釘釘審批申請流程
  • 配置信息:進行配置的維護,包括新增、編輯、刪除,PRE和生產環境操作需要釘釘流程審批
  • 操作日誌:針對應用的操作日誌記錄
  • 用戶設置:在使用rdms前,需要先將用戶git倉庫的token設置在rdms上,這樣用戶在rdms操作與gitlab相關的業務才能正常使用
3.2.2 RDMS幾個核心頁面的展示

首頁-創建應用

構建記錄

部署管理

容器管理

3.3 技術架構

對接系統的說明

  • 通行證:RDMS的目標用戶是研發中心人員,這些人員在通行證中都有默認的賬戶信息,與通行證打通,可以直接登錄使用
  • GitlabAPI:目前RDMS的CI還是採用的gitlab的ci支撐,包括新應用在rdms的創建到git倉庫的代碼初始化等,都需要調用gitlab的api接口
  • 釘釘flow:安全管控的原因,PRE和生產的任何操作都會觸發釘釘審批流,所屬項目的項目經理審批通過後才會獲取到數據或者執行操作指令
  • 福祿開放平台:提供了網關相關的功能和菜單、角色等維護功能,公司所有後端服務都需要入駐開放平台
  • 蜂巢:公司的調度作業平台,rdms的定時部署功能依賴該服務的支撐
  • 運維工單系統:rdms的CD流程沒有直接與kubernets進行交互,而是通過運維的工單系統包裝了運維底層的shell腳本層,然後提供給rdms相關的api接口,也是基於安全控制的考慮
  • shell腳本層:shell腳本層會調用kubernetes的api進行kubernetes的相關操作(部署、配置更新、容器重啟、日誌查看等);調用阿里雲的dns解析接口,對應用的域名自動解析;調用oss的接口,進行前端站點文件目錄的維護

3.4 後端應用的devops實現詳解

舉個栗子進行介紹

根據模板,創建一個應用

根據名稱默認生成域名

初始化代碼倉庫,默認生成develop分支

在rdms第一次部署到對應環境(開發、測試、生產等)時,會默認讀取appsettings.Development.json的文件,並寫入kubernets的configmap

構建完成,進行部署

在kubernets生成pod

通過域名訪問接口文檔

3.5 前端站點的devops實現詳解

同樣的,舉個栗子介紹

首頁-創建前端站點

根據名稱生成域名

初始化代碼倉庫,默認生成develop分支

配置文件,默認生成幾套環境的配置文件,站點的配置維護就是維護這幾個文件

部署應用

kubernetes的nginx容器內可以看到部署的文件,實際就是掛載的oss到該pod上

四、展望

目前RDMS投產一個月左右,我們希望能將devops理念在這個系統上進行持續的優化和實踐,包括研發中心小夥伴也很踴躍參与共建,提出了很多好的方向和建議

  • 完善devops鏈條功能:通過字面來看有dev、ops兩部分,我們後期需要加入test的比重,比如在CI部分,引入靜態代碼掃描、單測覆蓋率;在CD部分集成我們的自動化測、性能測試
  • 工具平台:RDMS的初衷是整合,針對研發中心經常使用的工具或者有相關工具化的需求,我們可以整合到rdms或者在RDMS上進行開發

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化