【原創】Linux中斷子系統(二)-通用框架處理

背景

  • Read the fucking source code! –By 魯迅
  • A picture is worth a thousand words. –By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

【原創】Linux中斷子系統(一)-中斷控制器及驅動分析講到了底層硬件GIC驅動,以及Arch-Specific的中斷代碼,本文將研究下通用的中斷處理的過程,屬於硬件無關層。當然,我還是建議你看一下上篇文章。

這篇文章會解答兩個問題:

  1. 用戶是怎麼使用中斷的(中斷註冊)?
  2. 外設觸發中斷信號時,最終是怎麼調用到中斷handler的(中斷處理)?

2. 數據結構分析

先來看一下總的數據結構,核心是圍繞着struct irq_desc來展開:

  • Linux內核的中斷處理,圍繞着中斷描述符結構struct irq_desc展開,內核提供了兩種中斷描述符組織形式:

    1. 打開CONFIG_SPARSE_IRQ宏(中斷編號不連續),中斷描述符以radix-tree來組織,用戶在初始化時進行動態分配,然後再插入radix-tree中;
    2. 關閉CONFIG_SPARSE_IRQ宏(中斷編號連續),中斷描述符以數組的形式組織,並且已經分配好;
    3. 不管哪種形式,最終都可以通過linux irq號來找到對應的中斷描述符;
  • 圖的左側灰色部分,主要在中斷控制器驅動中進行初始化設置,包括各個結構中函數指針的指向等,其中struct irq_chip用於對中斷控制器的硬件操作,struct irq_domain與中斷控制器對應,完成的工作是硬件中斷號到Linux irq的映射;

  • 圖的上側灰色部分,中斷描述符的創建(這裏指CONFIG_SPARSE_IRQ),主要在獲取設備中斷信息的過程中完成的,從而讓設備樹中的中斷能與具體的中斷描述符irq_desc匹配;

  • 圖中剩餘部分,在設備申請註冊中斷的過程中進行設置,比如struct irqactionhandler的設置,這個用於指向我們設備驅動程序中的中斷處理函數了;

中斷的處理主要有以下幾個功能模塊:

  1. 硬件中斷號到Linux irq中斷號的映射,並創建好irq_desc中斷描述符;
  2. 中斷註冊時,先獲取設備的中斷號,根據中斷號找到對應的irq_desc,並將設備的中斷處理函數添加到irq_desc中;
  3. 設備觸發中斷信號時,根據硬件中斷號得到Linux irq中斷號,找到對應的irq_desc,最終調用到設備的中斷處理函數;

上述的描述比較簡單,更詳細的過程,往下看吧。

3. 流程分析

3.1 中斷註冊

這一次,讓我們以問題的方式來展開:
先來讓我們回答第一個問題:用戶是怎麼使用中斷的?

  1. 熟悉設備驅動的同學應該都清楚,經常會在驅動程序中調用request_irq()接口或者request_threaded_irq()接口來註冊設備的中斷處理函數;
  2. request_irq()/request_threaded_irq接口中,都需要用到irq,也就是中斷號,那麼這个中斷號是從哪裡來的呢?它是Linux irq,它又是如何映射到具體的硬件設備的中斷號的呢?

先來看第二個問題:設備硬件中斷號到Linux irq中斷號的映射

  • 硬件設備的中斷信息都在設備樹device tree中進行了描述,在系統啟動過程中,這些信息都已經加載到內存中並得到了解析;
  • 驅動中通常會使用platform_get_irqirq_of_parse_and_map接口,去根據設備樹的信息去創建映射關係(硬件中斷號到linux irq中斷號映射);
  • 【原創】Linux中斷子系統(一)-中斷控制器及驅動分析提到過struct irq_domain用於完成映射工作,因此在irq_create_fwspec_mapping接口中,會先去找到匹配的irq domain,再去回調該irq domain中的函數集,通常irq domain都是在中斷控制器驅動中初始化的,以ARM GICv2為例,最終回調到gic_irq_domain_hierarchy_ops中的函數;
  • 如果已經創建好了映射,那麼可以直接進行返回linux irq中斷號了,否則的話需要irq_domain_alloc_irqs來創建映射關係;
  • irq_domain_alloc_irqs完成兩個工作:
    1. 針對linux irq中斷號創建一個irq_desc中斷描述符;
    2. 調用domain->ops->alloc函數來完成映射,在ARM GICv2驅動中對應gic_irq_domain_alloc函數,這個函數很關鍵,所以下文介紹一下;

gic_irq_domain_alloc函數如下:

  • gic_irq_domain_translate:負責解析出設備樹中描述的中斷號和中斷觸發類型(邊緣觸發、電平觸發等);
  • gic_irq_domain_map:將硬件中斷號和linux中斷號綁定到一個結構中,也就完成了映射,此外還綁定了irq_desc結構中的其他字段,最重要的是設置了irq_desc->handle_irq的函數指針,這個最終是中斷響應時往上執行的入口,這個是關鍵,下文講述中斷處理過程時還會提到;
  • 根據硬件中斷號的範圍設置irq_desc->handle_irq的指針,共享中斷入口為handle_fasteoi_irq,私有中斷入口為handle_percpu_devid_irq

上述函數執行完成后,完成了兩大工作:

  1. 硬件中斷號與Linux中斷號完成映射,併為Linux中斷號創建了irq_desc中斷描述符;
  2. 數據結構的綁定及初始化,關鍵的地方是設置了中斷處理往上執行的入口;

再看第一個問題:中斷是怎麼來註冊的?

設備驅動中,獲取到了irq中斷號后,通常就會採用request_irq/request_threaded_irq來註冊中斷,其中request_irq用於註冊普通處理的中斷,request_threaded_irq用於註冊線程化處理的中斷;

在講具體的註冊流程前,先看一下主要的中斷標誌位:

#define IRQF_SHARED		0x00000080              //多個設備共享一个中斷號,需要外設硬件支持
#define IRQF_PROBE_SHARED	0x00000100              //中斷處理程序允許sharing mismatch發生
#define __IRQF_TIMER		0x00000200              //時鐘中斷
#define IRQF_PERCPU		0x00000400              //屬於特定CPU的中斷
#define IRQF_NOBALANCING	0x00000800              //禁止在CPU之間進行中斷均衡處理
#define IRQF_IRQPOLL		0x00001000              //中斷被用作輪訓
#define IRQF_ONESHOT		0x00002000              //一次性觸發的中斷,不能嵌套,1)在硬件中斷處理完成后才能打開中斷;2)在中斷線程化中保持關閉狀態,直到該中斷源上的所有thread_fn函數都執行完
#define IRQF_NO_SUSPEND		0x00004000              //系統休眠喚醒操作中,不關閉該中斷
#define IRQF_FORCE_RESUME	0x00008000              //系統喚醒過程中必須強制打開該中斷
#define IRQF_NO_THREAD		0x00010000              //禁止中斷線程化
#define IRQF_EARLY_RESUME	0x00020000              //系統喚醒過程中在syscore階段resume,而不用等到設備resume階段
#define IRQF_COND_SUSPEND	0x00040000              //與NO_SUSPEND的用戶共享中斷時,執行本設備的中斷處理函數

  • request_irq也是調用request_threaded_irq,只是在傳參的時候,線程處理函數thread_fn函數設置成NULL;
  • 由於在硬件中斷號和Linux中斷號完成映射后,irq_desc已經創建好,可以通過irq_to_desc接口去獲取對應的irq_desc
  • 創建irqaction,並初始化該結構體中的各個字段,其中包括傳入的中斷處理函數賦值給對應的字段;
  • __setup_irq用於完成中斷的相關設置,包括中斷線程化的處理:
    1. 中斷線程化用於減少系統關中斷的時間,增強系統的實時性;
    2. ARM64默認開啟了CONFIG_IRQ_FORCED_THREADING,引導參數傳入threadirqs時,則除了IRQF_NO_THREAD外的中斷,其他的都將強制線程化處理;
    3. 中斷線程化會為每个中斷都創建一個內核線程,如果中斷進行共享,對應irqaction將連接成鏈表,每個irqaction都有thread_mask位圖字段,當所有共享中斷都處理完成后才能unmask中斷,解除中斷屏蔽;

3.2 中斷處理

當完成中斷的註冊后,所有結構的組織關係都已經建立好,剩下的工作就是當信號來臨時,進行中斷的處理工作。

來回顧一下【原創】Linux中斷子系統(一)-中斷控制器及驅動分析中的Arch-specific處理流程:

  • 中斷收到之後,首先會跳轉到異常向量表的入口處,進而逐級進行回調處理,最終調用到generic_handle_irq來進行中斷處理。

generic_handle_irq處理如下圖:

  • generic_handle_irq函數最終會調用到desc->handle_irq(),這個也就是對應到上文中在建立映射關係的過程中,調用irq_domain_set_info函數,設置好了函數指針,也就是handle_fasteoi_irqhandle_percpu_devid_irq
  • handle_fasteoi_irq:處理共享中斷,並且遍歷irqaction鏈表,逐個調用action->handler()函數,這個函數正是設備驅動程序調用request_irq/request_threaded_irq接口註冊的中斷處理函數,此外如果中斷線程化處理的話,還會調用__irq_wake_thread()喚醒內核線程;
  • handle_percpu_devid_irq:處理per-CPU中斷處理,在這個過程中會分別調用中斷控制器的處理函數進行硬件操作,該函數調用action->handler()來進行中斷處理;

來看看中斷線程化處理后的喚醒流程吧__handle_irq_event_percpu->__irq_wake_thread

  • __handle_irq_event_percpu->__irq_wake_thread將喚醒irq_thread中斷內核線程;
  • irq_thread內核線程,將根據是否為強制中斷線程化對函數指針handler_fn進行初始化,以便後續進行調用;
  • irq_thread內核線程將while(!irq_wait_for_interrupt)循環進行中斷的處理,當滿足條件時,執行handler_fn,在該函數中最終調用action->thread_fn,也就是完成了中斷的處理;
  • irq_wait_for_interrupt函數,將會判斷中斷線程的喚醒條件,如果滿足了,則將當前任務設置成TASK_RUNNING狀態,並返回0,這樣就能執行中斷的處理,否則就調用schedule()進行調度,讓出CPU,並將任務設置成TASK_INTERRUPTIBLE可中斷睡眠狀態;

3.3 總結

中斷的處理,總體來說可以分為兩部分來看:

  1. 從上到下:圍繞irq_desc中斷描述符建立好連接關係,這個過程就包括:中斷源信息的解析(設備樹),硬件中斷號到Linux中斷號的映射關係、irq_desc結構的分配及初始化(內部各個結構的組織關係)、中斷的註冊(填充irq_desc結構,包括handler處理函數)等,總而言之,就是完成靜態關係創建,為中斷處理做好準備;
  2. 從下到上,當外設觸發中斷信號時,中斷控制器接收到信號併發送到處理器,此時處理器進行異常模式切換,並逐步從處理器架構相關代碼逐級回調。如果涉及到中斷線程化,則還需要進行中斷內核線程的喚醒操作,最終完成中斷處理函數的執行。

