基於曝光融合框架的對比度增強算法

目錄

論文題目《A New Image Contrast Enhancement Algorithm Using Exposure Fusion Framework》翻譯以及分析

摘要

低光圖像不利於人眼觀察和計算機視覺算法識別,因為它們的可見度較低。雖然已經提出了許多圖像增強技術來解決這個問題。但是現有方法不可避免地存在對比度過低和過高的情況。在本文中,我們提出了一種精確的對比度增強的算法。具體來說,我們首先使用光照估計技術為圖像融合設計權重矩陣。然後,我們用相機的響應模型合成多重曝光圖像。接下來,我們找到最佳的曝光率,為了合成圖像在原始圖像曝光不足的區域進行更好的曝光。最後,輸入圖像和合成圖像根據權重矩陣進行融合以獲得圖像增強的結果。實驗表明,相比其他優秀的方法,我們的方法可以得到對比度和亮度失真更少的結果。

1. 研究現狀

圖像增強技術廣泛用於圖像處理中。一般來說,它可以使輸入圖像看起來更好,並且更適合於特定算法進行處理。對比度增強,作為一種增強技術,可以在圖像中显示曝光不足區域的信息。目前有大量的對比度增強的技術,主要包括基於直方圖,基於Retinex和基於除霧的方法的增強技術。

彩色圖像可以用三維數組表示。最簡單的對比度增強的方法是對每個對象執行相同的處理過程。例如,最早的圖像增強方法使用非線性單調函數進行灰度級映射。

考慮到不同灰度級別中元素的不均勻分佈,因此直方圖均衡化(HE)被廣泛用於改善對比度。許多直方圖均衡化的擴展方法將亮度保存和對比度限制考慮進去。但是基於直方圖均衡化的方法總是會過度增強和產生不切實際的結果。在模仿在人類視覺系統中,Retinex理論也廣泛用於圖像增強。通過將反射圖與光照圖分開,基於Retinex的算法可以明顯增強細節。但是這些方法在高對比度區域容易產生偽像。近年來,一些方法應用除霧技術來增強對比度並獲得良好的主觀視覺效果。但是這些方法也可能會因為對比度過度增強而導致顏色失真。

儘管圖像對比度增強方法研究了幾十年,但是好的增強效果仍然沒有很好的定義。此外,現有的弱光增強算法也沒有提供參考如何定位過度增強和不足增強區域。我們注意到不同的圖像曝光度可以作為增強算法的參考,如圖1所示。隨着曝光度的增加,一些低曝光區域變得曝光良好。增強的結果應保持曝光良好的區域保持不變,而曝光不足的區域則增強。與此同時,增強區域的對比度應與正確曝光區域保持一致。

在本文中,我們提出了一個新的框架來幫助緩解欠/過度增強的問題。我們的框架基於相機響應模型從輸入圖像合成的多張曝光圖像的曝光融合。基於我們的框架,我們提出了一種增強算法與其他幾種相比最先進的方法,可以獲得更少的對比度和亮度失真的結果。

2. 本文方法

2.1 曝光融合框架

在許多室外場景中,相機無法使所有像素曝光良好,因為它的動態範圍是有限的。如圖1所示儘管我們可以通過增加曝光量显示一些曝光不足的區域,與此同時曝光良好的區域可能同時曝光過度。為了讓所有像素的圖像曝光良好,我們可以融合這些圖像:
\[ R^c = \sum\limits_{i=1}^{N}W_i*P_i^c \]
其中 \(N\) 是圖像數量,\(P_i\) 是曝光集合中的第 \(i\) 個圖像,\(W_i\) 是第 \(i\) 個圖像的權重圖,\(c\) 是三個顏色通道的索引,\(R\) 是增強后的結果。三個顏色分量是相等的,所有像素是不均勻的:曝光良好的像素具有較大的權重,曝光不良的像素權重較小。權重已標準化,因此 \(\sum_{i=1}^{N}W_i=1\)。

問題是具有其他曝光設置的圖像不適用於圖像增強問題。幸運的是,使用不同的曝光度拍攝的照片是高度相關的。在我們的早期工作中,我們提出了相機響應模型以準確描述這些圖像之間的關聯,以便我們從輸入圖像生成一系列圖像。兩張不同曝光度的圖像的映射函數,我們稱為亮度轉換函數(BTF)。給定曝光率 \(k_i\) 和 亮度轉換函數 \(g\),我們可以映射輸入圖像 \(P\) 到曝光集合中的第 \(i\) 個圖像:
\[ P_i=g(P, k_i) \]
在本文中,我們僅以一定的曝光融合輸入圖像本身來降低複雜度,如圖2所示。融合圖像定義為:
\[ R^c=W*P^c+(1-W)*g(P^c,k) \]

增強問題可分為三個部分:\(W, g, k\) 的三個參數的估計。在以下小節中,我們將一一解決這三個問題。

2.2 權重矩陣估計

\(W\) 是增強算法的關鍵,其目的是增強曝光不足區域的低對比度,而保留曝光良好區域的對比度。我們需要給曝光良好的像素分配較大的權重值,給曝光不足的像素分配較小的權重值。直觀地來說,權重矩陣與場景光照正相關。由於高度光照區域有更大的可能性獲得更好的曝光,應分配給大的權重值以保持它們的對比度。我們計算權重矩陣為:
\[ W=T^{\mu} \]
其中 \(T\) 是場景光照圖,\(\mu\) 是控制增強程度的參數。場景光照估計位置映射 \(T\) 通過優化的方法解決。

(1)優化問題

亮度分量可以作為場景光照的估計。我們採用亮度分量作為光照估計的初始值:
\[ L(x)=max _{c\in \{R,G,B\}}P_c(x) \]
對於每個單獨的像素x。理想照明在結構相似的區域具有局部一致性。換句話說,\(T\) 應該保持有意義的圖像的結構並去除紋理邊緣。在文獻[5]中,我們可以通過最優方程得到 \(T\) :
\[ \min _{\mathbf{T}}\|\mathbf{T}-\mathbf{L}\|_{2}^{2}+\lambda\|\mathbf{M} \circ \nabla \mathbf{T}\|_{1} \]
其中 \(\|*\|_2\) 和 \(\|*\|_1\) 分別是 \(\ell_2\) 和 \(\ell_1\) 范數。一階微分濾波器 \(\nabla\) 包含水平方向梯度 \(\nabla_h\mathbf{T}\) 和垂直方向梯度 \(\nabla_v\mathbf{T}\)。\(M\) 是權重矩陣,\(\lambda\) 是係數。方程式的第一項最小化初始圖 \(\mathbf{L}\)和場景光照圖\(\mathbf{T}\),而第二項為保證 \(\mathbf{T}\) 的平滑性。

\(\mathbf{M}\) 的設計對於光照圖的細化很重要。局部窗口中的主要邊緣比具有複雜圖案的紋理貢獻了更多的相似方向梯度。因此,包含有意義的邊緣窗口中的權重應小於僅包含一個窗口的邊緣紋理。因此,我們將權重矩陣設計為
\[ \mathbf{M}_{d}(x)=\frac{1}{\left|\sum_{y \in \omega(x)} \nabla_{d} \mathbf{L}(y)\right|+\epsilon}, \quad d \in\{h, v\} \]
其中\(| ∗ |\) 是絕對值運算符,\(\omega(x)\) 是以像素 \(x\) 和 \(\epsilon\) 是一個很小的常數,以避免分母為零。

(2)封閉解

