南非減塑大功臣 寶特瓶廢棄塑膠製成磚

摘錄自2020年2月14日公視報導

在全球都出現塑膠垃圾問題時,南非開始用寶特瓶跟廢棄塑膠包裝,做成環保磚頭,來蓋托兒所等建築,成功減少塑膠垃圾。

根據2018年的「南非廢棄物狀況報告」指出,南非在2017年製造的4200萬噸廢物中,只有約11%被回收再利用。而2012年成立的南非當地民間團體「Waste-ED」,主要協助國家解決廢棄物品問題。除了教育學童相關觀念,還接受諮詢,引進這種塑膠瓶環保磚的製作,用來蓋學校或是簡易建築。

這種塑膠瓶環保磚,起源於菲律賓北部,後來應用在無法解決塑膠垃圾問題的發展中國家,協助當地政府廢物利用。目前開普敦郊區,已經有許多建築,包括托兒中心等建築牆壁,都是用這些環保磚製作。目前開普敦有超過2萬個、塑膠瓶環保磚的收集點,還跟學校合作,帶學童們一起參與製作跟使用環保磚。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

對照圖鑑也會誤判 日本野菇中毒事件頻傳

文:宋瑞文

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

007.OpenShift管理應用部署

一 REPLICATION CONTROLLERS

1.1 RC概述

RC確保pod指定數量的副本一直運行。如果pod被殺死或被管理員顯式刪除,複製控制器將自動部署相應的pod。類似地,如果運行的pod數量超過所需的數量,它會根據需要刪除pod,以匹配指定的副本計數。
RC的定義主要包括:

  • 所需的副本數量
  • 用於創建複製pod的pod定義
  • 用於標識後續管理操作的selector

selector是一組label,RC管理的所有pod都必須匹配這些標籤。RC實例化的pod定義中必須包含相同的標籤集。RC使用這個selector來確定已經運行了多少pod實例,以便根據需要進行調整。
提示:不執行自動縮放,因為它不跟蹤負載或流量。
儘管Kubernetes通常直接管理RC,但OpenShift推薦的方法是管理根據需要創建或更改RC的DC。

1.2 從DC創建RC

在OpenShift中創建應用程序的最常見方法是使用oc new-app命令或web控制台。以這種方式創建的應用程序使用DeploymentConfig資源在運行時創建RC來創建應用程序pod。DeploymentConfig資源定義定義了要創建的pod的副本的數量,以及要創建的pod的模板。
注意:不要將DeploymentConfig或ReplicationController資源中的template屬性誤認為OpenShift模板資源類型,OpenShift模板資源用於基於一些常用的語言運行時和框架構建應用程序。

1.3 pod副本數控制

DeploymentConfig或ReplicationController資源中的副本數量可以使用oc scale命令動態更改。
$ oc get dc
NAME REVISION DESIRED CURRENT TRIGGERED BY
myapp 1 3 3 config,image(scaling:latest)
$ oc scale –replicas=5 dc myapp
DeploymentConfig資源將更改信息傳遞至ReplicationController,該控制器通過創建新的pod(副本)或刪除現有的pod來響應更改。
雖然可以直接操作ReplicationController資源,但推薦的做法是操作DeploymentConfig資源。在觸發部署時,直接對ReplicationController資源所做的更改可能會丟失,例如,使用容器image的新版本重新創建pod。

1.4 自動伸縮pod