歡迎關注個人公眾號,不定期分享Linux內核機制文章

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

為.netcore助力–WebApiClient正式發布core版本

1 前言

WebApiClient已成熟穩定,發布了WebApiClient.JIT和WebApiClient.AOT兩個nuget包,累計近10w次下載。我對它的高可擴展性設計相當滿意和自豪,但WebApiClient並不因此而停下腳步,在一年前,我產生了編寫其core版本的想法,將asp.netcore服務端先進的思想融入到core版本,在性能與擴展性上得到進一步升華。
對應的,給它叫了WebApiClientCore的名字,為了對得起名字裏面的Core字,我在框架設計、性能優化上佔用整體開發時間一半以上。

2 框架設計

IActionInvoker

WebApiClient時還沒有IActionInvoker概念,對應的執行邏輯直接在ApiActionContext上實現。現在我覺得,Context應該是一個狀態數據類,而不能也成為一個執行者,因為一個執行者的實例可以無限次地執行多個Context實例。

Refit則更簡單粗暴,將所有實現都在一個RequestBuilderImplementation的類上:你們只要也只能使用我內置的Attribute聲明,一切執行在我這個類裡面包辦,因為我是一個萬能類。

Core版本增加了IActionInvoker概念,從中Context分開,用於執行Context,職責分明。在實現上又分為多種Invoker:Task聲明返回執行者ActionInvoker、ITask聲明返回處理處理者ActionTask,以及聚合的MultiplexedActionInvoker。

Middleware思想

WebApiClient時在處理各個特性、參數驗證、返回值驗證時沒有使用Middleware思想,特別是在處理響應結果和異常短路邏輯難以維護。

Refit還是簡單粗暴,將所有特性的解釋實現都在這個RequestBuilderImplementation的類上,因為我還是一個萬能類。

Core版本增加中間件Builder,將請求前的相關Attribute的執行編排Build為一個請求處理委託,將請求后相關Attribute的執行編排Build為一個響應處理委託,然後把兩個委託與真實http請求串在一起,Build出一個完整的請求響應委託。

得益於Middleware,流程中的請求前參數值驗證、結果處理特性短路、異常短路、請求后結果值驗和無條件執行IApiFilterAtrribue等這些複雜的流程變成簡單的管道處理;另外接口也變成支持服務端響應多種格式內容,每種格式內容在一個IApiReturnAttribute上實現和處理,比如請求為Accept: application/json, application/xml,不管服務器返回xml或json都能處理。

/// <summary>
/// 創建執行委託
/// </summary>
/// <param name="apiAction">action描述器</param>
/// <returns></returns>
public static Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)
{
    var requestHandler = BuildRequestHandler(apiAction);
    var responseHandler = BuildResponseHandler(apiAction);

    return async request =>
    {
        await requestHandler(request).ConfigureAwait(false);
        var response = await HttpRequest.SendAsync(request).ConfigureAwait(false);
        await responseHandler(response).ConfigureAwait(false);
        return response;
    };
}

Context思想

WebApiClient只有一個ApiActionContext,其Result和Exception屬性在請求前就可以訪問或設置,但實際上就算設置了值,流程也不會短路和中斷,屬於設計失誤。

Refit沒有相關Context概念,因為它不提供給用戶自定義擴展Attribute的能力,它內置的Attribute也沒有執行能力,一個RequestBuilderImplementation類夠了。

Core版本將設計了多個Context概念,不同階段有不同的Context,如同asp.netcore不同Filter的Context也不同一樣。對於一個Action,請求階段對應是ApiRequestContext,響應階段是ApiResponseContext;對於Action的參數,對應是ApiParameterContext。每種Context裏面都包含核心的HttpContext屬性,HttpContext包含請求消息、響應消息和接口配置選項等。

Interface思想

輸入WebApiClientCore命名空間,會發現定義了很多Interface,這些Interface都是為了用戶實現自定義特性用的,當然內置的所有特性,都是實現了這些接口而已。如果一個特性實現了多個接口,它就有多種能力,比如內置的HeaderAttribute,它既可以修飾於Interface和Method,也可以修飾參數。

WebApiClientCore的Attribute描述的邏輯,是由Attribute自我實現,所以整個請求的數據裝配邏輯是分散為各個Attribute上,用什麼Attribute就有什麼邏輯,包含框架之外的非內置的自定義Attribute。

Refit的內置Attribute只有欲描述邏輯,沒有實現邏輯,實現邏輯由RequestBuilderImplementation包辦,所以它不需要接口也沒有接口。

3 性能優化

更快的字符串替換

像[HttpGet(“objects/{id}”)]這樣的path參數,在restful中常常遇到,通過Span優化,Core版本在替換path參數值cpu佔用時間降低為原版本的十分之一。

更快的json序列化

得益於Sytem.Text.Json,json序列化和反序列化上性能顯明提升。

更少的緩衝區分配

WebApiClientCore使用了可回收復用的IBufferWriter,在json序列化得到json、json裝配為HttpContent只申請一次Buffer,而且HttpContent在發送之後,這個Buffer被回收復用。IBufferWriter還於用表單的uri編碼,編碼產生的Buffer不用申請新的內存內容,直接寫入表單的HttpContent。

更少的編碼操作

WebApiClientCore的json不再使用utf16的string中間類型,直接將對象序列化為網絡請求需要的utf8編碼二進制json;表單的key和Value編碼時,也不產生string中間類型,而是編碼后的二進制數據內容,然後寫入表單的IBufferWriter。

更快的緩存查找

WebApiClient創建代理類實例來執行一個請求時,要查找兩次緩存:通過接口類型查找字典緩存的接口代理類,然後實例化代理類;在ApiInterceptor裏面通過MethodInfo查找字典緩存的ApiActionDescriptor。

Refit執行同樣邏輯也使用了兩次字典緩存,接口和接口代理類安全字典緩存TypeMapping,接口和接口方法描述的字典緩存interfaceHttpMethods。

WebApiClientCore取消了字典緩存,使用靜態泛型類的字段作緩存,因為字段訪問遠比字典查找高效。同時通過巧妙的設計,在代理類攔截方法執行時,直接回傳IActionInvoker替換原來的MethodInfo,IActionInvoker包含了ApiActionDescriptor,而IActionInvoker與代理類型都一起緩存在靜態泛型類的字段,減少了一次必須的字典緩存查找過程。

性能對比

排除掉真實的網絡請求IO等待時間,WebApiClientCore使用的cpu時間僅僅為WebApiClient.JIT和Refit的三分之一。

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.836 (1903/May2019Update/19H1)
Intel Core i3-4150 CPU 3.50GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.202
  [Host]     : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
  DefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
Method Mean Error StdDev
HttpClient_GetAsync 3.146 μs 0.0396 μs 0.0370 μs
WebApiClientCore_GetAsync 12.421 μs 0.2324 μs 0.2174 μs
Refit_GetAsync 43.241 μs 0.6713 μs 0.6279 μs
Method Mean Error StdDev
HttpClient_PostJsonAsync 5.263 μs 0.0784 μs 0.0733 μs
WebApiClientCore_PostJsonAsync 13.823 μs 0.1874 μs 0.1753 μs
Refit_PostJsonAsync 45.218 μs 0.8166 μs 0.7639 μs

4 Nuget包與文檔

Nuget包

<PackageReference Include="WebApiClientCore" Version="1.0.*" />

項目地址與文檔

點擊項目鏈接,帶你GET到N種使用技能,不求star,只求提供良好建議。

https://github.com/dotnetcore/WebApiClient

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

Python機器學習筆記:SVM(2)——SVM核函數,Python機器學習筆記:SVM(1)——SVM概述,Python機器學習筆記:SVM(2)——SVM核函數,Python機器學習筆記:SVM(3)——證明SVM,Python機器學習筆記:SVM(4)——sklearn實現

  上一節我學習了完整的SVM過程,下面繼續對核函數進行詳細學習,具體的參考鏈接都在上一篇文章中,SVM四篇筆記鏈接為:

Python機器學習筆記:SVM(1)——SVM概述

Python機器學習筆記:SVM(2)——SVM核函數

Python機器學習筆記:SVM(3)——證明SVM

Python機器學習筆記:SVM(4)——sklearn實現

  熱身實例

  我在上一節有完整的學習了SVM算法,為了不讓自己這麼快就忘了,這裏先學習一個實例,回顧一下,並引出核函數的概念。

  數據是這樣的:有三個點,其中正例 x1(3,  3),x2(4,3),負例 x3(1,1)

   求解:

   約束條件為:

  這個約束條件是通過這個得到的(為什麼這裏強調一下呢,因為我們這個例子本身說的就是SVM的求解過程):

   我們可以知道  y1 = +1, y2 = +1 , y3 = -1,同時,代入 α ,則得到:

  α1 +  α2 – α3=0

  下面通過SVM求解實例。

  我們將數據代入原式中:

   由於 α1 + α2 = α3 化簡可得:

   然後分別對  α1 和 α2 求偏導,偏導等於 0 可得: α1 = 1.5   α2 = -1 ,但是我們發現這兩個解並不滿足約束條件  αi >= 0,i=1,2,3,所以解應該在邊界上(正常情況下,我們需要對上式求偏導,進而算出 w,b)。

  首先令  α1 = 0,得出 α2 = -2/13  ,α3 = -2/13   (不滿足約束)

  再令 α2 = 0,得出 α1 = 0.25  ,α3 = 0.25  (滿足約束)

  所以最小值在(0.25, 0,0.25)處取得。

  我們將 α 的結果代入求解:

   所以我們代入 w  b ,求得最終的平面方程為:

  熱身完畢,下面學習核函數,為了方便理解我們下面要說的核函數,我在知乎找了一個簡單易懂的故事,讓我們了解支持向量機,更是明白支持向量機的核函數是個什麼鬼,下面看故事。

