整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          HTML頁面基本結構和加載過程

          家好,我是皮皮。

          前言

          對于前端來說,HTML 都是最基礎的內容。

          今天,我們來了解一下 HTML 和網頁有什么關系,以及與 DOM 有什么不同。通過本講內容,你將掌握瀏覽器是怎么處理 HTML 內容的,以及在這個過程中我們可以進行怎樣的處理來提升網頁的性能,從而提升用戶的體驗。


          一、瀏覽器頁面加載過程

          不知你是否有過這樣的體驗:當打開某個瀏覽器的時候,發現一直在轉圈,或者等了好長時間才打開頁面……

          此時的你,會選擇關掉頁面還是耐心等待呢?

          這一現象,除了網絡不穩定、網速過慢等原因,大多數都是由于頁面設計不合理導致加載時間過長導致的。

          我們都知道,頁面是用 HTML/CSS/JavaScript 來編寫的。

          • HTML 的職責在于告知瀏覽器如何組織頁面,以及搭建頁面的基本結構;
          • CSS 用來裝飾 HTML,讓我們的頁面更好看;
          • JavaScript 則可以豐富頁面功能,使靜態頁面動起來。

          HTML由一系列的元素組成,通常稱為HTML元素。HTML 元素通常被用來定義一個網頁結構,基本上所有網頁都是這樣的 HTML 結構:

          <html>
              <head></head>
              <body></body>
          </html>

          其中:

          • html元素是頁面的根元素,它描述完整的網頁;
          • head元素包含了我們想包含在 HTML 頁面中,但不希望顯示在網頁里的內容;
          • body元素包含了我們訪問頁面時所有顯示在頁面上的內容,是用戶最終能看到的內容;


          HTML 中的元素特別多,其中還包括可用于 Web Components 的自定義元素。

          前面我們提到頁面 HTML 結構不合理可能會導致頁面響應慢,這個過程很多時候體現在<script><style>元素的設計上,它們會影響頁面加載過程中對 Javascript 和 CSS 代碼的處理。

          因此,如果想要提升頁面的加載速度,就需要了解瀏覽器頁面的加載過程是怎樣的,從根本上來解決問題。

          瀏覽器在加載頁面的時候會用到 GUI 渲染線程和 JavaScript 引擎線程(更詳細的瀏覽器加載和渲染機制將在第 7 講中介紹)。其中,GUI 渲染線程負責渲染瀏覽器界面 HTML 元素,JavaScript 引擎線程主要負責處理 JavaScript 腳本程序。

          由于 JavaScript 在執行過程中還可能會改動界面結構和樣式,因此它們之間被設計為互斥的關系。也就是說,當 JavaScript 引擎執行時,GUI 線程會被掛起。

          以網易云課堂官網為例,我們來看看網頁加載流程。

          (1)當我們打開官網的時候,瀏覽器會從服務器中獲取到 HTML 內容。

          (2)瀏覽器獲取到 HTML 內容后,就開始從上到下解析 HTML 的元素。

          (3)<head>元素內容會先被解析,此時瀏覽器還沒開始渲染頁面。

          我們看到<head>元素里有用于描述頁面元數據的<meta>元素,還有一些<link>元素涉及外部資源(如圖片、CSS 樣式等),此時瀏覽器會去獲取這些外部資源。除此之外,我們還能看到<head>元素中還包含著不少的<script>元素,這些<script>元素通過src屬性指向外部資源。

          (4)當瀏覽器解析到這里時(步驟 3),會暫停解析并下載 JavaScript 腳本。

          (5)當 JavaScript 腳本下載完成后,瀏覽器的控制權轉交給 JavaScript 引擎。當腳本執行完成后,控制權會交回給渲染引擎,渲染引擎繼續往下解析 HTML 頁面。

          (6)此時<body>元素內容開始被解析,瀏覽器開始渲染頁面。

          在這個過程中,我們看到<head>中放置的<script>元素會阻塞頁面的渲染過程:把 JavaScript 放在<head>里,意味著必須把所有 JavaScript 代碼都下載、解析和解釋完成后,才能開始渲染頁面。

          到這里,我們就明白了:如果外部腳本加載時間很長(比如一直無法完成下載),就會造成網頁長時間失去響應,瀏覽器就會呈現“假死”狀態,用戶體驗會變得很糟糕。

          因此,對于對性能要求較高、需要快速將內容呈現給用戶的網頁,常常會將 JavaScript 腳本放在<body>的最后面。這樣可以避免資源阻塞,頁面得以迅速展示。我們還可以使用defer/async/preload等屬性來標記<script>標簽,來控制 JavaScript 的加載順序。

          百度首頁

          三、DOM 解析

          對于百度這樣的搜索引擎來說,必須要在最短的時間內提供到可用的服務給用戶,其中就包括搜索框的顯示及可交互,除此之外的內容優先級會相對較低。

          瀏覽器在渲染頁面的過程需要解析 HTML、CSS 以得到 DOM 樹和 CSS 規則樹,它們結合后才生成最終的渲染樹并渲染。因此,我們還常常將 CSS 放在<head>里,可用來避免瀏覽器渲染的重復計算。


          二、HTML 與 DOM 有什么不同

          我們知道<p>是 HTML 元素,但又常常將<p>這樣一個元素稱為 DOM 節點,那么 HTML 和 DOM 到底有什么不一樣呢?

          根據 MDN 官方描述:文檔對象模型(DOM)是 HTML 和 XML 文檔的編程接口。

          也就是說,DOM 是用來操作和描述 HTML 文檔的接口。如果說瀏覽器用 HTML 來描述網頁的結構并渲染,那么使用 DOM 則可以獲取網頁的結構并進行操作。一般來說,我們使用 JavaScript 來操作 DOM 接口,從而實現頁面的動態變化,以及用戶的交互操作。

          在開發過程中,常常用對象的方式來描述某一類事物,用特定的結構集合來描述某些事物的集合。DOM 也一樣,它將 HTML 文檔解析成一個由 DOM 節點以及包含屬性和方法的相關對象組成的結構集合。


          三、DOM 解析

          我們常見的 HTML 元素,在瀏覽器中會被解析成節點。比如下面這樣的 HTML 內容:

          <html>
              <head>
                  <title>標題</title>
              </head>
              <body>
                  <a href='xx.com'>我的超鏈接</a>
                  <h1>頁面第一標題</h1>
              </body>
          </html>

          打開控制臺 Elements 面板,可以看到這樣的 HTML 結構,如下圖所示:

          在瀏覽器中,上面的 HTML 會被解析成這樣的 DOM 樹,如下圖所示:


          我們都知道,對于樹狀結構來說,常常使用parent/child/sibling等方式來描述各個節點之間的關系,對于 DOM 樹也不例外。

          舉個例子,我們常常會對頁面功能進行抽象,并封裝成組件。但不管怎么進行整理,頁面最終依然是基于 DOM 的樹狀結構,因此組件也是呈樹狀結構,組件間的關系也同樣可以使用parent/child/sibling這樣的方式來描述。同時,現在大多數應用程序同樣以root為根節點展開,我們進行狀態管理、數據管理也常常會呈現出樹狀結構。


          四、事件委托

          我們知道,瀏覽器中各個元素從頁面中接收事件的順序包括事件捕獲階段、目標階段、事件冒泡階段。其中,基于事件冒泡機制,我們可以實現將子元素的事件委托給父級元素來進行處理,這便是事件委托。

          如果我們在每個元素上都進行監聽的話,則需要綁定三個事件;(假設頁面上有a,b,c三個兄弟節點)

          function clickEventFunction(e) {
            console.log(e.target === this); // logs `true`
            // 這里可以用 this 獲取當前元素
          }
          // 元素a,b,c綁定
          element2.addEventListener("click", clickEventFunction, false);
          element5.addEventListener("click", clickEventFunction, false);
          element8.addEventListener("click", clickEventFunction, false);

          使用事件委托,可以通過將事件添加到它們的父節點,而將事件委托給父節點來觸發處理函數:

          function clickEventFunction(event) {
            console.log(e.target === this); // logs `false`
            // 獲取被點擊的元素
            const eventTarget = event.target;
            // 檢查源元素`event.target`是否符合預期
            // 此處控制廣告面板的展示內容
          }
          // 元素1綁定
          element1.addEventListener("click", clickEventFunction, false);

          這樣能解決什么問題呢?

          • 綁定子元素會綁定很多次的事件,而綁定父元素只需要一次綁定。
          • 將事件委托給父節點,這樣我們對子元素的增加和刪除、移動等,都不需要重新進行事件綁定。

          常見的使用方式主要是上述這種列表結構,每個選項都可以進行編輯、刪除、添加標簽等功能,而把事件委托給父元素,不管我們新增、刪除、更新選項,都不需要手動去綁定和移除事件。

          如果在列表數量內容較大的時候,對成千上萬節點進行事件監聽,也是不小的性能消耗。使用事件委托的方式,我們可以大量減少瀏覽器對元素的監聽,也是在前端性能優化中比較簡單和基礎的一個做法。

          注意:

          1. 如果我們直接在document.body上進行事件委托,可能會帶來額外的問題;
          2. 由于瀏覽器在進行頁面渲染的時候會有合成的步驟,合成的過程會先將頁面分成不同的合成層,而用戶與瀏覽器進行交互的時候需要接收事件。此時,瀏覽器會將頁面上具有事件處理程序的區域進行標記,被標記的區域會與主線程進行通信。
          3. 如果我們document.body上被綁定了事件,這時候整個頁面都會被標記;
          4. 即使我們的頁面不關心某些部分的用戶交互,合成器線程也必須與主線程進行通信,并在每次事件發生時進行等待。這種情況,我們可以使用passive: true選項來解決


          五、總結

          我們了解了 HTML 的作用,以及它是如何影響瀏覽器中頁面的加載過程的,同時還介紹了使用 DOM 接口來控制 HTML 的展示和功能邏輯。我們了解了DOM解析事件委托等相關概念。

          . SourceTree是什么

          • 擁有可視化界面的項目版本控制軟件,適用于git項目管理
          • window、mac可用

          2. 獲取項目代碼

          1. 點擊克隆/新建

          2. 在彈出框中輸入項目地址,http或者ssh地址都可以

          如果箭頭指向的倉庫類型表明“這不是一個標準的Git倉庫”,可能是有以下原因

          1) 項目地址獲取錯誤

          2) 沒有項目訪問權限

          3. 點擊“克隆”,等待項目克隆完成,完成后,左側只有一個分支master

          克隆完成后,得到的是發布后的master源碼,如果想要獲取最新的正在開發中的源碼,需要對項目流進行初始化,點擊“Git工作流”

          直接點“確定”,獲取develop分支源碼

          開發任務都是在develop分支上完成的

          4. 分支共有5種類型

          1) master,最終發布版本,整個項目中有且只有一個

          2) develop,項目的開發分支,原則上項目中有且只有一個

          3) feature,功能分支,用于開發一個新的功能

          4) release,預發布版本,介于develop和master之間的一個版本,主要用于測試

          5) hotfix,修復補丁,用于修復master上的bug,直接作用于master

          5. master和develop上文中已介紹過,當開發中需要增加一個新的功能時,可新建feature分支,用于增加新功能,并且不影響開發中的develop源碼,當新功能增加完成后,完成feature分支,將新功能合并到develop中,更新develop上的代碼

          1) 新建feature。首先當前開發分支指向develop,點擊“Git工作流”

          選擇“建立新的分支”

          在預覽中可看到,feature分支是從develop分出的,輸入功能名稱,點擊確定,項目結構中增加feature分支,并且當前開發分支指向新建的feature分支

          2) 在F_add_feature分支下進行開發任務,并提交

          以上操作分別增加了feature_1、feature_2、feature_3文件,共提交3次,現項目文件夾下共三個文件

          當切換為develop分支后,會發現,在develop下并沒有新增的三個文件,說明在feature下進行操作,并不影響develop分支源碼

          3) 完成feature開發后,將feature中的源碼合并到develop分支。將當前分支指向F_add_feature分支,點擊“Git工作流”,選擇“完成功能”

          預覽中,表明feature分支將合并到develop,點擊確定,進行提交合并,合并成功后

          4) 需要再增加新的功能時,重復以上操作即可

          5) 當多人協作開發時,可能會出現,不同人員對同一文件進行操作,從而引起合并沖突,對這種情況進行模擬,在當前新建兩個feature,分別對feature_1文件進行修改,然后分別合并

          feature_1在feature_1.txt下做如下操作

          feature_2在feature_1.txt下做如下操作

          先后合并F_feature_1和F_feature_2,會出現沖突

          點擊close,查看未提交的更改,提示feature_1.txt出現沖突,

          打開feature_1.txt

          出現<<<<<<< HEAD、=======、>>>>>>> feature/F_feature_2,HEAD和=號之間表示當前分支下的代碼,=號和>>>>>>> feature/F_feature_2之間表示要合并的分支下的代碼,>>>>>>> feature/F_feature_2表示了要合并的分支的分支名稱,

          根據情況區分要保留的代碼,要刪除的代碼,最后再刪除<<<<<<< HEAD、=======、和>>>>>>> feature/F_feature_2

          將修改的代碼再進行一次提交

          一旦出現feature合并沖突,要合并的feature分支不會被刪除,如F_feature_2,確保合并沒有問題后,可手動刪除F_feature_2

          6. 當開發到一定階段,可以發布測試版本時,可以從develop分支,建立release分支,進入預發布測試階段。點擊“Git工作流”,選擇“建立新的發布版本”

          預覽中可以看到,release是從develop分出的,輸入發布版本名‘R_v1.0’,點擊確定

          R_v1.0為階段性發布版本,主要用于發布前進行測試,后續的開發工作仍舊在develop上進行,如果在測試過程中發現問題,直接在release上進行修改,修改完成后進行提交

          7. 對release分支R_v1.0進行兩次修改后,測試完成,可以進行正式發布,在當前分支指向R_v1.0分支下,點擊“Git工作流”,選擇“完成發布版本”

          在預覽中可以看到,R_v1.0向develop和master分別合并,點擊確定,完成正式發布。

          完成合并后,默認指向develop為當前分支,master增加多個版本更新,將master分支推送到origin,完成線上發布

          8. 正式版本發布后,develop可繼續進行后續開發,當正式版本出現問題時,需要進行問題的修改,可以在master分支建立修改補丁hotfix。將當前分支切換到master,點擊“Git工作流”,選擇“建立新的修復補丁”

          預覽中hotfix分支是從master拉去出來的,輸入修復補丁名,點確定

          在該分支下進行master的問題修改,修改完成后進行提交。當所有補丁問題修改完成后,點擊“Git工作流”,選擇“完成修復補丁”

          預覽中,H_fix_1向master和develop分別合并,點擊確定,完成分支合并。

          合并完成后,默認當前分支為develop,master分支有版本需要更新,當前分支切換為master,進行推送,完成補丁修復。

          9. 在完成發布版本和完成修復補丁時,如果遇到沖突,可仿照上述5進行沖突修改,再進行后續操作

          者:崔家華

          東北大學|模式識別與智能系統研究生

          量子位 已獲授權編輯發布

          在模式識別領域中,K-近鄰算法(KNN算法)是一種用于分類和回歸的非參數統計方法。

          在這篇文章中,作者先詳細介紹了K-近鄰算法的基礎知識,接著在Python 3中演示了約會網站配對實戰和sklearn手寫數字識別。形象生動,簡明易懂。

          在文章正式開始前,可能你需要這些信息——

          Github代碼獲取:

          https://github.com/Jack-Cherish/Machine-Learning/

          Python版本: Python3.x

          運行平臺: Windows

          IDE: Sublime text3

          想入門的你還不快來上車。

          一. 簡單k-近鄰算法

          本文將從k-鄰近算法的思想開始講起,使用python3一步一步編寫代碼進行實戰訓練。并且,我也提供了相應的數據集,對代碼進行了詳細的注釋。除此之外,本文也對sklearn實現k-鄰近算法的方法進行了講解。

          實戰實例:電影類別分類、約會網站配對效果判定、手寫數字識別。

          本文出現的所有代碼和數據集,均可在我的github上下載,歡迎Follow、Star——

          下載地址:

          https://github.com/Jack-Cherish/Machine-Learning/tree/master/kNN

          1.k-近鄰法簡介

          k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與回歸方法。

          它的工作原理是:存在一個樣本數據集合,也稱作為訓練樣本集,并且樣本集中每個數據都存在標簽,即我們知道樣本集中每一個數據與所屬分類的對應關系。

          輸入沒有標簽的新數據后,將新的數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本最相似數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大于20的整數。

          最后,選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。

          舉個簡單的例子,我們可以使用k-近鄰算法分類一個電影是愛情片還是動作片。

          表1.1 每部電影的打斗鏡頭數、接吻鏡頭數以及電影類型

          表1.1 就是我們已有的數據集合,也就是訓練樣本集。這個數據集有兩個特征,即打斗鏡頭數和接吻鏡頭數。除此之外,我們也知道每個電影的所屬類型,即分類標簽。用肉眼粗略地觀察,接吻鏡頭多的,是愛情片。打斗鏡頭多的,是動作片。

          以我們多年的看片經驗,這個分類還算合理。如果現在給我一部電影,你告訴我這個電影打斗鏡頭數和接吻鏡頭數。

          不告訴我這個電影類型,我可以根據你給我的信息進行判斷,這個電影是屬于愛情片還是動作片。而k-近鄰算法也可以像我們人一樣做到這一點,不同的地方在于,我們的經驗更”牛逼”,而k-鄰近算法是靠已有的數據。

          比如,你告訴我這個電影打斗鏡頭數為2,接吻鏡頭數為102,我的經驗會告訴你這個是愛情片,k-近鄰算法也會告訴你這個是愛情片。

          你又告訴我另一個電影打斗鏡頭數為49,接吻鏡頭數為51,我”邪惡”的經驗可能會告訴你,這有可能是個”愛情動作片”,畫面太美,我不敢想象。 (如果說,你不知道”愛情動作片”是什么?請評論留言與我聯系,我需要你這樣像我一樣純潔的朋友。)

          但是k-近鄰算法不會告訴你這些,因為在它的眼里,電影類型只有愛情片和動作片,它會提取樣本集中特征最相似數據(最鄰近)的分類標簽,得到的結果可能是愛情片,也可能是動作片,但絕不會是”愛情動作片”。當然,這些取決于數據集的大小以及最近鄰的判斷標準等因素。

          2.距離度量

          我們已經知道k-近鄰算法根據特征比較,然后提取樣本集中特征最相似數據(最鄰近)的分類標簽。那么,如何進行比較呢?比如,我們還是以表1.1為例,怎么判斷紅色圓點標記的電影所屬的類別呢? 如圖1.1所示。

          圖1.1 電影分類

          我們可以從散點圖大致推斷,這個紅色圓點標記的電影可能屬于動作片,因為距離已知的那兩個動作片的圓點更近。k-近鄰算法用什么方法進行判斷呢?

          沒錯,就是距離度量。這個電影分類的例子有2個特征,也就是在2維實數向量空間,可以使用我們高中學過的兩點距離公式計算距離,如圖1.2所示。

          通過計算,我們可以得到如下結果:

          (101,20)->動作片(108,5)的距離約為16.55

          (101,20)->動作片(115,8)的距離約為18.44

          (101,20)->愛情片(5,89)的距離約為118.22

          (101,20)->愛情片(1,101)的距離約為128.69

          通過計算可知,紅色圓點標記的電影到動作片 (108,5)的距離最近,為16.55。如果算法直接根據這個結果,判斷該紅色圓點標記的電影為動作片,這個算法就是最近鄰算法,而非k-近鄰算法。那么k-鄰近算法是什么呢?k-近鄰算法步驟如下:

          計算已知類別數據集中的點與當前點之間的距離;

          按照距離遞增次序排序;

          選取與當前點距離最小的k個點;

          確定前k個點所在類別的出現頻率;

          返回前k個點所出現頻率最高的類別作為當前點的預測分類。

          比如,現在我這個k值取3,那么在電影例子中,按距離依次排序的三個點分別是動作片(108,5)、動作片(115,8)、愛情片(5,89)。在這三個點中,動作片出現的頻率為三分之二,愛情片出現的頻率為三分之一,所以該紅色圓點標記的電影為動作片。

          這個判別過程就是k-近鄰算法。

          3.Python3代碼實現

          我們已經知道了k-近鄰算法的原理,那么接下來就是使用Python3實現該算法,依然以電影分類為例。

          (1)準備數據集

          對于表1.1中的數據,我們可以使用numpy直接創建,代碼如下:

          運行結果,如圖1.3所示:

          圖1.3 運行結果

          (2)k-近鄰算法

          根據兩點距離公式,計算距離,選擇距離最小的前k個點,并返回分類結果。

          運行結果,如圖1.4所示:

          圖1.4 運行結果

          可以看到,分類結果根據我們的”經驗”,是正確的,盡管這種分類比較耗時,用時1.4s。

          到這里,也許有人早已經發現,電影例子中的特征是2維的,這樣的距離度量可以用兩 點距離公式計算,但是如果是更高維的呢?

          對,沒錯。我們可以用歐氏距離(也稱歐幾里德度量),如圖1.5所示。我們高中所學的兩點距離公式就是歐氏距離在二維空間上的公式,也就是歐氏距離的n的值為2的情況。

          圖1.5 歐氏距離公式

          看到這里,有人可能會問:“分類器何種情況下會出錯?”或者“答案是否總是正確的?”答案是否定的,分類器并不會得到百分百正確的結果,我們可以使用多種方法檢測分類器的正確率。此外分類器的性能也會受到多種因素的影響,如分類器設置和數據集等。

          不同的算法在不同數據集上的表現可能完全不同。為了測試分類器的效果,我們可以使用已知答案的數據,當然答案不能告訴分類器,檢驗分類器給出的結果是否符合預期結果。

          通過大量的測試數據,我們可以得到分類器的錯誤率-分類器給出錯誤結果的次數除以測試執行的總數。

          錯誤率是常用的評估方法,主要用于評估分類器在某個數據集上的執行效果。完美分類器的錯誤率為0,最差分類器的錯誤率是1.0。

          同時,我們也不難發現,k-近鄰算法沒有進行數據的訓練,直接使用未知的數據與已知的數據進行比較,得到結果。因此,可以說k-鄰近算法不具有顯式的學習過程。

          二.k-近鄰算法實戰之約會網站配對效果判定

          上一小結學習了簡單的k-近鄰算法的實現方法,但是這并不是完整的k-近鄰算法流程,k-近鄰算法的一般流程:

          • 收集數據:可以使用爬蟲進行數據的收集,也可以使用第三方提供的免費或收費的數據。一般來講,數據放在txt文本文件中,按照一定的格式進行存儲,便于解析及處理。

          • 準備數據:使用Python解析、預處理數據。

          • 分析數據:可以使用很多方法對數據進行分析,例如使用Matplotlib將數據可視化。

          • 測試算法:計算錯誤率。

          • 使用算法:錯誤率在可接受范圍內,就可以運行k-近鄰算法進行分類。

          已經了解了k-近鄰算法的一般流程,下面開始進入實戰內容。

          1.實戰背景

          海倫女士一直使用在線約會網站尋找適合自己的約會對象。盡管約會網站會推薦不同的任選,但她并不是喜歡每一個人。經過一番總結,她發現自己交往過的人可以進行如下分類:

          • 不喜歡的人

          • 魅力一般的人

          • 極具魅力的人

          海倫收集約會數據已經有了一段時間,她把這些數據存放在文本文件datingTestSet.txt中,每個樣本數據占據一行,總共有1000行。

          datingTestSet.txt數據下載:

          https://github.com/Jack-Cherish/Machine-Learning/blob/master/kNN/2.%E6%B5%B7%E4%BC%A6%E7%BA%A6%E4%BC%9A/datingTestSet.txt

          海倫收集的樣本數據主要包含以下3種特征:

          每年獲得的飛行常客里程數

          玩視頻游戲所消耗時間百分比

          每周消費的冰淇淋公升數

          這里不得不吐槽一句,海倫是個小吃貨啊,冰淇淋公斤數都影響自己擇偶標準。打開txt文本文件,數據格式如圖2.1所示。

          圖2.1 datingTestSet.txt格式

          2.準備數據:數據解析

          在將上述特征數據輸入到分類器前,必須將待處理的數據的格式改變為分類器可以接收的格式。分類器接收的數據是什么格式的?

          從上小結已經知道,要將數據分類兩部分,即特征矩陣和對應的分類標簽向量。在kNN_test02.py文件中創建名為file2matrix的函數,以此來處理輸入格式問題。 將datingTestSet.txt放到與kNN_test02.py相同目錄下,編寫代碼如下:

          運行上述代碼,得到的數據解析結果如圖2.2所示。

          圖2.2 數據解析結果

          可以看到,我們已經順利導入數據,并對數據進行解析,格式化為分類器需要的數據格式。接著我們需要了解數據的真正含義。可以通過友好、直觀的圖形化的方式觀察數據。

          3.分析數據:數據可視化

          在kNN_test02.py文件中編寫名為showdatas的函數,用來將數據可視化。編寫代碼如下:

          運行上述代碼,得到的數據解析結果如圖2.2所示。

          圖2.2 數據解析結果

          可以看到,我們已經順利導入數據,并對數據進行解析,格式化為分類器需要的數據格式。接著我們需要了解數據的真正含義。可以通過友好、直觀的圖形化的方式觀察數據。

          3.分析數據:數據可視化

          在kNN_test02.py文件中編寫名為showdatas的函數,用來將數據可視化。編寫代碼如下:

          運行上述代碼,可以看到可視化結果如圖2.3所示。

          圖2.3 數據可視化結果

          通過數據可以很直觀的發現數據的規律,比如以玩游戲所消耗時間占比與每年獲得的飛行常客里程數,只考慮這二維的特征信息,給我的感覺就是海倫喜歡有生活質量的男人。

          為什么這么說呢?每年獲得的飛行常客里程數表明,海倫喜歡能享受飛行常客獎勵計劃的男人,但是不能經常坐飛機,疲于奔波,滿世界飛。

          同時,這個男人也要玩視頻游戲,并且占一定時間比例。能到處飛,又能經常玩游戲的男人是什么樣的男人?很顯然,有生活質量,并且生活悠閑的人。我的分析,僅僅是通過可視化的數據總結的個人看法。我想,每個人的感受應該也是不盡相同。

          4.準備數據:數據歸一化

          表2.1給出了四組樣本,如果想要計算樣本3和樣本4之間的距離,可以使用歐拉公式計算。

          表2.1 約會網站樣本數據

          計算方法如圖2.4所示。

          圖2.4 計算公式

          我們很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大,也就是說,每年獲取的飛行常客里程數對于計算結果的影響將遠遠大于表2.1中其他兩個特征-玩視頻游戲所耗時間占比和每周消費冰淇淋公斤數的影響。

          而產生這種現象的唯一原因,僅僅是因為飛行常客里程數遠大于其他特征值。但海倫認為這三種特征是同等重要的,因此作為三個等權重的特征之一,飛行常客里程數并不應該如此嚴重地影響到計算結果。

          在處理這種不同取值范圍的特征值時,我們通常采用的方法是將數值歸一化,如將取值范圍處理為0到1或者-1到1之間。下面的公式可以將任意取值范圍的特征值轉化為0到1區間內的值:

          其中min和max分別是數據集中的最小特征值和最大特征值。雖然改變數值取值范圍增加了分類器的復雜度,但為了得到準確結果,我們必須這樣做。在kNN_test02.py文件中編寫名為autoNorm的函數,用該函數自動將數據歸一化。代碼如下:

          運行上述代碼,得到結果如圖2.4所示。

          圖2.4 歸一化函數運行結果

          從圖2.4的運行結果可以看到,我們已經順利將數據歸一化了,并且求出了數據的取值范圍和數據的最小值,這兩個值是在分類的時候需要用到的,直接先求解出來,也算是對數據預處理了。

          5.測試算法:驗證分類器

          機器學習算法一個很重要的工作就是評估算法的正確率,通常我們只提供已有數據的90%作為訓練樣本來訓練分類器,而使用其余的10%數據去測試分類器,檢測分類器的正確率。

          需要注意的是,10%的測試數據應該是隨機選擇的,由于海倫提供的數據并沒有按照特定目的來排序,所以我么你可以隨意選擇10%數據而不影響其隨機性。

          為了測試分類器效果,在kNN_test02.py文件中創建函數datingClassTest,編寫代碼如下:

          運行上述代碼,得到結果如圖2.5所示。

          圖2.5 驗證分類器結果

          從圖2.5驗證分類器結果中可以看出,錯誤率是3%,這是一個想當不錯的結果。我們可以改變函數datingClassTest內變量hoRatio和分類器k的值,檢測錯誤率是否隨著變量值的變化而增加。依賴于分類算法、數據集和程序設置,分類器的輸出結果可能有很大的不同。

          6.使用算法:構建完整可用系統

          我們可以給海倫一個小段程序,通過該程序海倫會在約會網站上找到某個人并輸入他的信息。程序會給出她對男方喜歡程度的預測值。

          在kNN_test02.py文件中創建函數classifyPerson,代碼如下:

          在cmd中,運行程序,并輸入數據(12,44000,0.5),預測結果是”你可能有些喜歡這個人”,也就是這個人魅力一般。一共有三個檔次:討厭、有些喜歡、非常喜歡,對應著不喜歡的人、魅力一般的人、極具魅力的人。結果如圖2.6所示。

          圖2.6 預測結果

          三、k-近鄰算法實戰之sklearn手寫數字識別

          1.實戰背景

          對于需要識別的數字已經使用圖形處理軟件,處理成具有相同的色彩和大小:寬高是32像素x32像素。盡管采用本文格式存儲圖像不能有效地利用內存空間,但是為了方便理解,我們將圖片轉換為文本格式,數字的文本格式如圖3.1所示。

          圖3.1 數字的文本格式

          與此同時,這些文本格式存儲的數字的文件命名也很有特點,格式為:數字的值_該數字的樣本序號,如圖3.2所示。

          圖3.2 文本數字的存儲格式

          對于這樣已經整理好的文本,我們可以直接使用Python處理,進行數字預測。數據集分為訓練集和測試集,使用上小結的方法,自己設計k-近鄰算法分類器,可以實現分類。數據集和實現

          代碼下載地址:

          https://github.com/Jack-Cherish/Machine-Learning/tree/master/kNN/3.%E6%95%B0%E5%AD%97%E8%AF%86%E5%88%AB

          這里不再講解自己用Python寫的k-鄰域分類器的方法,因為這不是本小節的重點。接下來,我們將使用強大的第三方Python科學計算庫Sklearn構建手寫數字系統。

          2.sklearn簡介

          • Scikit learn 也簡稱sklearn,是機器學習領域當中最知名的python模塊之一。sklearn包含了很多機器學習的方式:

          • Classification 分類

          • Regression 回歸

          • Clustering 非監督分類

          • Dimensionality reduction 數據降維

          • Model Selection 模型選擇

          • Preprocessing 數據與處理

          使用sklearn可以很方便地讓我們實現一個機器學習算法。一個復雜度算法的實現,使用sklearn可能只需要調用幾行API即可。所以學習sklearn,可以有效減少我們特定任務的實現周期。

          3.sklearn安裝

          在安裝sklearn之前,需要安裝兩個庫,即numpy+mkl和scipy。不要使用pip3直接進行安裝,因為pip3默安裝的是numpy,而不是numpy+mkl。

          第三方庫下載地址:

          http://www.lfd.uci.edu/~gohlke/pythonlibs/

          這個網站的使用方法,我在之前的文章里有講過:

          http://blog.csdn.net/c406495762/article/details/60156205

          找到對應python版本的numpy+mkl和scipy,下載安裝即可,如圖3.3和圖3.4所示。

          圖3.3 numpy+mkl

          圖3.4 scipy

          使用pip3安裝好這兩個whl文件后,使用如下指令安裝sklearn。

          4.sklearn實現k-近鄰算法簡介

          官網英文文檔:

          http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html

          sklearn.neighbors模塊實現了k-近鄰算法,內容如圖3.5所示。

          圖3.5 sklearn.neighbors

          我們使用sklearn.neighbors.KNeighborsClassifier就可以是實現上小結,我們實現的k-近鄰算法。KNeighborsClassifier函數一共有8個參數,如圖3.6所示。

          圖3.6 KNeighborsClassifier

          KNneighborsClassifier參數說明:

          n_neighbors:默認為5,就是k-NN的k的值,選取最近的k個點。

          weights:默認是uniform,參數可以是uniform、distance,也可以是用戶自己定義的函數。uniform是均等的權重,就說所有的鄰近點的權重都是相等的。distance是不均等的權重,距離近的點比距離遠的點的影響大。用戶自定義的函數,接收距離的數組,返回一組維數相同的權重。

          algorithm:快速k近鄰搜索算法,默認參數為auto,可以理解為算法自己決定合適的搜索算法。除此之外,用戶也可以自己指定搜索算法ball_tree、kd_tree、brute方法進行搜索,brute是蠻力搜索,也就是線性掃描,當訓練集很大時,計算非常耗時。

          kd_tree,構造kd樹存儲數據以便對其進行快速檢索的樹形數據結構,kd樹也就是數據結構中的二叉樹。以中值切分構造的樹,每個結點是一個超矩形,在維數小于20時效率高。

          ball tree是為了克服kd樹高緯失效而發明的,其構造過程是以質心C和半徑r分割樣本空間,每個節點是一個超球體。

          leaf_size:默認是30,這個是構造的kd樹和ball樹的大小。這個值的設置會影響樹構建的速度和搜索速度,同樣也影響著存儲樹所需的內存大小。需要根據問題的性質選擇最優的大小。

          metric:用于距離度量,默認度量是minkowski,也就是p=2的歐氏距離(歐幾里德度量)。

          p:距離度量公式。在上小結,我們使用歐氏距離公式進行距離度量。除此之外,還有其他的度量方法,例如曼哈頓距離。這個參數默認為2,也就是默認使用歐式距離公式進行距離度量。也可以設置為1,使用曼哈頓距離公式進行距離度量。

          metric_params:距離公式的其他關鍵參數,這個可以不管,使用默認的None即可。

          n_jobs:并行處理設置。默認為1,臨近點搜索并行工作數。如果為-1,那么CPU的所有cores都用于并行工作。

          KNeighborsClassifier提供了以一些方法供我們使用,如圖3.7所示。

          圖3.5 KNeighborsClassifier的方法

          由于篇幅原因,每個函數的怎么用,就不具體講解了。官方手冊已經講解的很詳細了,各位可以查看這個手冊進行學習,我們直接講手寫數字識別系統的實現。

          5.sklearn小試牛刀

          我們知道數字圖片是32x32的二進制圖像,為了方便計算,我們可以將32x32的二進制圖像轉換為1x1024的向量。

          對于sklearn的KNeighborsClassifier輸入可以是矩陣,不用一定轉換為向量,不過為了跟自己寫的k-近鄰算法分類器對應上,這里也做了向量化處理。然后構建kNN分類器,利用分類器做預測。創建kNN_test04.py文件,編寫代碼如下:

          運行上述代碼,得到如圖3.8所示的結果。

          圖3.8 sklearn運行結果

          上述代碼使用的algorithm參數是auto,更改algorithm參數為brute,使用暴力搜索,你會發現,運行時間變長了,變為10s+。更改n_neighbors參數,你會發現,不同的值,檢測精度也是不同的。自己可以嘗試更改這些參數的設置,加深對其函數的理解。

          四、總結

          1.kNN算法的優缺點

          • 優點

          簡單好用,容易理解,精度高,理論成熟,既可以用來做分類也可以用來做回歸;

          可用于數值型數據和離散型數據;

          訓練時間復雜度為O(n);無數據輸入假定;

          對異常值不敏感

          • 缺點

          計算復雜性高;空間復雜性高;

          樣本不平衡問題(即有些類別的樣本數量很多,而其它樣本的數量很少);

          一般數值很大的時候不用這個,計算量太大。但是單個樣本又不能太少,否則容易發生誤分。

          最大的缺點是無法給出數據的內在含義。

          2.其他

          關于algorithm參數kd_tree的原理,可以查看《統計學方法 李航》書中的講解;

          關于距離度量的方法還有切比雪夫距離、馬氏距離、巴氏距離等;

          下篇文章將講解決策樹,歡迎各位的捧場!

          如有問題,請留言。如有錯誤,還望指正,謝謝!

          五.參考說明

          本文中提到的電影類別分類、約會網站配對效果判定、手寫數字識別實例和數據集,均來自于《機器學習實戰》的第二章k-近鄰算法。

          本文的理論部分,參考自《統計學習方法 李航》的第三章k近鄰法以及《機器學習實戰》的第二章k-鄰近算法。


          點擊左下角“閱讀原文”處,可以進入作者的知乎專欄,可以查看高清完整版代碼

          還能解鎖更多優質文章~

          — 完 —

          誠摯招聘

          量子位正在招募編輯/記者,工作地點在北京中關村。期待有才氣、有熱情的同學加入我們!相關細節,請在量子位公眾號(QbitAI)對話界面,回復“招聘”兩個字。

          量子位 QbitAI

          ?'?' ? 追蹤AI技術和產品新動態


          主站蜘蛛池模板: 一区二区三区在线|欧| 日本免费电影一区| 国产精品福利区一区二区三区四区| 国产三级一区二区三区| 亚洲乱色熟女一区二区三区丝袜| 无码夜色一区二区三区| 国产成人无码AV一区二区在线观看| 中文精品一区二区三区四区| 四虎成人精品一区二区免费网站 | 美女福利视频一区二区| 亚洲男女一区二区三区| 香蕉久久av一区二区三区| 国产精品成人国产乱一区| 亚洲国产专区一区| 一区二区三区中文字幕| 波多野结衣中文一区| 无码国产亚洲日韩国精品视频一区二区三区 | 亚洲国产一区明星换脸| 一区二区在线播放视频| 日本精品一区二区三区在线视频| 无码乱码av天堂一区二区| 麻豆高清免费国产一区| 色妞色视频一区二区三区四区| 无码人妻精品一区二区| 中文字幕精品一区影音先锋| 色婷婷综合久久久久中文一区二区 | 久久国产午夜精品一区二区三区| 亚洲午夜在线一区| 中文精品一区二区三区四区| 无码国产精品一区二区免费I6| 日韩精品无码一区二区三区| 无码少妇精品一区二区免费动态| 国产色综合一区二区三区 | 欧美日韩综合一区二区三区| 国产精品电影一区| 国产主播福利精品一区二区| 午夜无码一区二区三区在线观看| 国产另类ts人妖一区二区三区| 国产一区二区视频在线播放| 99久久国产精品免费一区二区| 亚洲av无码一区二区乱子伦as|