OpenShift可以通過HorizontalPodAutoscaler資源類型根據應用程序pod上的當前負載自動調整部署配置。
HorizontalPodAutoscaler (HPA)資源使用OpenShift metrics子系統收集的性能指標,即如果沒有度量子系統(模塊),更確切地說是Heapster組件,自動縮放是不可能的。
創建HorizontalPodAutoscaler資源的推薦方法是使用oc autoscale命令,例如:
$ oc autoscale dc/myapp –min 1 –max 10 –cpu-percent=80
該命令創建一個HorizontalPodAutoscaler資源,該資源更改myapp部署配置上的副本數量,以將其pod的CPU使用量控制在請求的總CPU使用量的80%以下。
oc autoscale命令使用DC的名稱作為參數(在前面的示例中是myapp)創建一個HorizontalPodAutoscaler資源。
HorizontalPodAutoscaler資源的最大值和最小值用於容納突發負載,並避免重載OpenShift集群。如果應用程序上的負載變化太快,建議保留一些備用的pod來處理突然出現的用戶請求。相反,過多的pod會耗盡所有集群容量,並影響共享相同OpenShift集群的其他應用程序。
要獲取當前項目中關於HorizontalPodAutoscaler資源的信息,可使用oc get和oc describe命令。例如
$ oc get hpa/frontend
$ oc describe hpa/frontend
注意:HorizontalPodAutoscaler資源只適用於為引用性能指標定義資源請求的pod。
oc new-app命令創建的大多數pod沒有定義任何資源請求。因此,使用OpenShift autoscaler可能需要為應用程序創建定製的YAML或JSON資源文件,或者向項目添加資源範圍資源。

二 擴展程序實驗

2.1 前置準備

準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