為了簡化複雜度,我們將Eq.6近似為在文獻[5]中公式:
\[ \min _{\mathbf{T}} \sum_{x}\left((\mathbf{T}(x)-\mathbf{L}(x))^{2}+\lambda \sum_{d \in\{h, v\}} \frac{\mathbf{M}_{d}(x)\left(\nabla_{d} \mathbf{T}(x)\right)^{2}}{\left|\nabla_{d} \mathbf{L}(x)\right|+\epsilon}\right) \]
可以看出,該式子現在僅涉及二次項。 令\(md\),\(l\),\(t\) 和 \(\nabla_dl\) 分別表示向量化的\(\mathbf{M}_d\),\(\mathbf{L}\),\(\mathbf{T}\) 和 \(\nabla_d \mathbf{L}\)。 然後可以通過求解以下線性函數直接獲得解
\[ \left(\mathbf{I}+\lambda \sum_{d \in\{h, v\}}\left(\mathbf{D}_{\mathbf{d}}^{\top} \operatorname{Diag}\left(\mathbf{m}_{d} \oslash\left(\left|\nabla_{d} \mathbf{l}\right|+\epsilon\right)\right) \mathbf{D}_{\mathbf{d}}\right) \mathbf{t}=1\right. \]
其中\(\varnothing\)是按元素劃分,\(\mathbf{I}\) 是單位矩陣,運算符 \(Diag(v)\) 是用向量 \(v\) 構造對角矩陣,\(\mathbf{D}_d\) 是Toeplitz矩陣來自具有前向差異的離散梯度算子。我們的光照圖估計方法和文獻[5]的主要區別是設計矩陣\(\mathbf{M}\) 的權重。我們採用了簡化的策略,可以產生在文獻[5]中相似的結果。基於Retinex的其他光照分解技術方法可以運用到這裏來找到權重矩陣 \(\mathbf{W}\)。

2.3 相機響應模型

在我們的早期工作中,我們提出了一個稱為Beta-Gamma的相機響應模型校正模型在文獻[[16]中。我們模型的BTF定義為:
\[ g(\mathbf{P},k)=\beta\mathbf{P^{\gamma}}=e^{b(1-k^a)\mathbf{P^{(k^a)}}} \]
其中 \(\beta\) 和 \(\gamma\) 是該模型的兩個參數,可以通過相機參數 \(a\) ,\(b\) 和曝光率 \(k\) 計算得到。我們假設沒有任何相機信息,並使用固定相機參數(\(a = -0.3293\) ,\(b = 1.1258\))可以適配大多數相機。

2.4 確定曝光率

在本小節中,我們找到最佳的曝光率,以便合成圖像在原始圖像曝光不足的區域中曝光良好。第一,我們排除曝光良好的像素,並獲得整體上低曝光圖像。我們只需提取低光照像素為:
\[ \mathbf{Q}=\{\mathbf{P}(x)\mathbf{T}(x)<0.5\} \]
其中 \(\mathbf{Q}\) 僅包含曝光不足的像素。不同曝光下圖像的亮度變化很大但是顏色基本相同。因此,我們在估計 \(k\) 只考慮了亮度分量。亮度分量 \(\mathbf{B}\) 定義為三個通道的幾何平均值:
\[ \mathbf{B}:=\sqrt[3]{\mathbf{Q}_r\circ\mathbf{Q}_g\circ\mathbf{Q}_b} \]
其中\(\mathbf{Q}_r\),\(\mathbf{Q}_g\) 和 \(\mathbf{Q}_b\) 分別是輸入圖像 \(\mathbf{Q}\) 的紅色,綠色和藍色通道。 我們使用幾何平均值代替其他定義(例如算術平均值和加權算術平均值),因為它具有相同的BTF所有三個顏色通道的模型參數( \(\beta\) 和 \(\gamma\)),如下所示:
\[ \begin{aligned} \mathbf{B}^{\prime} &:=\sqrt[3]{\mathbf{Q}_{r}^{\prime} \circ \mathbf{Q}_{g}^{\prime} \circ \mathbf{Q}_{b}^{\prime}} \\ &=\sqrt[3]{\left(\beta \mathbf{Q}_{r}^{\gamma}\right) \circ\left(\beta \mathbf{Q}_{g}^{\gamma}\right) \circ\left(\beta \mathbf{Q}_{b}^{\gamma}\right)}=\beta(\sqrt[3]{\mathbf{Q}_{r} \circ \mathbf{Q}_{g} \circ \mathbf{Q}_{b}})^{\gamma} \\ &=\beta \mathbf{B}^{\gamma} \end{aligned} \]
曝光良好的圖像的可見度高於曝光不足的圖像,它可以為人類提供更豐富的信息。因此,最優的 \(k\) 應該提供最多的信息。為了衡量信息的數量,我們使用圖像熵來定義:
\[ \mathcal{H}(\mathbf{B})=-\sum_{i=1}^{N} p_{i} \cdot \log _{2} p_{i} \]
其中 \(p_i\) 是B的直方圖的第i個bin,用於計算數據數量在 \([\frac{i}{N},\frac{i=1}{N}]\),N是bin數( \(N\) 通常設置為256)。最後,通過最大化圖像增強的亮度熵來計算最佳 \(k\) 值:
\[ \hat{k}=\underset{k}{\operatorname{argmax}} \mathcal{H}(g(\mathbf{B}, k)) \]
最優的 \(k\) 值可以通過一維最小化求解。為了改善計算效率,我們在優化 \(k\) 時將輸入圖像的大小調整為 \(50 \times 50\)。

3. 實驗對比

為了評估我們方法的性能,我們在公開數據集與幾種最新技術(AMSR [9],LIME [5],Dong [4]和NPE [14])進行比較。這五個公共數據集分別為:VV,LIME-data [5],NPE [14](NPEdata,NPE-ex1,NPE-ex2和NPE-ex3),MEF [10]和IUS [8]。 MEF和IUS是多重曝光數據集,我們從每個多重曝光中選擇一個弱光圖像進行評估。

3.1 實驗細節

在我們的算法中,\(\mu\) 是控制整體增強程度的參數。當\(\mu = 0\)時,所得 \(\mathbf{R}\) 等於 \(\mathbf{P}\),即沒有增強效果。 當時,曝光不足像素和曝光良好像素均被增強。 當 \(\mu> 1\) 時,像素可能會飽和,從而導致 \(R\) 遭受細節損失。 為了更好的增強,同時保留曝光良好的區域,我們將 \(\mu\) 設置為1/2。

為了保持比較的公平性,我們增強算法的參數在所有實驗中都是固定的:\(\lambda = 1\),\(\epsilon=0.001\) ,\(\mu= 1/2\),和局部窗口\(\omega(x)\)的大小為5。我們算法中最耗時的部分是光照圖的優化。 我們採用多分辨率共軛梯度 \((\mathbf{O}(N))\) 來有效地求解它。 為了進一步加快我們的算法,我們通過對輸入圖像進行下採樣方法解決 \(\mathbf{T}\),然後將生成的 \(\mathbf{T}\) 向上採樣到原始大小。 如果我們向下採樣一次,增強結果中沒有視覺差異,但是計算效率大大提高了。

3.2 對比度失真

如前所述,僅曝光度不同的圖像可用作參考來評估增強結果準確性。 DRIM(動態範圍獨立指標)[1]可以測量圖像對比度的失真,而無需考慮圖像亮度變化。 我們用它來可視化對比增強結果與參考圖像之間的差異。

如圖3所示,該方法得到失真最小的結果。 Dong的結果出現嚴重的對比度失真。 儘管AMSR可以恢復細節,但對比度明顯下降使結果看起來暗淡和虛幻。 相比之下,LIME的結果看起來像有點生動,但它們會導致放大不可見的對比度。 圖5显示更多示例在視覺上進行比較。

3.3 亮度失真

我們用我們使用亮度等級誤差(LOE)客觀地衡量增強結果的亮度失真。 LOE定義為:
\[ LOE=\frac{1}{m}\sum_{x=1}^{m}RD(x) \]
對於像素 \(x\), 其中 \(RD(x)\) 是原始圖像 \(P\) 和增強后\(P^{‘}\) 之間的相對亮度階差,其定義如下:
\[ RD(x)=\sum_{y=1}^{m} U(\mathbf{L}(x), \mathbf{L}(y)) \oplus U\left(\mathbf{L}^{\prime}(x), \mathbf{L}^{\prime}(y)\right) \]
其中 \(m\) 是像素數量,\(\oplus\) 表示異或運算符,\(\mathbf{L}(x)\),\(\mathbf{L}^{\prime}(x)\) 分別是輸入圖像和增強后位置 \(x\) 處的亮度分量。 如果 \(p>= q\),函數 \(U(p,q)\) 返回1,其他情況為0。

正如[5,14]中所建議的那樣,下採樣用於計算LOE降低複雜度。 由於RD將隨着像素數量m的增加而增加,我們注意到LOE隨着圖片下採樣為不同尺寸而變化。 因此,我們將所有圖像下採樣到固定大小。 具體來說,我們均勻收集100行和列,以形成 \(100\times100\) 的下採樣圖像。如表1所示,我們的算法在所有數據集中均優於其他算法。 這個意味着我們的算法可以很好地保持圖像的自然性。 我們也提供了圖4中兩種情況的亮度失真的可視化,從中我們可以發現我們的結果具有最小的亮度失真。AMSR的結果失去了全局的亮度等級,並具有最大的亮度失真。儘管LIME的結果在視覺上令人愉悅,但它們也存在亮度不足的失真。 Dong和NPE的結果只能在曝光良好的區域保留亮度順序。

4. 結論

在本文中,我們提出了曝光融合框架和增強算法提供精確的對比度增強的算法。 基於我們的框架,我們解決了三個問題:(1)我們借鑒了光照估算技術獲得圖像融合的權重矩陣。(2)通過相機響應模型來合成多重曝光圖像。(3)我們找到了最好的曝光率,使合成圖像在原始圖像曝光不足的區域曝光良好。 最終的增強結果是通過融合根據權重矩陣對輸入圖像和合成圖像進行處理。實驗結果表明我們方法相比現階段最先進的方案的有效性。
可以在我們的項目網站上找到更多測試結果:。

5. 主要參考文獻

  1. Aydin, T.O., Mantiuk, R., Myszkowski, K., Seidel, H.P.: Dynamic range independent image quality assessment. ACM Trans. Graph. (TOG) 27(3), 69 (2008)
  2. Beghdadi, A., Le Negrate, A.: Contrast enhancement technique based on local detection of edges. Comput. Vis. Graph. Image Process. 46(2), 162–174 (1989)
  3. Chen, S.D., Ramli, A.R.: Minimum mean brightness error bi-histogram equalization in contrast enhancement. IEEE Trans. Consum. Electron. 49(4), 1310–1319(2003)
  4. Dong, X., Wang, G., Pang, Y., Li, W., Wen, J., Meng, W., Lu, Y.: Fast efficient algorithm for enhancement of low lighting video. In: 2011 IEEE International Conference on Multimedia and Expo, pp. 1–6. IEEE (2011)
  5. Guo, X.: Lime: a method for low-light image enhancement. In: Proceedings of the 2016 ACM on Multimedia Conference, pp. 87–91. ACM (2016)
  6. Ibrahim, H., Kong, N.S.P.: Brightness preserving dynamic histogram equalization for image contrast enhancement. IEEE Trans. Consum. Electron. 53(4), 1752–1758 (2007)
  7. Jobson, D.J., Rahman, Z., Woodell, G.A.: A multiscale retinex for bridging the gap between color images and the human observation of scenes. IEEE Trans. Image Process. 6(7), 965–976 (1997)
  8. Karaduzovic-Hadziabdic, K., Telalovic, J.H., Mantiuk, R.: Subjective and objective evaluation of multi-exposure high dynamic range image deghosting methods (2016)
  9. Lee, C.H., Shih, J.L., Lien, C.C., Han, C.C.: Adaptive multiscale retinex for image contrast enhancement. In: 2013 International Conference on Signal-Image Technology & Internet-Based Systems (SITIS), pp. 43–50. IEEE (2013)
  10. Ma, K., Zeng, K., Wang, Z.: Perceptual quality assessment for multi-exposure image fusion. IEEE Trans. Image Process. 24(11), 3345–3356 (2015)
  11. Peli, E.: Contrast in complex images. JOSA A 7(10), 2032–2040 (1990)
  12. Reza, A.M.: Realization of the contrast limited adaptive histogram equalization (clahe) for real-time image enhancement. J. VLSI Signal Process. Syst. Signal Image Video Technol. 38(1), 35–44 (2004)
  13. Wang, C., Ye, Z.: Brightness preserving histogram equalization with maximum entropy: a variational perspective. IEEE Trans. Consum. Electron. 51(4), 1326– 1334 (2005)
  14. Wang, S., Zheng, J., Hu, H.M., Li, B.: Naturalness preserved enhancement algorithm for non-uniform illumination images. IEEE Trans. Image Process. 22(9), 3538–3548 (2013)
  15. Xu, L., Yan, Q., Xia, Y., Jia, J.: Structure extraction from texture via relative total variation. ACM Trans. Graph. (TOG) 31(6), 139 (2012)
  16. Ying, Z., Li, G., Ren, Y., Wang, R., Wang, W.: A new low-light image enhancement algorithm using camera response model, manuscript submitted for publication (2017)

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

讓特斯拉再飛一會兒,GM 不介意在電動車市場落後

特斯拉無疑是目前電動車領域的領先者,從電池、馬達到控制元件都比各大車廠先進,然而通用汽車並不擔心,儘管特斯拉在 2019 年賣出破紀錄的 37 萬輛車,仍然離通用的 290 萬輛非常遙遠。

上個月才宣布經典悍馬車電動化的消息,通用汽車(GM)手上還有越賣越賠錢的 Chevy Bolt,這兩款電動車從價格與性能來看,都不是特斯拉的對手,事實上目前市面大部分電動車都不是,但為什麼這些大車廠似乎不太擔心?

「我們預估今年車市會持續低迷,美國市場約會下滑 50 萬輛」,GM 財務長迪瓦‧蘇里亞帝娃拉(Dhivya Suryadevara)預估,中國跟南美洲也同樣會繼續衰退。

一片悲觀中,特斯拉一枝獨秀的股價讓 GM 看起來毫無抵抗之力,不過這些老車廠可不像股價看起來這麼脆弱。

特斯拉去年創下銷售新高紀錄,總共售出約 37 萬輛電動車,但虧損 8.25 億美元(第三、第四季為正);通用汽車則賣出 290 萬輛車,利潤 67 億美元。市場雖然寵愛特斯拉,但當這些大廠開始砸錢時,規模非常驚人。

GM 最近陸續宣布要改建底特律廠,當作電動車與自駕車專用產線;同時還與南韓 LG Chem 合作興建新電池廠,總共投入 45 億美元,但什麼時候要真的把錢砸下去,還在看正確的時間點。

電動車的獲利關鍵:電池成本

「對 GM 來說,不介意在市場讓特斯拉領先,之後再砸大錢追上它。」彭博汽車產業分析師 Kevin Tynan 認為,當電動車真正能獲利時,GM 隨時都能衝出大量產能並快速生產。

隨著政府補貼逐漸縮水或退場,特斯拉的高價車款銷量大幅下滑,而在市場快速成長的 Model 3 最終也只在去年幫特斯拉多賺了 0.3% 營收。

電動車要能賺錢,關鍵還是回到製造成本,尤其是電池成本,想要降低成本就必須靠技術突破,然而特斯拉已經在 2019 第三季開始放慢資本支出的速度,以彌補前兩季的虧損,整個 2019 年資本支出跟前一年幾乎相同,因此才能在上一季交出漂亮財報,同時帶動股價一飛沖天。

反觀通用汽車,即使去年遭遇勞資問題,導致 36 億美元損失,全年獲利還是高達 67 億美元,面對萎縮的全球車市,GM 還是預估休旅車跟皮卡車能維持不錯獲利,而這些收入都將持續投入電池與電動車技術開發。

GM 電池工程總監 Tim Grewe 認為,要開發擁有價格效益的電池,還需要更先進的科技,而這些科技如今都還沒實現。在那之前,特斯拉將會持續稱霸電動車銷售市場,並繼續虧損。

最近特斯拉除了推進自行研發電池的進度,也在研討與寧德時代合作無鈷電池,以降低電池成本。這場電動車電池的爭戰,GM 決定讓特斯拉再飛一會,但會不會一不小心,就飛到再也追不到的地方了?

(合作媒體:。首圖來源:)

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

電動車將造成下個石油崩跌危機:BNEF

電動車目前佔車市的市佔仍然相當低,油國與石油業界對電動車也一向相當輕視,各項預測與未來石油產銷報告中往往都認為電動車的影響可略,不過,彭博新能源財經(Bloomberg New Energy Finance)的看法與他們相左,預言電動車將大行其道,而造成下一次油價崩跌危機。   目前電動車與充電式油電混合車只佔全球車市的 0.1%,在多數國家中都相當少見,而且車價也遠比汽油車高,油國組織(OPEC)十分輕視電動車的發展,認為到 2040 年電動車也只會佔車市的 1%,而石油業巨頭康菲(ConocoPhillips)也一樣不把電動車當一回事,執行長萊恩蘭斯(Ryan Lance)表示,電動車在 50 年內,甚至在他畢生之內,都不會有實際的影響力,另一家石油巨擘艾克森美孚(Exxon)也看扁電動車市佔只能達到 2%。   然而彭博新能源財經的看法完全相左,認為 2020 年代就會是電動車的時代。電動車的基本經濟層面逐漸往有利的方向發展,其中最重要的就是佔成本相當大比例的電池,在 2015 年電池成本下降 35%,以這個下降速度,6 年內電動車就會比汽油車更具經濟效益,電動車將會迎來市場起飛期,到 2040 年時,長程電動車將要價不到 2.2 萬美元,相當於約 73 萬元新台幣,在這樣的親民價格下,屆時全球 35% 新車銷售都將是電動車。   事實上,不用看到那麼遙遠,就在幾年內,不論是特斯拉(Tesla),雪佛蘭(Chevrolet)、日產(Nissan)等車廠都計劃推出 3 萬美元價位的長程電動車,相當於約百萬元新台幣,其他車廠與 IT 大廠則正投注數以十億美元計的資本,進行相關技術與車種的研發,預期到 2020 年,部分電動車種將會售價比同級汽油車便宜,效能還更好,屆時許多電動車將會銷售勝過同級汽油車,事實上,不用到 2020 年,目前特斯拉的 Model S 銷售就已經勝過同級的汽油高級車。   2015 年電動車銷售成長 60%,大體上與特斯拉預期到 2020 年為止的每年年成長率一致,在歷史上,這也剛好正是福特 T 型車當初取代馬車時的成長率,因此,與石油界所想的相反,電動車起飛很快會對石油需求發生實際影響,彭博社計算,若是電動車繼續以 60% 年成長率成長,最早至 2023 年,而若以比較精確的成長模型計算,至 2028 年,電動車將會減少每日 200 萬桶石油需求,而 2014 年石油價格大崩盤,其實也不過就是供過於求 200 萬桶而已。  
共享服務成為電動車發展推手   彭博新能源財經的較精確估算模型,基本構想是分別計算電動車的各主要零組件以及各種成本下降的情況,考量降價後消費者對電動車接受度的提升幅度,來預期電動車的普及速度,比較電動車與汽油車的維修成本、汽油成本,以及最重要的電池成本。目前電池成本佔電動車總製造成本約三分之一,而鋰電池成本正在快速下降,從過去每度電容量 1,000 美元以上,已經降至 400 美元以下,以彭博新能源財經預估的下降曲線,至 2030 年更有可能降至 100 美元上下。   另一方面則要考慮用電問題,若電動車真的如預期起飛,到 2040 年,將會年消耗 19 億度電力,相當於 2015 年全人類發電量的 10%。不過,電動車本身對電力系統也會有所幫助,電動車所用的電池,老化至容量剩 80% 時就必須汰換,但此時電池用來做為能源儲存用途還綽綽有餘,隨著電動車發展,二次使用的電池也將推動能源儲存,因而促進電網電力更有效的運用,也能推動風能、太陽能等潔淨能源的發展,而能補上電動車的電力需求。   但電池產量大增,不會因為原物料漲價而無法降價嗎?彭博新能源財經計算發現,到 2030 年,製造鋰電池所使用的鋰、鎳、錳、銅,也不過僅佔目前地球已知蘊藏量的 1% 而已,而到了 2030 年以後的新電池,則可能已經不是目前的鋰電池,而是未來的新電池技術,使用不同的原物料,打造更輕薄短小且更便宜的電池。   共享服務如 Uber 與 Lyft 也將成為電動車的推手,共乘機制讓汽車使用率提高,可能 1 年行駛里程數超過 2 萬英里,在如此高里程下,汽油車的維護成本將大為高於電動車,而電動車電池所省下來的汽油成本也將成正比增加,因而使得電池成本攤提下來相對更加便宜,因此彭博新能源財經推估,若是共享服務成功發展,將大為提升電動車市佔率,在 2040 年時達到新車出貨量的 5 成。   不過電動車是否會造成油價崩盤,還有許多變數,如儘管電動車本身成本降低,但是也要配合更多充電站建設才能促使消費者買單,此外。若是油價降至 20 美元且不再回升,低廉的油價本身就會促進石油需求,進而抵銷電動車所減少的汽油需求。但無論如何,電動車起飛只是開端,之後每年都會有更多電動車上路,取代傳統汽油車,而進一步持續減少石油在交通方面的需求,遲早有一天會讓石油走上末路。

(首圖來源: CC BY 2.0)    (本文授權轉載自《》─〈〉)

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

小白學 Python(22):time 和 calendar 模塊簡單使用

人生苦短,我選Python

前文傳送門

time 模塊

今天我們要介紹的是一個會經常用到的模塊—— time ,顧名思義,這是一個時間相關的模塊。前面我們也介紹過常用模塊,比如 os 模塊,在使用這些模塊前,我們需要先將它導入進來。 time 模塊的導入方式如下:

import time

先來一個簡單的樣例吧:

for i in range(0, 5):
    print(i)
    time.sleep(1)

打印結果我就不展示了,同學們應該都猜得到。那麼 time.sleep(1) 這句話的作用是什麼呢?

sleep() 方法是一個睡眠方法,意思就是程序執行到這裏,需要等待一會,什麼都不做,上面的代碼在執行的時候可以發現,每隔 1s 會打印一個数字, sleep() 裏面給出的參數是休眠的時間,單位是秒。

time 模塊的常用方法

首當其沖當然是獲取當前的時間戳。

print(time.time())

結果如下:

1573054874.6483195

這裏就看不懂了哇,我先來解釋下什麼是時間戳。

在程序中,一般已1970年1月1日0時0分0秒作為起始時間,時間戳就是從起始時間到現在的時長,在 Python 中,這個時長的單位是秒。那麼為什麼起始時間是1970年1月1日0時0分0秒呢?

emmmmmmmmmmmmm,這個我還真不知道,據我所了解的語言,所有的時間戳都是從這個時間點開始起算的。我順手幫各位同學百度了下,表示並沒有找到答案。

不要糾結這個問題了,我們看下一個。

print(time.localtime())

結果如下:

time.struct_time(tm_year=2019, tm_mon=11, tm_mday=6, tm_hour=23, tm_min=47, tm_sec=13, tm_wday=2, tm_yday=310, tm_isdst=0)

這個方法會給出詳細的當前的本地時間,可以細化到年、月、日、小時、分鐘、秒等。

注意: 這個時間是當前本地的計算的時間哦,如果修改計算機的時間,這個值會發生相應的改變的。

print(time.mktime(time.localtime()))

結果如下:

1573055380.0

各位同學看着打印結果應該已經猜到了, mktime() 可以將當前的本地時間轉化為一個時間戳。

以上不管是時間戳、還是本地時間,看起來並不方便,下面我們介紹如何格式化時間。

最簡單的方法,可以使用函數 asctime() 。

print(time.asctime(time.localtime()))

結果如下:

Wed Nov  6 23:53:52 2019

這個結果還帶着英文,並不符合中國人的習慣嘛,別急,我們還可以自定義格式。

print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))