1,故事分析:支持向量機(SVM)是什麼?

  下面故事來源於此(這是源作者鏈接):點擊我即可

  在很久以前的情人節,有一個大俠要去救他的愛人,但是魔鬼和他玩了一個遊戲。

  魔鬼在桌面上似乎有規律放了兩種顏色的球,說:“你用一根棍分開他們?要求:盡量在放更多球之後,仍然使用”

  於是大俠這樣做,幹得不錯吧:

  然後魔鬼又在桌上放了更多的球,似乎有一個球站錯了陣營。

  SVM就是試圖把棍放在最佳位置,好讓在棍的兩邊有盡可能大的間隙。

  現在即使魔鬼放了更多的球,棍仍然是一個好的分界線。

  然後,在SVM工具箱中有另一個更加重要的trick 。魔鬼看到大俠已經學會了一個trick,於是魔鬼給了大俠一個新的挑戰。

  現在,大俠沒有棍可以很好地幫他分開這兩種球了,現在怎麼辦,當然像所有武俠片中一樣大俠桌子一拍,球飛到空中。然後憑藉著大俠的輕功,大俠抓起一張紙,插到了兩種球的中間。

  現在,從魔鬼的角度看這些球,這些球好像是被一條曲線分開了。

  在之後,無聊的大人們,把這些球叫做 「data」,把棍子 叫做 「classifier」, 最大間隙trick 叫做「optimization」, 拍桌子叫做「kernelling」, 那張紙叫做「hyperplane」。

  所以說,Support Vector Machine,一個普通的SVM就是一條直線罷了,用來完美劃分linearly separable的兩類。但是這又不是一條普通的直線,這是無數條可以分類的直線當中最完美的,因為它恰好在兩個類的中間,距離兩個類的點都一樣遠。而所謂的Support vector就是這些離分界線最近的點,如果去掉這些點,直線多半是要改變位置的。如果是高維的點,SVM的分界線就是平面或者超平面。其實沒有差,都是一刀切兩塊,我們這裏統一叫做直線。

  再理解一下,當一個分類問題,數據是線性可分的,也就是用一根棍就可以將兩種小球分開的時候,我們只要將棍的位置放在讓小球距離棍的距離最大化的位置即可。尋找這個最大間隔的過程,就叫做最優化。但是,显示往往是殘酷的,一般的數據是線性不可分的。也就是找不到一個棍將兩種小球很好的分類,這時候我們就需要像大俠一樣,將小球排起,用一張紙代替小棍將兩種小球進行分類,想讓數據飛起,我們需要的東西就是核函數(kernel),用於切分小球的紙,就是超平面。

2,核函數的概念

  上面故事說明了SVM可以處理線性可分的情況,也可以處理非線性可分的情況。而處理非線性可分的情況是選擇了核函數(kernel),通過將數據映射到高位空間,來解決在原始空間中線性不可分的問題。

  我們希望樣本在特徵空間中線性可分,因此特徵空間的好壞對支持向量機的性能至關重要,但是在不知道特徵映射的情況下,我們是不知道什麼樣的核函數是適合的,而核函數也只是隱式的定義了特徵空間,所以,核函數的選擇對於一個支持向量機而言就顯得至關重要,若選擇了一個不合適的核函數,則數據將映射到不合適的樣本空間,從而支持向量機的性能將大大折扣。

  所以構造出一個具有良好性能的SVM,核函數的選擇是關鍵。而核函數的選擇包含兩部分工作:一是核函數類型的選擇,二是確定核函數類型后相關參數的選擇。

  我們知道,核函數的精妙之處在於不用真的對特徵向量做核映射,而是直接對特徵向量的內積進行變換,而這種變換卻等價於先對特徵向量做核映射然後做內積。

  SVM主要是在用對偶理論求解一個二次凸優化問題,其中對偶問題如下:

   求得最終結果:

  當然這是線性可分的情況,那麼如果問題本身是線性不可分的情況呢?那就是先擴維后再計算。具體來說,在線性不可分的情況下,支持向量機首先在低維空間中完成計算,然後通過核函數將輸入空間映射到高維特徵空間,最終在高維特徵空間中構造出最優分離超平面,從而把平面上本身不好分的非線性數據分開。如下圖所示,一堆數據在二維空間中無法劃分,從而映射到三維空間中劃分:

   而在我們遇到核函數之前,如果用原始的方法,那麼在用線性學習器學習一個非線性關係,需要選擇一個非線性特徵集,並且將數據寫成新的表達形式,這等價於應用一個固定的非線性映射,將數據映射到特徵空間,在特徵空間中使用線性學習器。因此,考慮的假設集是這種類型的函數:

   這裏 Φ :X -> F 是從輸入空間到某個特徵空間的映射,這意味着建立非線性學習器分為兩步:

  • 1,使用一個非線性映射將數據變換到一個特徵空間 F
  • 2,在特徵空間使用線性學習器分類

  而由於對偶形式就是線性學習器的一個重要性質,這意味着假設可以表達為訓練點的線性組合,因此決策規則可以用測試點和訓練點的內積來表示:

  為向量加上核映射后,要求解的最優化問題變為:

  根據核函數滿足的等式條件,它等價於下面的問題:

  其線性不可分情況的對偶形式如下:

   其中 Φ(xi) 表示原來的樣本擴維后的坐標。

  最後得到的分類判別函數為:

   和不用核映射相比,只是求解的目標函數,最後的判定函數對特徵向量的內積做了核函數變換。如果K是一個非線性函數,上面的決策函數則是非線性函數,此時SVM是非線性模型。當訓練樣本很多,支持向量的個數很大的時候,預測時的速度是一個問題,因此很多時候我們會使用線性支持向量機。

3,舉例說明核函數的巧妙之處

  下面先從一個小例子來闡述問題。假設我們有兩個數據, x = (x1,  x2,  x3)  y = (y1,  y2,  y3)。此時在3D空間已經不能對其進行線性劃分了,那麼我們通過一個函數將數據映射到更高維的空間,比如9維的話,那麼 f(x) = (x1x1, x1x2, x1x3, x2x1, x2x2, x2x3, x3x1, x3x2, x3x3),由於需要計算內積,所以在新的數據在 9 維空間,需要計算  <f(x),  f(y)> 的內積,需要花費 O(n^2)。

  再具體點,令 x = (1, 2, 3), y = (4, 5, 6),那麼 f(x)  = (1, 2, 3, 2, 4, 6, 3, 6, 9),f(y) = (16, 20, 24, 20, 25, 36, 24, 30, 36)

  此時: <f(x),  f(y)>  = 16 + 40 + 72 +40 +100 + 180 + 72 +180 +324 = 1024

  對於3D空間這兩個數據,似乎還能計算,但是如果將維數擴大到一個非常大數的時候,計算起來可就不是這麼一點點問題了。

  然而,我們發現  K(x, y) = (<x, y>)^2   ,代入上式: K(x, y) = (4 + 10 + 18)^2 = 32^2 = 1024

  也就是說 : K(x, y) = (<x, y>)^2  = <f(x),  f(y)>

  但是 K(x, y) 計算起來卻比 <f(x), f(y)> 簡單的多,也就是說只要用 K(x, y)來計算,效果與 <f(x), f(y)> 是一樣的,但是計算效率卻大幅度提高了,如  K(x, y) 是 O(n),而  <f(x), f(y)> 是 O(n^2),所以使用核函數的好處就是,可以在一個低維空間去完成一個高緯度(或者無限維度)樣本內積的計算,比如上面例子中 K(x, y)的3D空間對比 <f(x), f(y)> 的9D空間。

  下面再舉個例子來證明一下上面的問題,為了簡單起見,假設所有樣本點都是二維點,其值分別為(x,  y),分類函數為:

   它對應的映射方式為:

   可以驗證:任意兩個擴維后的樣本點在3維空間的內積等於原樣本點在二維空間的函數輸出

   有了這個核函數,以後的高維內積都可以轉換為低維的函數運算了,這裏也就是說只需要計算低維的內積,然後再平方。明顯問題得到解決且複雜度降低極大。總而言之:核函數它本質上隱含了從低維到高維的映射,從而避免直接計算高維的內積

  當然上述例子是多項式核函數的一個特例,其實核函數的種類還有很多,後文會一一介紹。

