標籤: 銷售文案

  • 使用請求頭認證來測試需要授權的 API 接口

    使用請求頭認證來測試需要授權的 API 接口

    使用請求頭認證來測試需要授權的 API 接口

    Intro

    有一些需要認證授權的接口在寫測試用例的時候一般會先獲取一個 token,然後再去調用接口,其實這樣做的話很不靈活,一方面是存在着一定的安全性問題,獲取 token 可能會有一些用戶名密碼之類的測試數據,還有就是獲取 token 的話如果全局使用同一個 token 會很不靈活,如果我要測試沒有用戶信息的話還比較簡單,我可以不傳遞 token,如果token里有兩個角色,我要測試另外一個角色的時候,只能給這個測試用戶新增一個角色然後再獲取token,這樣就很不靈活,於是我就嘗試把之前寫的自定義請求頭認證的代碼,整理了一下,集成到了一個 nuget 包里以方便其他項目使用,nuget 包是 WeihanLi.Web.Extensions,源代碼在這裏 https://github.com/WeihanLi/WeihanLi.Web.Extensions 有想自己改的可以直接拿去用,目前提供了基於請求頭的認證和基於 QueryString 的認證兩種認證方式。

    實現效果

    基於請求頭動態配置用戶的信息,需要什麼樣的信息就在請求頭中添加什麼信息,示例如下:

    再來看個單元測試的示例:

    [Fact]
    public async Task MakeReservationWithUserInfo()
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");
    
        request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId()); // 用戶Id
        request.Headers.TryAddWithoutValidation("UserName", Environment.UserName); // 用戶名
        request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager"); //用戶角色
    
        request.Content = new StringContent($@"{{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");
    
        using var response = await Client.SendAsync(request);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
    

    實現原理解析

    實現原理其實挺簡單的,就是實現了一種基於 header 的自定義認證模式,從 header 中獲取用戶信息並進行認證,核心代碼如下:

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (await Options.AuthenticationValidator(Context))
        {
            var claims = new List<Claim>();
            if (Request.Headers.TryGetValue(Options.UserIdHeaderName, out var userIdValues))
            {
                claims.Add(new Claim(ClaimTypes.NameIdentifier, userIdValues.ToString()));
            }
            if (Request.Headers.TryGetValue(Options.UserNameHeaderName, out var userNameValues))
            {
                claims.Add(new Claim(ClaimTypes.Name, userNameValues.ToString()));
            }
            if (Request.Headers.TryGetValue(Options.UserRolesHeaderName, out var userRolesValues))
            {
                var userRoles = userRolesValues.ToString()
                    .Split(new[] { Options.Delimiter }, StringSplitOptions.RemoveEmptyEntries);
                claims.AddRange(userRoles.Select(r => new Claim(ClaimTypes.Role, r)));
            }
    
            if (Options.AdditionalHeaderToClaims.Count > 0)
            {
                foreach (var headerToClaim in Options.AdditionalHeaderToClaims)
                {
                    if (Request.Headers.TryGetValue(headerToClaim.Key, out var headerValues))
                    {
                        foreach (var val in headerValues.ToString().Split(new[] { Options.Delimiter }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            claims.Add(new Claim(headerToClaim.Value, val));
                        }
                    }
                }
            }
    
            // claims identity 's authentication type can not be null https://stackoverflow.com/questions/45261732/user-identity-isauthenticated-always-false-in-net-core-custom-authentication
            var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
            var ticket = new AuthenticationTicket(
                principal,
                Scheme.Name
            );
            return AuthenticateResult.Success(ticket);
        }
    
        return AuthenticateResult.NoResult();
    }
    

    其實就是將請求頭的信息讀取到 Claims,然後返回一個 ClaimsPrincipalAuthenticationTicket,在讀取 header 之前有一個 AuthenticationValidator 是用來驗證請求是不是滿足使用 Header 認證,是一個基於 HttpContext 的斷言委託(Func<HttpContext, Task<bool>>),默認實現是驗證是否有 UserId 對應的 Header,如果要修改可以通過 Startup 來配置

    使用示例

    Startup 配置,和其它的認證方式一樣,Header 認證和 Query 認證也提供了基於 AuthenticationBuilder 的擴展,只需要在 services.AddAuthentication() 后增加 Header 認證的模式即可,示例如下:

    
    services.AddAuthentication(HeaderAuthenticationDefaults.AuthenticationSchema)
        .AddQuery(options =>
        {
            options.UserIdQueryKey = "uid";
        })
        .AddHeader(options =>
        {
            options.UserIdHeaderName = "X-UserId";
            options.UserNameHeaderName = "X-UserName";
            options.UserRolesHeaderName = "X-UserRoles";
        });
    

    默認的 Header 是 UserId/UserName/UserRoles,你也可以自定義為符合自己需要的配置,如果只是想新增一個轉換可以配置 AdditionalHeaderToClaims 增加自己需要的請求頭 => Claims 轉換,AuthenticationValidator 也可以自定義,就是上面提到的會首先會驗證是不是需要讀取 Header,驗證通過之後才會讀取 Header 信息並認證

    測試示例

    有一個接口我需要登錄之後才能訪問,需要用戶信息,類似下面這樣

    [HttpPost]
    [Authorize]
    public async Task<IActionResult> MakeReservation(
        [FromBody] ReservationViewModel model
        )
    {
        // ...
    }
    

    在測試代碼里我配置使用了 Header 認證,在請求的時候直接通過 Header 來控制用戶的信息

    Startup 配置:

    services
        .AddAuthentication(HeaderAuthenticationDefaults.AuthenticationSchema)
        .AddHeader()
        // 使用 Query 認證
        //.AddAuthentication(QueryAuthenticationDefaults.AuthenticationSchema)
        //.AddQuery()
        ;
    

    測試代碼:

    [Fact]
    public async Task MakeReservationWithUserInfo()
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");
        request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId());
        request.Headers.TryAddWithoutValidation("UserName", Environment.UserName);
        request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager");
    
        request.Content = new StringContent($@"{{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");
    
        using var response = await Client.SendAsync(request);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
    
    [Fact]
    public async Task MakeReservationWithInvalidUserInfo()
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");
    
        request.Headers.TryAddWithoutValidation("UserName", Environment.UserName);
    
        request.Content = new StringContent($@"{{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");
    
        using var response = await Client.SendAsync(request);
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }
    
    [Fact]
    public async Task MakeReservationWithoutUserInfo()
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations")
        {
            Content = new StringContent(
                @"{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}",
                Encoding.UTF8, "application/json")
        };
    
        using var response = await Client.SendAsync(request);
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }
    

    More

    QueryString 認證和請求頭認證是類似的,這裏就不再贅述,只是把請求頭上的參數轉移到 QueryString 上了,覺得不夠好用的可以直接 Github 上找源碼修改, 也歡迎 PR,源碼地址: https://github.com/WeihanLi/WeihanLi.Web.Extensions

    Reference

    • https://github.com/WeihanLi/WeihanLi.Web.Extensions
    • https://www.nuget.org/packages/WeihanLi.Web.Extensions
    • https://github.com/OpenReservation/ReservationServer/blob/dev/ActivityReservation.API.Test/TestStartup.cs
    • https://github.com/OpenReservation/ReservationServer/blob/dev/ActivityReservation.API.Test/Controllers/ReservationControllerTest.cs
    • https://www.cnblogs.com/weihanli/p/cutom-authentication-in-aspnetcore.html

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

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

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

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

      說實話,凡是涉及到要證明的東西(理論),一般都不好惹。絕大多數時候,看懂一個東西不難,但證明一個東西則需要點數學功底,進一步,證明一個東西也不是特別難,難的是從零開始發明這個東西的時候,則顯得艱難(因為任何時代,大部分人的研究所得都不過是基於前人的研究成果,前人所做的是開創性的工作,而這往往是最艱難最有價值的,他們被稱為真正的先驅。牛頓也曾說過,他不過是站在巨人的肩上,你,我更是如此)。

      正如陳希孺院士在他的著作《數理統計學簡史》的第四章,最小二乘法中所講:在科研上諸多觀念的革新和突破是有着很多的不易的,或者某個定理在某個時期由有個人點破了,現在的我們看來一切都是理所當然,但在一切沒有發現之前,可能許許多多的頂級學者畢其功於一役,耗盡一生,努力了幾十年最終也是無功而返。

      上一節我學習了SVM的核函數內容,下面繼續對SVM進行證明,具體的參考鏈接都在第一篇文章中,SVM四篇筆記鏈接為:

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

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

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

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

      話休絮煩,要證明一個東西先要弄清楚它的根基在哪裡,即構成它的基礎是哪些理論。OK,以下內容基本上都是上文沒有學習到的一些定理的證明,包括其背後的邏輯,來源背景等東西。

      本文包括內容:

    • 1,線性學習器中,主要闡述感知機算法
    • 2,非線性學習器中,主要闡述 Mercer定理
    • 3,損失函數
    • 4,最小二乘法
    • 5,SMO算法的推導

      同樣,在學習這些之前,我們再複習一下SVM,這裏使用(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的PPT來學習。

    熱身:SVM的整理

      這裏直接借用別人的PPT粘貼在這裏,讓自己再梳理一遍SVM。

    熱身1,Hard Margin SVM

    熱身2,Soft Margin SVM

    熱身3,LS-SVM

    1,線性學習器

    1.1 感知機算法

      這個感知器算法是在1956年提出的,年代久遠,依然影響着當今,當然,可以肯定的是,此算法亦非最優,後續會有更詳盡闡述。不過,有一點,你必須清楚,這個算法是為了干什麼的:不斷的訓練試錯以期尋找一個合適的超平面。

       下面,舉個例子。如下圖所示,憑我們的直覺可以看出,圖中紅線是最優超平面,藍線則是根據感知機算法在不斷的訓練中,最終,若藍線能通過不斷的訓練移動到紅線位置上,則代表訓練成功。

       既然需要通過不斷的訓練以讓藍線最終成為最優分類超平面,那麼到底需要訓練多少次呢?

      Novikoff 定理告訴我們當間隔是正的時候感知機算法會在有限次數的迭代中收斂,也就是說 Novikoff 定理證明了感知機算法的收斂性,即能得到一個界,不至於無窮循環下去。

      Novikoff 定理:如果分類超平面存在,僅需要在序列 S 上迭代幾次,在界為 (2R / γ)2 的錯誤次數下就可以找到分類超平面,算法停止。

      這裏的 R = max1<=i<=l||Xi|| ,γ 為擴充間隔。根據誤分次數公式可知,迭代次數與對應於擴充(包括偏置)權重的訓練集的間隔有關。

      順便再解釋下這個所謂的擴充間隔 γ , γ 即為樣本到分類間隔的距離,即從 γ 引出的最大分類間隔。之前我們推導過的內容,如下:

       在給出幾何間隔的定義之前,咱們首先來看下,如上圖所示,對於一個點 x,令其垂直投影到超平面上的對應的為 x0,由於 w 是垂直於超平面的一個向量, γ 為樣本 x 到分類間隔的距離,我們有:

       同時有一點值得注意:感知機算法雖然可以通過簡單迭代對線性可分數據生成正確分類的超平面,但不是最優效果,那怎麼才能得到最優效果呢,就是前面博文說的尋找最大分類間隔超平面。此外,Novikoff定理的證明請參考:http://www.cs.columbia.edu/~mcollins/courses/6998-2012/notes/perc.converge.pdf

    2,非線性學習器

    2.1 Mercer定理

       Mercer定理:如果函數 K 是 Rn *Rn – R 上的映射(也就是從兩個 n 維向量映射到實數域)。那麼如果K是一個有效核函數(也稱為 Mercer 核函數),那麼當且僅當對於訓練樣例 { x(1), x(2), …  x(m)},其相應的核函數矩陣是對稱半正定的。

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

      要理解這個 Mercer定理,先要了解什麼是半正定矩陣,要了解什麼是半正定矩陣,先得知道什麼是正定矩陣(矩陣理論博大精深,關於矩陣推薦一本書《矩陣分析與應用》),然後關於Mercer定理的證明參考:http://ftp136343.host106.web522.com/a/biancheng/matlab/2013/0120/648.html

      其實,核函數在SVM的分類效果中起到了重要的作用,下面鏈接有個 tutorial可以看看:https://people.eecs.berkeley.edu/~bartlett/courses/281b-sp08/7.pdf

    2.2 正定矩陣

      在百度百科,正定矩陣的定義如下:在線性代數里,正定矩陣(positive definite materix)有時會簡稱為正定陣。在線性代數中,正定矩陣的性質類似於複數中的正實數。與正定矩陣相對應的線性算子是對稱的正定雙線性形式。

      廣義的定義:設 M 為 n 階方陣,如果對任何非零向量 z,都有 zTMz > 0,其中 zT 表示 z 的轉置,就稱 M  為正定矩陣。

      狹義的定義:一個 n 階的實對稱矩陣 M 是正定的條件是當且僅當對所有的非零實係數向量 z,都有 zTMz > 0,其中 zT 表示 z 的轉置。

      正定矩陣的性質:

    • 1,正定矩陣的行列式恆為正
    • 2,實對稱矩陣 A 正定當且僅當 A 與單位矩陣合同
    • 3,若 A 是正定矩陣,則 A 的逆矩陣也是正定矩陣
    • 4,兩個正定矩陣的和是正定矩陣
    • 5,正實數域正定矩陣的乘積是正定矩陣

    3,損失函數

      之前提到過“支持向量機(SVM)是 90 年代中期發展起來的基於統計學習理論的一種機器學習方法,通過尋找結構化風險最小來提高學習機泛化能力,實現經驗風險和置信範圍的最小化,從而達到在統計樣本量較少的情況下,亦能獲得良好統計規律的目的。”但初次看到的人可能不了解什麼是結構化風險,什麼又是經驗風險。要了解這兩個所謂的“風險”,還得從監督學習說起。

      監督學習實際上就是一個經驗風險或者結構風險函數的最優化問題。風險函數度量平均意義下模型預測的好壞,模型每一次預測的好壞用損失函數來度量。它從假設空間 F 中選擇模型 f 作為決策函數,對於給定的輸入 X,由 f(x) 給出相應的輸出 Y,這個輸出的預測值 f(X)與真實值 Y 可能一致也可能不一致,用一個損失函數來度量預測錯誤的程度。損失函數記為 L(Y, f(X))。

      常用損失函數有以下幾種(摘抄於《統計學習方法》):

      (1) 0-1 損失函數

      (2)平方損失函數

       (3)絕對損失函數

       (4)對數損失函數

       給定一個訓練數據集

       模型 f(X) 關於訓練數據集的平均損失稱為經驗風險,如下:

       關於如何選擇模型,監督學習有兩種策略:經驗風險最小化和結構風險最小化。

      經驗風險最小化的策略認為,經驗風險最小的模型就是最優的模型,則按照經驗風險最小化求最優模型就是求解如下的最優化問題:

      當樣本容量很小時,經驗風險最小化的策略容易產生過擬合的現象。結構風險最小化可以防止過擬合。結構風險是在經驗風險的基礎上加上表示模型複雜度的正則化項或懲罰項,結構風險定義如下:

       其中 J(f) 為模型的複雜度,模型 f 越複雜,J(f) 值就越大,模型越簡單,J(f) 值就越小,也就是說J(f)是對複雜模型的乘法。λ>=0 是係數,用以衡量經驗風險和模型複雜度。結構風險最小化的策略認為結構風險最小的模型是最優的模型,所以求最優的模型就是求解下面的最優化問題:

       這樣,簡單學習問題就變成了經驗風險或結構化風險函數的最優化問題。如上式最優化問題的轉換。

       這樣一來,SVM就有第二種理解,即最優化+損失最小。如網友所言:“可以從損失函數和優化算法角度看SVM,Boosting,LR等算法,可能會有不同收穫”。

      關於損失函數:可以看看張潼的這篇《Statistical behavior and consistency of classification methods based on convex risk minimization》。各種算法中常用的損失函數基本都具有fisher一致性,優化這些損失函數得到的分類器可以看作是后驗概率的“代理”。此外,張潼還有另外一篇論文《Statistical analysis of some multi-category large margin classification methods》,在多分類情況下margin loss的分析,這兩篇對Boosting和SVM使用的損失函數分析的很透徹。

      關於統計學習方法的問題,可以參考:https://people.eecs.berkeley.edu/~bartlett/courses/281b-sp08/7.pdf

    4,最小二乘法

    4.1  什麼是最小二乘法?

      下面引用《正態分佈的前世今生》里的內容稍微簡單闡述一下。

      我們口頭經常經常說:一般來說,平均來說。如平均來說,不吸煙的健康優於吸煙者,之所有要加“平均” 二字,是因為凡是皆有例外,總存在某個特別的人他吸煙但由於經常鍛煉所以他的健康狀況可能會優於他身邊不吸煙的盆友。而最小二乘的一個最簡單例子便是算術平均。

      最小二乘法(又稱最小平方法)是一種數學優化技術。它通過最小化誤差的平方和尋找數據的最佳函數匹配。利用最小二乘法可以簡便的求得未知的數據,並使得這些求得的數據與實際數據之間誤差的平方和為最小。用函數表示為:

       使誤差(所謂誤差,當然是觀察值與實際真實值的差量)平方和達到最小以尋求估計值的方法,就叫做最小二乘法,用最小二乘法得到的估計,叫做最小二乘估計。當然,取平方和作為目標函數只是眾多可取的方法之一。

      最小二乘法的一般形式可表示為:

       有效的最小二乘法是勒讓得在1805年發表的,基本思想就是認為測量中有誤差,所以所有方程的累積誤差為:

       我們求解出導致累積誤差最小的參數即可:

       勒讓得在論文中對最小二乘法的優良性做了幾點說明:

    • 最小二乘的誤差平方和最小,並在各個方程的誤差之間建立了一種平衡,從而防止某個極端誤差取得支配地位。
    • 計算中只需要求偏導后求解線性方程組,計算過程明確便捷
    • 最小二乘可以導出算術平均值作為估計

      對於最後一點,從統計學的角度來看是很重要的一個性質。推理如下:假設真值為 Θ ,x1,…..xn 為 n 次測量值,每次測量的誤差為 ei = xi – Θ,按最小二乘法,誤差累積為:

      求解 Θ 使 L(Θ) 達到最小,正好是算術平均 xhat,其公式如下:

       由於算術平均是一個歷經考驗的方法,而以上的推理說明,算術平均是最小二乘的一個特例,所以從另外一個角度說明了最小二乘方法的優良性,使我們對最小二乘法更加有信息。

      最小二乘法發布之後很快得到了大家的認可接受,並迅速的在數據分析實踐中被廣泛使用。不過歷史上又有人把最小二乘法的發明歸功於高斯,這又是怎麼一回事呢?高斯在 1809 年也發表了最小二乘法,並且聲稱自己已經使用了這個方法多年。高斯發明了小行星定位的數學方法,並在數據分析中使用最小二乘方法進行計算,準確的預測了穀神星的位置。

      說了這麼多,貌似與SVM沒啥關係,但是別著急,請繼續聽,本質上說,最小二乘法即是一種參數估計方法,說到參數估計,咱們從一元線性模型說起。

    4.2 最小二乘法的解法

       什麼是一元線性模型呢?我們引用(https://blog.csdn.net/qll125596718/article/details/8248249)的內容,先來梳理一下幾個基本的概念:

    • 監督學習中,如果預測的變量是離散的,我們稱其為分類(如決策樹,支持向量機等),如果預測的變量是連續的,我們稱其為回歸。
    • 回歸分析中,如果只包括一個自變量和一個因變量,且二者的關係可用一條直線近似表示,這種回歸分析稱為一元線性回歸分析。
    • 如果回歸分析中包括兩個或兩個以上的自變量,且因變量和自變量之間是線性關係,則稱為多元線性回歸分析。
    • 對於二維空間線性是一條直線;對於三維空間線性是一個平面,對於多維空間線性是一個超平面。

      對於一元線性回歸模型,假設從總體中獲取了 n 組觀察值(X1, Y1),(X2, Y2),…(Xn, Yn)。對於平面中的這 n 個點,可以使用無數條曲線來擬合。要求樣本回歸函數盡可能好的擬合這組值。綜合起來看,這條直線處於樣本數據的中心位置最合理。

      選擇最佳擬合曲線的標準可以確定為:使總的擬合誤差(即總殘差)達到最小,有以下三個標準可以選擇:

    • 1,用“殘差和最小”確定直線位置是一個途徑。但是很快發現計算“殘差和” 存在相互抵消的問題。
    • 2,用“殘差絕對值和最小”確定直線位置也是一個途徑。但絕對值的計算比較麻煩。
    • 3,最小二乘法的原則是以“殘差平方和最小” 確定直線位置。用最小二乘法除了計算比較方便外,得到的估計量還具有優良特性。這種方法對異常值非常敏感。

      最常用的是普通最小二乘法(Ordinary Least Square, OLS ):所選擇的回歸模型應該使所有觀察值的殘差平方和達到最小,即採用平方損失函數。

       我們定義樣本回歸模型為:

       得到誤差 ei (ei為樣本)為:

       接着,定義平方損失函數 Q:

       則通過Q最小確定這條直線,即確定 β0hat,  β1hat, β0hat,  β1hat為變量,把它們看做是 Q 的函數,就變成了一個求極值的問題,可以通過求導數得到。

      求 Q 對兩個待估參數的偏導數:

       根據數學知識我們知道,函數的極值點為偏導為 0 的點,解得:

       這就是最小二乘法的解法,就是求得平方損失函數的極值點。自此,我們可以看到求解最小二乘法和求解SVM是何等相似,尤其是定義損失函數,而後通過偏導求極值。

    5,SMO算法

      無論Hard Margin 或 Soft Margin SVM,我們均給出了SVM的對偶問題,但並沒有說明對偶問題怎麼求解。由於矩陣Q的規模和樣本數相等,當訓練樣本數很大的時候,這個矩陣的規模很大,求解二次規劃問題的經典算法會遇到性能問題,也就是說同時求解 n 個拉格朗日乘子涉及很多次迭代,計算開銷太大,所以一般採用 Sequential Minimal Optimization(SMO)算法。

      SMO算法的基本思想每次只更新兩個乘子,迭代獲得最終解

      上文中,我們提到了求解對偶問題的序列最小最優化 SMO 算法,但並未提到其具體解法。首先看下最後懸而未決的問題:

       等價於求解:

       1998年,Microsoft Research 的John C. Platt 在論文《Sequential  Minimal Optimization:A Fast Alogrithm for Training Support Vector Machines》中提出針對上述問題的解法:SMO算法,它很快便成為最快的二次規劃優化算法,特別是針對線性SVM和數據稀疏時性能更優。這個算法的思路是每次在優化變量中挑出兩個分量進行優化,而讓其他分量固定,這樣才能保證滿足等式約束條件,這是一種分治法的思想。

      接下來,我們便參考 John C.Platt 的文章(找不到了。。。)來看看 SMO的解法。

    5.1 SMO算法的推導

      首先我們來定義特徵到結果的輸出函數:

       注:這個 u 與我們之前定義的 f(x) 實質上是一樣的。

       接着,重新定義下我們原始的優化問題,權當重新回顧,如下:

       求導得到:

       代入 u 的公式中,可得:

       通過引入拉格朗日乘子轉換為對偶問題后,得:

       注:這裏得到的 min 函數與我們之前的 max 函數實質上也是一樣,因為把符號變下,即由 min 轉換為 max 的問題,且 yi也與之前的 y(i) 等價,yj 亦如此。

      經過加入鬆弛變量后,模型修改為:

       從而最終我們的問題變為:

      下面要解決的問題是:在 αi = { α1, α2, α3,……, αn} 上求上述目標函數的最小值。為了求解這些乘子,每次從中任意抽取兩個乘子 α1 和 α2,然後固定 α1 和 α2 以外的乘子 {α3, α4,….αn},使得目標函數只是關於 α1 和 α2 的函數。這樣,不斷的從一堆乘子中任意抽取兩個求解,不斷地迭代求解子問題,最終達到求解原問題的目的。

      (注意:下面均使用兩個相同的表達式,是參考了兩個方法,並且這兩個方法均易於理解,可以說我先看第一個公式的文章,然後偶爾有次看到第二個公式的文章,發現也很好理解,所以粘貼在這裏,特地說明

      我們首先給出對於這兩個常量的優化問題(稱為子問題)的求解方法。假設選取的兩個分量為 αi, αj,其他分量都固定(即當做常數)。由於:

      所以對偶問題的子問題的目標函數可以表達為:

      (更普及一點,可以寫成下面這樣)

       其中C是一個常數,前面的二次項很容易計算出來,一次項要複雜一些,並且:

      這裏的變量 α* 為變量 a 在上一輪迭代后的值。上面的目標函數是一個兩變量的二次函數,我們可以直接給出最小值的解析解(公式解)。

       為了解決這個子問題,首要問題便是每次如何選取 α1 和 α2。實際上,其中一個乘子是違反 KKT條件最嚴重的,另外一個乘子則由另一個約束條件選取。

      根據KKT條件可以得到目標函數中 αi 取值的意義:

       這裏的 αi 還是拉格朗日乘子:

    • 1,對於第一種情況,表明 αi 是正常分類,在間隔邊界內部(我們知道正確分類的點 yi * f(xi) >= 0)
    • 2,對於第二種情況,表明了 αi 是支持向量,在間隔邊界上
    • 3,對於第三種情況,表明了 αi 是在兩條間隔邊界之間

      而最優解需要滿足KKT 條件,即上述三個條件都得滿足,以下幾種情況出現將會出現不滿足:

    • 1,yiui <=1,但是 αi < C 則不是不滿足的,而原本 αi = C
    • 2,yiui >=1,但是 αi > C 則不是不滿足的,而原本 αi = C
    • 3,yiui =1,但是 αi = 0 或者  αi = C 則不是不滿足的,而原本 0  < αi < C

      也就是說,如果存在不滿足 KKT 條件的 αi ,那麼需要更新這些 αi ,這是第一個約束條件。此外,更新的同時還要受到第二個約束條件的限制,即:

       因此,如果假設選擇的兩個乘子  α1 和 α2 ,他們在更新之前分別是  α1old 和 α2old,更新之後分別是  α1new 和 α2new,那麼更新前後的值需要滿足以下等式才能保證和為 0  的約束:

       其中,ξ 是常數,(上面兩個式子都一樣,只不過第二個更容易理解)。

      兩個因子不好同時求解,所以可選求第二個乘子 α2 的解(α2new),得到 α2 的解(α2new)之後,再利用 α2 的解(α2new)表示 α1 的解(α1new).

      為了求解 α2 的解(α2new),得先確定 α2new 的取值範圍。假設它的上下邊界分別為 H 和 L,那麼有:

       接下來,綜合下面兩個約束條件,求解 α2new 的取值範圍:

       由於 yi,  yj(也可以說為 y1  y2)的取值只能為 +1 或者 -1,那麼當他們異號,也就是當 y1 != y2 時,根據:

       可得:  α1old – α2old  = ξ   (  αi – αj  = ξ),它確定的可行域是一條斜率為1的直線段,因為αi αj 要滿足約束條件

      他們的可行域如下圖所示:

      上面兩條直線分別對應於 y1為 +1 和 -1 的情況。如果是上面那條直線,則 αj 的取值範圍為 [-ξ, C]。如果是下面的那條直線,則為 [0,C-ξ]。

      對於這兩種情況 αj 的下界和上界可以統一寫成如下形式:

      因為   αi – αj  = ξ ,所以又可以寫為:  L =  max (0, -ξ),  H = min(C, C-ξ)

      下邊界是直線和 x 軸交點的 x 坐標以及 0 的較大值;上邊界是直線和的交點的 x 坐標和 C 的較小值。

       再來看第二種情況,如果 yi  yj 同號,即當 y1 = y2 時,同樣根據:

       可得:  α1old + α2old  = ξ (  αi  +  αj  = ξ ),所以有:

     

       根據   αi  +  αj  = ξ  , 上式也可寫為:L =  max (0, ξ – C),  H = min(C, ξ)

      這種情況如下圖所示:

       如此,根據這兩個變量的等式約束條件( y1 和 y2 異號或者同號),可以消掉α2old ,可得出 α2new 的上下界分別為:

       回顧下第二個約束條件:

      下面我們來計算不考慮截斷時的函數極值。為了避免分 -1 和 +1 兩種情況,我們將上面的等式約束兩邊同時乘以 y1(第二種表達是乘以yi),可得:

       其中 α1 可以用 α2 表示,α1 = w – s*α2,從而我們把子問題的目標函數轉換為只含 α2 的問題:

       對 α2 求導(即對自變量求導),並令導數為零,可得:

       由於:

       化簡下:

       然後將:

       代入上式,可得:

       下面令(其中 Ei 表示預測值與真實值之差):

       然後上式兩邊同時除以 η ,得到一個關於單變量 α2 的解:

      在求得  αj 之後,根據等式約束條件我們就可以求得另外一個變量的值:

      目標函數的二階導數為 η,前面假設二階導數 η  > 0,從而保證目標函數是凸函數即開口向上的拋物線,有極小值。如果 η  < 0 或者 η  = 0,該怎麼處理?對於線性核或正定核函數,可以證明矩陣K的任意一個上述子問題對應的二階子矩陣半正定,因此必定有 η  >= 0。無論本次迭代時的初始值是多少,通過上面的子問題求解算法得到是在可行域里的最小值,因此每次求解更新這兩個變量的值之後,都能保證目標函數值小於或者等於初始值,即函數值下降。

       這個解沒有考慮其約束條件 0 <=  α2 <= C,即是未經剪輯時的解。

      然後考慮約束 0 <=  α2 <= C 可得到經過剪輯后的 α2new 的解析解為:

      (如果用αi,αj表示,則我們求的這個二次函數的最終極值點為:)

       求出了 α2new后,便可以求出α1new ,得:

      這三種情況下的二次函數極小值如下圖所示:

      上圖中第一種情況是拋物線的最小值點在 [L, H]中;第二種情況是拋物線的最小值點大於 H,被截斷為H;第三種情況是小於L,被截斷為L。

       那麼如何選擇乘子   α1 和 α2呢?

    1. 對於 α1 ,即第一個乘子,可以通過剛剛說的那3種不滿足 KKT的條件來找
    2. 而對於第二個乘子 α2 可以尋找滿足條件: max |Ei – Ej| 的乘子

      而 b 滿足下述條件:

       下面更新 b:

       且每次更新完兩個乘子的優化后,都需要再重新計算 b,及對應的 Ei值。

      最後更新所有的 αi,y 和 b,這樣模型就出來了,從而即可求出咱們開頭提出的分類函數:

       此外,這裡有一篇類似的文章,大家可以參考下(https://www.cnblogs.com/jerrylead/archive/2011/03/18/1988419.html)。

     5.2  SMO算法的步驟

      綜上,總結下SMO的主要步驟,如下:

       意思是:

    • 1,第一步:選取一對 αi 和 αj,選取方法使用啟髮式方法
    • 2,第二步:固定除αi 和 αj 之外的其他參數,確定W 極值條件下的 αi 和 αj 由 αi 表示

      假定在某一次迭代中,需要更新 x1,x2 對應的拉格朗日乘子 α1,α2,那麼這個小規模的二次規劃問題寫為:

       那麼在每次迭代中,如何更新乘子呢?引用下面地址(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的兩張PPT說明下:

       知道了如何更新乘子,那麼選取哪些乘子進行更新呢?具體有以下兩個步驟:

    • 步驟一:先“掃描”所有乘子,把第一個違反KKT條件的作為更新對象,令為 a1
    • 步驟二:在所有不違反KKT條件的乘子中,選擇使 |E1 – E2|最大的 a2 進行更新,使得能最大限度增大目標函數的值(類似於梯度下降,此外 Ei = ui – yi,而 u = w*x – b ,求出來的 E 代表函數 ui 對輸入 xi 的預測值與真實輸出類標記 yi 之差)

      最後,每次更細完兩個乘子的優化后,都需要再重新計算 b,及對應的 Ei 值。

      綜上,SMO算法的基本思想是把 Vapnik 在 1982年提出的 Chunking方法推到極致,SMO算法每次迭代只選出兩個分量 ai 和 aj 進行調整,其他分量則保持固定不變,在得到 解 ai 和 aj 之後,再用 ai 和 aj 改進其他分量。與通常的分解算法比較,儘管它可能需要更多的迭代次數,但每次迭代的計算量比較小,所以該算法表現出較好的快速收斂性,且不需要存儲核函數,也沒有矩陣運算。

    5.3  SMO算法的實現

      行文至此,我相信,SVM理解到了一定程度后,是的確能在腦海里從頭到尾推導出相關公式的,最初分類函數,最大化分類間隔,max1/||w||,min1/2||w||^2,凸二次規劃,拉格朗日函數,轉化為對偶問題,SMO算法,都為尋找一個最優解,一個最優分類平面。一步步梳理下來,為什麼這樣那樣,太多東西可以追究,最後實現。

      至於上文中將闡述的核函數則是為了更好的處理非線性可分的情況,而鬆弛變量則是為了糾正或約束少量“不安分”或脫離集體不好歸類的因子。

          台灣的林智仁教授寫了一個封裝SVM算法的libsvm庫,大家可以看看,此外這裏還有一份libsvm的註釋文檔。在這篇論文《fast training of support vector machines using sequential minimal optimization》中platt給出了SMO算法的邏輯代碼。

    5.4  SMO算法的優缺點

      優點:

    • 可保證解的全局最優解,不存在陷入局部極小值的問題
    • 分類器複雜度由支撐向量的個數,而非特徵空間(或核函數)的維數決定,因此較少因維數災難發生過擬合線性

      缺點:

    1. 需要求解二次規劃問題,其規模與訓練模式量成正比,因此計算複雜度高,且存儲開銷大,不適用於需進行在線學習/訓練的大規模分類問題  

      這篇文章主要參考:https://mp.weixin.qq.com/s/ZFWJUazMbAqeoSIkXjuG5g

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

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

  • 別再重複造輪子了,幾個值得應用到項目中的 Java 開源庫送給你

    別再重複造輪子了,幾個值得應用到項目中的 Java 開源庫送給你

    我是風箏,公眾號「古時的風箏」。文章會收錄在 JavaNewBee 中,更有 Java 後端知識圖譜,從小白到大牛要走的路都在裏面。公眾號回復『666』獲取高清大圖。

    風箏我作為一個野路子開發者,直到遇見下面的這幾個工具庫,才知道之前重複造了不少輪子,而且輪子還不一定有人家的圓。相信跟我一樣,沒事兒造輪子的人還不在少數,有些人就是對造輪子感興趣,這個咱也無話可說,但是,比如我,我是造輪子之前不知道這世上已經有好用的輪子了,害,無知限制了我的想象力。

    比如我們在拿到一個 List 集合之後,要對這個集合進行判空操作,以前我一直是這樣寫的:

    List<String> list = getList();
    if (list != null && list.size() > 0) {
        //do something
    }
    

    雖然這樣也沒什麼問題,但是,我懶啊,每次敲這麼多代碼,也挺累啊。有同學說,那你包裝成一個方法不就行了,每次調用個方法就 OK 啦。這不,同學,你就在造輪子了,已經有人幫你寫好了這樣類似的一系列方法了。

    來讓我們認識認識這些輪子吧。

    Java 8 Stream

    Stream 不算是工具庫,但是通過 stream 提供的一系列方法,可以實現集合的過濾、分組、集合轉換等諸多操作。

    例如下面的方法,實現列表元素根據某個字段去重的功能。

    List<User> userList = new ArrayList();
    //添加元素
    userList =  userList.stream().filter(distinctByKey(user->user.getUserId())).collect(Collectors.toList());
    
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
          Map<Object,Boolean> seen = new ConcurrentHashMap<>();
          return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }
    

    apache commons

    官方地址:http://commons.apache.org/

    這不是一個庫,而是一系列的工具庫。

    由於包含的庫過多,我就不一一列舉了,可以到官網一探。有集合處理的、數學計算的、IO 操作的等等,其中最常用的莫過於 Apache Commons Lang 和 Apache Commons Collections 這兩個。

    Apache Commons Lang 包括一系列工具類,有字符串相關的、時間處理的、反射的、併發包的等等,Apache Commons Collections 專門用作集合處理。

    下面舉幾個例子說明一下,更詳細的內容可以到官網查看文檔。

    字符串判空操作

    String s = "";
    Boolean isEmpty = StringUtils.isEmpty(s);
    

    獲取類的全名稱

    ClassUtils.getName(ClassUtils.class);
    

    判斷集合是否為空

    Boolean isNotEmpty = CollectionUtils.isNotEmpty(list);
    

    反射獲取某個類的所有 Field

    Field[] fields = FieldUtils.getAllFields(User.class);
    

    Google Guava

    官方地址:https://github.com/google/guava

    和 Apache Commons 有點兒類似,它也是包含了一系列的比如字符串、集合、反射、數學計算等的操作封裝,還可以用作 JVM 緩存。

    舉幾個例子說明:

    New 各種對象

    List<String> list = Lists.newArrayList();
    Set<String> set = Sets.newHashSet();
    Map<String,Object> map = Maps.newConcurrentMap();
    
    // 不可變集合
    ImmutableList<String> immutableList = ImmutableList.of("1", "2", "3");
    

    列錶轉符號分隔的字符串

    List<String> list = new ArrayList<String>();
    list.add("1");
    list.add("2");
    list.add("3");
    String result = Joiner.on("-").join(list);
    
    > 1-2-3
    

    求交集、並集、差集等

    例如下面代碼求 set1 和 set2 的交集

    Set<Integer> set1 = Sets.newHashSet(1, 2, 3, 4, 5, 6);
    Set<Integer> set2 = Sets.newHashSet(1,2,3,4);
           
    Sets.SetView<Integer> intersection = Sets.intersection(set1, set2);
    

    Joda Time

    官方地址:https://www.joda.org/joda-time/

    一個日期、時間處理的工具庫。如果你不是經常做日期處理,那差不多每次需要的時候都需要查詢相關的 API,而有了工具類就不一樣了,只要一個 “.”,你想要的方法就出現了,而 Joda Time 就是一款好用的工具庫。

    比如下面這個方法,計算到新年還有多少天。

    public Days daysToNewYear(LocalDate fromDate) {
      LocalDate newYear = fromDate.plusYears(1).withDayOfYear(1);
      return Days.daysBetween(fromDate, newYear);
    }
    

    OkHttp3

    官方地址:https://square.github.io/okhttp/

    一個 HTTP 客戶端,使用簡單,性能良好,是時候放棄 HttpClient 了。

    一個 get 請求:

    OkHttpClient client = new OkHttpClient();
    
    String run(String url) throws IOException {
      Request request = new Request.Builder()
          .url(url)
          .build();
    
      try (Response response = client.newCall(request).execute()) {
        return response.body().string();
      }
    }
    

    一個 post 請求:

    public static final MediaType JSON
        = MediaType.get("application/json; charset=utf-8");
    
    OkHttpClient client = new OkHttpClient();
    
    String post(String url, String json) throws IOException {
      RequestBody body = RequestBody.create(json, JSON);
      Request request = new Request.Builder()
          .url(url)
          .post(body)
          .build();
      try (Response response = client.newCall(request).execute()) {
        return response.body().string();
      }
    }
    

    Json 系列

    Jackson

    Spring 默認的 Json 序列化工具,其實已經足夠用了。

    Gson

    Google 出品,功能齊全。

    FastJson

    阿里出品,算法良好,性能最優。

    EasyExcel

    官方地址:https://www.yuque.com/easyexcel/doc/easyexcel

    阿里開源的 Excel 操作工具庫,可以看做是 Apache POI 的增強封裝版、優化版。

    如果你的數據量很大,那用 EasyExcel 可以節省內存,提升效率,並且沒有併發風險。

    如果你的 Excel 足夠複雜,那用 EasyExcel 會比你直接用 POI 少些很多代碼。

    比如我實現了下面這個 Excel 動態導出,包括動態表頭、動態合併單元格的功能,只用了很少的代碼,如果是使用 POI 的話,那可能代碼量增加不止一倍啊。

    TinyPinyin

    官方地址:https://github.com/promeG/TinyPinyin

    中文轉拼音,把你輸入的中文轉換成拼音。比如搜索功能要實現這樣的功能,輸入 “fengzheng” 搜索,會匹配到 “風箏”這個詞語,這就需要中文轉拼音了。

    有的同學說了,這不是拼音轉英文嗎?當然不是在輸入“fengzheng”的時候轉換了,而是在包含“風箏”的這條記錄中有一個拼音的額外字段,這樣搜索的時候直接匹配拼音那個字段。

    chinese_name pinyin_name
    風箏 fengzheng

    反射工具庫 – jOOR

    官方地址:https://github.com/jOOQ/jOOR

    它是 JDK 反射包的友好封裝,通過一系列簡單友好的鏈式操作實現反射調用。比如下面這個例子

    public interface StringProxy {
      String substring(int beginIndex);
    }
    
    String substring = on("java.lang.String")
                        .create("Hello World")
                        .as(StringProxy.class)
                        .substring(6);    
    

    簡單的代碼實現 JDK 動態代理,節省了不少代碼。

    MyBatis-Plus

    官方地址:https://mp.baomidou.com/

    只要你的項目中有數據庫訪問,那你肯定用過或者至少聽說過 MyBatis ,但是如果你只用 MyBatis 需要針對每個DAO方法寫對應的 SQL Statement(也就是 mapper.xml 中的代碼塊),當然有一些自動生成的工具,MyBatis 就有它提供的 MyBatis Generator,比如我也稍做加工,做過一個web 版的 MyBatis Generator,開發效率是提高了,但是每個 mapper.xml 文件的代碼量很大,於是 MyBatis-Plus 就要出場了。

    官網上對他的定義如下:

    1. 只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑。
    2. 只需簡單配置,即可快速進行 CRUD 操作,從而節省大量時間。
    3. 熱加載、代碼生成、分頁、性能分析等功能一應俱全。

    最後,在配上 MybatisX IDEA 插件,也是可以了。

    vjtools

    官方地址:https://github.com/vipshop/vjtools

    這是唯品會的開源工具包,這裏主要介紹其中的 vjkit 模塊,是關於文本,集合,併發等基礎功能的核心類庫。這個庫是我很早之前搜索日期操作的時候偶然發現的,我發現裏面日期處理的 API 相當全面而且很實用,還在我的項目中用過一段時間。

    最後

    好用的工具庫可以提高我們的開發效率,而且也是我們學習源碼的好去處,和其他的開源框架(比如 Spring、Dubbo)一樣,看看優秀的代碼是如何實現的。

    如果你還知道什麼好用、強大的開源工具包,歡迎在留言區分享,好東西不能獨享,讓更多的人受益。

    各位大佬,給個推薦,讓我奮發圖強

    我是風箏,公眾號「古時的風箏」。一個兼具深度與廣度的程序員鼓勵師,一個本打算寫詩卻寫起了代碼的田園碼農!你可選擇現在就關注我,或者看看歷史文章再關注也不遲。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

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

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

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

  • 移動UI系列 – 簡單地使用半衰期算法來預測手勢的滑動方向與速度

    移動UI系列 – 簡單地使用半衰期算法來預測手勢的滑動方向與速度

    前言

    有一個問題, 給定一個物體的運動軌跡, 包含時間和坐標的數組, 如何使用這個數據來預測物體未來的運動走勢??

    本文提供了一個很簡單的方式去實現這個算法. 效果夠用, 又簡單, 有一定的準確程度. 

     

    緣由

    以前做過的一些手機應用, 沒有做動畫的, 也沒做手勢. 這個做起來挺麻煩的. 

    最近開始了新的手機項目(微信項目模板) , 基於 Blazor server side , 其組件化的方式可以很方便地把各種常用的通用的東西封裝成 組件/控件 

    於是, 就打算讓這段時間辛苦一些, 一次過把動畫的部分補上, 讓以後的項目更加牛逼, 意思是讓客戶更加樂意地掏錢. 

     

    問題

    手勢的一個問題是力度/速度判斷, 劃得快, 劃得慢, 先快後慢, 先慢後快, 都得有一個合適的算法來得到一個最後速度. 

    這個算法一定要滿足基本的需求後, 要盡量簡單, 要目測, 理論上沒有bug . 

    這個時候, 半衰期算法就派上用場了. (不知道有沒有半衰期算法這玩意, 應該說是使用半衰期的原理)

     

    原理

    這是一個很簡單的權重計算, 有很多個不同時間點的坐標, 每個相鄰時間的兩個數據, 有距離差, 有時間差

    關鍵是解決先慢後快, 先快後慢的數據的處理問題, 那麼我們只需要

    把新的數據, 乘以更大的權重, 把老的數據, 乘以更小的權重, 最後得到這個距離權重的合成值, 就OK了. 

     

    預覽效果:

    注意, 因為gif的幀數不夠, 要更好效果還是複製代碼運行一遍. 

     

     

    測試代碼:

    <! DOCTYPE html > 
    < html > 
    < head > 
        < meta charset ="utf-8"  /> 
        < title > Half Life </ title > 
        < style > 
            html, body, canvas { width : 100% ; height : 100% ; margin : 0 ; padding : 0 ; box-sizing : border-box ; overflow : hidden ; }
        </ style > 
    </ head > 
    < body >
    
        < canvas ></ canvas >
    
        < script type ="text/javascript" >
    
            var CONST_HALF_LIFE_SPAN =  50 ;     //半衰期,設置得越小,就越看淡以前的速度
            var CONST_MAX_SAMPLES =  15 ;         //保留最後15個點的數據減少無意義的運算量,主要看瀏覽器觸發onmousemove的頻率來調整.
    
            var pts = [];
            document.onmousemove =  function (e) {
    
                //儲存數據
                pts.push({ time: Date.now(), x: e.clientX, y: e.clientY });
                 if (pts.length > CONST_MAX_SAMPLES) pts.shift();
    
                //計算
    
                var xs =  0 , ys =  0 ;     //走過的路的長度. 
                var previtem = pts[ 0 ];
                 for ( var index =  1 ; index < pts.length; index ++ ) {
                     var newitem = pts[index ];
                     var t = newitem.time - previtem.time;
    
                    //讓這個數據衰減一次,衰減程度由時間差決定. 
                    var halflifefactor = Math.pow( 0.5 , t / CONST_HALF_LIFE_SPAN);
    
                    //注意,這裏沒有計算速度,而是直接用距離來預測將來要走過的距離.
    
                    //走過的距離每一次都要衰減,每一段的路程都多次乘以各時間差的factor, 
                    //原理是, 0.5 ^ (t1 + t2 + t3...)等於0.5 ^ t1 * 0.5 ^ t2 * 0.5 ^ t3 * ... 
                    xs = xs * halflifefactor + newitem.x - previtem.x;
                    ys = ys * halflifefactor + newitem.y - previtem.y;
    
                    previtem = newitem;
                }
    
                //畫圖
    
                var CONST_EFFECT_FACTOR =  2 ;     //乘以一個因素來畫圖用,這裏數值可以充當'摩擦係數'大小的效果. 
                xs = Math.floor(xs * CONST_EFFECT_FACTOR);
                ys = Math.floor(ys * CONST_EFFECT_FACTOR);
    
                var x0 = e.clientX, y0 = e.clientY;
    
                var canvas = document.querySelector( " canvas " );
                 var ctx = canvas.getContext( " 2d " );
                 var w = canvas.width = canvas.offsetWidth;
                 var h = canvas.height = canvas.offsetHeight;
                ctx.clearRect( 0 , 0 , w, h);
                ctx.lineWidth =  5 ;
                ctx.strokeStyle =  " blue " ;
                ctx.beginPath();
                ctx.moveTo(x0, y0);
                ctx.lineTo(x0 + xs, y0 + ys);
                ctx.closePath();
                ctx.stroke();
    
                console.log(xs, ys)
            }
        </ script >
    
    </ body >
    
    </ html >

     

    簡單講解

    可以看出, 代碼量非常少. 與其說這是一種”算法” , 不然說是一種”思路” 

     

    代碼先是記錄每一點的數據, 然後放進 pts 數組 

    在鼠標移動後, 使用pts 數組, 計算出每一點的x , y 的移動量, 讓每一段的移動量都使用 半衰期 的方式進行調整. 

    最後得出的xs , ys ,是理論上“最近移動的距離.”

    使用這個數值,作為“參考值” ,就可以用於更多的判斷. 

     

    例如我最近做的一個簡單的 swipe 組件, 

    手指滑動了1/4個屏幕,然後再加上理論的參考值,  一共超過了1/2個屏幕,就切換到下一個panel 

    這個參考值很重要. 如果手指只是很慢地滑動, 那麼參考值就很小, 就不應該切換. 如果手指很快地滑動, 那麼就應該切換panel

     

    可改良的方案

    上面方案, 是計算’移動距離’ 的, 它有一個弊端, 無論劃得多快, 移動的總數是有上限的. 

    這段代碼完全可以 除以t , 得到一個 速度值,

    這更加合理, 但是注意速度的合成方式不能普通地累加. 

     

    這就留給有興趣的網友自己嘗試了. 也不難. 畢竟本文傳播的是 半衰期 的思路. 不宜說太細. 

     

     

    擴展思路

    其實這種算法, 一直都有人用. 很奇怪的就是, 沒有看到什麼人專門寫文章介紹? 

    半衰期除了計算運動軌跡, 還可以很好地去統計熱度. 

    例如一篇文章, 一個視頻, 有不同時段的點擊數量. 每一天都不一樣. 

     

    怎樣使用最少的儲存方式, 去儲存一個合理的熱度參考值? 

    可行的方法是,儲存兩個數值 (就夠了) : 

    articleRateValue 用於儲存熱度值

    articleRateTime 用於儲存熱度時間

     

    每一次點擊, 都使用這個公式儲存: 

    articleRateValue = newclickcount + articleRateValue * POW(0.5, (NOW – articleRateTime) / HALF_LIFE_SPAN )

    articleRateTime = NOW 

    排序的時候麻煩點, 要實時的計算 articleRateValue * POW(0.5, (NOW – articleRateTime) / HALF_LIFE_SPAN ) 來得到每個文章的熱度值. 

    (對於排序的問題, 還有兩個變通的做法, 如果以後有時間寫一篇文章細說這個熱度方案, 再詳細解說)

    (最簡單的變通方法是, 每晚找服務器空閑的時候, 把表裡的數值都重新計算一次, 平時排序的時候不計算) 

     

    注意這裡有一個 HALF_LIFE_SPAN 的概念. 如果 HALF_LIFE_SPAN 是一天, 那麼昨天的熱度就佔1/2 權重, 前天的就佔 1/4 權重, 大前天的佔1/8 

    這樣按天算也有個弊端, 我想按週, 按月算那怎麼辦? 

    再存兩組數值 :

    weeklyRateValue

    weeklyRateTime

    monthlyRateValue

    monthlyRateTime 

    如此類推. 

     

    這樣做的最大好處是, 無需詳細地紀錄每一次的點擊數據. 非常節省空間.  

    缺點是, 每一組數據, 只適合一個半衰期時段的數值, 要儲存多個參考值得準備多組數據. 

     

    最後

    時間過得真快. 這段時間挖的坑太多, 在業餘時間內根本沒有時間填坑..

    5月初的時候實現了BlazorCefApp , 到現在開了幾個有意義的坑, Blazor微信項目模板 算是一個. 

    但是由於沒有時間寫博客, 有時只是偶爾把測試的視頻放到B站: https://space.bilibili.com/540073960 

    有興趣用Blazor 來做微信項目或手機網頁項目的, 可以去了解一下. 當項目成熟後, 會發佈到github上. 

    —-

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

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

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

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

    【其他文章推薦】

    ※回頭車貨運收費標準

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

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

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

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

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

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

  • LeetCode 75,90%的人想不出最佳解的簡單題

    LeetCode 75,90%的人想不出最佳解的簡單題

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是LeetCode專題的44篇文章,我們一起來看下LeetCode的75題,顏色排序 Sort Colors。

    這題的官方難度是Medium,通過率是45%,點贊2955,反對209(國際版數據),從這份數據上我們大概能看得出來,這題的難度不大,並且點贊遠遠高於反對,說明題目的質量很不錯。事實上也的確如此,這題足夠簡單也足夠有趣,值得一做。

    題意

    給定一個n個元素的數組,數組當中的每一個元素表示一個顏色。一共有紅白藍三種顏色,分別用0,1和2來表示。要求將這些顏色按照大小進行排序,返回排序之後的結果。

    要求不能調用排序庫sort來解決問題。

    桶排序

    看完題目應該感受到了,如果沒有不能使用sort的限制,這題毫無難度。即使加上了限制難度也不大,我們既然不能調用sort,難道還不能自己寫個sort嗎?Python寫個快排也才幾行而已。

    自己寫sort當然是可以的,顯然這是下下策。因為元素只有3個值,互相之間的大小關係也就只有那麼幾種,排序完全沒有必要。比較容易想到,我們可以統計一下這三個數值出現的次數,幾個0幾個1幾個2,我們再把這些數拼在一起,還原之前的數據不就可以了嗎?

    這樣的確可行,但實際上這也是一種排序方案,叫做基數排序,也稱為桶排序,還有些地方稱為小學生排序(大概是小學生都能懂的意思吧)。基數排序的思想非常簡單,我們創建一個數組,用它的每一位來表示某個元素是否在原數組當中出現過。出現過則+1,沒出現過則一直是0。我們標記完原數組之後,再遍歷一遍標記的數組,由於下標天然有序,所以我們就可以得到排序之後的結果了。

    如果你還有些迷糊也沒有關係,我們把代碼寫出來就明白了,由於這題讓我們提供一個inplace的方法,所以我們在最後的時候需要對nums當中的元素重新賦值。

    class Solution:
        def sortColors(self, nums: List[int]) -> None:
            """  Do not return anything, modify nums in-place instead.  """
            bucket = [0 for _ in range(3)]
            for i in nums:
                bucket[i] += 1
    
            ret = []
            for i in range(3):
                ret += [i] * bucket[i]
    
            nums[:] = ret[:]
    

    和排序相比,我們只是遍歷了兩次數據,第一次是遍歷了原數組獲得了其中0,1和2的數量,第二次是將獲得的數據重新填充回原數組當中。相比於快排或者是其他一些排序算法的耗時,桶排序只遍歷了兩次數組,明顯要快得多。但遺憾的是這並不是最佳的方法,題目當中明確說了,還有隻需要遍歷一次原數組的方法。

    two pointers

    在我們介紹具體的算法之前,我們先來分析一下問題。既然顏色只有三種,那麼當我們排完序之後,整個數組會被分成三個部分,頭部是0,中間是1,尾部是2。

    我們可以用一個區間來收縮1的範圍,假設我們當前區間的首尾元素分別是l和r。當我們讀到0的時候,我們就將它和l交換,然後將l向後移動一位。當我們讀到2的時候,則將它和r進行交換,將r向左移動一位。也就是說我們保證l和r之間的元素只有1。

    我們之前曾經介紹過這種維護一個區間的做法,雖然都是維護了一個區間,但是操作上是有一些區別的。之前介紹的two pointers算法,也叫做尺取法,本質上是通過移動區間的右側邊界來容納新的元素,通過移動左邊界彈出數據的方式來維護區間內所有元素的合法性。而當前的做法中,一開始獲得的就是一個非法的區間,我們通過元素的遍歷以及區間的移動,最後讓它變得合法。兩者的思路上有一些細微的差別,但形式是一樣的,就是通過移動左右兩側的邊界來維護或者是達到合法。

    class Solution:
        def sortColors(self, nums: List[int]) -> None:
            """  Do not return anything, modify nums in-place instead.  """
            l, r = 0, len(nums)-1
            i = 0
            while i < len(nums):
                if i > r:
                    break
       # 如果遇到0,則和左邊交換
                if nums[i] == 0:
                    nums[l], nums[i] = nums[i], nums[l]
                    l += 1
                # 如果遇到2,則和右邊交換
                # 交換之後i需要-1,因為可能換來一個0
                elif nums[i] == 2:
                    nums[r], nums[i] = nums[i], nums[r]
                    r -= 1
                    continue
                i += 1
    

    這種方法我們雖然只遍歷了數組一次,但是由於交換的次數過多,整體運行的速度比上面的方法還要慢。所以遍歷兩次數組並不一定就比只遍歷一次要差,畢竟兩者都是的算法,相差的只是一個常數。遍歷的次數只是構成常數的部分之一。

    除了這個方法之外,我們還有其他維護區間的方法。

    維護區間

    接下來要說的方法非常巧妙,我個人覺得甚至要比上面的方法還有巧妙。

    我們來假想一下這麼一個場景,假設我們不是在原數組上操作數據,而是從其中讀出數據放到新的數組當中。我們先不去想應該怎麼擺放這個問題,我們就來假設我們原數組當中的數據已經放好了若干個,那麼這個時候的新數組會是什麼樣?顯然,應該是排好序的,前面若干個0,中間若干個1,最後若干個2。

    那麼問題來了,假設這個時候我們讀到一個0,那麼應該怎麼放呢?為了簡化敘述我們把它畫成圖:

    我們假設藍色部分是0,綠色部分是1,粉色部分是2。a是0最右側的下標,b是1部分最右側的下標,c是2部分最右側的下標。那麼這個時候,當我們需要放入一個0的時候,應該怎麼辦?

    我們結合圖很容易想明白,我們需要把0放在a+1的位置,那麼我們需要把後面1和2的部分都往右側移動一格,讓出一格位置出來放0。我們移動數組顯然帶來的開銷會過於大,實際上沒有必要移動整個部分,只需要移動頭尾元素即可。比如1的部分左側被0佔掉了一格,那麼為了保持長度不變,右側也需要延伸一格。同理,2的部分右側也需要延伸一格。那麼整個操作用代碼來表示就是:nums[a+1] = 0,nums[b+1] = 1, nums[c+1] = 2。

    假設我們讀入的數是1,那麼我們需要把b延長一個單位,但是這樣帶來的後果是2的部分被侵佔,所以需要將2也延長,補上被1侵佔的一個單位。如果讀到的是2,那麼直接延長2即可,因為2後面沒有其他顏色了。

    假設我們有一個空白的數組,我們可以這麼操作,但其實我們沒有必要專門創建一個數組,我們完全可以用原數組自己填充自己。因為我們從原數組上讀取的數和擺放的數是一樣的,我們直接把数字擺放在原數組的頭部,佔用之前讀取的數即可。

    光說可能還有些迷糊,看下代碼馬上就清楚了:

    class Solution:
        def sortColors(self, nums: List[int]) -> None:
            """  Do not return anything, modify nums in-place instead.  """
            # 記錄0,1和2的末尾位置
            zero, one, two = -1, -1, -1
            n = len(nums)
            for i in range(n):
                # 如果擺放0
                # 那麼1和2都往後平移一位,讓一個位置出來擺放0
                if nums[i] == 0:
                    nums[two+1] = 2
                    nums[one+1] = 1
                    nums[zero+1] = 0
                    zero += 1
                    one += 1
                    two += 1
                elif nums[i] == 1:
                    nums[two+1] = 2
                    nums[one+1] = 1
                    one += 1
                    two += 1
                else:
                    nums[two+1] = 2
                    two += 1
    

    總結

    到這裏,這道題的解法基本上都講完了。

    相信大家也都看出來了,從難度上來說這題真的不難,相信大家都能想出解法來,但是要想到最優解還是有些困難的。一方面需要我們對題目有非常深入的理解,一方面也需要大量的思考。這類題目沒有固定的解法,需要我們根據題目的要求以及實際情況自行設計解法,這也是最考驗思維能力以及算法設計能力的問題,比考察某個算法會不會的問題要有意思得多。

    希望大家都能從這題當中獲得樂趣,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

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

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

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

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

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

  • Kubernetes日誌的6個最佳實踐

    Kubernetes日誌的6個最佳實踐

    本文轉自Rancher Labs

    Kubernetes可以幫助管理部署在Pod中的上百個容器的生命周期。它是高度分佈式的並且各個部分是動態的。一個已經實現的Kubernetes環境通常涉及帶有集群和節點的幾個系統,這些系統託管着幾百個容器,而這些容器不斷地基於工作負載啟動、毀滅。

    當在Kubernetes中處理大量的容器化應用和工作負載時,主動進行監控和調試錯誤十分重要。在容器、節點或集群級別,這些錯誤都能在容器中看到。Kubernetes的日誌機制是一個十分重要的組件,可以用來管理和監控服務以及基礎設施。在Kubernetes中,日誌可以讓你跟蹤錯誤甚至可以調整託管應用程序的容器的性能。

    配置stdout(標準輸出)和stderr(標準錯誤)數據流

    圖片來源:kubernetes.io

    第一步是理解日誌是如何生成的。通過Kubernetes,日誌會被發送到兩個數據流——stdout和stderr。這些數據流將寫入JSON文件,並且此過程由Kubernetes內部處理。你可以配置將哪個日誌發送到哪個數據流中。而一個最佳實踐的建議是將所有應用程序日誌都發送到stdout並且所有錯誤日誌都發送到stderr。

    決定是否使用Sidecar模型

    Kubernetes建議使用sidecar容器來收集日誌。在這一方法中,每個應用程序容器將有一個鄰近的“streaming容器”,該容器將會將所有日誌流傳輸到stdout和stderr。Sidecar模型可以幫助避免在節點級別公開日誌,並且它可以讓你控制容器級別的日誌。

    然而,這一模型的問題是它能夠適用於小容量的日誌記錄,如果面對大規模的日誌記錄,可能會造成大量資源被佔用。因此,你需要為每個正在運行的應用程序容器單獨運行一個日誌容器。在Kubernetes文檔中,將sidecar模型形容為“幾乎沒有很大的開銷”。需要由你決定是否嘗試這一模型並在選擇它之前查看它所消耗的資源類型。

    替代方法是使用日誌代理,該代理在節點級別收集日誌。這樣可以減少開銷,並確保安全地處理日誌。Fluentd已成為大規模聚合Kubernetes日誌的最佳選擇。它充當Kubernetes與你要使用Kubernetes日誌的任意數量的端點之間的橋樑。你也可以選擇像Rancher這樣的Kubernetes管理平台,在應用商店已經集成了Fluentd,無需從頭開始安裝配置。

    確定Fluentd可以更好地匯總和路由日誌數據后,下一步就是確定如何存儲和分析日誌數據。

    選擇日誌分析工具:EFK或專用日誌記錄

    傳統上,對於以本地服務器為中心的系統,應用程序日誌存儲在系統中的日誌文件中。這些文件可以在定義的位置看到,也可以移動到中央服務器。但是對於Kubernetes,所有日誌都發送到磁盤上/var/log的JSON文件中。這種類型的日誌聚合併不安全,因為節點中的Pod可以是臨時的也可以是短暫的。刪除Pod時,日誌文件將丟失。如果你需要嘗試對部分日誌數據丟失進行故障排除時,這可能很難。

    Kubernetes官方推薦使用兩個選項:將所有日誌發送到Elasticsearch,或使用你選擇的第三方日誌記錄工具。同樣,這裏存在一個潛在的選擇。採用Elasticsearch路線意味着你需要購買一個完整的堆棧,即EFK堆棧,包括Elasticsearch、Fluentd和Kibana。每個工具都有其自己的作用。如上所述,Fluentd可以聚合和路由日誌。Elasticsearch是分析原始日誌數據並提供可讀輸出的強大平台。Kibana是一種開源數據可視化工具,可以從你的日誌數據創建漂亮的定製dashboard。這是一個完全開源的堆棧,是使用Kubernetes進行日誌記錄的強大解決方案。

    儘管如此,有些事情仍然需要牢記。Elasticsearch除了由名為Elastic的組織構建和維護,還有龐大的開源社區開發人員為其做貢獻。儘管經過大量的實踐檢驗,它可以快速、強大地處理大規模數據查詢,但在大規模操作時可能會出現一些問題。如果採用的是自我管理(Self-managed)的Elasticsearch,那麼需要有人了解如何構建大規模平台。

    替代方案是使用基於雲的日誌分析工具來存儲和分析Kubernetes日誌。諸如Sumo Logic和Splunk等工具都是很好的例子。其中一些工具利用Fluentd來將日誌路由到他們平台,而另一些可能有它們自己的自定義日誌代理,該代理位於Kubernetes中的節點級別。這些工具的設置十分簡單,並且使用這些工具可以花費最少的時間從零搭建一個可以查看日誌的dashboard。

    使用RBAC控制對日誌的訪問

    在Kubernetes中身份驗證機制使用的是基於角色訪問控制(RBAC)以驗證一個用戶的訪問和系統權限。根據用戶是否具有特權(authorization.k8s.io/decision )並向用戶授予原因(authorization.k8s.io/reason ),對在操作期間生成的審核日誌進行註釋。默認情況下,審核日誌未激活。建議激活它以跟蹤身份驗證問題,並可以使用kubectl進行設置。

    保持日誌格式一致

    Kubernetes日誌由Kubernetes架構中不同的部分生成。這些聚合的日誌應該格式一致,以便諸如Fluentd或FluentBit的日誌聚合工具更易於處理它們。例如,當配置stdout和stderr或使用Fluentd分配標籤和元數據時,需要牢記這一點。這種結構化日誌提供給Elasticsearch之後,可以減少日誌分析期間的延遲。

    在日誌收集守護進程上設置資源限制

    由於生成了大量日誌,因此很難在集群級別上管理日誌。DaemonSet在Kubernetes中的使用方式與Linux類似。它在後台運行以執行特定任務。Fluentd和filebeat是Kubernetes支持的用於日誌收集的兩個守護程序。我們必須為每個守護程序設置資源限制,以便根據可用的系統資源來優化日誌文件的收集。

    結 論

    Kubernetes包含多個層和組件,因此對其進行良好地監控和跟蹤能夠讓我們在面對故障時從容不迫。Kubernetes鼓勵使用無縫集成的外部“Kubernetes原生”工具進行日誌記錄,從而使管理員更輕鬆地獲取日誌。文章中提到的實踐對於擁有一個健壯的日誌記錄體繫結構很重要,該體繫結構在任何情況下都可以正常工作。它們以優化的方式消耗計算資源,並保持Kubernetes環境的安全性和高性能。

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

    【其他文章推薦】

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

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

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

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

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

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

    ※回頭車貨運收費標準

  • 自主SUV陣型中的又一爆款車型 有實力擠進前十

    自主SUV陣型中的又一爆款車型 有實力擠進前十

    空間人性化更貼心雖然軸距只有2630mm,但是柔軟適中的座椅,方正的車身造型使其乘坐空間表現還是很出色的,前後排都能獲得充裕的頭部和腿部空間,多達22處的人性化儲物空間十分便利,開口較大的後備箱內部很規整,可以輕鬆放入大型物品,後排座椅4/6比例摺疊后更為平坦,擴展性夠強,滿足日常需求。

    緊抓住時間的腳步,東風風神推出了旗下全新的緊湊型SUV-東風風神AX5,並於11月29日正式上市,其定位介於風神AX7和風神AX3之間,秉承着“流•動之美”的設計理念打造,擁有着超高的顏值,它的推出無疑將豐富東風風神旗下的產品線布局,一起來看一下這輛潛力股吧!

    外觀

    高顏值高原創

    第一眼看見東風風神AX5,會讓人有眼前一亮的感覺,外觀造型非常忠於原創,幾乎找不到任何借鑒的影子,秉承着“流•動之美”的設計理念,車子總體以簡潔的線條為主,用豐富的曲線勾勒出飽滿的車體。

    前臉造型富有層次感,配以絢麗的遠近光一體帶透鏡大燈,加上造型獨特的保險杠和大尺寸蜂窩狀進氣格柵,讓車頭看上去更加時尚大氣,立式造型獠牙狀日行燈有着較高的辨識度。

    波浪式的前後分段雙腰線絕對是獨樹一幟的設計,讓側面更加年輕動感,車頂行李架、腳踏板、190mm的離地間隙配以18英寸雙色旋風輪轂,兼顧了美觀和實用性。

    廠家為AX5車窗配備了晴雨擋,雖說成本不高,但是勝在實用性強,夠貼心,全系尾燈均採用LED光源,穿透力更好更厚道,後車窗下面小鴨尾的折角豐富了尾部的層次感,零星的鍍鉻裝飾、雙色保險桿的設計,簡單而不失大氣。

    內飾/配置

    科技享受生活

    內飾整體造型是比較家族化的設計,環抱式的座艙風格比較簡潔、幹練,並沒有太多花哨的地方,用料和做工都很夠到,比較上檔次,懸浮式液晶显示屏是一大亮點。

    搭載了WindLink車載智能互聯繫統也是一大利器,具備了車輛關懷、語音識別、手機App、智能導航等多項功能,功能性上可以媲美奔馳寶馬等豪華品牌系統,撥通控制中心后,說出你想去的地方,客服會自動將導航信息發送至車載導航。

    鏈接手機App后,可實現手機控制車窗的開啟與關閉,車門開鎖、空調控制(夏天就一進到車內就可以享受涼爽的空調了)、道路救援、尋車以及車輛信息显示,就如隨身攜帶了專業的車輛檢測電腦,隨時了解愛車哪些地方出了問題和什麼時候該保養了。

    空間

    人性化更貼心

    雖然軸距只有2630mm,但是柔軟適中的座椅,方正的車身造型使其乘坐空間表現還是很出色的,前後排都能獲得充裕的頭部和腿部空間,多達22處的人性化儲物空間十分便利,開口較大的後備箱內部很規整,可以輕鬆放入大型物品,後排座椅4/6比例摺疊后更為平坦,擴展性夠強,滿足日常需求。

    動力

    平順流暢更輕鬆

    全系搭載東風風神自主研發的1.4T全鋁合金小排量增壓發動機,自重更輕更節能,與之匹配的是一款5速手動變速器,或者是德國的格特拉克生產的6速濕式雙離合變速器,保證低油耗的同時有着高效的動力輸出,駕駛過程中動力輸出夠線性,換擋平順,電動助力方向盤操控精準又省力,調教紮實的底盤很舒適。

    競爭力分析

    突圍而出

    那東風風神AX5到底是不是一款誠意之作呢,我們來看一下全系標配的配置,打紅色框的配置如LED日間行車燈、多功能方向盤、手機智能互聯、定速巡航、智能啟停系統、ESp、上坡輔助等可是一般車型作為高中低配的差距配置,而AX5卻作為全系標配來打造的一款車,性價比很高,優勢就很明顯了,也很有誠意。

    我們再拿銷量比較好的傳祺GS4作對比,看一下風神AX5的競爭力如何。

    安全配置上傳祺GS4的頭部氣囊有所欠缺,倒車影像也沒有,只有個陡坡緩降配置。

    多媒體配置和座椅配置上風神AX5是完爆傳祺GS4,非常的豐富。

    最關鍵的高科技配置如發動機啟停、併線輔助、全景攝像頭風神AX5都有配備,簡直就是越級的配置,要知道20萬級別的SUV都未必有這些配置,競爭力很強。

    全文總結:作為老資格的自主品牌,風神AX5全系搭載1.4T渦輪增壓發動機,也是適應時代的要求,加上6速DCT雙離合變速箱,組成雙T鉑金動力組合,動力強,油耗低,高顏值個性鮮明的外觀有着很高的原創度,相比於競爭對手有着足夠優秀的配置來與之抗衡,WindLink智能互聯繫統非常實用,科技化十足,舒適的乘坐空間能滿足家庭日常所需,綜合實力夠強。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

    ※回頭車貨運收費標準

  • 8.38萬元起同級別最精緻的SUV車主怎麼說

    8.38萬元起同級別最精緻的SUV車主怎麼說

    但是就是動力不咋的,不符合主流的1。5T發動機的動力表現。這估計就是在你看得見摸得着的地方做的很好,但是內在就不好說了。不滿意的:點煙器和車載電腦的USB接口在扶手箱下面的一個小盒子里,使用極不方便,儲物空間太小,畢竟這個車子的尺寸比較小。

    其實剛開始寫這篇文章我是拒絕的,因為H2s才剛上市,車主根本不好找,但是很多讀者都想進一步了解一下H2s,想知道車主的使用情況。沒辦法,編者只有硬着頭皮,各種找朋友問同學,費了好大的勁才找到了一些車主,下面一起來看看這些車主對他們H2s的評價。

    長城汽車-哈弗H2s

    指導價:8.38-10.28萬

    哈弗H2s是在11月18日廣州車展上市的,憑藉更亮麗的外觀和更精緻的內飾,再加上新的動力系統贏得了很大的關注度。

    車主:雙截棍

    購買車型:2017款 藍標 1.5T 手動精英型

    裸車購買價:8.78萬

    最滿意的地方:外觀很好看,我買車最看重的是顏值,當時對H2s一見傾心。風琴式的中控台個人認為很不錯,有點天馬星空的感覺。不過就是不好打理。萬年不變的1.5T發動機提速較慢,不過畢竟是帶渦輪的發動機,速度上來使勁踩油門提速還是有一點的。我身高1.75米,體重150斤,膝蓋老是碰到車前面的塑料蓋,所以大長腿的慎買啊!

    不滿意的地方:新車異味好大,異響暫時沒發現,空間比較小,車子沒有胎壓監測。

    車主:馬小虎

    購買車型:2017款 紅標 1.5T 手動精英型

    裸車購買價:8.88萬

    最滿意的地方:當初在紅藍標之間猶豫,後來覺得年輕么。就要選個霸氣的外觀,大嘴的紅標也許更適合自己,雖然紅標比藍標貴了1000塊錢,但是卻多了胎壓監測裝置、后視鏡電動摺疊,這兩個配置還是值1000塊錢的。H2s的整體配置挺高的,內飾做工也比較好。但是就是動力不咋的,不符合主流的1.5T發動機的動力表現。這估計就是在你看得見摸得着的地方做的很好,但是內在就不好說了。

    不滿意的:點煙器和車載電腦的USB接口在扶手箱下面的一個小盒子里,使用極不方便,儲物空間太小,畢竟這個車子的尺寸比較小。車子跑了300公里,油耗9L,新車沒有太大的參考性。

    車主:狗子

    購買車型:2017款 紅標 1.5T 自動精英型

    裸車購買價:9.88萬

    最滿意的地方:價格很實惠,配置比較高,內飾很精緻,做工也很好。其中變速箱為格特拉克的7速濕式雙離合變速箱。十萬的車子用上了濕式雙離合,看來長城這次是真的下本了。以前備受吐槽的哈弗變速箱終於在H2s上得到了改善。這款變速箱反應速度很快,邏輯清晰,平順性很好,底盤調教很紮實,行駛質感很好,但是還是受到那個老舊的發動機的拖累,提速較差。如果換個1.5T發動機,體驗會好很多吧!

    不滿意的:1.5T發動機該換了,座椅靠前了安全帶就跑到了後面,懸架有點硬,兒童座椅的接口處理的很粗糙。油耗目前不到9L。

    總結:H2s用上了7速濕式雙離合變速箱,這一點值得表揚,同時外觀靚麗,配置較高,內飾精緻,行駛質感較好。但是1.5T發動機確實該換了。如果很在乎動力,那麼H2s會讓你失望的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

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

  • 如果你也喜歡自主品牌,那麼這些事情你有必要了解!

    如果你也喜歡自主品牌,那麼這些事情你有必要了解!

    可尼瑪事與願違啊。剛過去俗稱“金九銀十”銷售旺季的10月份,我國的乘用車銷量共計234。41萬輛,而光轎車就賣出了117。05萬輛。哪怕現在SUV市場再怎麼火,當月的轎車銷量依然要比SUV多出27。45萬輛。所以事實就是,轎車依然是中國汽車市場上最好賣的車型。

    憑藉著SUV浪潮的興起,我國的自主品牌頓時間風生水起,長城、吉利、傳祺…迅速取代合資品牌,成為中國車市的第一驅動力。甚至連一些名不見經傳的品牌,譬如力帆、野馬等也都憑藉推出SUV過上了翻身農奴把歌唱的好日子。咋一看,革面全面勝利,形勢一片大好!然鵝,在一片繁榮的背後貌似隱藏着危機四伏的危險。

    這個危機四伏的危險是什麼?

    咱們的自主品牌相當缺乏能夠拿得出手的轎車產品呀!除了帝豪、帝豪GL、艾瑞澤5、逸動之外,大家還能叫得上幾款口碑、銷量都不錯的自主品牌轎車么?沒有了。如果要說不光口碑好、銷量好,而且還有歷史傳承的自主品牌轎車,那就只有累計銷量剛剛突破百萬輛的帝豪了。而合資品牌呢?朗逸、福睿斯、高爾夫、卡羅拉,個個的銷量、口碑都是彪悍級的。

    SUV市場火熱,就拚命地研發SUV、銷售SUV固然是人之常情。畢竟吃飽了,才有力氣幹活,把現錢都掙了是正常思維。但咱們自主品牌偏偏是在這樣的思維下走到了極端。最典型的,莫過於剛剛憑藉哈弗H6單月7萬+銷量刷新中國單一車型月均銷量紀錄的長城。哈弗H6在SUV風頭無兩,但旗下的轎車車型C30,近半年來的月均的銷量卻只有千餘輛的水平。略有些營養不良的感覺。

    這個危機到底有多危險?

    有的人說:大家都買SUV了,誰還會買轎車?!我也是這樣想的啊!可尼瑪事與願違啊!剛過去俗稱“金九銀十”銷售旺季的10月份,我國的乘用車銷量共計234.41萬輛,而光轎車就賣出了117.05萬輛。哪怕現在SUV市場再怎麼火,當月的轎車銷量依然要比SUV多出27.45萬輛。所以事實就是,轎車依然是中國汽車市場上最好賣的車型。相信大夥都已經明白到轎車市場的重要性,但絕大多數的自主品牌卻竟然一致地選擇無視這個市場。這不叫集中力量干大事,這叫把所有雞蛋都放一個籃子里。就差合資品牌給你絆一跤,讓你的籃子里的雞蛋都摔破。

    合資品牌在轎車領域有多強勢?

    要說合資品牌跟自主品牌一樣,把全副身家押寶在SUV車型上那還不至於讓人膽戰心驚,但問題就在於合資品牌在轎車領域的牛逼程度簡直是垄斷式經營(不是人家想垄斷,是自主品牌把這市場拱手相讓)。同樣是“金九銀十”的10月份,轎車銷量的前十位企業分別為:一汽大眾、上汽大眾、上汽通用、東風日產、吉利控股、北京現代、長安福特、東風悅達起亞、廣汽本田和神龍汽車。十個茅坑,竟然有九個被合資品牌佔了。更可怕的是,自主品牌壓根連剩下的肉都吃不上。因為這十家車企的轎車銷量,已經佔到了整個轎車市場的72.53%。估計自主品牌也就能夠吃上兩口青菜吧,清淡點。

    自主品牌你們要當個“德智體美勞全面健康發展”的好學生呀!

    說實話,看着自主品牌由民間小作坊生產、向合資企業討落後的技術,到擁有媲美國際一線車企的現代化生產車間(去看看吉利台州的基地就一目瞭然了)以及發動機、汽車互聯等核心技術方面的創新性領先,我作為一个中國人是相當的自豪,我相信大家也是。但也正因為這一份對自主品牌的熱切期待,才更使得我們去發現自主品牌在轎車市場方面的不足,才憂心好不容易才建立起來的中國汽車工業因為轎車市場的乏力而付諸一旦。所以,請自主品牌們發展的步伐再穩一些、再穩一些。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

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

  • 國產SUV稱霸!最接地氣的銷量榜解析

    國產SUV稱霸!最接地氣的銷量榜解析

    期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。

    哈弗H6的銷量如此火爆,下個月還能賣7萬輛嗎?期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

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