結果如下:

2019-11-06 23:55:56

這樣看着就舒服多了么,我們可以通過 strftime() 來自定義日期格式。

這裏列舉一下日期格式化的符號:

  • %y 兩位數的年份表示(00-99)
  • %Y 四位數的年份表示(000-9999)
  • %m 月份(01-12)
  • %d 月內中的一天(0-31)
  • %H 24小時制小時數(0-23)
  • %I 12小時制小時數(01-12)
  • %M 分鐘數(00=59)
  • %S 秒(00-59)
  • %a 本地簡化星期名稱
  • %A 本地完整星期名稱
  • %b 本地簡化的月份名稱
  • %B 本地完整的月份名稱
  • %c 本地相應的日期表示和時間表示
  • %j 年內的一天(001-366)
  • %p 本地A.M.或P.M.的等價符
  • %U 一年中的星期數(00-53)星期天為星期的開始
  • %w 星期(0-6),星期天為星期的開始
  • %W 一年中的星期數(00-53)星期一為星期的開始
  • %x 本地相應的日期表示
  • %X 本地相應的時間表示
  • %Z 當前時區的名稱
  • %% %號本身

哇,這也太多了,記不住怎麼辦?

其實這個並不需要你都記下來,只需要記住常用的就好了,就比如我上面使用的年、月、日、時、分、秒。其餘的不常用的可以在有需要的時候再來查表。