2.2 創建應用

  1 [student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com
  2 [student@workstation ~]$ oc new-project scaling
  3 [student@workstation ~]$ oc new-app -o yaml -i php:7.0 \
  4 http://registry.lab.example.com/scaling > ~/scaling.yml		#將部署的yaml導出至本地
  5 [student@workstation ~]$ vi ~/scaling.yml
  6 ……
  7   spec:
  8     replicas: 3
  9     selector:
 10       app: scaling
 11       deploymentconfig: scaling				#修改副本數
 12 ……
 13 [student@workstation ~]$ oc create -f ~/scaling.yml	#以修改副本數后的yaml部署應用

 

2.3 監視部署

  1 [student@workstation ~]$ watch -n 3 oc get builds
  2 Every 3.0s: oc get builds                                                                Mon Jul 22 11:12:02 2019
  3 
  4 NAME        TYPE      FROM          STATUS     STARTED              DURATION
  5 scaling-1   Source    Git@0bdae71   Complete   About a minute ago   1m0s
  6 [student@workstation ~]$ oc get pods
  7 NAME              READY     STATUS      RESTARTS   AGE
  8 scaling-1-build   0/1       Completed   0          2m
  9 scaling-1-ft249   1/1       Running     0          1m
 10 scaling-1-gjvkp   1/1       Running     0          1m
 11 scaling-1-mtrxr   1/1       Running     0          1m

 

2.4 暴露服務

  1 [student@workstation ~]$ oc expose service scaling \
  2 --hostname=scaling.apps.lab.example.com

 

2.5 web查看相關信息

瀏覽器訪問https://master.lab.example.com,使用developer用戶和redhat密碼登陸。選擇scaling項目。
 

2.6 測試負載均衡

  1 [student@workstation ~]$ for i in {1..5};do curl -s \http://scaling.apps.lab.example.com | grep IP;done	#多次請求
  2  <br/> Server IP: 10.128.0.17
  3  <br/> Server IP: 10.129.0.35
  4  <br/> Server IP: 10.129.0.36
  5  <br/> Server IP: 10.128.0.17
  6  <br/> Server IP: 10.129.0.35

 
提示:瀏覽器可能無法嚴格檢查均衡性,因為OpenShift route存在會話關聯性(也稱為粘性會話)。即來自同一個web瀏覽器的所有請求都將轉到同一個pod。

2.7 擴容應用

  1 [student@workstation ~]$ oc describe dc scaling | grep Replicas
  2 Replicas:       3
  3         Replicas:       3 current / 3 desired
  4 [student@workstation ~]$ oc scale --replicas=5 dc scaling

 

  1 [student@workstation ~]$ oc get pods -o wide

2.8 測試負載均衡

  1 [student@workstation ~]$ for i in {1..5};do curl -s \http://scaling.apps.lab.example.com | grep IP;done	#多次請求
  2  <br/> Server IP: 10.128.0.17
  3  <br/> Server IP: 10.128.0.18
  4  <br/> Server IP: 10.129.0.35
  5  <br/> Server IP: 10.129.0.36
  6  <br/> Server IP: 10.129.0.37

 

三 pod調度控制

3.1 pod調度算法

pod調度程序確定新pod在OpenShift集群中的節點上的位置。該調度算法被設計為可高度配置和適應不同集群。OCP 3.9附帶的默認配置通過使用node label、affinity rules,anti-affinity rules中的定義來支持zone和regions的調用。
在OCP以前的版本中,安裝程序master節點標記為污點標記,表示不允許在master上部署pod。在新版的OCP 3.9中,在安裝和升級過程中,master會自動標記為可調度的。使得可以通過deploy調度pod至maste節點。而不僅僅是作為master的組件運行。
默認節點selector是在安裝和升級期間默認設置的。它被設置為node-role.kubernetes.io/compute=true,除非使用osm_default_node_selector的Ansible變量覆蓋它。
在安裝和升級期間,不管osm_default_node_selector配置如何,都會對庫存文件中定義的主機執行以下自動標記。
compute節點配置non-master、non-dedicated的角色(默認情況下,具有region=infra標籤的節點),節點使用node-role.kubernetes.io/compute=true標記。
master節點被標記為node-role.kubernetes.io/master=true,從而分配master節點角色。

3.2 調度算法步驟

  • 過濾節點

調度程序根據節點資源(如主機端口)的可用性篩選正在運行的節點列表,然後進一步根據節點selector和來自pod的資源請求篩選。最終的縮小是可運行pod的候選node列表。
pod可以定義與集群節點中的標籤匹配的節點選擇器,標籤不匹配的節點視為不合格。
pod還可以為計算資源(如CPU、內存和存儲)定義資源請求,沒有足夠的空閑計算機資源的節點視為不合格。

  • 對過濾后的節點列表進行優先級排序

候選節點列表使用多個優先級標準進行評估,這些標準加起來就是權重,權重值較高的節點更適合運行pod。
其中有affinity(親和規則)和anti-affinity(反親和規則),pod親和力較高的節點得分較高,而anti-affinity較高的節點權重低。
affinity的一個常見用法是:出於性能原因,將相關的pod安排得彼此親和。例如,需要保持彼此同步的pod使用相同的網絡棧。
anti-affinity的一個常見用法是:為了獲得高可用性,將相關的pod安排的盡量分散。例如,避免將所有pod從同一個應用程序調度到同一個節點。

  • 選擇最合適的節點。

根據權重對候選列表進行排序,並選擇權重最高的節點來承載pod。如果多個節點得分相同,則隨機選擇一個節點。
調度程序配置文件位於/etc/original/master/scheduler.json,其定義了一組predicates,用作過濾器或優先級函數。通過這種方式,可以將調度程序配置為支持不同的集群。

3.3 調度拓撲

對於大型數據中心,例如雲提供商,一個常見的拓撲結構是將主機組織成regions和zones:
region:是一個地理區域內的一組主機,這保證了它們之間的內網高速連接;
zone:也稱為可用區,是一組主機,它們可能一起失敗,因為它們共享公共的關鍵基礎設施組件,比如網絡、存儲或電源。
OpenShift pod調度器可支持根據region和zone標籤在集群內調度,如:

    • 從相同的RC創建的或從相同的DC創建的pod副本調度至具有相同region標籤值的節點中運行。
    • 副本Pod調位至具有不同zone標籤的節點中運行。

實例圖如下:

要實現上圖中的樣例拓撲,可以使用集群管理員通過以下命令oc label:

  1 $ oc label node1 region=ZheJiang zone=Cloud1A --overwrite
  2 $ oc label node node2 region=ZheJiang zone=Cloud1A --overwrite
  3 $ oc label node node3 region=ZheJiang zone=Cloud2A --overwrite
  4 $ oc label node node4 region=ZheJiang zone=Cloud2A --overwrite
  5 $ oc label node node5 region=HuNan zone=Cloud1B --overwrite
  6 $ oc label node node6 region=HuNan zone=Cloud1B --overwrite
  7 $ oc label node node7 region=HuNan zone=Cloud2B --overwrite
  8 $ oc label node node8 region=HuNan zone=Cloud2B --overwrite

 
提示:每個節點必須由其完全限定名(FQDN)標識,為了簡潔,如上命令使用了簡短的名稱。
對區域標籤的更改需要–overwrite選項,因為OCP 3.9高級安裝方法默認情況下使用region=infra標籤配置節點。
示例:要檢查分配給節點的標籤,可以使用oc get node命令和–show-labels選項。
$ oc get node node1.lab.example.com –show-labels
注意,一個節點可能有一些OpenShift分配的默認標籤,包含kubernetes.io後綴鍵值的標籤,此類標籤不應由集群管理員人為更改,因為它們由調度程序在內部使用。
集群管理員還可以使用-L選項來確定單個標籤的值。
示例:

  1 $ oc get node node1.lab.example.com -L region
  2 $ oc get node node1.lab.example.com -L region -L zone	#支持oc get跟多個-L選項

 

3.4 UNSCHEDULABLE節點

有時候,集群管理員需要關閉節點進行維護,如節點可能需要硬件升級或內核安全更新。要在對OpenShift集群用戶影響最小的情況下關閉節點,管理員應該遵循兩個步驟。
將節點標記為不可調度,從而防止調度程序向節點分配新的pod。

  1 $ oc adm manage-node --schedulable=false node2.lab.example.com

Drain節點,這將銷毀在pod中運行的所有pod,並假設這些pod將通過DC在其他可用節點中會重新創建。

  1 $ oc adm drain node2.lab.example.com

維護操作完成后,使用oc adm management -node命令將節點標記為可調度的。

  1 $ oc adm manage-node --schedulable=true node2.lab.example.com

3.5 控制pod位置

有些應用程序可能需要在一組指定的node上運行。例如,某些節點為某些類型的工作負載提供硬件加速,或者集群管理員不希望將生產應用程序與開發應用程序混合使用。此類需求,都可以使用節點標籤和節點選擇器來實現。
node selector是pod定義的一部分,但建議更改dc,而不是pod級別的定義。要添加節點選擇器,可使用oc edit命令或oc patch命令更改pod定義。
示例:配置myapp的dc,使其pods只在擁有env=qa標籤的節點上運行。

  1 $ oc patch dc myapp --patch '{"spec":{"template":{"nodeSelector":{"env":"qa"}}}}'

此更改將觸發一個新的部署,並根據新的節點選擇器調度新的pod。
如果集群管理員不希望讓開發人員控制他們pod的節點選擇器,那麼應該在項目資源中配置一個默認的節點選擇器。

3.5 管理默認項目

生產環境一個常見實踐是指定一組節點來運行OCP的系統基礎Pod,比如route和內部倉庫。這些pod在默認項目中定義。
通常可通過以下兩個步驟實現:

  1. 使用region=infra標籤標記專用節點;
  2. 為缺省名稱空間配置缺省節點選擇器。

要配置項目的默認節點選擇器,可使用openshift.io/node-selector鍵值向名稱空間資源添加註釋。可以使用oc edit或oc annotate命令。

  1 $ oc annotate --overwrite namespace default \
  2 openshift.io/node-selector='region=infra'

 
OCP 3.9 quick installer和advanced installer的Ansible playbook都支持Ansible變量,這些變量控制安裝過程中分配給節點的標籤,也控制分配給每個基礎設施pod的節點選擇器。
安裝OCP子系統(如metrics子系統)的劇本還支持這些子系統節點選擇器的變量。

四 控制Pod調度

4.1 前置準備

準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

4.2 本練習準備

  1 [student@workstation ~]$ lab schedule-control setup
  2 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com

 

4.3 查看region

  1 [student@workstation ~]$ oc get nodes -L region
  2 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  3 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  4 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
  5 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra

 

4.4 創建project

  1 [student@workstation ~]$ oc new-project schedule-control

4.5 創建應用

  1 [student@workstation ~]$ oc new-app --name=hello \
  2 --docker-image=registry.lab.example.com/openshift/hello-openshift

 

4.6 擴展應用

  1 [student@workstation ~]$ oc scale dc hello --replicas=5
  2 deploymentconfig "hello" scaled
  3 [student@workstation ~]$ oc get pod -o wide
  4 NAME            READY     STATUS    RESTARTS   AGE       IP            NODE
  5 hello-1-c5z2n   1/1       Running   0          7s        10.128.0.21   node1.lab.example.com
  6 hello-1-hhvp7   1/1       Running   0          34s       10.129.0.38   node2.lab.example.com
  7 hello-1-jqrkb   1/1       Running   0          7s        10.128.0.20   node1.lab.example.com
  8 hello-1-tgmbr   1/1       Running   0          7s        10.129.0.39   node2.lab.example.com
  9 hello-1-z2bn7   1/1       Running   0          7s        10.128.0.22   node1.lab.example.com

 

4.7 修改節點label

  1 [student@workstation ~]$ oc label node node2.lab.example.com region=apps --overwrite=true
  2 [student@workstation ~]$ oc get nodes -L region		#確認修改
  3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
  6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   apps

 

4.8 導出dc

  1 [student@workstation ~]$ oc get dc hello -o yaml > dc.yaml

4.9 修改node2調度策略

添加dc.yaml中的調度策略,使pod調度至apps標籤的node。

  1 [student@workstation ~]$ vi dc.yaml
  2 ……
  3   template:
  4 ……
  5     spec:
  6       nodeSelector:		#添加節點選擇器
  7         region: apps
  8 ……

 

4.10 應用更新

  1 [student@workstation ~]$ oc apply -f dc.yaml

4.11 確認驗證

  1 [student@workstation ~]$ oc get pod -o wide
  2 NAME            READY     STATUS    RESTARTS   AGE       IP            NODE
  3 hello-2-4c2gv   1/1       Running   0          40s       10.129.0.42   node2.lab.example.com
  4 hello-2-6966b   1/1       Running   0          38s       10.129.0.43   node2.lab.example.com
  5 hello-2-dcqbr   1/1       Running   0          36s       10.129.0.44   node2.lab.example.com
  6 hello-2-dlf8k   1/1       Running   0          36s       10.129.0.45   node2.lab.example.com
  7 hello-2-rnk4w   1/1       Running   0          40s       10.129.0.41   node2.lab.example.com

 
#驗證是否觸發了新的部署,並等待所有新的應用pod都準備好並運行。所有5個pod都應該調度至node2。

4.12 修改node1調度策略

  1 [student@workstation ~]$ oc label node node1.lab.example.com region=apps --overwrite=true
  2 [student@workstation ~]$ oc get node -L region
  3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   apps
  6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   apps

 

4.13 終止node2

  1 [student@workstation ~]$ oc adm manage-node --schedulable=false node2.lab.example.com
  2 NAME                    STATUS                     ROLES     AGE       VERSION
  3 node2.lab.example.com   Ready,SchedulingDisabled   compute   2d        v1.9.1+a0ce1bc657

 

4.14 刪除pod

刪除node2的pod,並使用node1創建的pod替換。

  1 [student@workstation ~]$ oc adm drain node2.lab.example.com --delete-local-data

4.15 查看pod

  1 [student@workstation ~]$ oc get pods -o wide
  2 NAME            READY     STATUS    RESTARTS   AGE       IP            NODE
  3 hello-2-bjsj4   1/1       Running   0          51s       10.128.0.25   node1.lab.example.com
  4 hello-2-kmmmn   1/1       Running   0          50s       10.128.0.23   node1.lab.example.com
  5 hello-2-n6wvj   1/1       Running   0          51s       10.128.0.24   node1.lab.example.com
  6 hello-2-plr65   1/1       Running   0          50s       10.128.0.26   node1.lab.example.com
  7 hello-2-xsz68   1/1       Running   0          51s       10.128.0.27   node1.lab.example.com

 

五 管理IS、image、Templates

5.1 image介紹

在OpenShift中,image是一個可部署的runtime模板,它包含運行單個容器的所有需求,還包括imag功能的元數據。image可以通過多種方式管理,如tag、import、pull和update。
image可以跨多個主機部署在多個容器中。開發人員可以使用Docker構建image,也可以使用OpenShift構建工具。
OpenShift實現了靈活的image管理機制。一個image名稱實際上可以引用同一image的許多不同版本。唯一的image由它的sha256哈希引用,Docker不使用版本號。相反,它使用tag來管理image,例如v1、v2或默認的latest tag。

5.2 IS

IS包括由tags標識的任意數量的容器images。它是相關image的統一虛擬視圖,類似於Docker image倉庫。開發人員有許多與image和IS交互的方法。例如,當添加或修改新image時,build和deployment可以接收通知,並通過運行新build或新deployment做出相應的動作。

5.3 標記image

OCP提供了oc tag命令,它類似於docker tag命令,但是,它是對IS而不是image進行操作。
可以向image添加tag,以便更容易地確定它們包含什麼。tag是指定image版本的標識符。
示例:將Apache web服務器2.4版本的映像,可將該image執行以下標記。
apache: 2.4
如果倉庫包含Apache web服務器的最新版本,他們可以使用latest標籤來表示這是倉庫中可用的最新image。
apache:latest
oc tag命令用於標籤image:
[user@demo ~]$ oc tag source destination
source:現有tag或圖像流中的圖像。
destination:標籤在一個或多個IS中的最新image。
示例:將ruby image的現有latest標記修改為當前版本v2.0標識,
[user@demo ~]$ oc tag ruby:latest ruby:2.0

5.4 刪除tag

若要從image中刪除標記,可使用-d參數。
[user@demo ~]$ oc tag -d ruby:latest
可以使用不同類型的標籤,默認行為使用permanent tag,即源文件發生更改,該tag也會及時指向image,與目標tag無關。
tracking tag指示在導入image期間導入目標tag的元數據。要確保目標tag在源tag更改時得到更新,需使用–alias=true標識。
[user@demo ~]$ oc tag –alias=true source destination
要重新導入tag,可使用–scheduled=true標識。
[user@demo ~]$ oc tag –scheduled=true source destination
要配置Docker始終從內部倉庫中獲取image,可使用–reference-policy=local標誌。默認情況下,image指向本地倉庫。從而實現在之後調用image的時候可以快速pull。
[user@demo ~]$ oc tag –reference-policy=local source destination

5.5 建議的tag形式

在管理tag時,開發人員應該考慮映像的生命周期,參考下錶開發人員用來管理映像的可能的標記命名約定。

描述 示例
Revision myimage:v2.0.1
Architecture myimage:v2.0-x86_64
Base Image myimage:v1.2-rhel7
Latest Image myimage:latest
Latest Stable Image myimage:stable

5.6 Templates介紹

模板描述一組對象,其中包含處理後生成對象列表的參數。可以處理模板來創建開發人員有權在項目中創建的任何內容,例如service、build、configuration和dc。
模板還可以定義一組標籤,應用於它定義的每個對象。開發人員可以使用命令行界面或web控制台從模板創建對象列表。

5.7 Templates管理

開發人員可以用JSON或YAML格式編寫模板,並使用命令行界面或web控制台導入它們。模板被保存到項目中,以供對該特定項目具有適當訪問權限的任何用戶重複使用。
示例:導入模板。
[user@demo ~]$ oc create -f filename
還可以在導入模板時分配標籤,這意味着模板定義的所有對象都將被標記。
[user@demo ~]$ oc create -f filename -l name=mylabel

5.8 使用模板

OCP提供了許多默認的instant app和QuickStart模板,允許開發人員為不同的語言快速創建新的應用程序。為Rails (Ruby)、Django (Python)、Node.js、CakePHP (PHP)和Dancer (Perl)提供了模板。
要列出集群中的可用模板,請運行oc get templates命令。參數-n指定要使用的項目。
[user@demo ~]$ oc get templates -n openshift
開發人員還可以使用web控制台瀏覽模板,當您選擇模板時,可以調整可用的參數來自定義模板定義的資源。

六 管理IS

6.1 前置準備

準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

6.2 本練習準備

  1 [student@workstation ~]$ lab schedule-is setup

6.3 創建項目

  1 [student@workstation ~]$ oc login -u developer -p redhat \
  2 https://master.lab.example.com
  3 [student@workstation ~]$ oc new-project schedule-is

 

6.4 創建應用

  1 [student@workstation ~]$ oc new-app --name=phpmyadmin \
  2 --docker-image=registry.lab.example.com/phpmyadmin/phpmyadmin:4.7

 

6.5 創建服務賬戶

  1 [student@workstation ~]$ oc login -u admin -p redhat
  2 [student@workstation ~]$ oc project schedule-is
  3 [student@workstation ~]$ oc create serviceaccount phpmyadmin-account

 

6.6 授權特權運行

  1 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid \
  2 -z phpmyadmin-account

 

6.7 更新pod

  1 [student@workstation ~]$ oc login -u developer
  2 [student@workstation ~]$ oc patch dc/phpmyadmin --patch \
  3 '{"spec":{"template":{"spec":{"serviceAccountName": "phpmyadmin-account"}}}}'

 
更新負責管理phpmyadmin部署的dc資源,以便使用新創建的服務帳戶。可以使用oc patch或oc edit命令。此命令可以從/home/student/DO280/labs/secure-review文件夾中的patch-dc.sh腳本中複製。

  1 [student@workstation ~]$ oc get pods		#確認驗證
  2 NAME                 READY     STATUS    RESTARTS   AGE
  3 phpmyadmin-2-vh29z   1/1       Running   0          3m

 
提示:name后的2表示這個pod是第二次部署,即進行過迭代。

6.8 更新內部倉庫image

  1 [student@workstation ~]$ cd /home/student/DO280/labs/schedule-is/
  2 [student@workstation schedule-is]$ ls
  3 phpmyadmin-latest.tar  trust_internal_registry.sh
  4 [student@workstation schedule-is]$ docker load -i phpmyadmin-latest.tar
  5 #使用docker load命令加載新的image。
  6 [student@workstation schedule-is]$ docker images
  7 REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
  8 <none>              <none>              93d0d7db5ce2        13 months ago       166 MB

 

6.9 tag鏡像

  1 [student@workstation schedule-is]$ docker tag 93d0d7db5ce2 \
  2 docker-registry-default.apps.lab.example.com/schedule-is/phpmyadmin:4.7
  3 #打完標記進行推送。

 

6.10 登錄docker倉庫


結論:docker倉庫會提示因為是自簽名證書,因此判定為不安全的方式。

6.11 修改信任

本環境使用/home/student/DO280/labs/secure-review文件夾中的trust_internal_registry.sh腳本,配置docker倉庫信任OpenShift內部倉庫。

  1 [student@workstation schedule-is]$ ./trust_internal_registry.sh

6.12 推送image

  1 [student@workstation schedule-is]$ docker push \
  2 docker-registry-default.apps.lab.example.com/schedule-is/phpmyadmin:4.7

 

6.13 確認更新

驗證當源image更新后,是否能自動觸發OpenShift進行pod更新。

  1 [student@workstation schedule-is]$ oc get pods
  2 NAME                 READY     STATUS    RESTARTS   AGE
  3 phpmyadmin-3-hnfjk   1/1       Running   0          23s

 

七 管理應用部署實驗

7.1 前置準備

準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

7.2 本練習準備

  1 [student@workstation ~]$ lab manage-review setup

7.3 確認region

  1 [student@workstation ~]$ oc login -uadmin -predhat https://master.lab.example.com
  2 [student@workstation ~]$ oc get nodes -L region
  3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
  6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra

 

7.4 修改region

  1 [student@workstation ~]$ oc label node node1.lab.example.com region=services --overwrite=true
  2 [student@workstation ~]$ oc label node node2.lab.example.com region=applications --overwrite=true
  3 [student@workstation ~]$ oc get nodes -L region
  4 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  5 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  6 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   services
  7 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   applications

 

7.5 創建項目

  1 [student@workstation ~]$ oc new-project manage-review

7.6 創建應用

  1 [student@workstation ~]$ oc new-app -i php:7.0 \
  2 http://registry.lab.example.com/version

 

7.7 擴展應用

  1 [student@workstation ~]$ oc scale dc version --replicas=3
  2 [student@workstation ~]$ oc get pods -o wide		#確認驗證
  3 NAME              READY     STATUS      RESTARTS   AGE       IP            NODE
  4 version-1-9626w   1/1       Running     0          40s       10.129.0.55   node2.lab.example.com
  5 version-1-build   0/1       Completed   0          1m        10.129.0.52   node2.lab.example.com
  6 version-1-f6vj2   1/1       Running     0          40s       10.129.0.56   node2.lab.example.com
  7 version-1-mrhk4   1/1       Running     0          45s       10.129.0.54   node2.lab.example.com

 
結論:應用程序pod並沒有均分在兩個集群node節點之間,因為每個節點屬於不同的region,並且默認的OpenShift調度器配置打開了區域粘性。

7.8 調度pod

  1 [student@workstation ~]$ oc export dc version -o yaml > version-dc.yml	#導出yaml
  2 spac
  3 ……
  4   template:
  5     metadata:
  6 ……
  7     spec:
  8       nodeSelector:		#添加節點選擇器
  9         region: applications
 10 ……

 

7.9 迭代部署

  1 [student@workstation ~]$ oc replace -f version-dc.yml	#迭代

7.10 確認驗證

  1 [student@workstation ~]$ oc get pod -o wide
  2 NAME              READY     STATUS      RESTARTS   AGE       IP            NODE
  3 version-1-build   0/1       Completed   0          15m       10.129.0.52   node2.lab.example.com
  4 version-2-2bmqq   1/1       Running     0          58s       10.129.0.60   node2.lab.example.com
  5 version-2-nz58r   1/1       Running     0          1m        10.129.0.59   node2.lab.example.com
  6 version-2-rlj2h   1/1       Running     0          1m        10.129.0.58   node2.lab.example.com

 
驗證是否啟動了新的部署,並且在node2節點上運行了一組新的版本莢。等待所有三個新的應用程序莢都準備好並運行

7.11 修改region

  1 [student@workstation ~]$ oc label node node1.lab.example.com region=applications --overwrite=true
  2 [student@workstation ~]$ oc get nodes -L region		#確認驗證
  3 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  4 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  5 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   applications
  6 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   applications

 

7.12 終止node2

  1 [student@workstation ~]$ oc adm manage-node --schedulable=false node2.lab.example.com
  2 NAME                    STATUS                     ROLES     AGE       VERSION
  3 node2.lab.example.com   Ready,SchedulingDisabled   compute   2d        v1.9.1+a0ce1bc657

 

7.13 刪除pod

刪除node2的pod,並使用node1創建的pod替換。

  1 [student@workstation ~]$ oc adm drain node2.lab.example.com --delete-local-data

7.14 查看pod

  1 [student@workstation ~]$ oc get pods -o wide
  2 NAME              READY     STATUS    RESTARTS   AGE       IP            NODE
  3 version-2-d9fhp   1/1       Running   0          3m        10.128.0.34   node1.lab.example.com
  4 version-2-jp5gr   1/1       Running   0          3m        10.128.0.35   node1.lab.example.com
  5 version-2-z5lv5   1/1       Running   0          3m        10.128.0.33   node1.lab.example.com

 

7.15 暴露服務

  1 [student@workstation ~]$ oc expose service version --hostname=version.apps.lab.example.com
  2 [student@workstation ~]$ curl http://version.apps.lab.example.com	#確認測試
  3 <html>
  4  <head>
  5   <title>PHP Test</title>
  6  </head>
  7  <body>
  8  <p>Version v1</p>
  9  </body>
 10 </html>

 

7.16 確認驗證

  1 [student@workstation ~]$ lab manage-review grade	#環境腳本判斷

7.17 還原環境

  1 [student@workstation ~]$ oc adm manage-node --schedulable=true node2.lab.example.com
  2 [student@workstation ~]$ oc label node node1.lab.example.com region=infra --overwrite=true
  3 [student@workstation ~]$ oc label node node2.lab.example.com region=infra --overwrite=true
  4 [student@workstation ~]$ oc get node -L region
  5 NAME                     STATUS    ROLES     AGE       VERSION             REGION
  6 master.lab.example.com   Ready     master    2d        v1.9.1+a0ce1bc657
  7 node1.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
  8 node2.lab.example.com    Ready     compute   2d        v1.9.1+a0ce1bc657   infra
  9 [student@workstation ~]$ oc delete project manage-review

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

【其他文章推薦】

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

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

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

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

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

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

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算法是一種迭代算法,用於含有隱變量的概率模型參數的極大似然估計。

使用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 入門之前,我已經入門了 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 自定義數據集圖片完成圖片分類任務

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

  • 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++ 自增語法,不提供 ++ 操作符?

在 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進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關注哦。

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

【其他文章推薦】

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

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

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

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

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

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