標籤: 銷售文案

  • Typescript的interface、class和abstract class

    interface,class,和abstract class這3個概念,既有聯繫,又有區別,本文嘗試着結合官方文檔來闡述這三者之間的關係。

    1. Declaration Merging

    Declaration Type Namespace Type Value
    Namespace X X
    Class X X
    Enum X X
    Interface X
    Type Alias X
    Function X
    Variable X

    首先我們來講一下上面這張表格,當我們第一列的關鍵字進行聲明時,我們在做什麼。

    namespace job {
       haircut(): void;
    }
    
    class Man{
    	name: string;
    }
    let imgss = new Man();
    
    enum Color {red, blue, yellow}
    
    interface dogfood {
    
      brand: string;
      price: number
    }
    type event = 'mouse' | 'keyboard';
    
    function foo(){}
    
    let a = 2;
    var b = {};
    const c = null;
    	
    

    namespace用來聲明一個命名空間,比較著名的命名空間有lodash,裏面有一堆工具函數,統統放在一個叫_的namespace裏面,同時你也可以let $ = _;所以namespace也聲明了一個值。

    class聲明了一個值,也聲明了一種類型,你可以把Man賦值給一個變量,所以class是一種值,也可以說imgss是一個Man(類型),此時Man承擔了一種類型的角色。

    enum聲明了一個值,也聲明了一種類型。我們說red是一種Color,Color在這裏承擔類型的角色,也可以把Color賦值給一個變量

    interface聲明了一種類型,但是你不能把dogfood賦值給某個變量,否則你會得到一個報錯“dogfood’ only refers to a type, but is being used as a value here`

    其他function,let,var,const都在聲明一個值,你 不能說xxx是一個a,或者xxx是一個foo,不能把值當成類型使用。

    2. interface和class

    我們知道,不算symbol,js中有6種基本類型,number,string,boolean,null, undefined, object。但是只依靠這幾種類型,來描述某個函數需要傳什麼樣的參數,是遠遠不夠的,這也是interface的使命–描述一個值(value)的形狀(type)。

    現在我們來看class,class首先也具有interface的能力,描述一個形狀,或者說代表一種類型。此外class還提供了實現,也就是說可以被實例化;

    所以class可以implements interface:

    interface ManLike {
      speak(): void;
      leg: number;
      hand: number;
    }
    class Human implements ManLike {
      leg: number = 2;
      hand: number = 2;
      speak() {
        console.log('i can speak');
      }
    }
    

    而interface可以extends class,此時的class承擔類型的角色

    interface Chinese extends Human {
      country: string;
    }
    

    那麼interface能不能extends enum或者type alias呢,這兩個兄弟也聲明了type啊,答案是不行的,官方報錯的信息:

    An interface can only extend an object type or intersection of object types with statically known members.
    

    3. class和abstract class

    class和abstract class的區別主要是abstract class不能被實例化:

    abstract Human {
    	name: string;
        abstract lang(): void;
    	toString() {
        	return `<human:${this.name}>`
        }
    }
    new Human // Cannot create an instance of an abstract class.
    

    4. interface和abstract class

    兩者都不能被實例化,但是abstract class 也可以被賦值給變量。
    interface 裏面不能有方法的實現,abstract class 可以提供部分的方法實現,這些方法可以被子類調用。

    參考: https://www.typescriptlang.org/docs/handbook/declaration-merging.html

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 走出舒適圈的信念和勇氣——“Learning by doing!” 我的軟工2020春季教學總結

          看着大家陸續提交個人學期總結,我還不敢去翻看,怕思緒紛飛思維定式,變了自己寫總結的初心和思路。一篇總結,開頭起筆尤是最難,總是想各式各樣的開頭,翻來覆去,寫了念,念了刪,寫了念,念了刪。還是回到初心,回首一下為什麼上這門課。經過一個學期,我的態度和想法有沒有變化,我的收穫和驚喜是什麼。

           為什麼我想上這個班?
           過去沒有一次課,會像這次一樣,具有一點使命感。過去也沒有一次課,全程都是線上進行。也沒有一次課,如果放大忐忑的心情,也會挺忐忑的。
           作為一門臨時穿插安排的選修課,上課前,我聽到的關於系裡同學的水平和积極主動性,負向居多。如果課程要求高和嚴,還可能一翻兩瞪眼,學生投訴或紛紛消極放棄,那麼我大多數的時間精力可能都會用來勸說、解釋和雞湯。如果上的太差,也有可能搞砸後續我想進行的課程教學改革和質量提升。雖然考慮過這些困難,但相比於還沒有為系裡學生完整上過一門課,還沒有近距離感受不同學生的精彩作品和風格,還沒有為後續其他課程的改革做探索和調研,前面那些疑慮早已經拋到九霄雲外,只有許多期待和興奮感。

           你們給我的驚喜:

           你們從個人Github開始,也結束於團隊協作的GitHub,而且不少組做的很不錯,互幫互學,其團隊Github實踐能力,遠勝過以往我的班級;
           你們其中有的組分享的Android或小程序開發的經驗和教程,寫的用心,媲美以往我的班級;
           你們不少組用上了一些自動化測試工具,而且妥帖,用的好,遠勝過以往我的班級;
           更難能可貴的是,你們的不少作品,都離用戶很近,教務課程表、查寢點名、圖書館佔座、校園失物招領……等等,都將可以被用得上,希望你們不斷將作品成型,離之更近。
           不少同學的能力和潛質,都讓我覺得相見恨晚,也相識太短。匆匆你們也就要進入畢業年級。什麼是課程?就是拋卻那些具體的什麼理論和知識,回憶自己能留下的,就是這門課要教給你的。 如果問我,我們短暫的線上相聚,這門課,要交給你們什麼呢?是“Learing by doing”嗎? 這也許是之一。做中學,其實就是做自己所不會的。我們常有的觀念是,我不會,所以我做不了。而“Learning by doing”給我們的勇氣和信念是:做我所不會的,但又是對於自己發展非常重要的,甚至是關鍵路徑上的實現和突破。換句簡單的話來說,這門課想交(交,不是教,我沒有寫錯別字)給你們的是:走出舒適圈。其中的信念和勇氣就是,我可以“Learning by doing!”

          你們給了我更多在專業推行課程實踐改革的信心和動力。信心是:你們有這麼多潛質和能力,怎麼就不能做成項目,達成自己的能力提升呢? 動力是:在你們最好的時節,遇到你們,如果我們不能抓住這樣的機會,把握這樣的機會,做的更好,錯過了,將可惜許多未來的你們。成為系裡不少項目的開發者、成為課程核心助教、成為我們改革的初創者和開拓者,是這門課和這個學期,你們給我的最大收穫。

          就算是自賦的使命感吧,我想,當初來,並不是讓自己來掙課時費的。希望能了解現狀,立足現實,理解問題,分析原因,給出方法,執意推行,做出成果。前四點,無論老師或是學生,多多少少都有感慨和認知。不少知名的企業家,也常常在不同場合,對義務教育、高等教育提出屬於自己的真知灼見,大多數時是痛心疾首,哀其不幸,怒其不爭,覺得應該這樣改那樣改。為什麼問題顯而易見,現狀人人不滿(至少是不滿意),但改變卻牛步而行,各層次教育依然故我。人人都能對教育發表評論,因為重要,教育關係千家萬戶,關係國計民生,關係百年基業;也因為平凡,人人都受過教育,當過學生,也大多教育別人(比如養兒育女)。但這些其實是一種錯覺。企業也很重要啊,但少有人能夠對企業經營管理指摘或評論,很少其他人指導企業經營者應該怎麼做。我想,原因可能在距離。是否直接與學生互動,感受和方法,可能會真的不一樣。教學不是做菜,學生不是食材,互動勝過一切。這也是慕課為什麼知易行難,選課人數多,堅持人數微乎其微的原因。即使堅持了,效果也遠遠不如近距離教學的收穫和感受。評論或建議教育的人的錯覺,就在於希望自己的想法能被教育者一以貫之,卻常常忽略受教育者的感受、過程、反饋和互動。教學相長,如同沒有一次軟件開發項目是可以完全一樣完全照搬的,也沒有一次課程教學是可以完全一樣完全照搬的。更沒有什麼理論或建議,是可以醍醐灌頂,直接有效快速解決教育難題的。孔聖人之所以較其他名家更偉大一些,稱為至聖先師,除了有真知灼見,更在於自己帶領弟子三千,是真正戰鬥在教育一線的教育者。與學生的互動,一問一答,一段經歷,一個故事,乃至於對學生的點評,都成為後人傳頌的至理名言。將距離拉近,師生作為教學相長的團隊一體,才能相互促進,提升和改變。
         《構建之法》的作者,也正是將在清華、北航、微軟亞洲研究院的教學實踐的課程講義,凝結成書,並不斷在教學實踐中推陳出新,改版完善,才讓書具有了強大和茁壯的生命力,書中凝練的教學做法不斷推廣鋪開和可持續化,成為不少其他教學研討場合里都會提到的話題。回到根源,是經過實踐檢驗的“Learning by doing”才具有了這樣的生命力。如何繼往開來,我想,也要回到這樣的初心。不斷實踐,不斷改變。比如,線上教學,是可以發揮這樣的優勢的。作者對某些博客夜以繼日或苦心費思的點評,礙於單篇博客本身的閱讀量,點評被看到率不算高,回復率更讓人着急,也不一定能夠符合這個時代視頻影音影響力的特點。所以,正是線上教學,視頻直播,考慮到傳播力、影響力、互動性,作者可以考慮不斷前進,為不同開展的學校,做一次線上互動的軟工講座,分享軟件工程思維、案例、心得,和互動問答,這些,都將長久影響不少學生,也能逐步累積與一線學生互動的思考、感受和來自廣大讀者受眾的聲音。對作者來說,這一步,其實也是走出舒適圈,“Learning by doing!”

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • EM(最大期望)算法推導、GMM的應用與代碼實現

    EM(最大期望)算法推導、GMM的應用與代碼實現

      EM算法是一種迭代算法,用於含有隱變量的概率模型參數的極大似然估計。

    使用EM算法的原因

      首先舉李航老師《統計學習方法》中的例子來說明為什麼要用EM算法估計含有隱變量的概率模型參數。

      假設有三枚硬幣,分別記作A, B, C。這些硬幣正面出現的概率分別是$\pi,p,q$。進行如下擲硬幣試驗:先擲硬幣A,根據其結果選出硬幣B或C,正面選硬幣B,反面邊硬幣C;然後擲選出的硬幣,擲硬幣的結果出現正面記作1,反面記作0;獨立地重複$n$次試驗,觀測結果為$\{y_1,y_2,…,y_n\}$。問三硬幣出現正面的概率。

      三硬幣模型(也就是第二枚硬幣正反面的概率)可以寫作

    $ \begin{aligned} &P(y|\pi,p,q) \\ =&\sum\limits_z P(y,z|\pi,p,q)\\ =&\sum\limits_z P(y|z,\pi,p,q)P(z|\pi,p,q)\\ =&\pi p^y(1-p)^{1-y}+(1-\pi)q^y(1-q)^{1-y} \end{aligned} $

      其中$z$表示硬幣A的結果,也就是前面說的隱變量。通常我們直接使用極大似然估計,即最大化似然函數

    $ \begin{aligned} &\max\limits_{\pi,p,q}\prod\limits_{i=1}^n P(y_i|\pi,p,q) \\ =&\max\limits_{\pi,p,q}\prod\limits_{i=1}^n[\pi p^{y_i}(1-p)^{1-y_i}+(1-\pi)q^{y_i}(1-q)^{1-y_i}]\\ =&\max\limits_{\pi,p,q}\sum\limits_{i=1}^n\log[\pi p^{y_i}(1-p)^{1-y_i}+(1-\pi)q^{y_i}(1-q)^{1-y_i}]\\ =&\max\limits_{\pi,p,q}L(\pi,p,q) \end{aligned} $

      分別對$\pi,p,q$求偏導並等於0,求解線性方程組來估計這三個參數。但是,由於它是帶有隱變量的,在獲取最終的隨機變量之前有一個分支選擇的過程,導致這個$\log$的內部是加和的形式,計算導數十分困難,而待求解的方程組不是線性方程組。當複雜度一高,解這種方程組幾乎成為不可能的事。以下推導EM算法,它以迭代的方式來求解這些參數,應該也算一種貪心吧。

    算法導出與理解

      對於參數為$\theta$且含有隱變量$Z$的概率模型,進行$n$次抽樣。假設隨機變量$Y$的觀察值為$\mathcal{Y} = \{y_1,y_2,…,y_n\}$,隱變量$Z$的$m$個可能的取值為$\mathcal{Z}=\{z_1,z_2,…,z_m\}$。

      寫出似然函數:

    $ \begin{aligned} L(\theta) &= \sum\limits_{Y\in\mathcal{Y}}\log P(Y|\theta)\\ &=\sum\limits_{Y\in\mathcal{Y}}\log \sum\limits_{Z\in \mathcal{Z}} P(Y,Z|\theta)\\ \end{aligned} $

      EM算法首先初始化參數$\theta = \theta^0$,然後每一步迭代都會使似然函數增大,即$L(\theta^{k+1})\ge L(\theta^k)$。如何做到不斷變大呢?考慮迭代前的似然函數(為了方便不用$\theta^{k+1}$):

    $ \begin{gather} \begin{aligned} L(\theta)=&\sum\limits_{Y\in \mathcal{Y}} \log\sum\limits_{Z\in \mathcal{Z}} P(Y,Z|\theta)\\ =&\sum\limits_{Y\in \mathcal{Y}} \log\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\frac{P(Y,Z|\theta)}{P(Z|Y,\theta^k)}\\ \end{aligned} \label{} \end{gather} $

      至於上式的第二個等式為什麼取出$P(Z|Y,\theta^k)$而不是別的,正向的原因我想不出來,馬後炮原因在後面記錄。

      考慮其中的求和

    $ \sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)=1$

      且由於$\log$函數是凹函數,因此由Jenson不等式得

    $ \begin{gather} \begin{aligned} L(\theta) \ge&\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\log\frac{P(Y,Z|\theta)}{P(Z|Y,\theta^k)}\\ =&B(\theta,\theta^k) \end{aligned}\label{} \end{gather} $

      當$\theta = \theta^k$時,有

    $ \begin{gather} \begin{aligned} L(\theta^k) \ge& B(\theta^k,\theta^k)\\ =&\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\log\frac{P(Y,Z|\theta^k)}{P(Z|Y,\theta^k)}\\ =&\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\log P(Y|\theta^k)\\ =&\sum\limits_{Y\in \mathcal{Y}}\log P(Y|\theta^k)\\ =&L(\theta^k)\\ \end{aligned} \label{} \end{gather} $

      也就是在這時,$(2)$式取等,即$L(\theta^k) = B(\theta^k,\theta^k)$。取

    $ \begin{gather} \theta^*=\text{arg}\max\limits_{\theta}B(\theta,\theta^k)\label{} \end{gather} $

      可得不等式

    $L(\theta^*)\ge B(\theta^*,\theta^k)\ge B(\theta^k,\theta^k) = L(\theta^k)$

      所以,我們只要優化$(4)$式,讓$\theta^{k+1} = \theta^*$,即可保證每次迭代的非遞減勢頭,有$L(\theta^{k+1})\ge L(\theta^k)$。而由於似然函數是概率乘積的對數,一定有$L(\theta) < 0$,所以迭代有上界並且會收斂。以下是《統計學習方法》中EM算法一次迭代的示意圖:

      進一步簡化$(4)$式,去掉優化無關項:

    $ \begin{aligned} \theta^*=&\text{arg}\max\limits_{\theta}B(\theta,\theta^k) \\ =&\text{arg}\max\limits_{\theta}\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\log\frac{P(Y,Z|\theta)}{P(Z|Y,\theta^k)} \\ =&\text{arg}\max\limits_{\theta}\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\log P(Y,Z|\theta) \\ =&\text{arg}\max\limits_{\theta}Q(\theta,\theta^k) \\ \end{aligned} $

      $Q$函數使用導數求極值的方程與沒有隱變量的方程類似,容易求解。

      綜上,EM算法的流程為:

      1. 設置$\theta^0$的初值。EM算法對初值是敏感的,不同初值迭代出來的結果可能不同。

      2. 更新$\theta^k = \text{arg}\max\limits_{\theta}Q(\theta,\theta^{k-1})$。理解上來說,通常將這一步分為計算$Q$與極大化$Q$兩步,即求期望E與求極大M,但在代碼中並不會將它們分出來,因此這裏濃縮為一步。另外,如果這個優化很難計算的話,因為有不等式的保證,直接取$\theta^k$為某個$\hat{\theta}$,只要有$Q(\hat{\theta},\theta^{k-1})\ge Q(\theta^{k-1},\theta^{k-1})$即可。

      3. 比較$\theta^k$與$\theta^{k-1}$的差異,比如求它們的差的二范數,若小於一定閾值就結束迭代,否則重複步驟2。

      下面記錄一下我對$(1)$式取出$P(Z|Y,\theta^k)$而不取別的$P$的理解:

      經過以上的推導,我認為這是為了給不等式取等創造條件。如果不能確定$L(\theta^k)$與$Q(\theta^k,\theta^k)$能否取等,那麼取$Q$的最大值$Q(\theta^*,\theta^k)$時,儘管有$Q(\theta^*,\theta^k)\ge Q(\theta^k,\theta^k)$,但並不能保證$L(\theta^*)\ge L(\theta^k)$,迭代的不減性質就就沒了。

      我這裏暫且把它看做一種巧合,是研究EM算法的大佬,碰巧想用Jenson不等式來迭代而構造出來的一種做法。本人段位還太弱,無法正向理解其中的緣故,只能以這種方式來揣度大佬的思路了。知乎大佬發的EM算法九層理解(點擊鏈接),我當前只能到第3層,有時間一定要拜讀一下深度學習之父的著作。

    高斯混合模型的應用

    迭代式推導

      假設高斯混合模型混合了$m$個高斯分佈,參數為$\theta = (\alpha_1,\theta_1,\alpha_2,\theta_2,…,\alpha_m,\theta_m),\theta_i=(\mu_i,\sigma_i)$則整個概率分佈為:

    $\displaystyle P(y|\theta) = \sum\limits_{i=1}^m\alpha_i \phi(y|\theta_i) =  \sum\limits_{i=1}^m\frac{\alpha_i }{\sqrt{2\pi}\sigma_i}\exp\left(-\frac{(y-\mu_i)^2}{2\sigma_i^2}\right),\;\text{where}\;\sum\limits_{j=1}^m\alpha_j = 1$

      對混合分佈抽樣$n$次得到$\{y_1,…,y_n\}$,則在第$k+1$次迭代,待優化式為:

    $\begin{gather}\begin{aligned} &\max\limits_{\theta}Q(\theta,\theta^k) \\ =&\max\limits_{\theta}\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta^k)\log P(Y,Z|\theta) \\ =&\max\limits_{\theta}\sum\limits_{Y\in \mathcal{Y}}\sum\limits_{Z\in \mathcal{Z}} \frac{P(Z,Y|\theta^k)}{P(Y|\theta^k)}\log P(Y,Z|\theta) \\ =&\max\limits_{\theta}\sum\limits_{i=1}^n\sum\limits_{j=1}^m \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)} \log \left[\alpha_j\phi(y_i|\theta_j)\right] \\ =&\max\limits_{\theta}\sum\limits_{i=1}^n\sum\limits_{j=1}^m \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)} \log \left[ \frac{\alpha_j}{\sqrt{2\pi}\sigma_j}\exp\left(-\frac{(y_i-\mu_j)^2}{2\sigma_j^2}\right) \right]\\ =&\max\limits_{\theta}\sum\limits_{j=1}^m \sum\limits_{i=1}^n \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)} \left[ \log \alpha_j – \log \sigma_j-\frac{(y_i-\mu_j)^2}{2\sigma_j^2} \right]\\  \end{aligned} \label{}\end{gather}$

    計算α

      定義

    $\displaystyle n_j = \sum\limits_{i=1}^n \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)}$

      則對於$\alpha$,優化式為

    $\begin{gather} \begin{aligned} \max\limits_{\alpha}\sum\limits_{j=1}^m n_j \log \alpha_j \end{aligned} \label{}\end{gather}$

      又因為$\sum\limits_{j=1}^m \alpha_j=1$,所以只需優化$m-1$個參數,上式變為:

    $ \max\limits_\alpha \left[ \begin{matrix} n_1&n_2&\cdots &n_{m-1}&n_{m}\\ \end{matrix} \right] \cdot \left[ \begin{matrix} \log\alpha_1\\ \log\alpha_2\\ \vdots\\ \log\alpha_{m-1}\\ \log(1-\alpha_1-\cdots-\alpha_{m-1})\\ \end{matrix} \right] $

      對每個$\alpha_j$求導並等於0,得到線性方程組:

    $\left[\begin{matrix}n_1+n_m&n_1&n_1&\cdots&n_1\\n_2&n_2+n_m&n_2&\cdots&n_2\\n_3&n_3&n_3+n_m&\cdots&n_3\\&&&\vdots&\\n_{m-1}&n_{m-1}&n_{m-1}&\cdots&n_{m-1}+n_m\\\end{matrix}\right]\cdot\left[\begin{matrix}\alpha_1\\\alpha_2\\\alpha_3\\\vdots\\\alpha_{m-1}\\\end{matrix}\right]=\left[\begin{matrix}n_1\\n_2\\n_3\\\vdots\\n_{m-1}\\\end{matrix}\right]$

      求解這個爪形線性方程組,得到

    $\left[\begin{matrix}\sum_{j=1}^mn_j/n_1&0&0&\cdots&0\\-n_2/n_1&1&0&\cdots&0\\-n_3/n_1&0&1&\cdots&0\\&&&\vdots&\\-n_{m-1}/n_1&0&0&\cdots&1\\\end{matrix}\right]\cdot\left[\begin{matrix}\alpha_1\\\alpha_2\\\alpha_3\\\vdots\\\alpha_{m-1}\\\end{matrix}\right]=\left[\begin{matrix}1\\0\\0\\\vdots\\0\\\end{matrix}\right]$

      因為

    $\displaystyle \sum\limits_{j=1}^m n_j =   \sum\limits_{j=1}^m\sum\limits_{i=1}^n \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)}=\sum\limits_{i=1}^n \sum\limits_{j=1}^m \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)} =\sum\limits_{i=1}^n 1 =  n$

      解得

    $\displaystyle\alpha_j = \frac{n_j}{n} = \frac{1}{n}\sum\limits_{i=1}^n \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)}$

    計算σ與μ

      與$\alpha$不同,它的方程組是所有$\alpha_j$之間聯立的;而$\sigma,\mu$的方程組則是$\sigma_j$與$\mu_j$之間聯立的。定義

    $\displaystyle p_{ji} = \frac{\alpha_j^k\phi(y_i|\theta_j^k)} {\sum\limits_{l=1}^m \alpha_l^k\phi(y_i|\theta_l^k)}$

      則對於$\sigma_j,\mu_j$,優化式為(比較$(6),(7)$式的區別)

    $\begin{gather}\displaystyle\min\limits_{\sigma_j,\mu_j}\sum\limits_{i=1}^n p_{ji} \left(\log \sigma_j+\frac{(y_i-\mu_j)^2}{2\sigma_j^2} \right)\label{}\end{gather}$

      對上式求導等於0,解得

    $ \begin{aligned} &\mu_j = \frac{\sum\limits_{i=1}^np_{ji}y_i}{\sum\limits_{i=1}^np_{ji}} = \frac{\sum\limits_{i=1}^np_{ji}y_i}{n_j} = \frac{\sum\limits_{i=1}^np_{ji}y_i}{n\alpha_j}\\ &\sigma^2_j = \frac{\sum\limits_{i=1}^np_{ji}(y_i-\mu_j)^2}{\sum\limits_{i=1}^np_{ji}} = \frac{\sum\limits_{i=1}^np_{ji}(y_i-\mu_j)^2}{n_j} = \frac{\sum\limits_{i=1}^np_{ji}(y_i-\mu_j)^2}{n\alpha_j} \end{aligned} $

    代碼實現

      對於概率密度為$P(x) = −2x+2,x\in (0,1)$的隨機變量,以下代碼實現GMM對這一概率密度的的擬合。共10000個抽樣,GMM混合了100個高斯分佈。

    #%%定義參數、函數、抽樣
    import numpy as np
    import matplotlib.pyplot as plt
    
    dis_num = 100 #用於擬合的分佈數量
    sample_num = 10000 #用於擬合的分佈數量
    alphas = np.random.rand(dis_num) 
    alphas /= np.sum(alphas)  
    mus = np.random.rand(dis_num)
    sigmas = np.random.rand(dis_num)**2#方差,不是標準差
    samples = 1-(1-np.random.rand(sample_num))**0.5 #樣本
    C_pi = (2*np.pi)**0.5
    
    dis_val = np.zeros([sample_num,dis_num])    #每個樣本在每個分佈成員上都有值,形成一個sample_num*dis_num的矩陣
    pij = np.zeros([sample_num,dis_num])        #pij矩陣
    def calc_dis_val(sample,alpha,mu,sigma,c_pi):
        return alpha*np.exp(-(sample[:,np.newaxis]-mu)**2/(2*sigma))/(c_pi*sigma**0.5) 
    def calc_pij(dis_v):  
        return dis_v / dis_v.sum(axis = 1)[:,np.newaxis]      
    #%%優化 
    for i in range(1000):
        print(i)
        dis_val = calc_dis_val(samples,alphas,mus,sigmas,C_pi)
        pij = calc_pij(dis_val)  
        nj = pij.sum(axis = 0)
        alphas_before = alphas
        alphas = nj / sample_num
        mus = (pij*samples[:,np.newaxis]).sum(axis=0)/nj
        sigmas = (pij*(samples[:,np.newaxis] - mus)**2 ).sum(axis=0)/nj
        a = np.linalg.norm(alphas_before - alphas)
        print(a)
        if  a< 0.001:
            break
    
    #%%繪圖 
    plt.rcParams['font.sans-serif']=['SimHei'] #用來正常显示中文標籤
    plt.rcParams['axes.unicode_minus']=False #用來正常显示負號
    def get_dis_val(x,alpha,sigma,mu,c_pi):
        y = np.zeros([len(x)]) 
        for a,s,m in zip(alpha,sigma,mu):   
            y += a*np.exp(-(x-m)**2/(2*s))/(c_pi*s**0.5)   
        return y
    def paint(alpha,sigma,mu,c_pi,samples):
        x = np.linspace(-1,2,500)
        y = get_dis_val(x,alpha,sigma,mu,c_pi) 
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.hist(samples,density = True,label = '抽樣分佈') 
        ax.plot(x,y,label = "擬合的概率密度")
        ax.legend(loc = 'best')
        plt.show()
    paint(alphas,sigmas,mus,C_pi,samples)

      以下是擬合結果圖,有點像是核函數估計,但是完全不同:

    EM算法的推廣

      EM算法的推廣是對EM算法的另一種解釋,最終的結論是一樣的,它可以使我們對EM算法的理解更加深入。它也解釋了我在$(1)$式下方提出的疑問:為什麼取出$P(Z|Y,\theta^k)$而不是別的。

      定義$F$函數,即所謂Free energy自由能(自由能具體是啥先不研究了):

    $ \begin{aligned} F(\tilde{P},\theta) &= E_{\tilde{P}}(\log P(Y,Z|\theta)) + H(\tilde{P})\\ &= \sum\limits_{Z\in \mathcal{Z}} \tilde{P}(Z)\log P(Y,Z|\theta) – \sum\limits_{Z\in \mathcal{Z}} \tilde{P}(Z)\log \tilde{P}(Z)\\ \end{aligned} $

      其中$\tilde{P}$是$Z$的某個概率分佈(不一定是單獨的分佈,可能是在某個條件下的分佈),$E_{\tilde{P}}$表示分佈$\tilde{P}$下的期望,$H$表示信息熵。

      我們計算一下,對於固定的$\theta$,什麼樣的$\tilde{P}$會使$F(\tilde{P},\theta) $最大。也就是找到一個函數$\tilde{P}_{\theta}$,使$F$極大,寫成優化的形式就是(這裡是找函數而不是找參數哦,理解上可能要用到泛函分析的內容):

    $ \begin{aligned} &\max\limits_{\tilde{P}} \sum\limits_{Z\in \mathcal{Z}} \tilde{P}(Z)\log P(Y,Z|\theta) – \sum\limits_{Z\in \mathcal{Z}} \tilde{P}(Z)\log \tilde{P}(Z)\\ &\;\text{s.t.}\; \sum\limits_{Z\in \mathcal{Z}}\tilde{P}(Z) = 1 \end{aligned} $

      拉格朗日函數(拉格朗日對偶性,點擊鏈接)為:

    $ \begin{aligned} L =  \sum\limits_{Z\in \mathcal{Z}} \tilde{P}(Z)\log P(Y,Z|\theta) – \sum\limits_{Z\in \mathcal{Z}} \tilde{P}(Z)\log \tilde{P}(Z)+ \lambda\left(1-\sum\limits_{Z\in \mathcal{Z}}\tilde{P}(Z)\right) \end{aligned} $

      因為每個$\tilde{P}(Z)$之間都是求和,沒有其它其它諸如乘積的操作,所以可以直接令$L$對某個$\tilde{P}(Z)$求導等於$0$來計算極值:

    $ \begin{aligned} \frac{\partial L}{\partial \tilde{P}(Z)} = \log P(Y,Z|\theta) – \log \tilde{P}(Z) -1 -\lambda = 0 \end{aligned} $

      於是可以推出:

    $ \begin{aligned} P(Y,Z|\theta) = e^{1+\lambda}\tilde{P}(Z) \end{aligned} $

      又由約束$\sum\limits_{Z\in \mathcal{Z}}\tilde{P}(Z) = 1$:

    $P(Y|\theta) = e^{1+\lambda}$

      於是得到

    $\begin{gather}\tilde{P}_{\theta}(Z) = P(Z|Y,\theta)\label{}\end{gather}$

      代回$F(\tilde{P},\theta)$,得到

    $ \begin{aligned} F(\tilde{P}_\theta,\theta) &= \sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta)\log P(Y,Z|\theta) – \sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta)\log P(Z|Y,\theta)\\ &= \sum\limits_{Z\in \mathcal{Z}} P(Z|Y,\theta)\log \frac{P(Y,Z|\theta)}{P(Z|Y,\theta)}\\ &= \log P(Y|\theta)\\ \end{aligned} $

      也就是說,對$F$關於$\tilde{P}$進行最大化后,$F$就是待求分佈的對數似然;然後再關於$\theta$最大化,也就算得了最終要估計的參數$\hat{\theta}$。所以,EM算法也可以解釋為$F$的極大-極大算法。優化結果$(8)$式也解釋了我之前在$(1)$式下方的提問。

      那麼,怎麼使用$F$函數進行估計呢?還是要用迭代來算,迭代方式是和前面介紹的一樣的(懶得記錄了,統計學習方法上直接看吧)。實際上,$F$函數的方法只是提供了EM算法的另一種解釋,具體方法上並沒有提升之處。

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • RabbitMQ入門,我是動了心的

    RabbitMQ入門,我是動了心的

    人一輩子最值得炫耀的不應該是你的財富有多少(雖然這話說得有點違心,呵呵),而是你的學習能力。技術更新迭代的速度非常快,那作為程序員,我們就應該擁有一顆擁抱變化的心,积極地跟進。

    在 RabbitMQ 入門之前,我已經入門了 Redis、Elasticsearch 和 MongoDB,這讓我感覺自己富有極客精神,非常良好。

    小夥伴們在繼續閱讀之前,我必須要聲明一點,我對 RabbitMQ 並沒有進行很深入的研究,僅僅是因為要用,就學一下。但作為一名負責任的技術博主,我是動了心的,這篇入門教程,小夥伴們讀完后絕對會感到滿意,忍不住無情地點贊,以及赤裸裸地轉發。

    當然了,小夥伴們遇到文章中有錯誤的地方,不要手下留情,可以組團過來捶我,但要保證一點,不要打臉,我怕毀容。

    01、RabbitMQ 是什麼

    首先,我知道,Rabbit 是一隻兔子(哎呀媽呀,忍不住秀了一波自己的英語功底),可愛的形象已經躍然於我的腦海中了。那 MQ 又是什麼呢?是 Message Queue 的首字母縮寫,也就是說 RabbitMQ 是一款開源的消息隊列系統。

    RabbitMQ 的主要特點在於健壯性好、易於使用、高性能、高併發、集群易擴展,以及強大的開源社區支持。反正就是很牛逼的樣子。

    九年前我做大宗期貨交易的時候,也需要消息推送,那時候還不知道去找這種現成的中間件,就用自定義的隊列實現,結果搞了不少 bug,有些到現在還沒有解決,真的是不堪回首的往事啊。

    下圖是 RabbitMQ 的消息模型圖(來源於網絡,侵刪),小夥伴們來感受下。

    1)P 是 Producer,代表生產者,也就是消息的發送者,可以將消息發送到 X

    2)X 是 Exchange(為啥不是 E,我也很好奇),代表交換機,可以接受生產者發送的消息,並根據路由將消息發送給指定的隊列

    3)Q 是 Queue,也就是隊列,存放交換機發送來的消息

    4)C 是 Consumer,代表消費者,也就是消息的接受者,從隊列中獲取消息

    聽我這樣一解釋,是不是對 RabbitMQ 的印象就很具象化了?小夥伴們,學起來吧!

    02、安裝 Erlang

    咦,怎麼不是安裝 RabbitMQ 啊?先來看看官方的解釋。

    英文看不太懂,沒關係,我來補充兩人話。RabbitMQ 服務器是用 Erlang 語言編寫的,它的安裝包里並沒有集成 Erlang 的環境,因此需要先安裝 Erlang。小夥伴們不要擔心,Erlang 安裝起來沒有任何難度。

    Erlang 下載地址如下:

    https://erlang.org/download/otp_versions_tree.html

    最新的版本是 23.0.1,我選擇的是 64 位的版本,104M 左右。下載完就可以雙擊運行安裝,傻瓜式的。

    需要注意的是,我安裝的過程中,電腦重啟了一次,好像要安裝一個什麼庫,重啟之前忘記保存圖片了(sorry)。重啟后,重新雙擊運行 otp_win64_23.0.1.exe 文件完成 Erlang 安裝。

    03、安裝 RabbitMQ

    Erlang 安裝成功后,就可以安裝 RabbitMQ 了。下載地址如下所示:

    https://www.rabbitmq.com/install-windows.html

    找到下圖中的位置,選擇紅色框中的文件進行下載。

    安裝包只有 16.5M 大小,還是非常輕量級的。下載完后直接雙擊運行 exe 文件就可以傻瓜式地安裝了。

    安裝成功后,就可以將 RabbitMQ 作為 Windows 服務啟動,可以從“開始”菜單管理 RabbitMQ Windows 服務。

    點擊「RabbitMQ Command Prompt (sbin dir)」,進入命令行,輸入 rabbitmqctl.bat status 可確認 RabbitMQ 的啟動狀態。

    可以看到 RabbitMQ 一些狀態信息:

    • 進程 ID,也就是 PID 為 2816
    • 操作系統為 Windows
    • 當前的版本號為 3.8.4
    • Erlang 的配置信息

    命令行界面看起來不夠優雅,因此我們可以輸入以下命令來啟用客戶端管理 UI 插件:

    rabbitmq-plugins enable rabbitmq_management

    看到以下信息就可以確認插件啟用成功了。

    在瀏覽器地址欄輸入 http://localhost:15672/ 可以進入管理端界面,如下圖所示:

    04、在 Java 中使用 RabbitMQ

    有些小夥伴可能會問,“二哥,我是一名 Java 程序員,我該如何在 Java 中使用 RabbitMQ 呢?”這個問題問得好,這就來,這就來。

    第一步,在項目中添加 RabbitMQ 客戶端依賴:

    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.9.0</version>
    </dependency>

    第二步,我們來模擬一個最簡單的場景,一個生產者發送消息到隊列中,一個消費者從隊列中讀取消息並打印。

    官方對 RabbitMQ 有一個很好的解釋,我就“拿來主義”的用一下。在我上高中的年代,同學們之間最流行的交流方式不是 QQ、微信,甚至短信這些,而是書信。因為那時候還沒有智能手機,況且上學期間學校也是命令禁用手機的,所以書信是情感表達的最好方式。好懷念啊。

    假如我向女朋友小巷寫了一封情書,內容如下所示:

    致小巷
    你好呀,小巷。
    你走了以後我每天都感到很悶,就像堂吉訶德一樣,每天想念托波索的達辛妮亞。我現在已經養成了一種習慣,就是每兩三天就要找你說幾句不想對別人說的話。
    。。。。。。
    王二,5月20日

    那這封情書要寄給小巷,我就需要跑到郵局,買上郵票,投遞到郵箱當中。女朋友要收到這封情書,就需要郵遞員盡心儘力,不要弄丟了。

    RabbitMQ 就像郵局一樣,只不過處理的不是郵件,而是消息。之前解釋過了,P 就是生產者,C 就是消費者。

    新建生產者類 Wanger :

    public class Wanger {
        private final static String QUEUE_NAME = "love";
        public static void main(String[] args) throws IOException, TimeoutException {
            ConnectionFactory factory = new ConnectionFactory();

            try (Connection connection = factory.newConnection();
                 Channel channel = connection.createChannel()) {
                channel.queueDeclare(QUEUE_NAME, falsefalsefalsenull);
                String message = "小巷,我喜歡你。";
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
                System.out.println(" [王二] 發送 '" + message + "'");
            }
        }
    }

    1)QUEUE_NAME 為隊列名,也就是說,生產者發送的消息會放到 love 隊列中。

    2)通過以下方式創建服務器連接:

    ConnectionFactory factory = new ConnectionFactory();
    try (Connection connection = factory.newConnection();
                 Channel channel = connection.createChannel()) {

    ConnectionFactory 是一個非常方便的工廠類,可用來創建到 RabbitMQ 的默認連接(主機名為“localhost”)。然後,創建一個通道( Channel)來發送消息。

    Connection 和 Channel 類都實現了 Closeable 接口,所以可以使用 try-with-resource 語句,如果有小夥伴對 try-with-resource 語句不太熟悉,可以查看我之前寫的我去文章。

    3)在發送消息的時候,必須設置隊列名稱,通過 queueDeclare() 方法設置。

    4)basicPublish() 方法用於發布消息:

    • 第一個參數為交換機(exchange),當前場景不需要,因此設置為空字符串;
    • 第二個參數為路由關鍵字(routingKey),暫時使用隊列名填充;
    • 第三個參數為消息的其他參數(BasicProperties),暫時不配置;
    • 第四個參數為消息的主體,這裏為 UTF-8 格式的字節數組,可以有效地杜絕中文亂碼。

    生產者類有了,接下來新建消費者類 XiaoXiang:

    public class XiaoXiang {
        private final static String QUEUE_NAME = "love";
        public static void main(String[] args) throws IOException, TimeoutException {
            ConnectionFactory factory = new ConnectionFactory();
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();

            channel.queueDeclare(QUEUE_NAME, falsefalsefalsenull);
            System.out.println("等待接收消息");

            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                System.out.println(" [小巷] 接收到的消息 '" + message + "'");
            };
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
        }
    }

    1)創建通道的代碼和生產者差不多,只不過沒有使用 try-with-resource 語句來自動關閉連接和通道,因為我們希望消費者能夠一直保持連接,直到我們強制關閉它。

    2)在接收消息的時候,必須設置隊列名稱,通過 queueDeclare() 方法設置。

    3)由於 RabbitMQ 將會通過異步的方式向我們推送消息,因此我們需要提供了一個回調,該回調將對消息進行緩衝,直到我們做好準備接收它們為止。

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [小巷] 接收到的消息 '" + message + "'");
    };

    basicConsume() 方法用於接收消息:

    • 第一個參數為隊列名(queue),和生產者相匹配(love)。

    • 第二個參數為 autoAck,如果為 true 的話,表明服務器要一次性交付消息。怎麼理解這個概念呢?小夥伴們可以在運行消費者類 XiaoXiang 類之前,先多次運行生產者類 Wanger,向隊列中發送多個消息,等到消費者類啟動后,你就會看到多條消息一次性接收到了,就像下面這樣。

    等待接收消息
     [小巷] 接收到的消息 '小巷,我喜歡你。'
     [小巷] 接收到的消息 '小巷,我喜歡你。'
     [小巷] 接收到的消息 '小巷,我喜歡你。'
    • 第三個參數為 DeliverCallback,也就是消息的回調函數。

    • 第四個參數為 CancelCallback,我暫時沒搞清楚是幹嘛的。

    在消息發送的過程中,也可以使用 RabbitMQ 的管理面板查看到消息的走勢圖,如下所示。

    05、鳴謝

    好了,我親愛的小夥伴們,以上就是本文的全部內容了,是不是看完后很想實操一把 RabbitMQ,趕快行動吧!如果你在學習的過程中遇到了問題,歡迎隨時和我交流,雖然我也是個菜鳥,但我有熱情啊。

    另外,如果你想寫入門級別的文章,這篇就是最好的範例。

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

    【其他文章推薦】

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

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

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

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

  • Tensorflow2 自定義數據集圖片完成圖片分類任務

    Tensorflow2 自定義數據集圖片完成圖片分類任務

    對於自定義數據集的圖片任務,通用流程一般分為以下幾個步驟:

    • Load data

    • Train-Val-Test

    • Build model

    • Transfer Learning

    其中大部分精力會花在數據的準備和預處理上,本文用一種較為通用的數據處理手段,並通過手動構建,簡單模型, 層數較深的resnet網絡,和基於VGG19的遷移學習。

    你可以通過這個例子,快速搭建網絡,並訓練處一個較為滿意的結果。

    1. Load data

    數據集來自Pokemon的5分類數據, 每一種的圖片數量為200多張,是一個較小型的數據集。

    官方項目鏈接:

    Keras and Convolutional Neural Networks (CNNs)

    1.1 數據集介紹

    Pokemon文件夾中包含5個子文件,其中每個子文件夾名為對應的類別名。文件夾中包含有png, jpeg的圖片文件。

    1.2 解題思路

    • 由於文件夾中沒有劃分,訓練集和測試集,所以需要構建一個csv文件讀取所有的文件,及其類別

    • shuffle數據集以後,劃分Train_val_test

    • 對數據進行預處理, 數據標準化,數據增強, 可視化處理

    “””python
    # 創建数字編碼錶

      import os
      import glob
      import random
      import csv
      import tensorflow as tf
      from tensorflow import keras
      import matplotlib.pyplot as plt
      import time
      
      
      def load_csv(root, filename, name2label):
          """
          將分散在各文件夾中的圖片, 轉換為圖片和label對應的一個dataset文件, 格式為csv
          :param root: 文件路徑(每個子文件夾中的文件屬於一類)
          :param filename: 文件名
          :param name2label: 類名編碼錶  {'類名1':0, '類名2':1..}
          :return: images, labels
          """
          # 判斷是否csv文件已經生成
          if not os.path.exists(os.path.join(root, filename)):  # join-將路徑與文件名何為一個路徑並返回(沒有會生成新路徑)
              images = []  # 存的是文件路徑
              for name in name2label.keys():
                  # pokemon\pikachu\00000001.png
                  # glob.glob() 利用通配符檢索路徑內的文件,類似於正則表達式
                  images += glob.glob(os.path.join(root, name, '*'))  # png, jpg, jpeg
              print(name2label)
              print(len(images), images)
      
              random.shuffle(images)
      
              with open(os.path.join(root, filename), 'w', newline='') as f:
                  writer = csv.writer(f)
                  for img in images:
                      name = img.split(os.sep)[1]  # os.sep 表示分隔符 window-'\\' , linux-'/'
                      label = name2label[name]  # 0, 1, 2..
                      # 'pokemon\\bulbasaur\\00000000.png', 0
                      writer.writerow([img, label])  # 如果不設定newline='', 2個數據會分為2行寫
                  print('write into csv file:', filename)
      
          # 讀取現有文件
          images, labels = [], []
          with open(os.path.join(root, filename)) as f:
              reader = csv.reader(f)
              for row in reader:
                  # 'pokemon\\bulbasaur\\00000000.png', 0
                  img, label = row
                  label = int(label)  # str-> int
                  images.append(img)
                  labels.append(label)
      
          assert len(images) == len(labels)
      
          return images, labels
      
      
      def load_pokemon(root, mode='train'):
          """
          # 創建数字編碼錶
          :param root: root path
          :param mode: train, valid, test
          :return: images, labels, name2label
          """
      
          name2label = {}  # {'bulbasaur': 0, 'charmander': 1, 'mewtwo': 2, 'pikachu': 3, 'squirtle': 4}
          for name in sorted(os.listdir(os.path.join(root))):
              # sorted() 是為了復現結果的一致性
              # os.listdir - 返迴路徑下的所有文件(文件夾,文件)列表
              if not os.path.isdir(os.path.join(root, name)):  # 是否為文件夾且是否存在
                  continue
              # 每個類別編碼一個数字
              name2label[name] = len(name2label)
      
          # 讀取label
          images, labels = load_csv(root, 'images.csv', name2label)
      
          # 劃分數據集 [6:2:2]
          if mode == 'train':
              images = images[:int(0.6 * len(images))]
              labels = labels[:int(0.6 * len(labels))]  # len(images) == len(labels)
      
          elif mode == 'valid':
              images = images[int(0.6 * len(images)):int(0.8 * len(images))]
              labels = labels[int(0.6 * len(labels)):int(0.8 * len(labels))]
      
          else:
              images = images[int(0.8 * len(images)):]
              labels = labels[int(0.8 * len(labels)):]
      
          return images, labels, name2label
      
      
      # imagenet 數據集均值, 方差
      img_mean = tf.constant([0.485, 0.456, 0.406])  # 3 channel
      img_std = tf.constant([0.229, 0.224, 0.225])
      
      def normalization(x, mean=img_mean, std=img_std):
          # [224, 224, 3]
          x = (x - mean) / std
          return x
      
      def denormalization(x, mean=img_mean, std=img_std):
          x = x * std + mean
          return x
      
      
      def preprocess(x, y):
          # x: path, y: label
          x = tf.io.read_file(x)  # 2進制
          # x = tf.image.decode_image(x)
          x = tf.image.decode_jpeg(x, channels=3)  # RGBA
          x = tf.image.resize(x, [244, 244])
      
          # data augmentation
          # x = tf.image.random_flip_up_down(x)
          x = tf.image.random_flip_left_right(x)
          x = tf.image.random_crop(x, [224, 224, 3])  # 模型縮減比例不宜過大,否則會增大訓練難度
      
          x = tf.cast(x, dtype=tf.float32) / 255. # unit8 -> float32
          # U[0,1] -> N(0,1)  # 提高訓練準確度
          x = normalization(x)
      
          y = tf.convert_to_tensor(y)
      
          return x, y
      
      def main():
          images, labels, name2label = load_pokemon('pokemon', 'train')
          print('images:', len(images), images)
          print('labels:', len(labels), labels)
          # print(name2label)
      
          # .map()函數要位於.batch()之前, 否則 x=tf.io.read_file()會一次讀取一個batch的圖片,從而報錯
          db = tf.data.Dataset.from_tensor_slices((images, labels)).map(preprocess).shuffle(1000).batch(32)
      
          # tf.summary()
          # 提供了各類方法(支持各種多種格式)用於保存訓練過程中產生的數據(比如loss_value、accuracy、整個variable),
          # 這些數據以日誌文件的形式保存到指定的文件夾中。
      
          # 數據可視化:而tensorboard可以將tf.summary()
          # 記錄下來的日誌可視化,根據記錄的數據格式,生成折線圖、統計直方圖、圖片列表等多種圖。
          # tf.summary()
          # 通過遞增的方式更新日誌,這讓我們可以邊訓練邊使用tensorboard讀取日誌進行可視化,從而實時監控訓練過程。
          writer = tf.summary.create_file_writer('logs')
          for step, (x, y) in enumerate(db):
              with writer.as_default():
                  x = denormalization(x)
                  tf.summary.image('img', x, step=step, max_outputs=9)  # STEP:默認選項,指的是橫軸显示的是訓練迭代次數
      
                  time.sleep(5)
      
      
      
      if __name__ == '__main__':
          main()
    

    “””

    2. 構建模型進行訓練

    2.1 自定義小型網絡

    由於數據集數量較少,大型網絡的訓練中往往會出現過擬合情況,這裏就定義了一個2層卷積的小型網絡。
    引入early_stopping回調函數后,3個epoch沒有較大變化的情況下,模型訓練的準確率為0.8547

    “””
    # 1. 自定義小型網絡
    model = keras.Sequential([
    layers.Conv2D(16, 5, 3),
    layers.MaxPool2D(3, 3),
    layers.ReLU(),
    layers.Conv2D(64, 5, 3),
    layers.MaxPool2D(2, 2),
    layers.ReLU(),
    layers.Flatten(),
    layers.Dense(64),
    layers.ReLU(),
    layers.Dense(5)
    ])

      model.build(input_shape=(None, 224, 224, 3))  
      model.summary()
      
      early_stopping = EarlyStopping(
          monitor='val_loss',
          patience=3,
          min_delta=0.001
      )
      
      
      model.compile(optimizer=optimizers.Adam(lr=1e-3),
                     loss=losses.CategoricalCrossentropy(from_logits=True),
                     metrics=['accuracy'])
      model.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
                 callbacks=[early_stopping])
      model.evaluate(db_test)
    

    “””

    2.2 自定義的Resnet網絡

    resnet 網絡對於層次較深的網絡的可訓練型提升很大,主要是通過一個identity layer保證了深層次網絡的訓練效果不會弱於淺層網絡。
    其他文章中有詳細介紹resnet的搭建,這裏就不做贅述, 這裏構建了一個resnet18網絡, 準確率0.7607。

    “””
    import os

      import numpy as np
      import tensorflow as tf
      from tensorflow import keras
      from tensorflow.keras import layers
      
      tf.random.set_seed(22)
      np.random.seed(22)
      os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
      assert tf.__version__.startswith('2.')
      
      
      class ResnetBlock(keras.Model):
      
          def __init__(self, channels, strides=1):
              super(ResnetBlock, self).__init__()
      
              self.channels = channels
              self.strides = strides
      
              self.conv1 = layers.Conv2D(channels, 3, strides=strides,
                                         padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
              self.bn1 = keras.layers.BatchNormalization()
              self.conv2 = layers.Conv2D(channels, 3, strides=1,
                                         padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
              self.bn2 = keras.layers.BatchNormalization()
      
              if strides != 1:
                  self.down_conv = layers.Conv2D(channels, 1, strides=strides, padding='valid')
                  self.down_bn = tf.keras.layers.BatchNormalization()
      
          def call(self, inputs, training=None):
              residual = inputs
      
              x = self.conv1(inputs)
              x = tf.nn.relu(x)
              x = self.bn1(x, training=training)
              x = self.conv2(x)
              x = tf.nn.relu(x)
              x = self.bn2(x, training=training)
      
              # 殘差連接
              if self.strides != 1:
                  residual = self.down_conv(inputs)
                  residual = tf.nn.relu(residual)
                  residual = self.down_bn(residual, training=training)
      
              x = x + residual
              x = tf.nn.relu(x)
              return x
      
      
      class ResNet(keras.Model):
      
          def __init__(self, num_classes, initial_filters=16, **kwargs):
              super(ResNet, self).__init__(**kwargs)
      
              self.stem = layers.Conv2D(initial_filters, 3, strides=3, padding='valid')
      
              self.blocks = keras.models.Sequential([
                  ResnetBlock(initial_filters * 2, strides=3),
                  ResnetBlock(initial_filters * 2, strides=1),
                  # layers.Dropout(rate=0.5),
      
                  ResnetBlock(initial_filters * 4, strides=3),
                  ResnetBlock(initial_filters * 4, strides=1),
      
                  ResnetBlock(initial_filters * 8, strides=2),
                  ResnetBlock(initial_filters * 8, strides=1),
      
                  ResnetBlock(initial_filters * 16, strides=2),
                  ResnetBlock(initial_filters * 16, strides=1),
              ])
      
              self.final_bn = layers.BatchNormalization()
              self.avg_pool = layers.GlobalMaxPool2D()
              self.fc = layers.Dense(num_classes)
      
          def call(self, inputs, training=None):
              # print('x:',inputs.shape)
              out = self.stem(inputs, training = training)
              out = tf.nn.relu(out)
      
              # print('stem:',out.shape)
      
              out = self.blocks(out, training=training)
              # print('res:',out.shape)
      
              out = self.final_bn(out, training=training)
              # out = tf.nn.relu(out)
      
              out = self.avg_pool(out)
      
              # print('avg_pool:',out.shape)
              out = self.fc(out)
      
              # print('out:',out.shape)
      
              return out
      
      
      def main():
          num_classes = 5
      
          resnet18 = ResNet(5)
          resnet18.build(input_shape=(None, 224, 224, 3))
          resnet18.summary()
      
      
      if __name__ == '__main__':
          main()
    

    “””

    “””
    # 2.resnet18訓練, 圖片數量較小,訓練結果不是特別好
    # resnet = ResNet(5) # 0.7607
    # resnet.build(input_shape=(None, 224, 224, 3))
    # resnet.summary()
    “””

    2.3 VGG19遷移學習

    遷移學習利用了數據集之間的相似性,對於數據集數量較少的時候,訓練效果會遠優於其他。
    在訓練過程中,使用include_top=False, 去掉最後分類的基層Dense, 重新構建並訓練就可以了。準確率0.9316

    “””
    # 3. VGG19遷移學習,遷移學習利用數據集之間的相似性, 結果遠好於其他2種
    # 為了方便,這裏仍然使用resnet命名
    net = tf.keras.applications.VGG19(weights=’imagenet’, include_top=False, pooling=’max’ )
    net.trainable = False
    resnet = keras.Sequential([
    net,
    layers.Dense(5)
    ])
    resnet.build(input_shape=(None, 224, 224, 3)) # 0.9316
    resnet.summary()

      early_stopping = EarlyStopping(
          monitor='val_loss',
          patience=3,
          min_delta=0.001
      )
      
      
      resnet.compile(optimizer=optimizers.Adam(lr=1e-3),
                     loss=losses.CategoricalCrossentropy(from_logits=True),
                     metrics=['accuracy'])
      resnet.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
                 callbacks=[early_stopping])
      resnet.evaluate(db_test)
    

    “””

    附錄:

    train_scratch.py 代碼

    “””

    import os
    
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    
    import tensorflow as tf
    import numpy as np
    from tensorflow import keras
    from tensorflow.keras import layers, optimizers, losses
    from tensorflow.keras.callbacks import EarlyStopping
    
    tf.random.set_seed(22)
    np.random.seed(22)
    assert tf.__version__.startswith('2.')
    
    # 設置GPU顯存按需分配
    # gpus = tf.config.experimental.list_physical_devices('GPU')
    # if gpus:
    #     try:
    #         # Currently, memory growth needs to be the same across GPUs
    #         for gpu in gpus:
    #             tf.config.experimental.set_memory_growth(gpu, True)
    #         logical_gpus = tf.config.experimental.list_logical_devices('GPU')
    #         print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")
    #     except RuntimeError as e:
    #         # Memory growth must be set before GPUs have been initialized
    #         print(e)
    
    from pokemon import load_pokemon, normalization
    from resnet import ResNet
    
    
    def preprocess(x, y):
        # x: 圖片的路徑,y:圖片的数字編碼
        x = tf.io.read_file(x)
        x = tf.image.decode_jpeg(x, channels=3)  # RGBA
        # 圖片縮放
        # x = tf.image.resize(x, [244, 244])
        # 圖片旋轉
        # x = tf.image.rot90(x,2)
        # 隨機水平翻轉
        x = tf.image.random_flip_left_right(x)
        # 隨機豎直翻轉
        # x = tf.image.random_flip_up_down(x)
    
        # 圖片先縮放到稍大尺寸
        x = tf.image.resize(x, [244, 244])
        # 再隨機裁剪到合適尺寸
        x = tf.image.random_crop(x, [224, 224, 3])
    
        # x: [0,255]=> -1~1
        x = tf.cast(x, dtype=tf.float32) / 255.
        x = normalization(x)
        y = tf.convert_to_tensor(y)
        y = tf.one_hot(y, depth=5)
    
        return x, y
    
    
    batchsz = 32
    
    # create train db
    images1, labels1, table = load_pokemon('pokemon', 'train')
    db_train = tf.data.Dataset.from_tensor_slices((images1, labels1))
    db_train = db_train.shuffle(1000).map(preprocess).batch(batchsz)
    # create validation db
    images2, labels2, table = load_pokemon('pokemon', 'valid')
    db_val = tf.data.Dataset.from_tensor_slices((images2, labels2))
    db_val = db_val.map(preprocess).batch(batchsz)
    # create test db
    images3, labels3, table = load_pokemon('pokemon', mode='test')
    db_test = tf.data.Dataset.from_tensor_slices((images3, labels3))
    db_test = db_test.map(preprocess).batch(batchsz)
    
    
    # 1. 自定義小型網絡
    # resnet = keras.Sequential([
    #     layers.Conv2D(16, 5, 3),
    #     layers.MaxPool2D(3, 3),
    #     layers.ReLU(),
    #     layers.Conv2D(64, 5, 3),
    #     layers.MaxPool2D(2, 2),
    #     layers.ReLU(),
    #     layers.Flatten(),
    #     layers.Dense(64),
    #     layers.ReLU(),
    #     layers.Dense(5)
    # ])  # 0.8547
    
    
    # 2.resnet18訓練, 圖片數量較小,訓練結果不是特別好
    # resnet = ResNet(5)  # 0.7607
    # resnet.build(input_shape=(None, 224, 224, 3))
    # resnet.summary()
    
    
    # 3. VGG19遷移學習,遷移學習利用數據集之間的相似性, 結果遠好於其他2種
    net = tf.keras.applications.VGG19(weights='imagenet', include_top=False, pooling='max' )
    net.trainable = False
    resnet = keras.Sequential([
        net,
        layers.Dense(5)
    ])
    resnet.build(input_shape=(None, 224, 224, 3))   # 0.9316
    resnet.summary()
    
    early_stopping = EarlyStopping(
        monitor='val_loss',
        patience=3,
        min_delta=0.001
    )
    
    
    resnet.compile(optimizer=optimizers.Adam(lr=1e-3),
                   loss=losses.CategoricalCrossentropy(from_logits=True),
                   metrics=['accuracy'])
    resnet.fit(db_train, validation_data=db_val, validation_freq=1, epochs=100,
               callbacks=[early_stopping])
    resnet.evaluate(db_test)
    

    “””

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 【離散優化】覆蓋問題

    覆蓋問題

    我們知道設施選址問題有兩類基礎問題,分別是中值問題和覆蓋問題,下面要介紹的就是覆蓋問題。

    什麼是覆蓋問題?

    覆蓋問題是以所期望的服務範圍滿足大多數或者所有用戶需求為前提,確定設施的位置。覆蓋模型的思想是離服務設施較近的用戶越多,則服務越好。

    覆蓋問題的分類

    覆蓋問題主要分為兩類:

    • 集合覆蓋問題(Location Set Covering Problem,LSCP)
    • 最大覆蓋問題(Maximum Covering Location Problem,MCLP)

    覆蓋模型常用於哪些場景?

    由於 P-中值模型常以總距離或者總時間作為測度指標,使得其並不適用於一些特殊的場景,比如消防中心和救護車等應急設施的區位選址問題,而覆蓋模型則比較適用於這些場景。

    如何定義覆蓋?

    如果需求點 \(i\) 到備選設施點 \(j\) 的距離或者時間小於臨界值 \(D_c\),那麼稱需求點 \(i\) 被候選設施點 \(j\) 覆蓋。、

    下面介紹兩類覆蓋問題的數學模型表達

    集合覆蓋問題 (Location Set Covering Problem,LSCP)

    目標函數:

    \[\min \sum_{j \in J}x_j \]

    約束:

    \[\sum_{j \in N_i} x_j \geqslant 1 \quad \forall i \in I \tag{c-1} \]

    \[x_j \in \{0, 1\} \quad \forall j \in J \tag{c-2} \]

    其中,

    • \(N_i = \{j:a_{ij}=1\}\) 是覆蓋需求點 \(i\) 的候選設施點的集合,變量 \(a_{ij}\) 用來判斷需求點 \(i\) 是否被候選設施點 \(j\) 覆蓋,若是,則 \(a_{ij}=1\),否則 \(a_{ij}=0\)
    • 目標函數旨在尋求設施總量最小
    • 約束 \(c-1\) 保證每個需求點至少被一個設施服務範圍所覆蓋
    • 約束 \(c-2\) 是決策變量的取值範圍

    在某些場景中,集合覆蓋問題有以下兩個缺點:

    • 為了保證所有需求點均被覆蓋而引入過多的設施,以至於超出預算
    • 模型無法區分需求點的需求強度

    現實生活中,常常由於預算或者資源的約束,有限的設施不能保證空間中所有需求點都被覆蓋,此時,優先考慮需求強度大的需求點是十分必要的,下面要介紹的最大覆蓋模型就是為了解決這個問題而被提出。

    最大覆蓋問題(Maximum Covering Location Problem,MCLP)

    目標函數

    \[\max \sum_{i \in N_i} \omega_iz_i \]

    約束

    \[z_i \leqslant \sum_{j \in N_i}x_j \quad \forall i \in I \tag{c-1} \]

    \[\sum_{j\in J}x_j = p \tag{c-2} \]

    \[x_j \in \{0,1\} \quad \forall j \in J \tag{c-3} \]

    \[z_i = \{0, 1\} \quad \forall i \in I \tag{c-4} \]

    其中,

    • \(\omega_i\) 為需求點 \(i\) 的需求強度

    • \(z_i\) 用來判斷需求點 \(i\) 是否被覆蓋,若覆蓋,則為 1,否則為 0

    • 目標函數旨在尋求有限設施(\(p\) 個)覆蓋的需求最多

    • 約束 \(c-1\) 要求除非在備選設施點中已定位一個設施可以覆蓋需求點 \(i\),否則需求點 \(i\) 將不被記作被覆蓋

    • 約束 \(c-2\) 限制設施的總數為 \(p\)

    • 約束 \(c-3, c-4\) 是決策變量的取值範圍

    更多種類的選址問題

    以上介紹的覆蓋問題的基礎模型框架,然而具體問題一般是較為複雜的設施選址問題,這就需要我們對基礎模型設置不同的條件從而進行擴展,比如:

    • 用於環境污染防治的鄰避型設施選址問題
    • 用於不同服務等級的層次型設置選址問題
    • 用於商業競爭的競爭型設施選址問題
    • 選址問題也開始考慮動態、不確定性等因素

    總結

    總結以上兩類問題,我們可以發現最大覆蓋模型和集合覆蓋模型的主要區別在於對設施數量和需求強度的關注不同,前者一般適用於建設經費充足或者設施成本相同的情況,後者則適用於有設施成本約束的選址決策。

    參考文獻

    本文內容主要從論文《設施選址問題中的基礎模型與求解方法比較》總結而來。

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • Python 為什麼不支持 i++ 自增語法,不提供 ++ 操作符?

    Python 為什麼不支持 i++ 自增語法,不提供 ++ 操作符?

    在 C/C++/Java 等等語言中,整型變量的自增或自減操作是標配,它們又可分為前綴操作(++i 和 –i)與後綴操作(i++ 和 i–),彼此存在着一些細微差別,各有不同的用途。

    這些語言的使用者在接觸 Python 時,可能會疑惑為什麼它不提供 ++ 或 — 的操作呢?在我前不久發的《Python的十萬個為什麼?》里,就有不少同學在調查問卷中表示了對此話題感興趣。

    Python 中雖然可能出現 ++i 這種前綴形式的寫法,但是它並沒有“++”自增操作符,此處只是兩個“+”(正數符號)的疊加而已,至於後綴形式的“++”,則完全不支持(SyntaxError: invalid syntax)。

    本期“Python為什麼 ”欄目,我們將會從兩個主要的角度來回答:Python 為什麼不支持 i++ 自增語法? (PS:此處自增指代“自增和自減”,下同)

    首先,Python 當然可以實現自增效果,即寫成i += 1 或者 i = i + 1 ,這在其它語言中也是通用的。

    雖然 Python 在底層用了不同的魔術方法(__add__()__iadd__() )來完成計算,但表面上的效果完全相同。

    所以,我們的問題可以轉化成:為什麼上面的兩種寫法會勝過 i++,成為 Python 的最終選擇呢?

    1、Python 的整數是不可變類型

    當我們定義i = 1000 時,不同語言會作出不同的處理:

    • C 之類的語言(寫法 int i = 1000)會申請一塊內存空間,並給它“綁定”一個固定的名稱 i,同時寫入一個可變的值 1000。在這裏,i 的地址以及類型是固定的,而值是可變的(在一定的表示範圍內)
    • Python(寫法i = 1000)也會申請一塊內存空間,但是它會“綁定”給数字 1000,即這個 1000 的地址以及類型是固定的(immutable),至於 i,只是一個名稱標籤貼在 1000 上,自身沒有固定的地址和類型

    所以當我們令 i “自增”時(i = i + 1),它們的處理是不同的:

    • C 之類的語言先找到 i 的地址上存的數值,然後令它加 1,操作后新的數值就取代了舊的數值
    • Python 的操作過程是把 i 指向的数字加 1,然後把結果綁定到新申請的一塊內存空間,再把名稱標籤 i “貼”到新的数字上。新舊数字可以同時存在,不是取代關係

    打一個不太恰當的比方:C 中的 i 就像一個宿主,数字 1000 寄生在它上面;而 Python 中的 1000 像個宿主,名稱 i 寄生在它上面。C 中的 i 與 Python 中的 1000,它們則寄生在底層的內存空間上……

    還可以這樣理解:C 中的變量 i 是一等公民,数字 1000 是它的一個可變的屬性;Python 中的数字 1000 是一等公民,名稱 i 是它的一個可變的屬性。

    有了以上的鋪墊,我們再來看看 i++,不難發現:

    • C 之類的語言,i++ 可以表示 i 的数字屬性的增加,它不會開闢新的內存空間,也不會產生新的一等公民
    • Python 之類的語言,i++ 如果是對其名稱屬性的操作,那樣就沒有意義了(總不能按字母表順序,把 i 變成 j 吧);如果理解成對数字本體的操作,那麼情況就會變得複雜:它會產生新的一等公民 1001,因此需要給它分配一個內存地址,此時若佔用 1000 的地址,則涉及舊對象的回收,那原有對於 1000 的引用關係都會受到影響,所以只能開闢新的內存空間給 1001

    Python 若支持 i++,其操作過程要比 C 的 i++ 複雜,而且其含義也不再是“令数字增加1”(自增),而是“創建一個新的数字”(新增), 這樣的話,“自增操作符”(increment operator)就名不副實了。

    Python 在理論上可以實現 i++ 操作,但它就必須重新定義“自增操作符”,還會令有其它語言經驗的人產生誤解,不如就讓大家直接寫成i += 1 或者 i = i + 1 好了。

    2、Python 有可迭代對象

    C/C++ 等語言設計出 i++,最主要的目的是為了方便使用三段式的 for 結構:

    for(int i = 0; i < 100; i++){
        // 執行 xxx
    }
    

    這種程序關心的是数字本身的自增過程,数字做加法與程序體的執行相關聯。

    Python 中沒有這種 for 結構的寫法,它提供了更為優雅的方式:

    for i in range(100):
        # 執行 xxx
    
    my_list = ["你好", "我是Python貓", "歡迎關注"]
    for info in my_list:
        print(info)
    

    這裏體現了不同的思維方式,它關心的是在一個數值範圍內的迭代遍歷,並不關心也不需要人為對数字做加法。

    Python 中的可迭代對象/迭代器/生成器提供了非常良好的迭代/遍歷用法,能夠做到對 i++ 的完全替代。

    例如,上例中實現了對列表內值的遍歷,Python 還可以用 enumerate() 實現對下標與具體值的同時遍歷:

    my_list = ["你好", "我是Python貓", "歡迎關注"]
    for i, info in enumerate(my_list):
        print(i, info)
    
    # 打印結果:
    0 你好
    1 我是Python貓
    2 歡迎關注
    

    再例如對於字典的遍歷,Python 提供了 keys()、values()、items() 等遍歷方法,非常好用:

    my_dict = {'a': '1', 'b': '2', 'c': '3'}
    for key in my_dict.keys():
        print(key)
    
    for key, value in my_dict.items():
        print(key, value)
    

    有了這樣的利器,哪裡還有 i++ 的用武之地呢?

    不僅如此,Python 中基本上很少使用i += 1 或者 i = i + 1 ,由於存在着隨處可見的可迭代對象,開發者們很容易實現對一個數值區間的操作,也就很少有對於某個數值作累加的訴求了。

    所以,回到我們開頭的問題,其實這兩種“自增”寫法並沒有勝出 i++ 多少,只因為它們是通用型操作,又不需要引入新的操作符,所以 Python 才延續了一種基礎性的支持。真正的贏家其實是各種各樣的可迭代對象!

    稍微小結下:Python 不支持自增操作符,一方面是因為它的整數是不可變類型的一等公民,自增操作(++)若要支持,則會帶來歧義;另一方面主要因為它有更合適的實現,即可迭代對象,對遍歷操作有很好的支持。

    如果你覺得本文分析得不錯,那你應該會喜歡這些文章:

    1、Python為什麼使用縮進來劃分代碼塊?

    2、Python 的縮進是不是反人類的設計?

    3、Python 為什麼不用分號作語句終止符?

    4、Python 為什麼沒有 main 函數?為什麼我不推薦寫 main 函數?

    5、Python 為什麼推薦蛇形命名法?

    寫在最後:本文屬於“Python為什麼”系列(Python貓出品),該系列主要關注 Python 的語法、設計和發展等話題,以一個個“為什麼”式的問題為切入點,試着展現 Python 的迷人魅力。部分話題會推出視頻版,請在 B 站收看,觀看地址:視頻地址

    公眾號【Python貓】, 本號連載優質的系列文章,有Python為什麼系列、喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦。

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

    【其他文章推薦】

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

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

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

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

  • JAVA設計模式 2【創建型】原型模式的理解與使用、理解淺克隆和深克隆

    JAVA設計模式 2【創建型】原型模式的理解與使用、理解淺克隆和深克隆

    在本節中,我們將學習和使用原型模式;這一節學習的原型模式也是創建型 模式的其中之一。再次複習一下:創建型 模式就是描述如何去更好的創建一個對象。

    我們都知道,在JAVA 語言中。使用new 關鍵字創建一個新對象。將新的對象放到堆內存 裏面。當然,這個內存肯定是有大小限制的,況且,JAVA 不同於C語言等。 有內存管理機制,就是我們常說的垃圾回收器GC,才可以保證內存不被溢出。

    說這些其實就是為了表示:為啥要用單例模式,能節省內存的時候,能用一個對象解決重複的事情,絕對不會創建多個。

    概述

    原型模式描述的如何快速創建重複的對象,並且減少new 關鍵字的使用。

    • 抽象原型類
    • 具體原型類
    • 訪問類

    容我來一個一個解釋:

    抽象原型類 也就是我們具體要實現的某個類,這個類在JAVA 裏面是有具體的接口的,其實是一個空接口,Cloneable

     * @author  unascribed
     * @see     java.lang.CloneNotSupportedException
     * @see     java.lang.Object#clone()
     * @since   JDK1.0
     */
    public interface Cloneable {
    }
    

    我們會發現,這個類沒有任何的方法,怎麼來實現它,不要慌。先接着走。

    具體原型類 也就是我們具體要克隆 的對象。比如我們重複的要創建100個學生Student 對象,那麼具體的學生對象就是具體原型類

    public class Student implements Cloneable {
    
        private int id;
    
        private String name;
    
        private int sex;
    }
    

    訪問類 我就不必多說了

    淺克隆和深克隆

    原型模式其實也分淺克隆和深克隆。如何理解這兩個概念呢?

    淺克隆

    protected native Object clone() throws CloneNotSupportedException;
    

    淺克隆,只需要具體原型類 實現Cloneable 接口,並且重寫父類Object類的clone() 方法,即可實現對象的淺克隆。

    Student student1 = new Student(1, "李四");
    Student student2 = student1.clone();
    
    System.out.println(student1);
    System.out.println(student2);
    
    System.out.println(student1 == student2);
    ---------------------
    學號:1,姓名:李四
    學號:1,姓名:李四
    false
    
    • 通過執行clone() 方法即可創建一個相同的,具有同樣屬性的對象。
    • 並且是新的對象,內存地址有所不同。

    我們來看看,對於引用類型的變量,淺克隆是否可以進行克隆;

    Teacher teacher = new Teacher(1, "張老師");
    
    Student student1 = new Student(1, "李四", teacher);
    Student student2 = student1.clone();
    
    System.out.println(student1);
    System.out.println(student2);
    
    System.out.println(student1 == student2);
    ------------
    學號:1,姓名:李四,老師=Teacher@1b6d3586
    學號:1,姓名:李四,老師=Teacher@1b6d3586
    false
    

    我們發現,引用類型並沒有被克隆,也就是說:

    特點

    • 淺克隆對於基本類型,可以進行完全的克隆,並且克隆的對象是一個新的對象
    • 但是對象裏面的引用,是無法被克隆的。

    深克隆(序列化)

    何謂序列化?

    我們創建的都是保存在內存裏面的,只要被虛擬機GC進行回收,那麼這個對象的任何屬性都是消失,我們能不能找一個方法,將內存中這種對象的屬性以及對象的狀態通過某種東西保存下來,比如保存到數據庫,下次從數據庫將這個對象還原到內存裏面。 這就是序列化。

    • 序列化 內存對象->序列字符
    • 反序列化 序列字符->內存對象

    請參考: https://baike.baidu.com/item/序列化/2890184

    JAVA 序列化

     * @see java.io.Externalizable
     * @since   JDK1.1
     */
    public interface Serializable {
    }
    

    JAVA 提供了一個空接口,其實這個接口和上面的Cloneable 一樣,都是一個空接口,其實這個空接口就是作為一種標識 你的對象實現了這個接口,JAVA 認為你的這個就可以被序列化 ,就是這麼簡單。

    Teacher teacher = new Teacher(1, "張老師");
    
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream stream = new ObjectOutputStream(outputStream);
    
    stream.writeObject(teacher);
    System.out.println(Arrays.toString(outputStream.toByteArray()));
    ----------
    [-84, -19, 0, 5, 115, 114, 0, 7, 84, 101, 97,。。。。。。
    

    通過將對象序列化、其實也就是將內存中的對象轉化為二進制 字節數組

    反序列化

    Teacher teacher = new Teacher(1, "張老師");
    System.out.println(teacher);
    
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream stream = new ObjectOutputStream(outputStream);
    
    stream.writeObject(teacher);
    System.out.println(Arrays.toString(outputStream.toByteArray()));
    
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
    ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
    
    Teacher teacher1 = (Teacher) inputStream.readObject();
    System.out.println(teacher1);
    ---------------
    id=1,name=張老師
    [-84, -19, 0, 5, 115, xxxxx,-127, -27, -72, -120]
    id=1,name=張老師
    

    通過序列化和反序列化,即可對象的深克隆

    小結

    這一節,在講述 原型模式的同時,將原有實現原型模式的clone() 淺克隆,延伸到深克隆這一概念。其實JAVA 的原型模式,實現起來較為簡單。但還是要按需要實現,Object 類提供的 clone 淺克隆 是沒辦法克隆對象的引用類型的。需要克隆引用類型,還是需要序列化 深克隆

    參考

    http://c.biancheng.net/view/1343.html
    https://www.liaoxuefeng.com/wiki/1252599548343744/1298366845681698

    代碼示例

    https://gitee.com/mrc1999/Dev-Examples

    歡迎關注

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • Java多線程之內存模型

    Java多線程之內存模型

    目錄

    • 多線程需要解決的問題
      • 線程之間的通信
      • 線程之間的同步
    • Java內存模型
      • 內存間的交互操作
      • 指令屏障
      • happens-before規則
    • 指令重排序
      • 從源程序到字節指令的重排序
      • as-if-serial語義
      • 程序順序規則
    • 順序一致性模型
      • 順序一致性模型特性
      • 順序一致性模型特性
      • 當程序未正確同步會發生什麼
    • 參考資料

    多線程需要解決的問題

    在多線程編程中,線程之間如何通信和同步是一個必須解決的問題:

    線程之間的通信:

    線程之間有兩種通信的方式:消息傳遞和共享內存

    • 共享內存:線程之間共享程序的公共狀態,通過讀——寫修改公共狀態進行隱式通信。如上面代碼中的numLock可以被理解為公共狀態
    • 消息傳遞:線程之間沒有公共狀態,必須通過發送消息來進行显示通信
      在java中,線程是通過共享內存來完成線程之間的通信

    線程之間的同步:

    同步指程序中永固空值不同線程間的操作發生的相對順序的機制

    • 共享內存:同步是显示進行的,程序員需要指定某個方法或者某段代碼需要在線程之間互斥執行。如上面代碼中的Lock加鎖和解鎖之間的代碼塊,或者被synchronized包圍的代碼塊
    • 消息傳遞:同步是隱式執行的,因為消息的發送必然發生在消息的接收之前,例如使用Objetc#notify(),喚醒的線程接收信號一定在發送喚醒信號的發送之後。

    Java內存模型

    在java中,所有的實例域,靜態域、數組都被存儲在堆空間當中,堆內存在線程之間共享。

    所有的局部變量,方法定義參數和異常處理器參數不會被線程共享,在每個線程棧中獨享,他們不會存在可見性和線程安全問題。

    從Java線程模型(JMM)的角度來看,線程之間的共享變量存儲在主內存當中,每個線程擁有一個私有的本地內存(工作內存)本地內存存儲了該線程讀——寫共享的變量的副本。
    JMM只是一個抽象的概念,在現實中並不存在,其中所有的存儲區域都在堆內存當中。JMM的模型圖如下圖所示:

    而java線程對於共享變量的操作都是對於本地內存(工作內存)中的副本的操作,並沒有對共享內存中原始的共享變量進行操作;

    以線程1和線程2為例,假設線程1修改了共享變量,那麼他們之間需要通信就需要兩個步驟:

    1. 線程1本地內存中修改過的共享變量的副本同步到共享內存中去
    2. 線程2從共享內存中讀取被線程1更新過的共享變量
      這樣才能完成線程1的修改對線程2的可見。

    內存間的交互操作

    為了完成這一線程之間的通信,JMM為內存間的交互操作定義了8個原子操作,如下錶:

    操作 作用域 說明
    lock(鎖定) 共享內存中的變量 把一個變量標識為一條線程獨佔的狀態
    unlock(解鎖) 共享內存中的變量 把一個處於鎖定的變量釋放出來,釋放后其他線程可以進行訪問
    read(讀取) 共享內存中的變量 把一個變量的值從共享內存傳輸到線程的工作內存。供隨後的load操作使用
    load(載入) 工作內存 把read操作從共享內存中得到的變量值放入工作內存的變量副本當中
    use(使用) 工作內存 把工作內存中的一個變量值傳遞給執行引擎
    assign(賦值) 工作內存 把一個從執行引擎接受到的值賦值給工作內存的變量
    store(存儲) 作用於工作內存 把一個工作內存中的變量傳遞給共享內存,供後續的write使用
    write(寫入) 共享內存中的變量 把store操作從工作內存中得到的變量的值放入主內存

    JMM規定JVM四線時必須保證上述8個原子操作是不可再分割的,同時必須滿足以下的規則:

    1. 不允許readloadstorewrite操作之一單獨出現,即不允許只從共享內存讀取但工作內存不接受,或者工作捏村發起回寫但是共享內存不接收
    2. 不允許一個線程捨棄assign操作,即當一個線程修改了變量后必須寫回工作內存和共享內存
    3. 不允許一個線程將未修改的變量值寫回共享內存
    4. 變量只能從共享內存中誕生,不允許線程直接使用未初始化的變量
    5. 一個變量同一時刻只能由一個線程對其執行lock操作,但是一個變量可以被同一個線程重複執行多次lock,但是需要相同次數的unlock
    6. 如果對一個變量執行lock操作,那麼會清空工作內存中此變量的值,在執行引擎使用這個變量之前需要重新執行load和assign
    7. 不允許unlock一個沒有被鎖定的變量,也不允許unlock一個其他線程lock的變量
    8. 對一個變量unlock之前必須把此變量同步回主存當中。

    longdouble的特殊操作
    在一些32位的處理器上,如果要求對64位的longdouble的寫具有原子性,會有較大的開銷,為了照固這種情況,
    java語言規範鼓勵但不要求虛擬機對64位的longdouble型變量的寫操作具有原子性,當JVM在這種處理器上運行時,
    可能會把64位的long和double拆分成兩次32位的寫

    指令屏障

    為了保證內存的可見性,JMM的編譯器會禁止特定類型的編譯器重新排序;對於處理器的重新排序,
    JMM會要求編譯器在生成指令序列時插入特定類型的的內存屏障指令,通過內存屏障指令巾紙特定類型的處理器重新排序

    JMM規定了四種內存屏障,具體如下:

    屏障類型 指令示例 說明
    LoadLoad Barriers Load1;LoadLoad;Load2 確保Load1的數據先於Load2以及所有後續裝在指令的裝載
    StoreStore Barries Store1;StoreStore;Store2 確保Store1數據對於其他處理器可見(刷新到內存)先於Store2及後續存儲指令的存儲
    LoadStore Barriers Load1;LoadStore;Store2 確保Load1的裝載先於Store2及後續所有的存儲指令
    StoreLoad Barrier Store1;StoreLoad;Load2 確保Store1的存儲指令先於Load1以及後續所所有的加載指令

    StoreLoad是一個“萬能”的內存屏障,他同時具有其他三個內存屏障的效果,現代的處理器大都支持該屏障(其他的內存屏障不一定支持),
    但是執行這個內存屏障的開銷很昂貴,因為需要將處理器緩衝區所有的數據刷回內存中。

    happens-before規則

    在JSR-133種內存模型種引入了happens-before規則來闡述操作之間的內存可見性。在JVM種如果一個操作的結果過需要對另一個操作可見,
    那麼兩個操作之間必然要存在happens-bsfore關係:

    • 程序順序規則:一個線程中的個每個操作,happens-before於該線程的後續所有操作
    • 監視器鎖規則:對於一個鎖的解鎖,happens-before於隨後對於這個鎖的加鎖
    • volatitle變量規則:對於一個volatile的寫,happens-before於認意後續對這個volatile域的讀
    • 線程啟動原則:對線程的start()操作先行發生於線程內的任何操作
    • 線程終止原則:線程中的所有操作先行發生於檢測到線程終止,可以通過Thread.join()、Thread.isAlive()的返回值檢測線程是否已經終止
    • 線程終端原則:對線程的interrupt()的調用先行發生於線程的代碼中檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測是否發生中斷
    • 對象終結原則:一個對象的初始化完成(構造方法執行結束)先行發生於它的finalize()方法的開始。
    • 傳遞性:如果A happens-before B B happends-beforeC,那麼A happends-before C

    指令重排序

    從源程序到字節指令的重排序

    眾所周知,JVM執行的是字節碼,Java源代碼需要先編譯成字節碼程序才能在Java虛擬機中運行,但是考慮下面的程序;

    int a = 1;
    int b = 1;
    

    在這段代碼中,ab沒有任何的相互依賴關係,因此完全可以先對b初始化賦值,再對a變量初始化賦值;

    事實上,為了提高性能,編譯器和處理器通常會對指令做重新排序。重排序分為3種:

    1. 編譯器優化的重排序。編譯器在不改變單線程的程序語義的前提下,可以安排字語句的執行順序。編譯器的對象是語句,不是字節碼,
      但是反應的結果就是編譯后的字節碼和寫的語句順序不一致。
    2. 執行級并行的重排序。現代處理器採用了并行技術,來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
    3. 內存系統的重排序,由於處理器使用了緩存和讀/寫緩衝區,這使得加載和存儲操作看上去可能是在亂序執行。

    數據依賴性:如果兩個操作訪問同一個變量,且兩個操作有一個是寫操作,則這兩個操作存在數據依賴性,改變這兩個操作的執行順序,就會改變執行結果。

    儘管指令重排序會提高代碼的執行效率,但是卻為多線程編程帶來了問題,多線程操作共享變量需要一定程度上遵循代碼的編寫順序,
    也需要將修改的共享數據存儲到共享內存中,不按照代碼順序執行可能會導致多線程程序出現內存可見性的問題,那又如何實現呢?

    as-if-serial語義

    as-if-serial語義:不論程序怎樣進行重排序,(單線程)程序的執行結果不能被改變。編譯器、runtime和處理器都必須支持as-if-serial語義。

    程序順序規則

    假設存在以下happens-before程序規則:

        1) A happens-before B
        2) B happens-before C
        3) A happens-before C
    

    儘管這裏存在A happens-before B這一關係,但是JMM並不要求A一定要在B之前執行,僅僅要求A的執行結果對B可見。
    即JMM僅要求前一個操作的結果對於后一個操作可見,並且前一個操作按照順序排在後一個操作之前。
    但是若前一個操作放在後一個操作之後執行並不影響執行結果,則JMM認為這並不違法,JMM允許這種重排序。

    順序一致性模型

    在一個線程中寫一個變量,在另一個線程中同時讀取這個變量,讀和寫沒有通過排序來同步來排序,就會引發數據競爭。

    數據競爭的核心原因是程序未正確同步。如果一個多線程程序是正確同步的,這個程序將是一個沒有數據競爭的程序。

    順序一致性模型只是一個參考模型。

    順序一致性模型特性

    • 一個線程中所有的操作必須按照程序的順序來執行。
    • 不管線程是否同步,所有的線程都只能看到一個單一的執行順序。

    在順序一致性模型中每個曹祖都必須原子執行且立刻對所有線程可見。

    當程序未正確同步會發生什麼

    當線程未正確同步時,JMM只提供最小的安全性,當讀取到一個值時,這個值要麼是之前寫入的值,要麼是默認值。
    JMM保證線程的操作不會無中生有。為了保證這一特點,JMM在分配對象時,首先會對內存空間清0,然後才在上面分配對象。

    未同步的程序在JMM種執行時,整體上是無序的,執行結果也無法預知。位同步程序子兩個模型中執行特點有如下幾個差異:

    • 順序一致性模型保證單線程內的操作會按照程序的順序執行,而JMM不保證單線程內的操作會按照程序的順序執行
    • 順序一致性模型保證所有線程只能看到一致的操作執行順序,而JMM不保證所有線程能看到一致的操作執行順序
    • JMM不保證對64位的longdouble型變量具有寫操作的原子性,而順序一致性模型保證對所有的內存的讀/寫操作都具有原子性

    參考資料

    java併發編程的藝術-方騰飛,魏鵬,程曉明著

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

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

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

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

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

  • 【Mongodb】 可複製集搭建

    【Mongodb】 可複製集搭建

    可複製集 replica set

    概念圖

    可複製集需要至少3個以上的mongodb節點,其中有一個主節點promary,其餘的為副本節點secondary

    可複製集有三個角色:

    • 主要成員(Primary):主要接收所有寫操作。就是主節點。
    • 副本成員(Secondary):從主節點通過複製操作以維護相同的數據集,即備份數據,不可寫操作,但可以讀操作(但需要配置)。是默認的一種從節點類型。
    • 仲裁者(Arbiter):不保留任何數據的副本,只具有投票選舉作用。當然也可以將仲裁服務器維護為副本集的一部分,即副本成員同時也可以是仲裁者。也是一種從節點類型。

    關於仲裁者:
    如果主節點+副本節點是偶數推薦添加仲裁者,如果主節點+ 副本節點是奇數可以不添加仲裁者。仲裁者將永遠是仲裁者,而主要人員可能會退出並成為次要人員,而次要人員可能成為選舉期間的主要人員。

    為什麼要用可複製集?它有什麼重要性?

    1. 避免數據丟失,保障數據安全,提高系統安全性;
      (最少3節點,最大50節點)
    2. 自動化災備機制,主節點宕機后通過選舉產生新主機;提高系統健壯性;
      (7個選舉節點上限)
    3. 讀寫分離,負載均衡,提高系統性能;

    搭建

    準備三個mongodb節點

    正準備三個mongodb節點,我們先搭建一個主節點,2個副本節點的模式
    修改配置mongo.conf

    • 第一個
    systemLog:
      #MongoDB發送所有日誌輸出的目標指定為文件 
      destination: file
      #mongod或mongos應向其發送所有診斷日誌記錄信息的日誌文件的路徑 
      path: "/home/amber/mongodb/mongodb-001/log/mongod.log" 
      #當mongos或mongod實例重新啟動時,mongos或mongod會將新條目附加到現有日誌文件的末尾。 
      logAppend: true
    storage: 
      #mongod實例存儲其數據的目錄。storage.dbPath設置僅適用於mongod。 
      dbPath: "/home/amber/mongodb/mongodb-001/data/db" 
      journal:
        #啟用或禁用持久性日誌以確保數據文件保持有效和可恢復。 
        enabled: true
    processManagement:
      #啟用在後台運行mongos或mongod進程的守護進程模式。 
      fork: true 
      #指定用於保存mongos或mongod進程的進程ID的文件位置,其中mongos或mongod將寫入其PID 
      pidFilePath: "/home/amber/mongodb/mongodb-001/log/mongod.pid" 
    net:
      #服務實例綁定所有IP,有副作用,副本集初始化的時候,節點名字會自動設置為本地域名,而不是ip 
      #bindIpAll: true 
      #服務實例綁定的IP 
      bindIp: 0.0.0.0
      #bindIp 
      #綁定的端口 
      port: 27017
    replication: 
      #副本集的名稱 
      replSetName: myrs
    
    • 第二個第三個配置
      把上述文件中的mongodb-001換成mongodb-002``mongodb-003
      端口分別換成27018 27019

    然後分別在mongodb-00X的根目錄下執行啟動命令

    ./bin/mongod -f ./conf/mongod.conf
    

    檢查進程

    ps -ef|grep mongod
    

    設置主節點

    進入27017的那個mongod的客戶端,並且執行

    rs.initiate({
          _id: "myrs", //  需要和replSetName的名稱一致
          version: 1,
          members: [{ _id: 0, host : "192.168.xx.xx:27017" }]});
    

    或者

    rs.initiate({}) 
    

    執行結果

    提示:
    1)“ok”的值為1,說明創建成功。
    2)命令行提示符發生變化,變成了一個從節點角色,此時默認不能讀寫。稍等片刻,回車,變成主節  點。
    

    配置副本節點

    再27017的mongod客戶端,也就是主節點上執行192.168.xx.xx:27018是副本節點的ip和端口

    rs.add("192.168.xx.xx:27018")
    rs.add("192.168.xx.xx:27019")
    

    使用

    rs.status()
    

    就可以看到members會有三個節點了

    測試

    再主節點插入一條數據

    use article;
    db.comment.insert({name: "amber"})
    

    再從節點查看,結果

    這是因為需要再從節點再次進行

    rs.slaveok() // 確認當前節點是副本節點
    db.comment.find(); // 查看當前數據
    

    可以看到已經有數據了,這樣一主二從的可複製成功了

    如果關閉主節點,在從節點執行rs.status();
    可以看到原來的主節點的health變成了0

    27018變成了新的主節點

    主節點的選舉原則

    MongoDB在副本集中,主節點選舉的觸發條件:
    1) 主節點故障
    2) 主節點網絡不可達(默認心跳信息為10秒)
    3) 人工干預(rs.stepDown(600))

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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