有時候時間之間不使用短橫杠 - 來隔開,而選擇使用斜杠 / 隔開,這個怎麼辦?

這個很簡單咯,看我的:

print(time.strftime("%Y/%m/%d %H:%M:%S", time.localtime()))

結果如下:

2019/11/07 00:02:18

calendar 模塊

都聊到這裏了,我們順便再聊一個模塊,日曆。

先看下代碼演示吧,這個就比較有意思了:

import calendar

print(calendar.calendar(theyear=2020, w=2, l=1, c=6))

結果如下:

                                  2020

      January                   February                   March
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                      1  2                         1
 6  7  8  9 10 11 12       3  4  5  6  7  8  9       2  3  4  5  6  7  8
13 14 15 16 17 18 19      10 11 12 13 14 15 16       9 10 11 12 13 14 15
20 21 22 23 24 25 26      17 18 19 20 21 22 23      16 17 18 19 20 21 22
27 28 29 30 31            24 25 26 27 28 29         23 24 25 26 27 28 29
                                                    30 31

       April                      May                       June
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                   1  2  3       1  2  3  4  5  6  7
 6  7  8  9 10 11 12       4  5  6  7  8  9 10       8  9 10 11 12 13 14
13 14 15 16 17 18 19      11 12 13 14 15 16 17      15 16 17 18 19 20 21
20 21 22 23 24 25 26      18 19 20 21 22 23 24      22 23 24 25 26 27 28
27 28 29 30               25 26 27 28 29 30 31      29 30

        July                     August                  September
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
       1  2  3  4  5                      1  2          1  2  3  4  5  6
 6  7  8  9 10 11 12       3  4  5  6  7  8  9       7  8  9 10 11 12 13
13 14 15 16 17 18 19      10 11 12 13 14 15 16      14 15 16 17 18 19 20
20 21 22 23 24 25 26      17 18 19 20 21 22 23      21 22 23 24 25 26 27
27 28 29 30 31            24 25 26 27 28 29 30      28 29 30
                          31

      October                   November                  December
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                         1          1  2  3  4  5  6
 5  6  7  8  9 10 11       2  3  4  5  6  7  8       7  8  9 10 11 12 13
12 13 14 15 16 17 18       9 10 11 12 13 14 15      14 15 16 17 18 19 20
19 20 21 22 23 24 25      16 17 18 19 20 21 22      21 22 23 24 25 26 27
26 27 28 29 30 31         23 24 25 26 27 28 29      28 29 30 31
                          30

我們把 2020 年的日曆打印出來了。

  • w = 每個日期之間的間隔字符數
  • l = 每周所佔用的行數
  • c = 每個月之間的間隔字符數

以後我們看日曆可以使用這個函數看了。

要用你們用,反正我是不用,我選擇使用這個:

除了直接返回全年的日曆,calendar 還支持返回指定月份的日曆:

print(calendar.month(2019, 11))

結果如下:

   November 2019
Mo Tu We Th Fr Sa Su
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30

我們還可以直接獲得某月的總天數:

print(calendar.monthlen(2019, 11))

結果如下:

30

這個功能好像有點雞肋,我們獲取某月的天數難道不是都靠那句兒歌么?

一三五七八十臘,三十一天永不差

我們還可以知道指定的日期對應的星期數:

print(calendar.weekday(2019, 11, 7))

結果如下:

3

這個我覺得蠻實用的,再也不用自己寫算法去推算了。

示例代碼

本系列的所有代碼小編都會放在代碼管理倉庫 Github 和 Gitee 上,方便大家取用。

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

淺談什麼是動態規劃以及相關的「股票」算法題:一網打盡「買賣股票的最佳時機」

本文首發於公眾號「五分鐘學算法」,是系列文章之一。

個人網站:

動態規劃

1 概念

  動態規劃算法是通過拆分問題,定義問題狀態和狀態之間的關係,使得問題能夠以遞推(或者說分治)的方式去解決。在學習動態規劃之前需要明確掌握幾個重要概念。

  階段:對於一個完整的問題過程,適當的切分為若干個相互聯繫的子問題,每次在求解一個子問題,則對應一個階段,整個問題的求解轉化為按照階段次序去求解。

  狀態:狀態表示每個階段開始時所處的客觀條件,即在求解子問題時的已知條件。狀態描述了研究的問題過程中的狀況。

  決策:決策表示當求解過程處於某一階段的某一狀態時,可以根據當前條件作出不同的選擇,從而確定下一個階段的狀態,這種選擇稱為決策。

  策略:由所有階段的決策組成的決策序列稱為全過程策略,簡稱策略。

  最優策略:在所有的策略中,找到代價最小,性能最優的策略,此策略稱為最優策略。

  狀態轉移方程:狀態轉移方程是確定兩個相鄰階段狀態的演變過程,描述了狀態之間是如何演變的。

2 使用場景

能採用動態規劃求解的問題的一般要具有 3 個性質:

  (1)最優化:如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優化原理。子問題的局部最優將導致整個問題的全局最優。換句話說,就是問題的一個最優解中一定包含子問題的一個最優解。

  (2)無後效性:即某階段狀態一旦確定,就不受這個狀態以後決策的影響。也就是說,某狀態以後的過程不會影響以前的狀態,只與當前狀態有關,與其他階段的狀態無關,特別是與未發生的階段的狀態無關。

   (3)重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規劃算法同其他算法相比就不具備優勢)

3 算法流程

  (1)劃分階段:按照問題的時間或者空間特徵將問題劃分為若干個階段。
  (2)確定狀態以及狀態變量:將問題的不同階段時期的不同狀態描述出來。
  (3)確定決策並寫出狀態轉移方程:根據相鄰兩個階段的各個狀態之間的關係確定決策。
  (4)尋找邊界條件:一般而言,狀態轉移方程是遞推式,必須有一個遞推的邊界條件。
  (5)設計程序,解決問題

實戰練習

下面的三道算法題都是來源於 LeetCode 上與股票買賣相關的問題 ,我們按照 動態規劃 的算法流程來處理該類問題。

股票買賣這一類的問題,都是給一個輸入數組,裏面的每個元素表示的是每天的股價,並且你只能持有一支股票(也就是你必須在再次購買前出售掉之前的股票),一般來說有下面幾種問法:

  • 只能買賣一次
  • 可以買賣無數次
  • 可以買賣 k 次

需要你設計一個算法去獲取最大的利潤。

買賣股票的最佳時機

題目來源於 LeetCode 上第 121 號問題:買賣股票的最佳時機。題目難度為 Easy,目前通過率為 49.4% 。

題目描述

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多只允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。

注意你不能在買入股票前賣出股票。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
     注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格。

示例 2:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

題目解析

我們按照動態規劃的思想來思考這道問題。

狀態

有 買入(buy) 和 賣出(sell) 這兩種狀態。

轉移方程

對於買來說,買之後可以賣出(進入賣狀態),也可以不再進行股票交易(保持買狀態)。

對於賣來說,賣出股票后不在進行股票交易(還在賣狀態)。

只有在手上的錢才算錢,手上的錢購買當天的股票后相當於虧損。也就是說當天買的話意味着損失-prices[i],當天賣的話意味着增加prices[i],當天賣出總的收益就是 buy+prices[i] 。

所以我們只要考慮當天買和之前買哪個收益更高,當天賣和之前賣哪個收益更高。

  • buy = max(buy, -price[i]) (注意:根據定義 buy 是負數)
  • sell = max(sell, prices[i] + buy)

邊界

第一天 buy = -prices[0], sell = 0,最後返回 sell 即可。

代碼實現

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length <= 1)
            return 0;
        int buy = -prices[0], sell = 0;
        for(int i = 1; i < prices.length; i++) {
            buy = Math.max(buy, -prices[i]);
            sell = Math.max(sell, prices[i] + buy);

        }
        return sell;
    }
}

買賣股票的最佳時機 II

題目來源於 LeetCode 上第 122 號問題:買賣股票的最佳時機 II。題目難度為 Easy,目前通過率為 53.0% 。

題目描述

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參与多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
     因為這樣屬於同時參与了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

題目解析

狀態

有 買入(buy) 和 賣出(sell) 這兩種狀態。

轉移方程

對比上題,這裏可以有無限次的買入和賣出,也就是說 買入 狀態之前可擁有 賣出 狀態,所以買入的轉移方程需要變化。

  • buy = max(buy, sell – price[i])
  • sell = max(sell, buy + prices[i] )

邊界

第一天 buy = -prices[0], sell = 0,最後返回 sell 即可。

代碼實現

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length <= 1)
            return 0;
        int buy = -prices[0], sell = 0;
        for(int i = 1; i < prices.length; i++) {
            sell = Math.max(sell, prices[i] + buy);
            buy = Math.max( buy,sell - prices[i]);
        }
        return sell;
    }
}

買賣股票的最佳時機 III

題目來源於 LeetCode 上第 123 號問題:買賣股票的最佳時機 III。題目難度為 Hard,目前通過率為 36.1% 。

題目描述

給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。

注意: 你不能同時參与多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
     隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。   
     因為這樣屬於同時參与了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1] 
輸出: 0 
解釋: 在這個情況下, 沒有交易完成, 所以最大利潤為 0。

題目解析

這裏限制了最多兩筆交易。

狀態

有 第一次買入(fstBuy) 、 第一次賣出(fstSell)、第二次買入(secBuy) 和 第二次賣出(secSell) 這四種狀態。

轉移方程

這裏最多兩次買入和兩次賣出,也就是說 買入 狀態之前可擁有 賣出 狀態,賣出 狀態之前可擁有 買入 狀態,所以買入和賣出的轉移方程都需要變化。

  • fstBuy = max(fstBuy , -price[i])
  • fstSell = max(fstSell,fstBuy + prices[i] )
  • secBuy = max(secBuy ,fstSell -price[i]) (受第一次賣出狀態的影響)
  • secSell = max(secSell ,secBuy + prices[i] )

邊界

  • 一開始 fstBuy = -prices[0]
  • 買入后直接賣出,fstSell = 0
  • 買入后再賣出再買入,secBuy - prices[0]
  • 買入后再賣出再買入再賣出,secSell = 0

最後返回 secSell 。

代碼實現

class Solution {
    public int maxProfit(int[] prices) {
        int fstBuy = Integer.MIN_VALUE, fstSell = 0;
        int secBuy = Integer.MIN_VALUE, secSell = 0;
        for(int i = 0; i < prices.length; i++) {
            fstBuy = Math.max(fstBuy, -prices[i]);
            fstSell = Math.max(fstSell, fstBuy + prices[i]);
            secBuy = Math.max(secBuy, fstSell -  prices[i]);
            secSell = Math.max(secSell, secBuy +  prices[i]); 
        }
        return secSell;

    }
}

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