4,核函數的計算原理

  通過上面的例子,我們大概可以知道核函數的巧妙應用了,下面學習一下核函數的計算原理。

  如果有一種方法可以在特徵空間中直接計算內積  <Φ(xi , Φ(x)> ,就像在原始輸入點的函數中一樣,就有可能將兩個步驟融合到一起建立一個非線性的學習器,這樣直接計算的方法稱為核函數方法。

  設 x 是輸入空間(歐式空間或者離散集合),H為特徵空間(希爾伯特空間),如果存在一個從 x 到 H 的映射:

  核是一個函數 K,對於所有 x, z ∈ χ, 則滿足:

   則稱Κ(x,z)為核函數,φ(x)為映射函數,φ(x)∙φ(z)為x,z映射到特徵空間上的內積。

  參考網友的理解:任意兩個樣本點在擴維后的空間的內積,如果等於這兩個樣本點在原來空間經過一個函數后的輸出,那麼這個函數就叫核函數

  由於映射函數十分複雜難以計算,在實際中,通常都是使用核函數來求解內積,計算複雜度並沒有增加,映射函數僅僅作為一種邏輯映射,表徵着輸入空間到特徵空間的映射關係。至於為什麼需要映射后的特徵而不是最初的特徵來參与計算,為了更好地擬合是其中一個原因,另外的一個重要原因是樣例可能存在線性不可分的情況,而將特徵映射到高維空間后,往往就可分了。

  下面將核函數形式化定義。如果原始特徵內積是 <X ,  Z>,映射 <Φ(xi • Φ(x)>,那麼定義核函數(Kernel)為:

  到這裏,我們可以得出結論,如果要實現該節開頭的效果,只需要計算 Φ(x) ,然後計算 Φ(x)TΦ(x)即可,然而這種計算方式是非常低效的。比如最初的特徵是n維的,我們將其映射到 n2 維,然後再計算,這樣需要O(n2 ) 的時間,那麼我們能不能想辦法減少計算時間呢?

  先說結論,當然是可以的,畢竟我們上面例子,活生生的說明了一個將需要 O(n2 ) 的時間 轉換為 需要O(n ) 的時間。

  先看一個例子,假設x和z都是n維度的,

  展開后,得到:

  這個時候發現我們可以只計算原始特徵 x 和 z 內積的平方(時間複雜度為O(n)),就等價於計算映射后特徵的內積。也就是說我們不需要花O(n2 ) 的時間了。

  現在看一下映射函數(n = 3),根據上面的公式,得到:

  也就是說核函數  Κ(x,z) = (xTz)2  只能選擇這樣的 φ 作為映射函數時才能夠等價於映射后特徵的內積

  再看另外一個核函數,高斯核函數:

  這時,如果 x 和 z 很相近 (||x – z || 約等於 0),那麼核函數值為1,如果 x 和 z 相差很大(||x – z ||  >> 0),那麼核函數值約等於0.由於這個函數類似於高斯分佈,因此稱為高斯核函數,也叫做徑向基函數(Radial Basis Function 簡稱為RBF)。它能夠把原始特徵映射到無窮維。

  下面有張圖說明在低維線性不可分時,映射到高維后就可分了,使用高斯核函數。

  注意,使用核函數后,怎麼分類新來的樣本呢?線性的時候我們使用SVM學習出w和b,新來樣本x的話,我們使用 wTx + b 來判斷,如果值大於等於1,那麼是正類,小於等於是負類。在兩者之間,認為無法確定。如果使用了核函數后,wTx + b 就變成了 wTΦ(x) + b,是否先要找到 Φ(x) ,然後再預測?答案肯定不是了,找 Φ(x) 很麻煩,回想我們之前說過的。

  只需將 <(x(i) , x> 替換成  (x(i) , x),然後值的判斷同上。

4.1  核函數有效性的判定

  問題:給定一個函數K,我們能否使用K來替代計算 Φ(x)TΦ(x),也就說,是否能夠找出一個 Φ,使得對於所有的x和z,都有 K(x, z) = Φ(x)TΦ(x),即比如給出了 K(x, z) = (xTz)2,是否能夠認為K是一個有效的核函數。

  下面來解決這個問題,給定m個訓練樣本(x(1),x(2), ….,x(m)),每一個x(i) 對應一個特徵向量。那麼,我們可以將任意兩個 x(i) 和 x(j) 帶入K中,計算得到Kij = K(x(i), x(j))。i 可以從1到m,j 可以從1到m,這樣可以計算出m*m的核函數矩陣(Kernel Matrix)。為了方便,我們將核函數矩陣和 K(x, z) 都使用 K 來表示。如果假設 K 是有效地核函數,那麼根據核函數定義:

  可見,矩陣K應該是個對稱陣。讓我們得出一個更強的結論,首先使用符號ΦK(x)來表示映射函數 Φ(x) 的第 k 維屬性值。那麼對於任意向量 z,得:

  最後一步和前面計算 K(x, z) = (xTz)2 時類似。從這個公式我們可以看出,如果K是個有效的核函數(即 K(x, z)   Φ(x)TΦ(z)等價),那麼,在訓練集上得到的核函數矩陣K應該是半正定的(K>=0)。這樣我們得到一個核函數的必要條件:K是有效的核函數 ==> 核函數矩陣K是對稱半正定的。

  Mercer定理表明為了證明K是有效的核函數,那麼我們不用去尋找 Φ ,而只需要在訓練集上求出各個 Kij,然後判斷矩陣K是否是半正定(使用左上角主子式大於等於零等方法)即可。

 

5,核函數:如何處理非線性數據

  來看個核函數的例子。如下圖所示的兩類數據,分別分佈為兩個圓圈的形狀,這樣的數據本身就是線性不可分的,此時我們該如何把這兩類數據分開呢?

   事實上,上圖所示的這個數據集,是用兩個半徑不同的圓圈加上了少量的噪音生成得到的,所以,一個理想的分界應該是“圓圈” 而不是“一條線”(超平面)。如果用 X1 和 X2 來表示這個二維平面的兩個坐標的話,我們知道一條二次曲線(圓圈是二次曲線的一種特殊情況)的方程可以寫作這樣的形式:

   注意上面的形式,如果我們構造另外一個五維的空間,其中五個坐標的值分別為:

   那麼顯然,上面的方程在新的坐標系下可以寫做:

   關於新的坐標 Z,這正是一個 hyper plane 的方程!也就是說,如果我們做一個映射:

   將X按照上面的規則映射為 Z,那麼在新的空間中原來的數據將變成線性可分的,從而使用之前我們推導的線性分類算法就可以進行處理了。這正是Kernel方法處理非線性問題的基本思想。

  再進一步描述 Kernel 的細節之前,不妨再來看看上述例子在映射過後的直觀形態。當然,我們無法將五維空間畫出來,不過由於我這裏生成數據的時候用了特殊的情形,所以這裏的超平面實際的方程是這個樣子的(圓心在X2軸上的一 個正圓):

   因此我只需要把它映射到下面這樣一個三維空間中即可:

   下圖即是映射之後的結果,將坐標軸經過適當的旋轉,就可以很明顯的看出,數據是可以通過一個平面來分開的

  核函數相當於把原來的分類函數:

   映射成:

   而其中的 α 可以通過求解如下 dual 問題而得到的:

   這樣一來問題就解決了嗎?似乎是的:拿到非線性數據,就找一個映射(Φ(•),然後一股腦把原來的數據映射到新空間中,再做線性SVM即可。不過事實上問題好像沒有這麼簡單)。

  細想一下,剛才的方法是不是有問題:

  在最初的例子里,我們對一個二維空間做映射,選擇的新空間是原始空間的所有一階和二階的組合,得到了五個維度;

  如果原始空間是三維(一階,二階和三階的組合),那麼我們會得到:3(一次)+3(二次交叉)+3(平方)+3(立方)+1(x1 * x2 * x3) + 2*3(交叉,一個一次一個二次,類似 x1*x2^2)=19 維的新空間,這個數目是呈指數級爆炸性增長的,從而勢必這給 Φ(•) 的計算帶來非常大的困難,而且如果遇到無窮維的情況,就根本無從計算了。

  這個時候,可能就需要Kernel出馬了。

  不妨還是從最開始的簡單例子觸發,設兩個向量為:

   而 Φ(•) 即是前面說的五維空間的映射,因此映射過後的內積為:

   (公式說明:上面的這兩個推導過程中,所說的前面的五維空間的映射,這裏說的便是前面的映射方式,回顧下之前的映射規則,再看看那個長的推導式,其實就是計算x1,x2各自的內積,然後相乘相加即可,第二個推導則是直接平方,去掉括號,也很容易推出來)

  另外,我們又注意到:

   二者有很多相似的地方,實際上,我們只要把某幾個維度線性縮放一下,然後再加上一個常數維度,具體來說,上面這個式子的計算結果實際上和映射

   之後的內積  <Φ(xi • Φ(x)>  的結果是相等的,那麼區別在什麼地方呢?

  • 1,一個是映射到高維空間中,然後再根據內積的公式進行計算
  • 2,另一個則直接在原來的低維空間中進行計算,而不需要顯式地寫出映射后的結果

  (公式說明:上面之中,最後的兩個式子,第一個算式,是帶內積的完全平方式,可以拆開,然後,再通過湊一個得到,第二個算式,也是根據第一個算式湊出來的)

  回想剛才提到的映射的維度爆炸,在前一種方法已經無法計算的情況下,后一種方法卻依舊能從容處理,甚至是無窮維度的情況也沒有問題。

  我們把這裏的計算兩個向量在隱式映射過後的空間中的內積的函數叫做核函數(kernel Function),例如,在剛才的例子中,我們的核函數為:

   核函數能簡化映射空間中的內積運算——剛好“碰巧”的是,在我們的SVM里需要計算的地方數據向量總是以內積的形式出現的。對比剛才我們上面寫出來的式子,現在我們的分類函數為:

   其中 α 由如下 dual 問題計算而得:

  這樣一來計算的問題就算解決了,避免了直接在高維空間中進行計算,而結果卻是等價的!當然,因為我們這裏的例子非常簡單,所以可以手工構造出對應於 Φ(•) 的核函數出來,如果對於任意一個映射,想要構造出對應的核函數就非常困難了。

6,核函數的本質

  下面概況一下核函數的意思:

  • 1,實際上,我們會經常遇到線性不可分的樣例,此時,我們的常用做法是把樣例特徵映射到高位空間中去(比如之前有個例子,映射到高維空間后,相關特徵便被分開了,也就達到了分類的目的)
  • 2,進一步,如果凡是遇到線性不可分的樣例,一律映射到高維空間,那麼這個維度大小是會高到可怕的(甚至是無窮維),所以核函數就隆重出場了,核函數的價值在於它雖然也是將特徵進行從低維到高維的轉換,但核函數絕就絕在它事先在低維上進行計算,而將實質上的分類效果表現在了高維上,也就是上文所說的避免了直接在高維空間中的複雜計算。

  下面引用這個例子距離下核函數解決非線性問題的直觀效果。

  假設現在你是一個農場主,圈養了一批羊群,但為了預防狼群襲擊羊群,你需要搭建一個籬笆來把羊群圈起來。但是籬笆應該建在哪裡呢?你很可能需要依據羊群和狼群的位置搭建一個“分類器”,比如下圖這幾種不同的分類器,我們可以看到SVM完成了一個很完美的解決方案。

   這個例子側面簡單說明了SVM使用非線性分類器的優勢,而邏輯模式以及決策樹模式都是使用了直線方法。

7,幾種常見的核函數

  核函數有嚴格的數學要求,所以設計一個核函數是非常困難的,科學家們經過很多很多嘗試也就只嘗試出來幾個核函數,所以我們就不要在這方面下無用功了,直接拿這常見的幾個核函數使用就OK。

  下面來分別學習一下這幾個常見的核函數。

7.1  線性核(Linear Kernel )

  基本原理:實際上就是原始空間中的內積

   這個核存在的主要目的是使得“映射后空間中的問題” 和 “映射前空間中的問題” 兩者在形式上統一起來了(意思是說:我們有的時候,寫代碼或者寫公式的時候,只要寫個模板或者通用表達式,然後再代入不同的核,便可以了。於此,便在形式上統一了起來,不用再找一個線性的和一個非線性的)

     線性核,主要用於線性可分的情況,我們可以看到特徵空間到輸入空間的維度是一樣的。在原始空間中尋找最優線性分類器,具有參數少速度快的優勢。對於線性可分數據,其分類效果很理想。因此我們通常首先嘗試用線性核函數來做分類,看看效果如何,如果不行再換別的。

優點

  • 方案首選,奧多姆剃刀定理
  • 簡單,可以快速解決一個QP問題
  • 可解釋性強:可以輕易知道哪些feature是重要的

限制

  • 只能解決線性可分問題

7.2 多項式核(Polynomial Kernel)

  基本原理:依靠升維使得原本線性不可分的數據線性可分。

  多項式核函數可以實現將低維的輸入空間映射到高維的特徵空間。多項式核適合於正交歸一化(向量正交且模為1)數據,屬於全局核函數,允許相距很遠的數據點對核函數的值有影響。參數d越大,映射的維度越高,計算量就會越大。

優點

  • 可解決非線性問題
  • 可通過主觀設置Q來實現總結的預判

缺點

  • 多項式核函數的參數多,當多項式的階數d比較高的是,由於學習複雜性也會過高,易出現“過擬合”現象,核矩陣的元素值將趨於無窮大或者無窮小,計算複雜度會大道無法計算。

 

7.3  高斯核(Gaussian Kernel)/ 徑向基核函數(Radial Basis Function)

  徑向基核函數是SVM中常用的一個核函數。徑向基函數是一個採用向量作為自變量的函數,能夠基於向量距離運算輸出一個標量。

   也可以寫成如下格式:

  徑向基函數是指取值僅僅依賴於特定點距離的實值函數,也就是:

  任意一個滿足上式特性的函數 Φ 都叫徑向量函數,標準的一般使用歐氏距離,儘管其他距離函數也是可以的。所以另外兩個比較常用的核函數,冪指數核,拉普拉斯核也屬於徑向基核函數。此外不太常用的徑向基核還有ANOVA核,二次有理核,多元二次核,逆多元二次核。

  高斯徑向基函數是一種局部性強的核函數,其可以將一個樣本映射到一個更高維的空間內,該核函數是應用最廣的一個,無論大樣本還是小樣本都有比較好的性能,而且其相對於多項式核函數參數要少,因此大多數情況下在不知道用什麼樣的核函數的時候優先使用高斯核函數

  徑向基核函數屬於局部核函數,當數據點距離中心點變遠時,取值會變小。高斯徑向基核對數據中存在的噪聲有着較好的抗干擾能力,由於其很強的局部性,其參數決定了函數作用範圍,隨着參數 σ 的增大而減弱。

優點

  • 可以映射到無線維
  • 決策邊界更為多樣
  • 只有一個參數,相比多項式核容易選擇

缺點

  • 可解釋性差(無限多維的轉換,無法算出W)
  • 計算速度比較慢(當解決一個對偶問題)
  • 容易過擬合(參數選不好時容易overfitting)

上述所講的徑向核函數表達式

  冪指數核(Exponential Kernel)

  拉普拉斯核(LaplacIan Kernel)

 

   ANOVA 核(ANOVA Kernel)

  二次有理核(Rational Quadratic Kernel)

  多元二次核(Multiquadric Kernel)

  逆多元二次核(Inverse Multiquadric Kernel)

7.4  Sigmoid核

   Sigmoid核函數來源於神經網絡,被廣泛用於深度學習和機器學習中

  採用Sigmoid函數作為核函數時,支持向量機實現的就是一種多層感知器神經網絡,應用SVM方法,隱含層節點數目(它確定神經網絡的結構),隱含層節點對輸入節點的權重都是在設計(訓練)的過程中自動確定的。而且支持向量機的理論基礎決定了它最終求得的是全局最優值而不是局部最優值,也保證了它對未知樣本的良好泛化能力而不會出現過學習線性。

8,核函數的選擇

8.1,先驗知識

  利用專家的先驗知識預先選定核函數

8.2,交叉驗證

  採取Cross-Validation方法,即在進行核函數選取時,分別試用不同的核函數,歸納誤差最小的核函數就是最好的核函數。如針對傅里恭弘=叶 恭弘核,RBF核,結合信號處理問題中的函數回歸問題,通過仿真實驗,對比分析了在相同數據條件下,採用傅里恭弘=叶 恭弘核的SVM要比採用RBF核的SVM誤差小很多。

8.3,混合核函數

  採用由Smits等人提出的混合核函數方法,該方法較之前兩者是目前選取核函數的主流方法,也是關於如何構建核函數的又一開創性的工作,將不同的核函數結合起來後有更好的特性,這是混合核函數方法的基本思想。

8.4,經驗

  當樣本特徵很多時,特徵的維度很高,這是往往樣本線性可分,可考慮用線性核函數的SVM或者LR(如何不考慮核函數,LR和SVM都是線性分類算法,也就是說他們的分類決策面都是線性的)

  當樣本的數量很多,但特徵較少時,可以手動添加一些特徵,使樣本線性可分,再考慮用線性核函數的SVM或者LR

  當樣本特徵維度不高時,樣本數量也不多時,考慮使用高斯核函數(RBF核函數的一種,指數核函數和拉普拉斯核函數也屬於RBF核函數)

8.5,吳恩達給出的選擇核函數的方法

   如果特徵的數量大道和樣本數量差不多,則選用LR或者線性核的SVM

  如果特徵的數量小,樣本的數量正常,則選用SVM+ 高斯核函數

  如果特徵的數量小,而樣本的數量很大,則需要手工添加一些特徵從而變成第一種情況

8.6  核函數選擇的例子

  這裏簡單說一下核函數與其他參數的作用(後面會詳細學習關於使用Sklearn學習SVM):

  • kernel=’linear’ 時,C越大分類效果越好,但有可能會過擬合(default C=1)
  • kernel=’rbf’時,為高斯核,gamma值越小,分類界面越連續;gamma值越大,分類界面越“散”,分類效果越好,但有可能會過擬合。

  我們來看一個簡單的例子,數據為[-5.0 , 9.0] 的隨機數組,函數如下 :

  下面分別使用三種核SVR:兩種乘法係數高斯核rbf和一種多項式核poly。代碼如下:

from sklearn import svm
import numpy as np
from matplotlib import pyplot as plt
import warnings

warnings.filterwarnings('ignore')

X = np.arange(-5.0 , 9.0 , 0.1)
# print(X)
X = np.random.permutation(X)
# print('1X:',X)
X_ = [[i] for i in X]
b = 0.5
y = 0.5 * X ** 2.0 + 3.0 * X + b + np.random.random(X.shape) * 10.0
y_ = [i for i in y]

# degree = 2 , gamma=, coef0 =
rbf1 = svm.SVR(kernel='rbf',C=1,)
rbf2 = svm.SVR(kernel='rbf',C=20,)
poly = svm.SVR(kernel='poly',C=1,degree=2)

rbf1.fit(X_ , y_)
rbf2.fit(X_ , y_)
poly.fit(X_ , y_)


result1 = rbf1.predict(X_)
result2 = rbf2.predict(X_)
result3 = poly.predict(X_)


plt.plot(X,y,'bo',fillstyle='none')
plt.plot(X,result1,'r.')
plt.plot(X,result2,'g.')
plt.plot(X,result3,'b.')
plt.show()

  結構圖如下:

  藍色是poly,紅色是c=1的rbf,綠色c=20的rbf。其中效果最好的是C=20的rbf。如果我們知道函數的表達式,線性規劃的效果更好,但是大部分情況下我們不知道數據的函數表達式,因此只能慢慢實驗,SVM的作用就在這裏了。

9,總結

  支持向量機是一種分類器。之所以稱為“機”是因為它會產生一個二值決策結果,即它是一種決策“機”。支持向量機的泛化錯誤率較低,也就是說它具有良好的學習能力,且學到的結果具有很好的推廣性。這些優點使得支持向量機十分流行,有些人認為它是監督學習中最好的定式算法。

  支持向量機視圖通過求解一個二次優化問題來最大化分類間隔。在過去,訓練支持向量機常採用非常複雜並且低效的二次規劃求解方法。John Platt 引入了SMO算法,此算法可以通過每次只優化2個 α 值來加快SVM的訓練速度。

  核方法或者說核技巧會將數據(有時候是非線性數據)從一個低維空間映射到一個高維空間,可以將一個在低維空間中的非線性問題轉化為高維空間下的線性問題來求解。核方法不止在SVM中適用,還可以用於其他算法。而其中的徑向基函數是一個常用的度量兩個向量距離的核函數。

 

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

研究:人類接觸野生動物恐招致病毒入侵 三種類別動物風險尤高

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

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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

對於單例模式面試官會怎樣提問呢?你又該如何回答呢?

前言

在面試的時候面試官會怎麼在單例模式中提問呢?你又該如何回答呢?可能你在面試的時候你會碰到這些問題:

  • 為什麼說餓漢式單例天生就是線程安全的?

  • 傳統的懶漢式單例為什麼是非線程安全的?

  • 怎麼修改傳統的懶漢式單例,使其線程變得安全?

  • 線程安全的單例的實現還有哪些,怎麼實現?

  • 雙重檢查模式、Volatile關鍵字 在單例模式中的應用

  • ThreadLocal 在單例模式中的應用

  • 枚舉式單例

那我們該怎麼回答呢?那答案來了,看完接下來的內容就可以跟面試官嘮嘮單例模式了

 

單例模式簡介

單例模式是一種常用的軟件設計模式,其屬於創建型模式,其含義即是一個類只有一個實例,併為整個系統提供一個全局訪問點 (向整個系統提供這個實)。

結構:

                      

單例模式三要素:

  • 私有的構造方法;

  • 私有靜態實例引用;

  • 返回靜態實例的靜態公有方法。

單例模式的優點

  • 在內存中只有一個對象,節省內存空間;

  • 避免頻繁的創建銷毀對象,可以提高性能;

  • 避免對共享資源的多重佔用,簡化訪問;

  • 為整個系統提供一個全局訪問點。

單例模式的注意事項

  在使用單例模式時,我們必須使用單例類提供的公有工廠方法得到單例對象,而不應該使用反射來創建,使用反射將會破壞單例模式 ,將會實例化一個新對象。

 

單線程實現方式

在單線程環境下,單例模式根據實例化對象時機的不同分為,

  • 餓漢式單例(立即加載)餓漢式單例在單例類被加載時候,就實例化一個對象並將引用所指向的這個實例;

  • 懶漢式單例(延遲加載),只有在需要使用的時候才會實例化一個對象將引用所指向的這個實例。

 

從速度和反應時間角度來講,餓漢式(又稱立即加載)要好一些;從資源利用效率上說,懶漢式(又稱延遲加載)要好一些。

餓漢式單例

// 餓漢式單例
public class HungrySingleton{
​
    // 私有靜態實例引用,創建私有靜態實例,並將引用所指向的實例
    private static HungrySingleton singleton = new HungrySingleton();
    // 私有的構造方法
    private HungrySingleton(){}
    //返回靜態實例的靜態公有方法,靜態工廠方法
    public static HungrySingleton getSingleton(){
        return singleton;
    }
}

餓漢式單例,在類被加載時,就會實例化一個對象並將引用所指向的這個實例;更重要的是,由於這個類在整個生命周期中只會被加載一次,只會被創建一次,因此惡漢式單例線程安全的。

那餓漢式單例為什麼是天生就線程安全呢?

因為類加載的方式是按需加載,且只加載一次。由於一個類在整個生命周期中只會被加載一次,在線程訪問單例對象之前就已經創建好了,且僅此一個實例。即線程每次都只能也必定只可以拿到這個唯一的對象。

懶漢式單例

// 懶漢式單例
public class LazySingleton {
    // 私有靜態實例引用
    private static LazySingleton singleton;
    // 私有的構造方法
    private LazySingleton(){}
    // 返回靜態實例的靜態公有方法,靜態工廠方法
    public static LazySingleton getSingleton(){
        //當需要創建類的時候創建單例類,並將引用所指向的實例
        if (singleton == null) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

懶漢式單例是延遲加載,只有在需要使用的時候才會實例化一個對象,並將引用所指向的這個對象。

由於是需要時創建,在多線程環境是不安全的,可能會併發創建實例,出現多實例的情況,單例模式的初衷是相背離的。那我們需要怎麼避免呢?可以看接下來的多線程中單例模式的實現形式。

那為什麼傳統的懶漢式單例為什麼是非線程安全的?

非線程安全主要原因是,會有多個線程同時進入創建實例(if (singleton == null) {}代碼塊)的情況發生。當這種這種情形發生后,該單例類就會創建出多個實例,違背單例模式的初衷。因此,傳統的懶漢式單例是非線程安全的。

 

多線程實現方式

  在單線程環境下,無論是餓漢式單例還是懶漢式單例,它們都能夠正常工作。但是,在多線程環境下就有可能發生變異:

  • 餓漢式單例天生就是線程安全的,可以直接用於多線程而不會出現問題

  • 懶漢式單例本身是非線程安全的,因此就會出現多個實例的情況,與單例模式的初衷是相背離的。

 

那我們應該怎麼在懶漢的基礎上改造呢?

  • synchronized方法

  • synchronized塊

  • 使用內部類實現延遲加載

synchronized方法

// 線程安全的懶漢式單例
public class SynchronizedSingleton {
    private static SynchronizedSingleton synchronizedSingleton;
    private SynchronizedSingleton(){}
    // 使用 synchronized 修飾,臨界資源的同步互斥訪問
    public static synchronized SynchronizedSingleton getSingleton(){
        if (synchronizedSingleton == null) {
            synchronizedSingleton = new SynchronizedSingleton();
        }
        return synchronizedSingleton;
    }
}

  使用 synchronized 修飾 getSingleton()方法,將getSingleton()方法進行加鎖,實現對臨界資源的同步互斥訪問,以此來保證單例。

雖然可現實線程安全,但由於同步的作用域偏大、鎖的粒度有點粗,會導致運行效率會很低。

synchronized塊

// 線程安全的懶漢式單例
public class BlockSingleton {
    private static BlockSingleton singleton;
    private BlockSingleton(){}
    public static BlockSingleton getSingleton2(){
        synchronized(BlockSingleton.class){  // 使用 synchronized 塊,臨界資源的同步互斥訪問
            if (singleton == null) { 
                singleton = new BlockSingleton();
            }
        }
        return singleton;
    }
}

 其實synchronized塊跟synchronized方法類似,效率都偏低。

使用內部類實現延遲加載

// 線程安全的懶漢式單例
public class InsideSingleton {
    // 私有內部類,按需加載,用時加載,也就是延遲加載
    private static class Holder {
        private static InsideSingleton insideSingleton = new InsideSingleton();
    }
    private InsideSingleton() {
    }
    public static InsideSingleton getSingleton() {
        return Holder.insideSingleton;
    }
}
  • 如上述代碼所示,我們可以使用內部類實現線程安全的懶漢式單例,這種方式也是一種效率比較高的做法。其跟餓漢式單例原理是相同的, 但可能還存在反射攻擊或者反序列化攻擊 。

 

雙重檢查(Double-Check idiom)現實

雙重檢查(Double-Check idiom)-volatile

使用雙重檢測同步延遲加載去創建單例,不但保證了單例,而且提高了程序運行效率。

// 線程安全的懶漢式單例
public class DoubleCheckSingleton {
    //使用volatile關鍵字防止重排序,因為 new Instance()是一個非原子操作,可能創建一個不完整的實例
    private static volatile DoubleCheckSingleton singleton;
    private DoubleCheckSingleton() {
    }
​
    public static DoubleCheckSingleton getSingleton() {
        // Double-Check idiom
        if (singleton == null) {
            synchronized (DoubleCheckSingleton.class) {       
                // 只需在第一次創建實例時才同步
                if (singleton == null) {      
                    singleton = new DoubleCheckSingleton();      
                }
            }
        }
        return singleton;
    }
​
}

為了在保證單例的前提下提高運行效率,我們需要對singleton實例進行第二次檢查,為的式避開過多的同步(因為同步只需在第一次創建實例時才同步,一旦創建成功,以後獲取實例時就不需要同步獲取鎖了)。

但需要注意的必須使用volatile關鍵字修飾單例引用,為什麼呢?

 如果沒有使用volatile關鍵字是可能會導致指令重排序情況出現,在Singleton 構造函數體執行之前,變量 singleton可能提前成為非 null 的,即賦值語句在對象實例化之前調用,此時別的線程將得到的是一個不完整(未初始化)的對象,會導致系統崩潰。

此可能為程序執行步驟:

  1. 線程 1 進入 getSingleton() 方法,由於 singleton 為 null,線程 1 進入 synchronized 塊 ;

  2. 同樣由於 singleton為 null,線程 1 直接前進到 singleton = new DoubleCheckSingleton()處,在new對象的時候出現重排序,導致在構造函數執行之前,使實例成為非 null,並且該實例並未初始化的(原因在NOTE);

  3. 此時,線程 2 檢查實例是否為 null。由於實例不為 null,線程 2 得到一個不完整(未初始化)的 Singleton 對象

  4. 線程 1 通過運行 Singleton對象的構造函數來完成對該對象的初始化。

  這種安全隱患正是由於指令重排序的問題所導致的。而volatile 關鍵字正好可以完美解決了這個問題。使用volatile關鍵字修飾單例引用就可以避免上述災難。

NOTE

new 操作會進行三步走,預想中的執行步驟:

memory = allocate();        //1:分配對象的內存空間
ctorInstance(memory);       //2:初始化對象
singleton = memory;        //3:使singleton3指向剛分配的內存地址

**但實際上,這個過程可能發生無序寫入(指令重排序),可能會導致所下執行步驟:

memory = allocate();        //1:分配對象的內存空間
singleton3 = memory;        //3:使singleton3指向剛分配的內存地址
ctorInstance(memory);       //2:初始化對象

雙重檢查(Double-Check idiom)-ThreadLocal

  藉助於 ThreadLocal,我們可以實現雙重檢查模式的變體。我們將臨界資源線程局部化,具體到本例就是將雙重檢測的第一層檢測條件 if (instance == null) 轉換為 線程局部範圍內的操作 。

// 線程安全的懶漢式單例
public class ThreadLocalSingleton 
    // ThreadLocal 線程局部變量
    private static ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal<ThreadLocalSingleton>();
    private static ThreadLocalSingleton singleton = null;
    private ThreadLocalSingleton(){}
    public static ThreadLocalSingleton getSingleton(){
        if (threadLocal.get() == null) {        // 第一次檢查:該線程是否第一次訪問
            createSingleton();
        }
        return singleton;
    }
​
    public static void createSingleton(){
        synchronized (ThreadLocalSingleton.class) {
            if (singleton == null) {          // 第二次檢查:該單例是否被創建
                singleton = new ThreadLocalSingleton();   // 只執行一次
            }
        }
        threadLocal.set(singleton);      // 將單例放入當前線程的局部變量中 
    }
}

藉助於 ThreadLocal,我們也可以實現線程安全的懶漢式單例。但與直接雙重檢查模式使用,使用ThreadLocal的實現在效率上還不如雙重檢查鎖定。

 

枚舉實現方式

它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,

直接通過Singleton.INSTANCE.whateverMethod()的方式調用即可。方便、簡潔又安全。

public enum EnumSingleton {
    instance;
    public void whateverMethod(){
        //dosomething
    }
}

 

 

測試單例線程安全性

 使用多個線程,並使用hashCode值計算每個實例的值,值相同為同一實例,否則為不同實例。

public class Test {
    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new TestThread();
​
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
​
        }
    }
}
class TestThread extends Thread {
    @Override
    public void run() {
        // 對於不同單例模式的實現,只需更改相應的單例類名及其公有靜態工廠方法名即可
        int hash = Singleton5.getSingleton5().hashCode();  
        System.out.println(hash);
    }
}

 

 

小結

單例模式是 Java 中最簡單,也是最基礎,最常用的設計模式之一。在運行期間,保證某個類只創建一個實例,保證一個類僅有一個實例,並提供一個訪問它的全局訪問點 ,介紹單例模式的各種寫法:

  • 餓漢式單例(線程安全)

  • 懶漢式單例

    • 傳統懶漢式單例(線程安全);

    • 使用synchronized方法實(線程安全);

    • 使用synchronized塊實現懶漢式單例(線程安全);

    • 使用靜態內部類實現懶漢式單例(線程安全)。

  • 使用雙重檢查模式

    • 使用volatile關鍵字(線程安全);

    • 使用ThreadLocal實現懶漢式單例(線程安全)。

  • 枚舉式單例

 

各位看官還可以嗎?喜歡的話,動動手指點個,點個關注唄!!謝謝支持! 歡迎關注公眾號【Ccww技術博客】,原創技術文章第一時間推出

 

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

奧運再生能源準備不延期 東京主場館的透明太陽能板 滋養草坪又能發電

文:宋瑞文(加州能源特約撰述)

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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

學者:野火致觀光客減少 有助控制澳洲疫情

摘錄自2020年4月13日中央社報導

一位澳洲學者說,幾個月前的森林大火雖然造成重大傷亡和損失,但減少可能染疫的觀光客到訪,意外地降低2019新型冠狀病毒(COVID-19,武漢肺炎)疫情對澳洲的衝擊。

澳洲國立大學傳染病教授柯里諾(Peter Collignon)說,要是沒有這場災難,澳洲的新型冠狀病毒疫情恐怕一發不可收拾。澳洲觀光及交通業界論壇組織(Tourism and Transport Forum)執行長奧斯蒙(Margy Osmond)表示,早在武漢肺炎疫情爆發之前,澳洲觀光業本就因為森林大火而受到重創。

澳洲衛生部最新的數據顯示,截至澳洲東部標準時間(AEST)13日下午3時止,澳洲2019冠狀病毒疾病確診病例24小時內增加46例,累計達6359例,其中61人死亡。確診人數最多的新南威爾斯州有2863個病例,約占45%。全國有36萬2000多人已接受病毒檢測。

生活環境
國際新聞
澳洲
澳洲野火
地方觀光
控制疫情
武漢肺炎
動物與大環境變遷

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

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

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

中國傳統市場照賣 澳:全球健康威脅

摘錄自2020年4月15日自由時報報導

武漢肺炎疫情被認為起源自中國傳統市場販售的野味,最早傳出疫情的武漢市華南海鮮批發市場目前仍被關閉。但另一個武漢市最大的傳統市場「白沙洲大市場」,不僅在封城期間維持基本的運作,解封後也很快就恢復疫情爆發前的熱絡景況。

為防堵疫情因市場活動二度爆發,民眾必須持有健康認證「綠碼」、量測體溫後才能進入白沙洲大市場,載貨的車輛也需經過消毒。白沙洲大市場販賣蔬菜、魚貨、冷藏雞肉與當季食品,當地攤販指出,該市場禁止宰殺雞、豬、羊等家禽畜,必須在專門的屠宰場完成宰殺後,再送至市場販賣。

澳洲總理莫里森14日指出,他難以理解世衛組織竟坐視中國的傳統市場繼續營運,並表示這不是第一次出現源自市場的病毒,稱傳統市場為「全球健康威脅」。

但世衛組織上週表示,傳統市場為中國人民重要的收入與食物來源,不支持中國關閉所有傳統市場,且中國目前只允許傳統市場銷售安全的食物,衛生水準已經提升。世衛組織特使納巴羅也於14日受訪時說,為防止下一次的全球大流行,世衛組織建議各國應該關閉「危險的」傳統市場,但也強調,世衛組織無法強制任何國家關閉市場。

生活環境
國際新聞
中國新聞
中國
武漢
傳統市場
食品安全

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

仰賴自然卻摧毀自然 珍古德:導致病毒大爆發

摘錄自2020年4月14日自由時報報導

世界著名的英國靈長類動物學家珍古德(Jane Goodall)11日於電話訪談中指出,武漢肺炎(COVID-19)的大爆發是由於人類長期以來對自然的漠視和對動物的不尊重。

《法新社》報導,珍古德認為,病毒的大流行從很久以前就被預言了。以砍伐森林為例,動物棲地減少,不同物種被迫靠近,因而互相傳染疾病,久之,當生活空間少到牠們不得已向人類居住地接近時,人類便被傳染。她補充:「當然,野生動物的捕食也是原因之一,特別是中國和非洲。」

她進一步表示,中國禁止野生動物市場是一件好事,「希望是永久性的」,且期待亞洲其他國家能夠跟進。但珍古德也明白,在非洲會相對難以實施該禁令,因為許多民眾賴以為生,「當人們沒有其他管道養活自己和家人時,你不可能直接禁止他做(野生動物買賣)。」

土地水文
土地利用
國際新聞
珍古德
冠狀病毒
災害

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

CentOS8.1中使用通用二進制包安裝MySQL8.0

  寫在前的的話: 在IT技術日新月異的今天,老司機也可能在看似熟悉的道路上翻車,甚至是大型翻車現場!自己一個人開車過去翻個車不可怕,可怕的是帶着整個團隊甚至是整個公司一起翻車山崖下,解決辦法就是:新出現的道路自己先過一遍,留好坑位標記,將來帶隊過去時不再翻車!!!

 

  最近剛好在進行權限系統的微服務化改造,要重新搭一套開發服務器環境。今天搭的是MySQL數據庫服務器,MySQL 8.0出來也有些年月了,現在(2020)大多數公司還沒在生產上用上,於是乎就想嘗個鮮,選擇了在CentOS8.1上進行MySQL8.0服務器的搭建。當前來說CentOS8.1也算比較新了!

 

  老樣子,先裝一個全新的CentOS8.1虛擬機,選擇裝配基本的Server軟件包,網絡模式選擇【橋接模式(自動)】(主要為了讓宿主機和虛擬機的網絡處於等級),IP地址相關信息切記選擇手工配置,不能用DHCP進行動態分配(有DNS服務器輔助除外),為什麼呢?因為你是在配服務器,IP地址要固定下來,不然每次啟動后的IP都不同,那就很尷尬了!

 

  服務器操作系統準備好后該去下載MySQL8.0了,去哪裡下呢?當然是MySQL8.0社區版官網!可是跑到網官一看,估計有些同學就一臉懵逼,純英文的不說,安裝包還各樣種樣的!怎麼選呢?

  首先要選定操作系統平台,我這邊是CentOS8.1 x86_64位架構,那我要選Linux版本的,最好是CentOS專用的!結果找了一圈暫時還沒有CentOS專用的版本(RedHat專用版本到是有,其實我們知道CentOS是就源自RedHat)…為了保險起見,我們選Linux通用版(Linux-Generic),結果出來的列表也不少!這麼多都是些啥,見圖標解吧:

 

  我選擇【64位通用二進制最小安裝壓縮包(不含調試組件及調式符號)】,理由:

  1. 我的目標操作系統是64位的CentOS;
  2. 我不需要做MySQL的調試;
  3. CentOS8.1上帶有專用的解壓安裝工具tar;
  4. 小包從官網下載和上傳到服務器都快(MySQL數據庫功能也完整,別看小了那麼多!);

  點擊Download后出現的界面中會建議你登錄你的Oracle Web賬號,不用登錄(當然你有賬號也可以登錄),直接點下面的一小行字——“No thanks,just start my download.

 

  將目標包(mysql-8.0.20-linux-x86_64-minimal.tar.xz)下載好后,使用SecureCRT上傳到服務器 /opt 目錄下!(Linux眾多目錄的作用)

  接下來開始正式安裝MySQL8.0,記住我們的前提是:全新的、乾淨的CentOS8.1操作系統,之前沒有裝過MySQL的!如果之前裝過MySQL是要先把相關目錄和配置文件刪除乾淨才能再裝的!謹記!謹記!謹記!

  第一步:使用yum包管理器檢查並安裝異步IO依賴包 libaio ,如果沒裝這個包,數據目錄初始化和後續服務器的啟動都將失敗,如果檢查發現沒裝,要將它裝上:

# yum search libaio 
# yum -y install libaio 

  第二步:由於CentOS是源自RedHat的,而RedHat系列的操作系統中沒有MySQL通用二進制安裝包(不管有沒有壓縮)中的MySQL客戶端(bin/mysql)組件所需要的 /lib64/libtinfo.so.5 文件,為了解決這個問題,需要安裝一個含有該文件的 ncurses-compat-libs 包(截圖是已經裝過一次的結果):

# yum -y install ncurses-compat-libs

  第三步:轉到 mysql-8.0.20-linux-x86_64-minimal.tar.xz 壓縮包存放目錄 /opt ,使用 tar 命令進行解壓(直接解壓到當前目錄):

# cd /opt
# tar -xvf mysql-8.0.20-linux-x86_64-minimal.tar.xz

  解壓得到一個新目錄:/opt/mysql-8.0.20-linux-x86_64-minimal ,該目錄即為MySQL的真實安裝目錄

  第四步:創建用於運行MySQL的組和普通用戶(非操作系統用戶):

# groupadd mysql
# useradd -r -g mysql -s /bin/false mysql

  第五步:在用戶手動安裝軟件推薦安裝目錄 /usr/local 中創建MySQL的真實安裝目錄的軟鏈接目錄(軟鏈接目錄不能是已經存在的目錄,相當於Windows快捷方式):

# cd /usr/local
# ln -s /opt/mysql-8.0.20-linux-x86_64-minimal mysql

  第六步:在mysql軟鏈接目錄中創建導入導出操作安全目錄(該目錄用於使具有FILE權限的用戶可以安全地執行導入導出操作):

# cd /usr/local/mysql
# mkdir mysql-files
# chown mysql:mysql mysql-files
# chmod 750 mysql-files

  (其實,第六步做完后就已經有辦法可以臨時點亮MySQL服務器了,這裏不講,因為我們要配的是一個長期運行的MySQL數據庫服務器)

  第七步:在mysql軟鏈接目錄下創建數據目錄:

# cd /usr/local/mysql
# mkdir data
# chown mysql:mysql data
# chmod 750 data

  第八步:創建MySQL服務啟動需要用到的靜態配置文件(如果目錄下已經有了同名文件,則需要換一個名字,之前沒裝過MySQL一般是不會有的!):

# cd /etc
# touch my.cnf
# chown root:root my.cnf
# chmod 644 my.cnf

  第九步:使用vi或vim打開第八步創建的配置文件 /etc/my.cnf ,加入MySQL服務的配置信息

[mysqld]
datadir=/usr/local/mysql/data
socket=/tmp/mysql.sock
port=3306
log-error=/usr/local/mysql/data/mysqldb.xgclassroom.err
user=mysql
secure_file_priv=/usr/local/mysql/mysql-files
local_infile=OFF

  注意:配置中log-error的值一般中根據你安裝操作系統時設置的Host名稱相關,大家的不一樣,當然你也可以直接指定新名字!

  另外,如果要InnoDB的相關配置項,那麼只能在數據目錄初始化(第十步)之前在my.cnf中進行配置,主要有 innodb_data_home_dir,innodb_data_file_path,,innodb_log_file_size,innodb_log_group_home_dir 和 innodb_page_size 這幾項,未配置的情況下,它們都使用默認值。

  第十步:初始化第七步創建的數據目錄(因為要用到第八和第九步創建的配置文件)

# cd /usr/local/mysql
# bin/mysqld --defaults-file=/etc/my.cnf --initialize

  注意:數據目錄初始化成功后,會在第九步所設置的log-error日誌文件(我的是 /usr/local/mysql/data/mysqldb.xgclassroom.err)中生成 root@localhost 的初始密碼(賬號冒號後面的QdbB=9e!lT6=就是,要記住這個初始密碼,後面登錄root賬號是要它來修改初始密碼),類似下面的信息(可以使用cat命令查看):

...省略...
A temporary password is generated for root@localhost: yi5w%J*hws6E
...省略...

  現在我們還缺了最最最重要的一項配置——讓MySQL服務隨操作系統的啟動自動啟動!繼續配置ing!

  在Linux系統中目前系統服務主要以 systemd 服務單元的形式存在(類似windows平台的services.msc下管理的各個服務),Linux系統下一切皆文件,systemd 服務單元也是由一個個systemd 服務單元配置文件組成,systemd 服務單元配置文件 = systemd 服務單元!配置文件名就是服務單元名!所有服務單元的配置文件統一放在 /usr/lib/systemd/system 目錄下。

  第十一步:在系統服務單元配置文件存放目下創建MySQL的服務單元配置文件:

# cd /usr/lib/systemd/system
# touch mysqld.service
# chmod 644 mysqld.service

  第十二步:使用vi或vim打開第十一步創建的MySQL服務單元配置文件 /usr/lib/systemd/system/mysqld.service,並加入MySQL服務單元配置信息:

[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target

[Install]
WantedBy=multi-user.target

[Service]
User=mysql
Group=mysql

# Have mysqld write its state to the systemd notify socket
Type=notify

# Disable service start and stop timeout logic of systemd for mysqld service.
TimeoutSec=0

# Start main service
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf $MYSQLD_OPTS 

# Use this to switch malloc implementation
EnvironmentFile=-/etc/sysconfig/mysql

# Sets open_files_limit
LimitNOFILE = 10000

Restart=on-failure

RestartPreventExitStatus=1

# Set environment variable MYSQLD_PARENT_PID. This is required for restart.
Environment=MYSQLD_PARENT_PID=1

PrivateTmp=false

  第十三步:啟用MySQL服務單元配置

# systemctl enable mysqld.service

  經過上面的一系列安裝和配置步驟后,此時我們已經可以通過systemctl工具手工管理MySQL服務了:

# systemctl {start|stop|restart|status} mysqld

  第十四步:啟動MySQL服務,並查看MySQL服務狀態:

# systemctl start mysqld
# systemctl status mysqld

  第十五步:重啟服務器,檢驗MySQL服務是否隨服務器一起啟動了:

# reboot

  …重啟系統中…

# systemctl status mysqld

  如果最終MySQL服務狀態正常,那麼CentOS8.1上MySQL8.0的安裝就算是完成了,但是不要高興的太早了!還有好多事要做:

  1、將服務器上的MySQL客戶端(bin/mysql)配置到系統環境變量PATH中:

  如果不將MySQL客戶端(bin/mysql)配到環境變量中,你會發現即使MySQL服務在正常運行,但直接在系統終端輸入mysql是找不到該命令的:

[root@mysqldb /]# mysql
-bash: mysql: command not found

  當然,你用完整MySQL客戶端(bin/mysql)命令路徑是可以運行該命令的(雖然報錯,但那表示命令可以使用了):

[root@mysqldb /]# /usr/local/mysql/bin/mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

  系統環境變量PATH在環境變量配置文件 /etc/profile 中配置:

# vim /etc/profile

在文件的最後新起一行,插入:

export PATH=$PATH:/usr/local/mysql/bin

保存退出后,使用下面的命令手動使修改生效:

# source /etc/profile

  現在你可以直接在任意目錄下執行mysql命令了:

[root@mysqldb /]# mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

  

  2、root賬號初始密碼修改:

  使用root初始密碼(記錄在第九步所設置的log-error日誌文件)登錄MySQL,並修改密碼:

[root@mysqldb /]# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.20

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>alter user 'root'@'localhost' identified by '1qaz@WSX';

 Query OK, 0 rows affected (0.00 sec)

 

  3、進行root賬號的服務器本機登錄測試:

  修改完root的默認密碼后,退出MySQL並使用新密碼重新嘗試登錄:

mysql> exit
Bye
[root@mysqldb /]# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.20 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

 

  4、對MySQL使用的端口號進行防火牆例外設置:

  先用root賬號登錄MySQL,檢查一下當前正在使用的端口號:

mysql> show global variables like 'port';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| port          | 3306  |
+---------------+-------+
1 row in set (0.01 sec)

  將目標端口號(例中為3306)添加到防火牆例外列表,並重新載入防火牆

# firewall-cmd --zone=public --add-port=3306/tcp --permanent
success
# firewall-cmd --reload
success

  注意1:一定要帶上–permanent 參數才能永遠生效,否則系統重啟后丟失,另外 –zone 、–add-port 和 –permanent參數前面是兩個-;

  注意2:一定要重新載入防火牆,讓設置生效;

 

  5、創建遠程登錄和使用MySQL的普通用戶(因為安全起見,root賬號一般不要設置成可以遠程登錄,要設置也盡量設置成只可在固定的某個IP遠程登錄):

  以root賬號登錄MySQL,使用以下SQL命令創建一個可以任意網絡互通的點登錄的賬號xurm 密碼 1qaz@WSX

create user xurm IDENTIFIED with mysql_native_password by '1qaz@WSX' account unlock;
select host,user from user;
grant all on *.* to xurm WITH GRANT OPTION;
FLUSH PRIVILEGES;

 

  認認真真上邊的步驟和細節進行安裝和配置后,現在可以從開發機的客戶端,用普通賬號登錄遠程MySQL服務器就可以愉快的玩耍了:

 

  以下記錄一次手賤導致的翻車現場:

  使用 # systemctl stop mysqld.service 成功關閉mysql服務后,嘗試直接使用 /usr/local/mysql/bin/mysqld 嘗試啟動mysql造成的後果:bin/mysqld 嘗試啟動時覆蓋並破壞了第十步數據目錄初始生成的有關文件,導至MySQL無法啟動,這時嘗試使用 # systemctl start mysqld.service也無法再啟動MySQL服務~~~

....
2020-06-06T04:38:39.614356Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2020-06-06T04:38:40.618323Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2020-06-06T04:38:41.619650Z 1 [ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
2020-06-06T04:38:41.620710Z 1 [ERROR] [MY-012592] [InnoDB] Operating system error number 11 in a file operation.
2020-06-06T04:38:41.621045Z 1 [ERROR] [MY-012596] [InnoDB] Error number 11 means 'Resource temporarily unavailable'
2020-06-06T04:38:41.621712Z 1 [ERROR] [MY-012215] [InnoDB] Cannot open datafile './ibdata1'
2020-06-06T04:38:41.622047Z 1 [ERROR] [MY-012959] [InnoDB] Could not open or create the system tablespace. 
If you tried to add new data files to the system tablespace, and it failed here, you should now edit innodb_data_file_path in my.cnf back to
what it was, and remove the new ibdata files InnoDB created in this failed attempt. InnoDB only wrote those files full of zeros, but did not
yet use them in any way. But be careful: do not remove old data files which contain your precious data!
2020-06-06T04:38:41.622372Z 1 [ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Cannot open a file. 2020-06-06T04:38:42.119489Z 1 [ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine 2020-06-06T04:38:42.122437Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed. 2020-06-06T04:38:42.127147Z 0 [ERROR] [MY-010119] [Server] Aborting 2020-06-06T04:38:42.127857Z 0 [System] [MY-010910] [Server] /usr/local/mysql/bin/mysqld: Shutdown complete (mysqld 8.0.20) MySQL Community Server - GPL.

  提示信息中讓我在/etc/my.cnf配置文件中將 innodb_data_file_path 配置項的值改回原來的去,實際上我並沒有在/etc/my.cnf配置文件配置該項,而是一直使用着默認的 innodb_data_file_path 配置;

  提示信息讓我刪除啟動失敗的嘗試中InnoDB生成的新ibdata相關文件,講真的,因為一開始沒有對比過data/目錄中的文件,現在我都不知道哪些是InnoDB生成的新ibdata相關文件;

  最後想了一下,我是剛剛安裝的數據庫系統,也還沒有重要數據在上邊,現在data/目錄遭到破壞,那最快的辦法就是清空data/目錄,並重新初始化它,應該就能解決問題了(已經運行了段時間的生產數據庫千萬不要這麼玩,要備份好所有數據文件,否則會死得很難看!!!最好另尋它法,最最最好就不要讓這樣的車禍出現!):

  • 清空data/目錄:
#  rm -rf /usr/local/mysql/data/*

 

  • 使用第十步中的命令重新初始化數據目錄:
# cd /usr/local/mysql
# bin/mysqld --defaults-file=/etc/my.cnf --initialize

注意:數據目錄重新初始化成功后,會在第九步所設置的log-error日誌文件(我的是 /usr/local/mysql/data/mysqldb.xgclassroom.err)中生成新的 root@localhost 的初始密碼

 

  • 重啟嘗試啟動MySQL服務:
# systemctl start mysqld
Job for mysqld.service failed because the control process exited with error code.
See "systemctl status mysqld.service" and "journalctl -xe" for details.

  哦呵…又雙叒叕翻車了,重啟失敗!!! 慌得一逼!繼續…

 

  • 查看一下日誌文件/usr/local/mysql/data/mysqldb.xgclassroom.err
# cat mysqldb.xgclassroom.err 
2020-06-06T05:31:31.249888Z 0 [System] [MY-013169] [Server] /opt/mysql-8.0.20-linux-x86_64-minimal/bin/mysqld (mysqld 8.0.20) initializing of server in progress as process 2662
2020-06-06T05:31:31.256260Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2020-06-06T05:31:31.509465Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2020-06-06T05:31:31.995068Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: iqaaJCdnj3&e
2020-06-06T05:33:09.978908Z 0 [System] [MY-010116] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.20) starting as process 2714
2020-06-06T05:33:09.987836Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2020-06-06T05:33:10.212686Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2020-06-06T05:33:11.314035Z 0 [ERROR] [MY-011292] [Server] Plugin mysqlx reported: 'Preparation of I/O interfaces failed, X Protocol won't be accessible'
2020-06-06T05:33:11.314175Z 0 [ERROR] [MY-011300] [Server] Plugin mysqlx reported: 'Setup of bind-address: '*' port: 33060 failed, `bind()` 
failed with error: Address already in use (98). Do you already have another mysqld server running with Mysqlx ?' 2020-06-06T05:33:11.314342Z 0 [ERROR] [MY-011300] [Server] Plugin mysqlx reported: 'Setup of socket: '/tmp/mysqlx.sock' failed,
another process with PID 1734 is using UNIX socket file' 2020-06-06T05:33:11.413373Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed. 2020-06-06T05:33:11.414505Z 0 [ERROR] [MY-010262] [Server] Can't start server: Bind on TCP/IP port: Address already in use 2020-06-06T05:33:11.414625Z 0 [ERROR] [MY-010257] [Server] Do you already have another mysqld server running on port: 3306 ? 2020-06-06T05:33:11.414918Z 0 [ERROR] [MY-010119] [Server] Aborting 2020-06-06T05:33:12.939872Z 0 [System] [MY-010910] [Server] /usr/local/mysql/bin/mysqld: Shutdown complete (mysqld 8.0.20) MySQL Community Server - GPL.

  有個PID為1734的進程在佔用3306和33060端口?而且還可能是另一個mysqld server,我造,不會是真的吧!使用# systemctl status mysqld 都查過了,沒有服務了~

 

  • 查看一下3306端口的佔用情況先:
# netstat -anp | grep 3306
tcp6       0      0 :::33060                :::*                    LISTEN      1734/mysqld         
tcp6       0      0 :::3306                 :::*                    LISTEN      1734/mysqld 

  竟然真的有個mysqld進程(PID=1734)以tpc6的協議模式佔著3306和33060端口~

 

  • 查看一下這個mysqld進程的詳情,看看它是是哪裡來的:
# ps -aux | grep mysqld
mysql      1734  0.5 19.0 1773244 352616 ?      Sl   12:12   0:29 /usr/local/mysql/bin/mysqld
root       2786  0.0  0.0  12108  1080 pts/1    S+   13:45   0:00 grep --color=auto mysqld

  果然是手賤作的孽,直接通過/usr/local/mysql/bin/mysqld手動啟動的服務沒關徹底…

 

  • 幹掉佔用3306和33060端口的無效mysqld進程:
# kill -9 1734

 

  • 再次使用# systemctl start mysqld 命令啟動mysql:
# systemctl start mysqld
# systemctl status mysqld
● mysqld.service - MySQL Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-06-06 13:47:15 CST; 20s ago
     Docs: man:mysqld(8)
           http://dev.mysql.com/doc/refman/en/using-systemd.html
 Main PID: 2795 (mysqld)
   Status: "Server is operational"
    Tasks: 39 (limit: 11337)
   Memory: 327.7M
   CGroup: /system.slice/mysqld.service
           └─2795 /usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf

Jun 06 13:47:14 mysqldb.xgclassroom systemd[1]: Starting MySQL Server...
Jun 06 13:47:15 mysqldb.xgclassroom systemd[1]: Started MySQL Server.

 

  終於把車開回正路來了!!!使用日誌文件/usr/local/mysql/data/mysqldb.xgclassroom.err中記錄的新的初始密碼登錄mysql並修改初始化密碼就可以了!

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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