特斯拉休旅車 Model X Signarue 9 月 29 日開始交車

美國電動車大廠特斯拉(Tesla)於 9 月 2 日宣布,第一部豪華電動跨界車款 Model X Signarue 系列訂 9 月 29 日開始交車。Model X 早在 2012 年初就已用概念車的形式發表,但交車時間一延再延。特斯拉發言人說,這款車的售價約在 13.2 萬到 14.4 萬美元之間。   特斯拉執行長穆斯克在推特上表示,第一批生產的車輛將在 29 日當天於加州 Fremont 廠交車。而較平價的 Model 3 則將於 2 年後開始生產。   限定版的 Signature 系列車,車身將有 Model X 所沒有的獨特紅,還有自動停車和升級的音效設備等。

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

transformer模型簡介

Transformer模型由《Attention is All You Need》提出,有一個完整的Encoder-Decoder框架,其主要由attention(注意力)機制構成。論文地址:。

其整體結構如圖所示:

 

模型分為編碼器(Encoder)和解碼器(Decoder)兩部分,包含內部結構的總體結構如下圖所示:

 

                                                      圖二

在論文中編碼器部分由6個相同編碼器疊在一起,解碼器部分也是由6個相同解碼器疊在一起,編碼器之間不共享參數。(這裏不一定要是6個)

在將詞向量表示送入編碼器、解碼器之前,先做positional encoding,下面依次對positional encoding、encoding、decoding進行介紹:

1、positional encoding

 

 

如圖所示,由於attention機制不包含位置信息,因此句子首先進行embedding得到詞向量表示,同時為了增加位置信息,根據句子中詞的位置信息給詞嵌入添加位置編碼向量,論文中添加位置編碼的方法是:構造一個跟輸入embedding維度一樣的矩陣,然後跟輸入embedding相加得到multi-head attention 的輸入。

作者希望引入絕對位置的編碼公式,讓模型能夠學習到相對位置信息,作者使用的positional encoding生成固定位置表示如下:

已知三角函數公式如下:

 

 

 作者希望通過絕對位置的編碼公式,讓模型可以學習到相對位置信息。雖然如此獲得的 position embeddings,兩者之間的點積能夠反應相對距離,但它缺乏方向性,並且這種特性(相對距離)會被原始 Transformer 的注意力機制破壞。

基於公式 (1),位置t的位置嵌入可以表示為:

 

 

 

 

 

 

2、encoding

如圖二左邊結構所示,編碼器主要由前饋神經網絡層與多頭自注意力層構成,值得注意的是,在每個編碼器中的每個子層(自注意力、前饋網絡)的周圍都有一個殘差連接,並且都跟隨着一個“層-歸一化”步驟。這裏先介紹attention機制,還是舉個栗子:

假設我們想要翻譯這個句子:

“The animal didn’t cross the street because it was too tired”

那麼it在這句話中是是指animal還是street,人類好理解這句話,但是對機器來說就很困難了。當模型處理這個單詞“it”的時候,自注意力機制會允許“it”與“animal”建立聯繫。隨着模型處理輸入序列的每個單詞,自注意力會關注整個輸入序列的所有單詞,幫助模型對本單詞更好地進行編碼。如下圖。

 

當我們在編碼器#5(棧中最上層編碼器)中編碼“it”這個單詞的時,注意力機制的部分會去關注“The Animal”,將它的表示的一部分編入“it”的編碼中。

接下來介紹attention實現的思想。

計算自注意力的第一步就是從每個編碼器的輸入向量(每個單詞的詞向量)中生成三個向量。也就是說對於每個單詞,我們創造一個查詢向量、一個鍵向量和一個值向量。這三個向量是通過詞嵌入與三個權重矩陣后相乘創建的。在論文中這三個向量的維度比詞嵌入向量要低,實際中維度更低不是必須的,只是架構上的選擇,可以使多頭注意力的大部分計算保持不變。

計算自注意力的第二步是計算得分。假設我們需要對第一個詞’Thinking’計算自注意力向量那麼需要拿輸入句子中的每個單詞對“Thinking”打分。這些分數決定了在編碼單詞“Thinking”的過程中有多重視句子的其它部分。

這些分數是通過打分單詞(所有輸入句子的單詞)的鍵向量與“Thinking”的查詢向量相點積來計算的。所以如果我們是處理位置最靠前的詞的自注意力的話,第一個分數是q1和k1的點積,第二個分數是q1和k2的點積。

 

第三步和第四步是將分數除以8(8是論文中使用的鍵向量的維數64的平方根,這會讓梯度更穩定。這裏也可以使用其它值,8隻是默認值),然後通過softmax傳遞結果。softmax的作用是使所有單詞的分數歸一化,得到的分數都是正值且和為1。

 

這個softmax分數決定了每個單詞對編碼當下位置(“Thinking”)的貢獻。顯然,已經在這個位置上的單詞將獲得最高的softmax分數,但有時關注另一個與當前單詞相關的單詞也會有幫助。

第五步是將每個值向量乘以softmax分數(這是為了準備之後將它們求和)。這裏的直覺是希望關注語義上相關的單詞,並弱化不相關的單詞。

第六步是對加權值向量求和,然後即得到自注意力層在該位置的輸出。

 

這樣自注意力的計算就完成了。得到的向量就可以傳給前饋神經網絡。

在現實中自注意力機制是通過矩陣來實現的,與上面思路一樣:

第一步是計算查詢矩陣、鍵矩陣和值矩陣,如下圖所示:

 

將前面的計算步驟可以合併成:

 

介紹完自注意力機制后,介紹在論文中使用的多頭自注意力機制“multi-headed” attention。

 

每個頭都是獨立的查詢/鍵/值權重矩陣,從而產生不同的查詢/鍵/值矩陣。在論文中採用的是8頭,那麼經過8次不同權重矩陣運算,我們會得到8個不同的Z矩陣。

 

然後我們將這8個矩陣壓縮成一個矩陣,實現原理是將這8個矩陣拼接在一起,然後再用一個權重矩陣與之相乘,得到一個融合所有注意力頭信息的矩陣Z,再將其求和與歸一化後傳給前饋層。

 

Decoding(解碼器):

解碼器內部組件與編碼器大同小異,需要注意的是,解碼器的第一個注意力層被稱作MaskedMulti-Head Attention,通過加入了MASK操作,使得我們只被允許處理輸出序列中更靠前的那些位置,即我們只能attend到前面已經處理過的語句。第二個注意力層被稱作encoder-decoder attention layer,由圖二可知,它的query來自前一級的decoder層的輸出,key、value來自encoder的輸出,encoder的輸出可以幫助解碼器關注輸入序列哪些位置合適。接下來送入前饋層,然後重複這些步驟,直到到達一個特殊的終止符號,它表示transformer的解碼器已經完成了它的輸出。每個步驟的輸出在下一個時間步被提供給底端解碼器,並且就像編碼器之前做的那樣,這些解碼器會輸出它們的解碼結果 。另外,就像我們對編碼器的輸入所做的那樣,我們會嵌入並添加位置編碼給那些解碼器,來表示每個單詞的位置。

 

在解碼完成後會輸出一個實數向量,經過一個簡單的全連接神經網絡(線性變換層)映射到一個被稱作對數幾率(logits)的向量里,假設從訓練集中學習一萬個單詞,那麼對數幾率向量為一萬個單元格長度的向量——每個單元格對應某一個單詞的分數。接下來的Softmax 層便會把那些分數變成概率(都為正數、上限1.0)。概率最高的單元格被選中,並且它對應的單詞被作為這個時間步的輸出。

 

 

 參考:

   

           

         

 

 

(完)

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

實現中國新能源汽車大發展,還需解決4大難題

一系列政策推出後,新能源汽車進入了快速發展階段,據數據統計,前10個月,我國新能源汽車產銷分別增長了2.7倍和2.9倍,而且目前私人購買新能源汽車增長快速。然而,制約新能源汽車發展的因素逐漸顯現。  
1.充電基礎設施建設滯後   Interbrand公司的孟祥峰博士表示,到2014年底全國的示範城市推廣的新能源汽車數量是9.1萬輛,但是充電樁只有3.1萬個,充電樁和新能源車的比例明顯不足。   11月18日,國家發展改革委等四部門聯合發佈《電動汽車充電基礎設施發展指南(2015~2020年)》表示,到2020年,新增集中式充換電站超過1.2萬座,分散式充電樁超過480萬個,以滿足全國500萬輛電動汽車充電需求。  
2.動力電池產業佈局系統性不足   從去年下半年開始,隨著整車銷量的快速增長,動力電池產品供不應求,限制了整車的生產和推廣。廣汽集團總經理曾慶洪表示,國家要求2025年電池的能量密度要達到每公斤350瓦時,現在是150~250瓦時左右。   清華大學教授歐陽明高透露,我國今年1-9月份乘用車電池的用量超過了61億瓦時,預計全年至少會超過110億瓦時,預計2020年有可能達到1000億瓦時。同時,歐陽明高表示,我國今年電池的投資大概接近1000億元,已有和在建的產能投資也超過了800億元,預計明年下半年電池供需總量基本平衡。  
3.整車技術有待提升   廣州日報記者在第二屆廣州國際電動汽車展覽會上看到,目前我國自主推出的電動車在車型方面仍有待提升,不少電動車的外形與傳統燃油車各種“靚”形成較大反差。一對夫婦看了某品牌的電動汽車後表示,開這種車確實是綠色出行,但很卡通的外形用來見商務客戶就不太合適了。  
4.過分依賴補貼   清華大學教授歐陽明高表示,目前國內新能源汽車存在的問題之一就是補貼的依賴還比較嚴重,補貼對整個市場產生的作用應該說過大,需要進行合理化。他認為,尤其是6~8米的客車,目前來說補貼的合理性有待完善。   中國國際貿易促進委員會汽車行業分會會長王俠也表示,我國的汽車市場規模龐大,全產業鏈發展的自然基礎,但是能否實現全產業鏈的協調發展,其中政策的制定是系統的工程,沒有補貼不行,補貼時間過長也不行。王俠表示,企業的當務之急是,實現核心技術的突破,降低成本,提高產品的安全性和可靠性,改變單純依靠補貼的盈利模式。   資料來源:廣州日報

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

Uber Go 語言編碼規範

Uber Go 語言編碼規範

是一家美國硅谷的科技公司,也是 Go 語言的早期 adopter。其開源了很多 golang 項目,諸如被 Gopher 圈熟知的 、 等。2018 年年末 Uber 將內部的 開源到 GitHub,經過一年的積累和更新,該規範已經初具規模,並受到廣大 Gopher 的關注。本文是該規範的中文版本。本版本會根據原版實時更新。

## 版本

  • 當前更新版本:2019-11-13 版本地址:
  • 如果您發現任何更新、問題或改進,請隨時 fork 和 PR
  • Please feel free to fork and PR if you find any updates, issues or improvement.

目錄

介紹

樣式 (style) 是支配我們代碼的慣例。術語樣式有點用詞不當,因為這些約定涵蓋的範圍不限於由 gofmt 替我們處理的源文件格式。

本指南的目的是通過詳細描述在 Uber 編寫 Go 代碼的注意事項來管理這種複雜性。這些規則的存在是為了使代碼庫易於管理,同時仍然允許工程師更有效地使用 Go 語言功能。

該指南最初由 和 編寫,目的是使一些同事能快速使用 Go。多年來,該指南已根據其他人的反饋進行了修改。

本文檔記錄了我們在 Uber 遵循的 Go 代碼中的慣用約定。其中許多是 Go 的通用準則,而其他擴展準則依賴於下面外部的指南:

所有代碼都應該通過golint和go vet的檢查並無錯誤。我們建議您將編輯器設置為:

  • 保存時運行 goimports
  • 運行 golint 和 go vet 檢查錯誤

您可以在以下 Go 編輯器工具支持頁面中找到更為詳細的信息:

指導原則

指向 interface 的指針

您幾乎不需要指向接口類型的指針。您應該將接口作為值進行傳遞,在這樣的傳遞過程中,實質上傳遞的底層數據仍然可以是指針。

接口實質上在底層用兩個字段表示:

  1. 一個指向某些特定類型信息的指針。您可以將其視為”type”。
  2. 數據指針。如果存儲的數據是指針,則直接存儲。如果存儲的數據是一個值,則存儲指向該值的指針。

如果希望接口方法修改基礎數據,則必須使用指針傳遞。

接收器 (receiver) 與接口

使用值接收器的方法既可以通過值調用,也可以通過指針調用。

例如,

type S struct {
  data string
}

func (s S) Read() string {
  return s.data
}

func (s *S) Write(str string) {
  s.data = str
}

sVals := map[int]S{1: {"A"}}

// 你只能通過值調用 Read
sVals[1].Read()

// 這不能編譯通過:
//  sVals[1].Write("test")

sPtrs := map[int]*S{1: {"A"}}

// 通過指針既可以調用 Read,也可以調用 Write 方法
sPtrs[1].Read()
sPtrs[1].Write("test")

同樣,即使該方法具有值接收器,也可以通過指針來滿足接口。

type F interface {
  f()
}

type S1 struct{}

func (s S1) f() {}

type S2 struct{}

func (s *S2) f() {}

s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}

var i F
i = s1Val
i = s1Ptr
i = s2Ptr

//  下面代碼無法通過編譯。因為 s2Val 是一個值,而 S2 的 f 方法中沒有使用值接收器
//   i = s2Val

中有一段關於 的精彩講解。

零值 Mutex 是有效的

零值 sync.Mutex 和 sync.RWMutex 是有效的。所以指向 mutex 的指針基本是不必要的。

Bad Good
“`go mu := new(sync.Mutex) mu.Lock() “` “`go var mu sync.Mutex mu.Lock() “`

如果你使用結構體指針,mutex 可以非指針形式作為結構體的組成字段,或者更好的方式是直接嵌入到結構體中。
如果是私有結構體類型或是要實現 Mutex 接口的類型,我們可以使用嵌入 mutex 的方法:

“`go type smap struct { sync.Mutex // only for unexported types(僅適用於非導出類型) data map[string]string } func newSMap() *smap { return &smap{ data: make(map[string]string), } } func (m *smap) Get(k string) string { m.Lock() defer m.Unlock() return m.data[k] } “` “`go type SMap struct { mu sync.Mutex // 對於導出類型,請使用私有鎖 data map[string]string } func NewSMap() *SMap { return &SMap{ data: make(map[string]string), } } func (m *SMap) Get(k string) string { m.mu.Lock() defer m.mu.Unlock() return m.data[k] } “`
為私有類型或需要實現互斥接口的類型嵌入。 對於導出的類型,請使用專用字段。

在邊界處拷貝 Slices 和 Maps

slices 和 maps 包含了指向底層數據的指針,因此在需要複製它們時要特別注意。

接收 Slices 和 Maps

請記住,當 map 或 slice 作為函數參數傳入時,如果您存儲了對它們的引用,則用戶可以對其進行修改。

Bad Good
“`go func (d *Driver) SetTrips(trips []Trip) { d.trips = trips } trips := … d1.SetTrips(trips) // 你是要修改 d1.trips 嗎? trips[0] = … “` “`go func (d *Driver) SetTrips(trips []Trip) { d.trips = make([]Trip, len(trips)) copy(d.trips, trips) } trips := … d1.SetTrips(trips) // 這裏我們修改 trips[0],但不會影響到 d1.trips trips[0] = … “`

返回 slices 或 maps

同樣,請注意用戶對暴露內部狀態的 map 或 slice 的修改。

Bad Good
“`go type Stats struct { mu sync.Mutex counters map[string]int } // Snapshot 返回當前狀態。 func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() return s.counters } // snapshot 不再受互斥鎖保護 // 因此對 snapshot 的任何訪問都將受到數據競爭的影響 // 影響 stats.counters snapshot := stats.Snapshot() “` “`go type Stats struct { mu sync.Mutex counters map[string]int } func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() result := make(map[string]int, len(s.counters)) for k, v := range s.counters { result[k] = v } return result } // snapshot 現在是一個拷貝 snapshot := stats.Snapshot() “`

使用 defer 釋放資源

使用 defer 釋放資源,諸如文件和鎖。

Bad Good
“`go p.Lock() if p.count < 10 { p.Unlock() return p.count } p.count++ newCount := p.count p.Unlock() return newCount // 當有多個 return 分支時,很容易遺忘 unlock “` “`go p.Lock() defer p.Unlock() if p.count < 10 { return p.count } p.count++ return p.count // 更可讀 “`

Defer 的開銷非常小,只有在您可以證明函數執行時間處於納秒級的程度時,才應避免這樣做。使用 defer 提升可讀性是值得的,因為使用它們的成本微不足道。尤其適用於那些不僅僅是簡單內存訪問的較大的方法,在這些方法中其他計算的資源消耗遠超過 defer。

Channel 的 size 要麼是 1,要麼是無緩衝的

channel 通常 size 應為 1 或是無緩衝的。默認情況下,channel 是無緩衝的,其 size 為零。任何其他尺寸都必須經過嚴格的審查。考慮如何確定大小,是什麼阻止了 channel 在負載下被填滿並阻止寫入,以及發生這種情況時發生了什麼。

Bad Good
“`go // 應該足以滿足任何情況! c := make(chan int, 64) “` “`go // 大小:1 c := make(chan int, 1) // 或者 // 無緩衝 channel,大小為 0 c := make(chan int) “`

枚舉從 1 開始

在 Go 中引入枚舉的標準方法是聲明一個自定義類型和一個使用了 iota 的 const 組。由於變量的默認值為 0,因此通常應以非零值開頭枚舉。

Bad Good
“`go type Operation int const ( Add Operation = iota Subtract Multiply ) // Add=0, Subtract=1, Multiply=2 “` “`go type Operation int const ( Add Operation = iota + 1 Subtract Multiply ) // Add=1, Subtract=2, Multiply=3 “`

在某些情況下,使用零值是有意義的(枚舉從零開始),例如,當零值是理想的默認行為時。

type LogOutput int

const (
  LogToStdout LogOutput = iota
  LogToFile
  LogToRemote
)

// LogToStdout=0, LogToFile=1, LogToRemote=2

錯誤類型

Go 中有多種聲明錯誤(Error) 的選項:

  • 對於簡單靜態字符串的錯誤
  • 用於格式化的錯誤字符串
  • 實現 Error() 方法的自定義類型
  • 用 的 Wrapped errors

返回錯誤時,請考慮以下因素以確定最佳選擇:

  • 這是一個不需要額外信息的簡單錯誤嗎?如果是這樣, 足夠了。
  • 客戶需要檢測並處理此錯誤嗎?如果是這樣,則應使用自定義類型並實現該 Error() 方法。
  • 您是否正在傳播下游函數返回的錯誤?如果是這樣,請查看本文後面有關錯誤包裝 部分的內容。
  • 否則 就可以了。

如果客戶端需要檢測錯誤,並且您已使用創建了一個簡單的錯誤 ,請使用一個錯誤變量。

Bad Good
“`go // package foo func Open() error { return errors.New(“could not open”) } // package bar func use() { if err := foo.Open(); err != nil { if err.Error() == “could not open” { // handle } else { panic(“unknown error”) } } } “` “`go // package foo var ErrCouldNotOpen = errors.New(“could not open”) func Open() error { return ErrCouldNotOpen } // package bar if err := foo.Open(); err != nil { if err == foo.ErrCouldNotOpen { // handle } else { panic(“unknown error”) } } “`

如果您有可能需要客戶端檢測的錯誤,並且想向其中添加更多信息(例如,它不是靜態字符串),則應使用自定義類型。

Bad Good
“`go func open(file string) error { return fmt.Errorf(“file %q not found”, file) } func use() { if err := open(); err != nil { if strings.Contains(err.Error(), “not found”) { // handle } else { panic(“unknown error”) } } } “` “`go type errNotFound struct { file string } func (e errNotFound) Error() string { return fmt.Sprintf(“file %q not found”, e.file) } func open(file string) error { return errNotFound{file: file} } func use() { if err := open(); err != nil { if _, ok := err.(errNotFound); ok { // handle } else { panic(“unknown error”) } } } “`

直接導出自定義錯誤類型時要小心,因為它們已成為程序包公共 API 的一部分。最好公開匹配器功能以檢查錯誤。

// package foo

type errNotFound struct {
  file string
}

func (e errNotFound) Error() string {
  return fmt.Sprintf("file %q not found", e.file)
}

func IsNotFoundError(err error) bool {
  _, ok := err.(errNotFound)
  return ok
}

func Open(file string) error {
  return errNotFound{file: file}
}

// package bar

if err := foo.Open("foo"); err != nil {
  if foo.IsNotFoundError(err) {
    // handle
  } else {
    panic("unknown error")
  }
}

錯誤包裝 (Error Wrapping)

一個(函數/方法)調用失敗時,有三種主要的錯誤傳播方式:

  • 如果沒有要添加的其他上下文,並且您想要維護原始錯誤類型,則返回原始錯誤。
  • 添加上下文,使用 以便錯誤消息提供更多上下文 , 可用於提取原始錯誤。
    Use fmt.Errorf if the callers do not need to detect or handle that specific error case.

  • 如果調用者不需要檢測或處理的特定錯誤情況,使用 。

建議在可能的地方添加上下文,以使您獲得諸如“調用服務 foo:連接被拒絕”之類的更有用的錯誤,而不是諸如“連接被拒絕”之類的模糊錯誤。

在將上下文添加到返回的錯誤時,請避免使用“failed to”之類的短語來保持上下文簡潔,這些短語會陳述明顯的內容,並隨着錯誤在堆棧中的滲透而逐漸堆積:

Bad Good
“`go s, err := store.New() if err != nil { return fmt.Errorf( “failed to create new store: %s”, err) } “` “`go s, err := store.New() if err != nil { return fmt.Errorf( “new store: %s”, err) } “`
“` failed to x: failed to y: failed to create new store: the error “` “` x: y: new store: the error “`

但是,一旦將錯誤發送到另一個系統,就應該明確消息是錯誤消息(例如使用err標記,或在日誌中以”Failed”為前綴)。

另請參見 . 不要只是檢查錯誤,要優雅地處理錯誤

處理類型斷言失敗

的單個返回值形式針對不正確的類型將產生 panic。因此,請始終使用“comma ok”的慣用法。

Bad Good
“`go t := i.(string) “` “`go t, ok := i.(string) if !ok { // 優雅地處理錯誤 } “`

不要 panic

在生產環境中運行的代碼必須避免出現 panic。panic 是 級聯失敗的主要根源 。如果發生錯誤,該函數必須返回錯誤,並允許調用方決定如何處理它。

Bad Good
“`go func foo(bar string) { if len(bar) == 0 { panic(“bar must not be empty”) } // … } func main() { if len(os.Args) != 2 { fmt.Println(“USAGE: foo “) os.Exit(1) } foo(os.Args[1]) } “` “`go func foo(bar string) error { if len(bar) == 0 { return errors.New(“bar must not be empty”) } // … return nil } func main() { if len(os.Args) != 2 { fmt.Println(“USAGE: foo “) os.Exit(1) } if err := foo(os.Args[1]); err != nil { panic(err) } } “`

panic/recover 不是錯誤處理策略。僅當發生不可恢復的事情(例如:nil 引用)時,程序才必須 panic。程序初始化是一個例外:程序啟動時應使程序中止的不良情況可能會引起 panic。

var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))

即使在測試代碼中,也優先使用t.Fatal或者t.FailNow而不是 panic 來確保失敗被標記。

Bad Good
“`go // func TestFoo(t *testing.T) f, err := ioutil.TempFile(“”, “test”) if err != nil { panic(“failed to set up test”) } “` “`go // func TestFoo(t *testing.T) f, err := ioutil.TempFile(“”, “test”) if err != nil { t.Fatal(“failed to set up test”) } “`

使用 go.uber.org/atomic

使用 包的原子操作對原始類型 (int32, int64等)進行操作,因為很容易忘記使用原子操作來讀取或修改變量。

通過隱藏基礎類型為這些操作增加了類型安全性。此外,它包括一個方便的atomic.Bool類型。

Bad Good
“`go type foo struct { running int32 // atomic } func (f* foo) start() { if atomic.SwapInt32(&f.running, 1) == 1 { // already running… return } // start the Foo } func (f *foo) isRunning() bool { return f.running == 1 // race! } “` “`go type foo struct { running atomic.Bool } func (f *foo) start() { if f.running.Swap(true) { // already running… return } // start the Foo } func (f *foo) isRunning() bool { return f.running.Load() } “`

性能

性能方面的特定準則只適用於高頻場景。

優先使用 strconv 而不是 fmt

將原語轉換為字符串或從字符串轉換時,strconv速度比fmt快。

Bad Good
“`go for i := 0; i < b.N; i++ { s := fmt.Sprint(rand.Int()) } “` “`go for i := 0; i < b.N; i++ { s := strconv.Itoa(rand.Int()) } “`
“` BenchmarkFmtSprint-4 143 ns/op 2 allocs/op “` “` BenchmarkStrconv-4 64.2 ns/op 1 allocs/op “`

避免字符串到字節的轉換

不要反覆從固定字符串創建字節 slice。相反,請執行一次轉換並捕獲結果。

Bad Good
“`go for i := 0; i < b.N; i++ { w.Write([]byte(“Hello world”)) } “` “`go data := []byte(“Hello world”) for i := 0; i < b.N; i++ { w.Write(data) } “`
“` BenchmarkBad-4 50000000 22.2 ns/op “` “` BenchmarkGood-4 500000000 3.25 ns/op “`

盡量初始化時指定 Map 容量

在盡可能的情況下,在使用 make() 初始化的時候提供容量信息

make(map[T1]T2, hint)

為 make() 提供容量信息(hint)嘗試在初始化時調整 map 大小,
這減少了在將元素添加到 map 時增長和分配的開銷。
注意,map 不能保證分配 hint 個容量。因此,即使提供了容量,添加元素仍然可以進行分配。

Bad Good
“`go m := make(map[string]os.FileInfo) files, _ := ioutil.ReadDir(“./files”) for _, f := range files { m[f.Name()] = f } “` “`go files, _ := ioutil.ReadDir(“./files”) m := make(map[string]os.FileInfo, len(files)) for _, f := range files { m[f.Name()] = f } “`
`m` 是在沒有大小提示的情況下創建的; 在運行時可能會有更多分配。 `m` 是有大小提示創建的;在運行時可能會有更少的分配。

規範

一致性

本文中概述的一些標準都是客觀性的評估,是根據場景、上下文、或者主觀性的判斷;

但是最重要的是,保持一致.

一致性的代碼更容易維護、是更合理的、需要更少的學習成本、並且隨着新的約定出現或者出現錯誤后更容易遷移、更新、修復 bug

相反,一個單一的代碼庫會導致維護成本開銷、不確定性和認知偏差。所有這些都會直接導致速度降低、
代碼審查痛苦、而且增加 bug 數量

將這些標準應用於代碼庫時,建議在 package(或更大)級別進行更改,子包級別的應用程序通過將多個樣式引入到同一代碼中,違反了上述關注點。

相似的聲明放在一組

Go 語言支持將相似的聲明放在一個組內。

Bad Good
“`go import “a” import “b” “` “`go import ( “a” “b” ) “`

這同樣適用於常量、變量和類型聲明:

Bad Good
“`go const a = 1 const b = 2 var a = 1 var b = 2 type Area float64 type Volume float64 “` “`go const ( a = 1 b = 2 ) var ( a = 1 b = 2 ) type ( Area float64 Volume float64 ) “`

僅將相關的聲明放在一組。不要將不相關的聲明放在一組。

Bad Good
“`go type Operation int const ( Add Operation = iota + 1 Subtract Multiply ENV_VAR = “MY_ENV” ) “` “`go type Operation int const ( Add Operation = iota + 1 Subtract Multiply ) const ENV_VAR = “MY_ENV” “`

分組使用的位置沒有限制,例如:你可以在函數內部使用它們:

Bad Good
“`go func f() string { var red = color.New(0xff0000) var green = color.New(0x00ff00) var blue = color.New(0x0000ff) … } “` “`go func f() string { var ( red = color.New(0xff0000) green = color.New(0x00ff00) blue = color.New(0x0000ff) ) … } “`

import 分組

導入應該分為兩組:

  • 標準庫
  • 其他庫

默認情況下,這是 goimports 應用的分組。

Bad Good
“`go import ( “fmt” “os” “go.uber.org/atomic” “golang.org/x/sync/errgroup” ) “` “`go import ( “fmt” “os” “go.uber.org/atomic” “golang.org/x/sync/errgroup” ) “`

包名

當命名包時,請按下面規則選擇一個名稱:

  • 全部小寫。沒有大寫或下劃線。
  • 大多數使用命名導入的情況下,不需要重命名。
  • 簡短而簡潔。請記住,在每個使用的地方都完整標識了該名稱。
  • 不用複數。例如net/url,而不是net/urls。
  • 不要用“common”,“util”,“shared”或“lib”。這些是不好的,信息量不足的名稱。

另請參閱 和 .

函數名

我們遵循 Go 社區關於使用 的約定。有一個例外,為了對相關的測試用例進行分組,函數名可能包含下劃線,如:TestMyFunction_WhatIsBeingTested.

導入別名

如果程序包名稱與導入路徑的最後一個元素不匹配,則必須使用導入別名。

import (
  "net/http"

  client "example.com/client-go"
  trace "example.com/trace/v2"
)

在所有其他情況下,除非導入之間有直接衝突,否則應避免導入別名。

Bad Good
“`go import ( “fmt” “os” nettrace “golang.net/x/trace” ) “` “`go import ( “fmt” “os” “runtime/trace” nettrace “golang.net/x/trace” ) “`

函數分組與順序

  • 函數應按粗略的調用順序排序。
  • 同一文件中的函數應按接收者分組。

因此,導出的函數應先出現在文件中,放在struct, const, var定義的後面。

在定義類型之後,但在接收者的其餘方法之前,可能會出現一個 newXYZ()/NewXYZ()

由於函數是按接收者分組的,因此普通工具函數應在文件末尾出現。

Bad Good
“`go func (s *something) Cost() { return calcCost(s.weights) } type something struct{ … } func calcCost(n []int) int {…} func (s *something) Stop() {…} func newSomething() *something { return &something{} } “` “`go type something struct{ … } func newSomething() *something { return &something{} } func (s *something) Cost() { return calcCost(s.weights) } func (s *something) Stop() {…} func calcCost(n []int) int {…} “`

減少嵌套

代碼應通過盡可能先處理錯誤情況/特殊情況並儘早返回或繼續循環來減少嵌套。減少嵌套多個級別的代碼的代碼量。

Bad Good
“`go for _, v := range data { if v.F1 == 1 { v = process(v) if err := v.Call(); err == nil { v.Send() } else { return err } } else { log.Printf(“Invalid v: %v”, v) } } “` “`go for _, v := range data { if v.F1 != 1 { log.Printf(“Invalid v: %v”, v) continue } v = process(v) if err := v.Call(); err != nil { return err } v.Send() } “`

不必要的 else

如果在 if 的兩個分支中都設置了變量,則可以將其替換為單個 if。

Bad Good
“`go var a int if b { a = 100 } else { a = 10 } “` “`go a := 10 if b { a = 100 } “`

頂層變量聲明

在頂層,使用標準var關鍵字。請勿指定類型,除非它與表達式的類型不同。

Bad Good
“`go var _s string = F() func F() string { return “A” } “` “`go var _s = F() // 由於 F 已經明確了返回一個字符串類型,因此我們沒有必要顯式指定_s 的類型 // 還是那種類型 func F() string { return “A” } “`

如果表達式的類型與所需的類型不完全匹配,請指定類型。

type myError struct{}

func (myError) Error() string { return "error" }

func F() myError { return myError{} }

var _e error = F()
// F 返回一個 myError 類型的實例,但是我們要 error 類型

對於未導出的頂層常量和變量,使用_作為前綴

在未導出的頂級vars和consts, 前面加上前綴_,以使它們在使用時明確表示它們是全局符號。

例外:未導出的錯誤值,應以err開頭。

基本依據:頂級變量和常量具有包範圍作用域。使用通用名稱可能很容易在其他文件中意外使用錯誤的值。

Bad Good
“`go // foo.go const ( defaultPort = 8080 defaultUser = “user” ) // bar.go func Bar() { defaultPort := 9090 … fmt.Println(“Default port”, defaultPort) // We will not see a compile error if the first line of // Bar() is deleted. } “` “`go // foo.go const ( _defaultPort = 8080 _defaultUser = “user” ) “`

結構體中的嵌入

嵌入式類型(例如 mutex)應位於結構體內的字段列表的頂部,並且必須有一個空行將嵌入式字段與常規字段分隔開。

Bad Good
“`go type Client struct { version int http.Client } “` “`go type Client struct { http.Client version int } “`

使用字段名初始化結構體

初始化結構體時,幾乎始終應該指定字段名稱。現在由 強制執行。

Bad Good
“`go k := User{“John”, “Doe”, true} “` “`go k := User{ FirstName: “John”, LastName: “Doe”, Admin: true, } “`

例外:如果有 3 個或更少的字段,則可以在測試表中省略字段名稱。

tests := []struct{
  op Operation
  want string
}{
  {Add, "add"},
  {Subtract, "subtract"},
}

本地變量聲明

如果將變量明確設置為某個值,則應使用短變量聲明形式 (:=)。

Bad Good
“`go var s = “foo” “` “`go s := “foo” “`

但是,在某些情況下,var 使用關鍵字時默認值會更清晰。例如,聲明空切片。

Bad Good
“`go func f(list []int) { filtered := []int{} for _, v := range list { if v > 10 { filtered = append(filtered, v) } } } “` “`go func f(list []int) { var filtered []int for _, v := range list { if v > 10 { filtered = append(filtered, v) } } } “`

nil 是一個有效的 slice

nil 是一個有效的長度為 0 的 slice,這意味着,

  • 您不應明確返回長度為零的切片。應該返回nil 來代替。

    Bad Good
    “`go if x == “” { return []int{} } “` “`go if x == “” { return nil } “`
  • 要檢查切片是否為空,請始終使用len(s) == 0。而非 nil。

    Bad Good
    “`go func isEmpty(s []string) bool { return s == nil } “` “`go func isEmpty(s []string) bool { return len(s) == 0 } “`
  • 零值切片(用var聲明的切片)可立即使用,無需調用make()創建。

    Bad Good
    “`go nums := []int{} // or, nums := make([]int) if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) } “` “`go var nums []int if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) } “`

小變量作用域

如果有可能,盡量縮小變量作用範圍。除非它與 的規則衝突。

Bad Good
“`go err := ioutil.WriteFile(name, data, 0644) if err != nil { return err } “` “`go if err := ioutil.WriteFile(name, data, 0644); err != nil { return err } “`

如果需要在 if 之外使用函數調用的結果,則不應嘗試縮小範圍。

Bad Good
“`go if data, err := ioutil.ReadFile(name); err == nil { err = cfg.Decode(data) if err != nil { return err } fmt.Println(cfg) return nil } else { return err } “` “`go data, err := ioutil.ReadFile(name) if err != nil { return err } if err := cfg.Decode(data); err != nil { return err } fmt.Println(cfg) return nil “`

避免參數語義不明確(Avoid Naked Parameters)

函數調用中的意義不明確的參數可能會損害可讀性。當參數名稱的含義不明顯時,請為參數添加 C 樣式註釋 (/* ... */)

Bad Good
“`go // func printInfo(name string, isLocal, done bool) printInfo(“foo”, true, true) “` “`go // func printInfo(name string, isLocal, done bool) printInfo(“foo”, true /* isLocal */, true /* done */) “`

對於上面的示例代碼,還有一種更好的處理方式是將上面的 bool 類型換成自定義類型。將來,該參數可以支持不僅僅局限於兩個狀態(true/false)。

type Region int

const (
  UnknownRegion Region = iota
  Local
)

type Status int

const (
  StatusReady = iota + 1
  StatusDone
  // Maybe we will have a StatusInProgress in the future.
)

func printInfo(name string, region Region, status Status)

使用原始字符串字面值,避免轉義

Go 支持使用 ,也就是 ” ` ” 來表示原生字符串,在需要轉義的場景下,我們應該盡量使用這種方案來替換。

可以跨越多行並包含引號。使用這些字符串可以避免更難閱讀的手工轉義的字符串。

Bad Good
“`go wantError := “unknown name:\”test\”” “` “`go wantError := `unknown error:”test”` “`

初始化 Struct 引用

在初始化結構引用時,請使用&T{}代替new(T),以使其與結構體初始化一致。

Bad Good
“`go sval := T{Name: “foo”} // inconsistent sptr := new(T) sptr.Name = “bar” “` “`go sval := T{Name: “foo”} sptr := &T{Name: “bar”} “`

初始化 Maps

對於空 map 請使用 make(..) 初始化, 並且 map 是通過編程方式填充的。
這使得 map 初始化在表現上不同於聲明,並且它還可以方便地在 make 后添加大小提示。

Bad Good
“`go var ( // m1 讀寫安全; // m2 在寫入時會 panic m1 = map[T1]T2{} m2 map[T1]T2 ) “` “`go var ( // m1 讀寫安全; // m2 在寫入時會 panic m1 = make(map[T1]T2) m2 map[T1]T2 ) “`
聲明和初始化看起來非常相似的。 聲明和初始化看起來差別非常大。

在盡可能的情況下,請在初始化時提供 map 容量大小,詳細請看 。

另外,如果 map 包含固定的元素列表,則使用 map literals(map 初始化列表) 初始化映射。

Bad Good
“`go m := make(map[T1]T2, 3) m[k1] = v1 m[k2] = v2 m[k3] = v3 “` “`go m := map[T1]T2{ k1: v1, k2: v2, k3: v3, } “`

基本準則是:在初始化時使用 map 初始化列表 來添加一組固定的元素。否則使用 make (如果可以,請盡量指定 map 容量)。

字符串 string format

如果你為Printf-style 函數聲明格式字符串,請將格式化字符串放在外面,並將其設置為const常量。

這有助於go vet對格式字符串執行靜態分析。

Bad Good
“`go msg := “unexpected values %v, %v\n” fmt.Printf(msg, 1, 2) “` “`go const msg = “unexpected values %v, %v\n” fmt.Printf(msg, 1, 2) “`

命名 Printf 樣式的函數

聲明Printf-style 函數時,請確保go vet可以檢測到它並檢查格式字符串。

這意味着您應盡可能使用預定義的Printf-style 函數名稱。go vet將默認檢查這些。有關更多信息,請參見 。

如果不能使用預定義的名稱,請以 f 結束選擇的名稱:Wrapf,而不是Wrap。go vet可以要求檢查特定的 Printf 樣式名稱,但名稱必須以f結尾。

$ go vet -printfuncs=wrapf,statusf

另請參閱 .

編程模式

表驅動測試

當測試邏輯是重複的時候,通過 使用 table 驅動的方式編寫 case 代碼看上去會更簡潔。

Bad Good
“`go // func TestSplitHostPort(t *testing.T) host, port, err := net.SplitHostPort(“192.0.2.0:8000”) require.NoError(t, err) assert.Equal(t, “192.0.2.0”, host) assert.Equal(t, “8000”, port) host, port, err = net.SplitHostPort(“192.0.2.0:http”) require.NoError(t, err) assert.Equal(t, “192.0.2.0”, host) assert.Equal(t, “http”, port) host, port, err = net.SplitHostPort(“:8000”) require.NoError(t, err) assert.Equal(t, “”, host) assert.Equal(t, “8000”, port) host, port, err = net.SplitHostPort(“1:8”) require.NoError(t, err) assert.Equal(t, “1”, host) assert.Equal(t, “8”, port) “` “`go // func TestSplitHostPort(t *testing.T) tests := []struct{ give string wantHost string wantPort string }{ { give: “192.0.2.0:8000”, wantHost: “192.0.2.0”, wantPort: “8000”, }, { give: “192.0.2.0:http”, wantHost: “192.0.2.0”, wantPort: “http”, }, { give: “:8000”, wantHost: “”, wantPort: “8000”, }, { give: “1:8”, wantHost: “1”, wantPort: “8”, }, } for _, tt := range tests { t.Run(tt.give, func(t *testing.T) { host, port, err := net.SplitHostPort(tt.give) require.NoError(t, err) assert.Equal(t, tt.wantHost, host) assert.Equal(t, tt.wantPort, port) }) } “`

很明顯,使用 test table 的方式在代碼邏輯擴展的時候,比如新增 test case,都會顯得更加的清晰。

我們遵循這樣的約定:將結構體切片稱為tests。 每個測試用例稱為tt。此外,我們鼓勵使用give和want前綴說明每個測試用例的輸入和輸出值。

tests := []struct{
  give     string
  wantHost string
  wantPort string
}{
  // ...
}

for _, tt := range tests {
  // ...
}

功能選項

功能選項是一種模式,您可以在其中聲明一個不透明 Option 類型,該類型在某些內部結構中記錄信息。您接受這些選項的可變編號,並根據內部結構上的選項記錄的全部信息採取行動。

將此模式用於您需要擴展的構造函數和其他公共 API 中的可選參數,尤其是在這些功能上已經具有三個或更多參數的情況下。

Bad Good
“`go // package db func Connect( addr string, timeout time.Duration, caching bool, ) (*Connection, error) { // … } // Timeout and caching must always be provided, // even if the user wants to use the default. db.Connect(addr, db.DefaultTimeout, db.DefaultCaching) db.Connect(addr, newTimeout, db.DefaultCaching) db.Connect(addr, db.DefaultTimeout, false /* caching */) db.Connect(addr, newTimeout, false /* caching */) “` “`go type options struct { timeout time.Duration caching bool } // Option overrides behavior of Connect. type Option interface { apply(*options) } type optionFunc func(*options) func (f optionFunc) apply(o *options) { f(o) } func WithTimeout(t time.Duration) Option { return optionFunc(func(o *options) { o.timeout = t }) } func WithCaching(cache bool) Option { return optionFunc(func(o *options) { o.caching = cache }) } // Connect creates a connection. func Connect( addr string, opts …Option, ) (*Connection, error) { options := options{ timeout: defaultTimeout, caching: defaultCaching, } for _, o := range opts { o.apply(&options) } // … } // Options must be provided only if needed. db.Connect(addr) db.Connect(addr, db.WithTimeout(newTimeout)) db.Connect(addr, db.WithCaching(false)) db.Connect( addr, db.WithCaching(false), db.WithTimeout(newTimeout), ) “`

還可以參考下面資料:

本文由zshipu.com學習筆記或整理或轉載,如有侵權請聯繫,必改之。

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

福特砸45億美元擴增電動車產線

美商福特汽車(Ford)宣布將投資45億美元擴增電動車生產線,目標在五年內將旗下電動車產品的比例從目前的13%提高到40%左右,同時陸續推出共13款新的電動車與油電混和車。

隨著汽車排放標準愈趨嚴格,福特汽車認為電動車領域的吸引力將比過去更高,因此宣布擴增電動車產線。執行長Mark Fields 表示,在即將推出的13款新車中,也包括有快速充電功能的Focus Electric車款。

近來油價低廉,美國電動車、油電混和車的銷量因而受到壓抑,但福特認為車商仍必須讓消費者了解電動車款的優點,且認為電動車的投資未來將能與一般汽油車擁有差不多的獲益。

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!