一、概述
深度學習的一個重要手段是訓練數(shù)據(jù)和訓練過程的可視化,因此,我們關于深度學習的系列介紹文章就從Matplotlib開始。
Matplotlib是一個在Python下實現(xiàn)的類MatLab的第三方庫,是Python下最出色的繪圖庫,功能很完善,同時也繼承了Python簡單明了的風格,它可以很方便地設計和輸出二維以及三維的數(shù)據(jù),提供了常規(guī)的笛卡爾坐標、極坐標、求坐標、三維坐標等,可以畫普通圖、散點圖、三維圖、等高線圖等。
Matplotlib有各種應用,本文的例子主要集中在深度學習方面。如果您希望在股票走勢、報表分析或其他方面使用Matplotlib,其中使用的繪圖元素會有稍有不同。
在學習Matplotlib過程中,筆者也感覺到,Matplotlib有很多值得改進的地方。例如,如果Matplotlib能畫出相對一條線段特定角度的線段,或者能夠將一條線段延長特定的長度,等等,那么它在平面幾何的求解方面一定會有更大的作用。Matplotlib是一個完全開源的項目,實現(xiàn)這些功能是有可能的,當然這有賴于有志之士貢獻自己的精力和時間。
二、一個簡單的例子
我們先從一個簡單的例子開始。需要說明的是,為了讓更多的用戶能夠實驗本文檔中的示例代碼,我們使用Windows下的交互式Python工具Juypter Notebook作為代碼運行環(huán)境。Juypter Notebook的安裝過程可參見其他文檔,假設您已經(jīng)成功安裝了該軟件,運行Anocoda3軟件包中的Juypter Notebook可以看到你如下的界面。

首先,在文本框中輸入以下命令,并點擊Run按鈕。
%matpplotlib inline
%matplotlib命令可以在當前的Jupyter
Notebook環(huán)境中啟用繪圖。這個命令提供一個可選參數(shù),指定使用哪個matplotlib后端。一般情況下,我們都使用inline后臺,這將在Jupyter Notebook輸出中直接呈現(xiàn)繪圖。需要了解,這條命令并不是程序代碼的一部分。當在Ubuntu的Python環(huán)境下運行時,就必須將這一行去掉。
接下來,是實際的程序代碼:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-np.pi, np.pi, np.pi/100)
y = np.sin(x)
plt.plot(x, y)
plt.show()
以上程序的輸出入下圖所示。

語句import numpy as np導入Python的科學計算包。Matplotlib 充分利用了Python下的Numeric(Numarray)模塊,提供了一種利用Python進行數(shù)據(jù)可視化的解決方案,進一步加強了Python用來進行科學計算的能力。
語句import matplotlib.pyplot as plt導入matplotlib包中的pyplot模塊。
語句x = np.arange(-np.pi, np.pi, np.pi/100)在從-π到π,以π/100為步進,生成一個數(shù)組。換句話說,在[-π,π)區(qū)間內(nèi),按平均間隔取100個值,得到一個數(shù)組。
語句y
= np.sin(x)對x數(shù)組計算正弦函數(shù)值,得到是對應長度的數(shù)組。
語句plt.plot(x,
y)調(diào)用pyplot模塊的plot函數(shù),傳入x和y數(shù)組。這個函數(shù)會用直線將x和y數(shù)組中對應元素的點用直線連接起來,最終得到一條正弦曲線。
最后,調(diào)用plt.show()函數(shù)將這條正弦曲線呈現(xiàn)出來。
我們看到,在上面的例子中,我們通過4條有效語句,就繪制了一條正弦曲線。實際上,matplotlib和python一樣簡潔明了,只要我們熟悉了其思想,就可以繪制出符合我們需要的各種圖形。
在編寫代碼過程中,如果需要幫助,您可以訪問:
[if !supportLists]2???????[endif]畫廊:Matplotlib的Gallery頁面(https://matplotlib.org/gallery.html)中有上百幅縮略圖,打開之后都有源程序。如果您需要繪制某種類型的圖,可以在畫廊中搜索對應的例子,大多數(shù)情況下,通過復制/粘貼基本上就能搞定。
[if !supportLists]2???????[endif]API:如果要了解模塊中某個函數(shù)的使用方法,可以訪問Matplotlib的API文檔(https://matplotlib.org/api/index.html)。
[if !supportLists]2???????[endif]幫助:如果要了解模塊中某個函數(shù)的使用方法,也可以在Jupyter Notebook環(huán)境下使用 help 命令,如下圖所示。

三、Matplotlib組成
要理解Matplotlib繪圖,可以用我們?nèi)粘I钪械睦L畫來進行類比。一般來說,我們繪畫的過程是這樣的:首先架上畫板,然后在畫板上鋪一張畫紙。有時候我們也會在不同位置鋪上幾張畫紙,每張畫紙是各自獨立的參考系。我們在畫紙上畫各種類型的圖畫,比如水墨畫、油畫、工筆畫、寫意畫、素描、漫畫等。除此以外,一副完整的畫,還需要加上題跋。書籍、碑帖、字畫等前面的文字叫做題,寫在后面的文字,叫做跋,總稱題跋。
Matplotlib中專業(yè)術語大致對應如下:

[if !supportLists]2???????[endif]圖形(Figure):類似于上面提到的畫板??梢哉J為是Matplotlib繪圖的基礎。
[if !supportLists]2???????[endif]軸圖(Axes)或子圖(Subplot):類似于上面的畫紙。畫板上有一張或多張畫紙,實際繪圖是在軸圖或子圖上完成的。
[if !supportLists]2???????[endif]X軸(X Axis)和Y軸(Y Axis):繪圖所依據(jù)的坐標系統(tǒng),相對于上面的參考系。只不過對于專業(yè)的畫家,參考系是在自己的頭腦中,并不一定要標在圖紙上面。而在Matplotlib是,X軸和Y軸一般都需要呈現(xiàn)出來,包括其標簽(Label)、軸線(Spine)、刻度(Tick)以及刻度標簽等。
[if !supportLists]2???????[endif]標題(Title)和圖例(Legend):類似于上面的題跋,具體來說:標題對應題,圖例對應跋。和繪畫一樣,Matplotlib中的標題和圖例也可以放在軸圖或子圖上的不同位置。
[if !supportLists]2???????[endif]數(shù)據(jù)(Data):無疑,Matplotlib中的數(shù)據(jù)相對于繪畫中主要部分:內(nèi)容。Matplotlib支持的繪圖類型包括:普通圖(Plot)、散點圖(Scatter)和三維圖(3D)等。對于繪圖過程的每一筆,還可以指定使用的顏色(Color)、樣式(Style)以及記號(Marker)等。
[if !supportLists]2???????[endif]文本(Text)/注釋(Annotation):這在日常繪畫中并不常見,但我們也可以把它歸入繪畫中的主要內(nèi)容部分。Matplotlib使用文本和注釋對重要數(shù)據(jù)進行說明。
上面的術語也可以參考下面的圖。

上圖顯示的是只有一個軸圖的情況。Matplotlib也支持在一個圖形中繪制幾個軸圖。這個時候,就需要通過調(diào)用特定的函數(shù)將軸圖添加進來。每個軸圖是一個擁有自己獨立坐標系統(tǒng)的繪圖區(qū)域,它可以在圖像的任意位置。

當然,軸圖也可以一定規(guī)律來放置,這時候,我們稱其為子圖(Subplot)。軸圖和子圖是相互關聯(lián)的:軸圖是靈活的子圖,而子圖是按格柵方式組織,通過行與列來定位的特殊軸圖。下圖就是按2行2列排列方式顯示深度學習中經(jīng)常使用到的一些激勵函數(shù)。

四、坐標系統(tǒng)
為了說明坐標系統(tǒng),我們對第一個例子進行補充,這次我們將加上標題、刻度、刻度標簽、圖例等,并調(diào)整坐標系的位置。我們希望輸出的效果圖如下所示。

因為要顯示中文標題,所以首先在代碼中添加如下語句。
plt.rcParams['font.sans-serif']=['SimHei']
接下來三條語句生成數(shù)據(jù),具體的解釋可以參考前面的例子。
x? = np.arange(-np.pi, np.pi, np.pi/100)
y1? = np.sin(x)
y2? = np.cos(x)
坐標系統(tǒng)是相對于軸域而言的。如前所述,Matplotlib使用Figure和Axes進行繪圖,前者類似于畫板,后者類似于畫紙。如果我們沒有調(diào)用語句添加Figure和Axes,系統(tǒng)將使用默認的畫板和畫紙。調(diào)用gcf()和gca()函數(shù)將分別取得系統(tǒng)的默認畫板和畫紙,然后就可以調(diào)用畫板和畫紙的函數(shù)進行操作。
ax.set_title('正弦函數(shù)和余弦函數(shù)')為Matplotlib繪圖添加標題。一般來說,標題會顯示在圖像的上方中心位置。您也可以傳入loc=”left”或loc=”right”參數(shù),將標題設置在上方左邊位置或右邊位置。當然,也可以在其他位置。
Matplotlib軸域的坐標系默認有l(wèi)eft、bottom、right、top四根軸線,要將坐標系顯示在圖形中央,需要隱藏其中的兩條,并移動另外兩條。例如:將left軸和bottom軸的位置設置在中央,將right軸和top軸的顏色設為none達到隱藏效果。此外,對于left軸和bottom軸,調(diào)用set_smart_bounds方法。
ax.spines['left'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('center')
ax.spines['top'].set_color('none')
ax.spines['left'].set_smart_bounds(True)
ax.spines['bottom'].set_smart_bounds(True)
接下來三條語句設置X軸的范圍和刻度。范圍設置在[-π,π)區(qū)間,并在-π、-π/2、0、π/2和π等位置表示對應的刻度標簽。
Matplotlib軸域的坐標系默認有l(wèi)eft、bottom、right、top四根軸線,要將坐標系顯示在圖形中央,需要隱藏其中的兩條,并移動另外兩條。例如:將left軸和bottom軸的位置設置在中央,將right軸和top軸的顏色設為none達到隱藏效果。此外,對于left軸和bottom軸,調(diào)用set_smart_bounds方法。
ax.set_xlim(-np.pi,? np.pi)
ax.set_xticks([-np.pi,? -np.pi/2, 0, np.pi/2, np.pi])
ax.set_xticklabels(['-$\pi$',? '-$\pi$/2', '0', '$\pi$/2', '$\pi$'])
對于Y軸,我們想呈現(xiàn)主刻度線和輔刻度線的效果。
def? major_tick(y, pos):
??? if not y in [-1.0, -0.5, 0, 0.5, 1.0]:
??????? return ""
??? return y
ax.yaxis.set_major_locator(MultipleLocator(0.500))
ax.yaxis.set_major_formatter(FuncFormatter(major_tick))
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
在坐標系統(tǒng)都設置好之后,調(diào)用plot函數(shù)分別畫正弦和余弦兩條曲線。系統(tǒng)會默認為兩條曲線使用兩種不同的顏色進行呈現(xiàn)。這次,我們傳入label參數(shù)為兩條曲線加上標簽。
ax.plot(x,? y1, label="sin()")
ax.plot(x,? y2, label="cos()")
之后,調(diào)用legend添加圖例并顯示,它會將上述兩條曲線的顏色和對應的標簽進行說明。輸出圖紙,圖例顯示在左上角位置,其他情況下可能會顯示在不同的位置。您也可以通過參數(shù)指定圖例顯示的位置。
五、數(shù)據(jù)屬性
這里所說的屬性,是指Matplotlib基本繪圖元素的屬性,包括顏色、線型、記號等。用一個例子可以很清楚地了解。

分析一下這個例子的實現(xiàn)。
首先準備數(shù)據(jù),
t =? np.arange(0.0, 1.0, 0.1)
s =? np.sin(2*np.pi*t)
然后定義線性和記號,
linestyles? = ['_', '-', '--', ':']
markers? = []
for m? in Line2D.markers:
??? try:
??????? if len(m) == 1 and m != ' ':
?????? ?????markers.append(m)
??? except TypeError:
??????? pass
styles? = markers + [
??? r'$\lambda$',
??? r'$\bowtie$',
??? r'$\circlearrowleft$',
??? r'$\clubsuit$',
??? r'$\checkmark$']
然后定義線性和記號,
linestyles? = ['_', '-', '--', ':']
markers? = []
for m? in Line2D.markers:
??? try:
??????? if len(m) == 1 and m != ' ':
??????????? markers.append(m)
??? except TypeError:
??????? pass
styles? = markers + [
??? r'$\lambda$',
??? r'$\bowtie$',
??? r'$\circlearrowleft$',
??? r'$\clubsuit$',
??? r'$\checkmark$']
然后定義顏色。
colors? = ('b', 'g', 'r', 'c', 'm', 'y', 'k')
程序的核心部分是一個雙層循環(huán)。外層對行進行循環(huán),內(nèi)層對一行的列進行循環(huán)。對于某一行的某一列,我們?yōu)槠涮砑訉淖訄D,并計算出對應的顏色、線型和記號,然后進行繪制,在繪制時,調(diào)用對應的函數(shù)把左邊系刻度標簽去掉了。
axisNum? = 0
for row? in range(6):
??? for col in range(5):
??????? axisNum += 1
??????? ax = plt.subplot(6, 5, axisNum)
??????? color = colors[axisNum % len(colors)]
??????? if axisNum < len(linestyles):
??????????? plt.plot(t, s,? linestyles[axisNum], color=color, markersize=10)
??????? else:
??????????? style = styles[(axisNum -? len(linestyles)) % len(styles)]
??????????? plt.plot(t, s, linestyle='None',? marker=style, color=color, markersize=10)
??????? ax.set_yticklabels([])
??????? ax.set_xticklabels([])
5.1 顏色
在上面的例子中,我們用到'b', 'g', 'r', 'c', 'm', 'y', 'k'等幾種顏色。實際上,我們可以通過名字引用更多的顏色。這些顏色定義在colors模塊的BASE_COLORS和mcolors.CSS4_COLORS中。下面是按色彩和飽和度進行排序,以4列方式呈現(xiàn)的顏色名稱和效果圖,我們把它放到這里方便查詢。

上例也是通過一個Matplotlib程序輸出的,這個程序可以在畫廊中搜索到。
5.2 線型
前面的例子中,給出了四種線性:'_', '-', '--', ':',效果如下圖。

如果您希望用到更多的線型,可以將linestyle參數(shù)指定為(, ())形式,如下表所示。
'solid'(0, ())
'loosely dotted' (0, (1, 10))
'dotted' (0, (1, 5))
'densely dotted' (0, (1, 1))
'loosely dashed' (0, (5, 10))
'dashed' (0, (5, 5))
'densely dashed' (0, (5, 1))
'loosely dashdotted' (0, (3, 10, 1, 10))
'dashdotted'(0, (3, 5, 1, 5))
'densely dashdotted' (0, (3, 1, 1, 1))
'loosely dashdotdotted'(0, (3, 10, 1, 10, 1, 10))
'dashdotdotted' (0, (3, 5, 1, 5, 1, 5))
'densely dashdotdotted' (0, (3, 1, 1, 1, 1, 1)))
這些形式對應的效果圖如下。

5.3 記號
前面例子中的記號從Line2D.markers數(shù)組中進行遍歷。具體使用時,我們可以將marker參數(shù)指定為以下類型之一:'.',',','o','v','^','<','>','1','2','3','4','8','s','p','P','*',
'h','H','+','x','X','D','d','|','_',對應效果如下圖。

六、注釋和文本
接下來給出一個注釋的例子。


這個例子要在一個程序里畫兩個圖。其實現(xiàn)方式也很直接。
首先創(chuàng)建第一個figure,并添加一個子圖。
fig? = plt.figure(1, figsize=(8, 5))
ax? = fig.add_subplot(111, autoscale_on=False, xlim=(-1, 5), ylim=(-4, 3))
創(chuàng)建第二個Figure和子圖的邏輯相似,只不過是,這里需要調(diào)用figure的clf方法將前面畫板上的內(nèi)容清空。
fig? = plt.figure(2)
fig.clf()
ax? = fig.add_subplot(111, autoscale_on=False, xlim=(-1, 5), ylim=(-5, 3))
在第二個子圖上,我們畫了一個橢圓,代碼如下。
el? = Ellipse((2, -1), 0.5, 0.5)
ax.add_patch(el)
當然,我們關注的還是注釋部分,下面分別介紹。
第一個圖的第一個注釋內(nèi)容是staight,所注釋的點座位為(0, 1)。
ax.annotate('straight',? xy=(0, 1), xycoords='data', xytext=(-50, 30), textcoords='offset points', arrowprops=dict(arrowstyle="->"))
第一個圖的第二個注釋內(nèi)容包括兩行內(nèi)容,第一行為arc3,,第二行為rad 0.2。所注釋的點座位為(0.5, -1)
ax.annotate('arc3,\nrad? 0.2', xy=(0.5, -1), xycoords='data', xytext=(-80, -60), textcoords='offset? points', arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
第一個圖的第三個注釋內(nèi)容也包括兩行內(nèi)容,第一行為arc,,第二行為angle 50。所注釋的點座位為(1, 1)。
ax.annotate('arc,\nangle? 50', xy=(1., 1), xycoords='data', xytext=(-90, 50), textcoords='offset? points', arrowprops=dict(arrowstyle="->",? connectionstyle="arc,angleA=0,armA=50,rad=10"))
第一個圖的第四個注釋。
ax.annotate('arc,\narms',? xy=(1.5, -1), xycoords='data', xytext=(-80, -60), textcoords='offset points',? arrowprops=dict(arrowstyle="->", connectionstyle="arc,angleA=0,armA=40,angleB=-90,armB=30,rad=7"))
第一個圖的第五個注釋。
ax.annotate('angle,\nangle? 90', xy=(2., 1), xycoords='data', xytext=(-70, 30), textcoords='offset? points', arrowprops=dict(arrowstyle="->", connectionstyle="angle,angleA=0,angleB=90,rad=10"))
第一個圖的第六個注釋。
ax.annotate('angle3,\nangle? -90', xy=(2.5, -1), xycoords='data', xytext=(-80, -60), textcoords='offset? points', arrowprops=dict(arrowstyle="->", connectionstyle="angle3,angleA=0,angleB=-90"))
第一個圖的第七個注釋。
ax.annotate('angle,\nround',? xy=(3., 1), xycoords='data', xytext=(-60, 30), textcoords='offset points',? bbox=dict(boxstyle="round", fc="0.8"), arrowprops=dict(arrowstyle="->",? connectionstyle="angle,angleA=0,angleB=90,rad=10"))
第一個圖的第八個注釋。
ax.annotate('angle,\nround4',? xy=(3.5, -1), xycoords='data', xytext=(-70, -80), textcoords='offset points',? size=20, bbox=dict(boxstyle="round4,pad=.5", fc="0.8"), arrowprops=dict(arrowstyle="->",? connectionstyle="angle,angleA=0,angleB=-90,rad=10"))
第一個圖的第九個注釋。
ax.annotate('angle,\nshrink',? xy=(4., 1), xycoords='data', xytext=(-60, 30), textcoords='offset points', bbox=dict(boxstyle="round",? fc="0.8"), arrowprops=dict(arrowstyle="->", shrinkA=0,? shrinkB=10,???????????????????????????
? connectionstyle="angle,angleA=0,angleB=90,rad=10"))
第一個圖的第十個注釋。
#? You can pass an empty string to get only annotation arrows rendered
ann? = ax.annotate('', xy=(4., 1.), xycoords='data', xytext=(4.5, -1),? textcoords='data', arrowprops=dict(arrowstyle="<->", connectionstyle="bar",? ec="k", shrinkA=5, shrinkB=5))
第二個圖的第一個注釋。
ax.annotate('$->$',? xy=(2., -1), xycoords='data', xytext=(-150, -140), textcoords='offset points',? bbox=dict(boxstyle="round", fc="0.8"), arrowprops=dict(arrowstyle="->",? patchB=el,???????????????????????????
? connectionstyle="angle,angleA=90,angleB=0,rad=10"))
第二個圖的第二個注釋。
ax.annotate('arrow\nfancy',? xy=(2., -1), xycoords='data', xytext=(-100, 60), textcoords='offset points',? size=20,
#? bbox=dict(boxstyle="round", fc="0.8"),
arrowprops=dict(arrowstyle="fancy",? fc="0.6", ec="none", patchB=el,???????????????????????????
? connectionstyle="angle3,angleA=0,angleB=-90"))
第二個圖的第三個注釋。
ax.annotate('arrow\nsimple',? xy=(2., -1), xycoords='data', xytext=(100, 60), textcoords='offset points',? size=20,
#? bbox=dict(boxstyle="round", fc="0.8"),
arrowprops=dict(arrowstyle="simple",? fc="0.6", ec="none", patchB=el, connectionstyle="arc3,rad=0.3"))
第二個圖的第四個注釋。
ax.annotate('$->$',? xy=(2., -1), xycoords='data', xytext=(-150, -140), textcoords='offset? points', bbox=dict(boxstyle="round", fc="0.8"), arrowprops=dict(arrowstyle="->",? patchB=el,???????????????????????????
? connectionstyle="angle,angleA=90,angleB=0,rad=10"))
第二個圖的第五個注釋。
ann? = ax.annotate('bubble,\ncontours', xy=(2., -1), xycoords='data', xytext=(0,? -70), textcoords='offset points', size=20,? bbox=dict(boxstyle="round", fc=(1.0, 0.7, 0.7), ec=(1., .5, .5)),
arrowprops=dict(arrowstyle="wedge,tail_width=1.",? fc=(1.0, 0.7, 0.7), ec=(1., .5, .5), patchA=None, patchB=el, relpos=(0.2,? 0.8), connectionstyle="arc3,rad=-0.1"))
第二個圖的第六個注釋。
ann? = ax.annotate('bubble', xy=(2., -1), xycoords='data', xytext=(55, 0),? textcoords='offset points', size=20, va="center", bbox=dict(boxstyle="round",? fc=(1.0, 0.7, 0.7), ec="none"), arrowprops=dict(arrowstyle="wedge,tail_width=1.",? fc=(1.0, 0.7, 0.7), ec="none", patchA=None, patchB=el, relpos=(0.2,? 0.5)))
在Matplotlib繪圖的過程中,必須會用到一些特殊文本,例如數(shù)學符號。

第一行對應的輸入為r"$W^{3\beta}_{\delta_1 \rho_1 \sigma_2} = U^{3\beta}_{\delta_1
\rho_1} + \frac{1}{8 \pi 2} \int^{\alpha_2}_{\alpha_2} d \alpha^\prime_2
\left[\frac{ U^{2\beta}_{\delta_1 \rho_1} - \alpha^\prime_2U^{1\beta}_{\rho_1
\sigma_2} }{U^{0\beta}_{\rho_1 \sigma_2}}\right]$";
第二行對應的字符串為:r"$\alpha_i > \beta_i,\ \alpha_{i+1}^j = {\rm sin}(2\pi f_j
t_i) e^{-5 t_i/\tau},\ \ldots$",
第三行對應的字符串為:r"$\frac{3}{4},\ \binom{3}{4},\ \stackrel{3}{4},\ \left(\frac{5
- \frac{1}{x}}{4}\right),\ \ldots$";
第四行對應的字符串為:r"$\sqrt{2},\ \sqrt[3]{x},\ \ldots$";
第五行對應的字符串為:r"$\mathrm{Roman}\ , \ \mathit{Italic}\ , \ \mathtt{Typewriter}
\ \mathrm{or}\ \mathcal{CALLIGRAPHY}$";
第六行對應的字符串為:r"$\acute a,\ \bar a,\ \breve a,\ \dot a,\ \ddot a, \ \grave a,
\ \hat a,\ \tilde a,\ \vec a,\ \widehat{xyz},\ \widetilde{xyz},\ \ldots$";
第七行對應的字符串為:r"$\alpha,\ \beta,\ \chi,\ \delta,\ \lambda,\ \mu,\ \Delta,\
\Gamma,\ \Omega,\ \Phi,\ \Pi,\ \Upsilon,\ \nabla,\ \aleph,\ \beth,\ \daleth,\
\gimel,\ \ldots$";
第八行對應的字符串為:r"$\coprod,\ \int,\ \oint,\ \prod,\ \sum,\ \log,\ \sin,\
\approx,\ \oplus,\ \star,\ \varpropto,\ \infty,\ \partial,\ \Re,\ \leftrightsquigarrow,
\ \ldots$"。
有時候,我們需要將文本旋轉一定角度呈現(xiàn)。只需要在調(diào)用pyplot的text方法時指定rotate參數(shù)。例如,下面的例子中,先畫了一條角度為45度的直線。
#? Plot diagonal line (45 degrees)
h? = plt.plot(np.arange(0, 10), np.arange(0, 10))
我們在第一個位置寫上一條45度的文本。
#? Locations to plot text
l1? = np.array((1, 1))
#? Rotate angle
angle? = 45
#? Plot text
th1? = plt.text(l1[0], l1[1], 'text not rotated correctly', fontsize=16,? rotation=angle, rotation_mode='anchor')
上面的方法只限于坐標系X軸和Y軸比例相同的情況。如果不相同,則需要進行變換。下面的代碼中,我們在第二個位置寫下角度變換后的文本。
#? set limits so that it no longer looks on screen to be 45 degrees
plt.xlim([-10,? 20])
#? Locations to plot text
l2? = np.array((5, 5))
#? Rotate angle
trans_angle? = plt.gca().transData.transform_angles(np.array((45,)),
???????????????????????????? ??????????????????????l2.reshape((1, 2)))[0]
#? Plot text
th2? = plt.text(l2[0], l2[1], 'text rotated correctly', fontsize=16, rotation=trans_angle,? rotation_mode='anchor')
從下圖可以看到,沒有變換角度的文本和所畫的直線不對應,而變換過角度的文本和所畫的直線則完全對應。

七、軸圖和子圖
軸圖和子圖的區(qū)別在前面已經(jīng)介紹過。簡而言之,子圖是特殊的軸圖,軸圖是靈活的子圖。它們分別調(diào)用add_axes和add_subplot函數(shù)向圖形中添加一個軸圖,兩個函數(shù)都返回matplotlib.axes.Axes對象。但是,兩個函數(shù)的調(diào)用機制有很大不同。
函數(shù)add_axes的調(diào)用方法是add_axes(rect),其中rect是一個列表[x0, y0,
width, height],標記新軸圖在圖形坐標系中左下角的位置(x0,y0),以及新軸圖的寬度width和高度height。因此,軸圖定位在畫板的絕對坐標位置。例如,以下語句在畫板上添加一個和畫板一樣大小的軸圖。
Fig =? plt.figure()
ax? = fig.add_axes([0,0,1,1])
函數(shù)add_subplot的調(diào)用方法并不直接提供放置軸圖的位置選項。而是指定按照子圖柵格擺放軸圖的方式。最常用也是最簡單的指定位置的方法是使用3個整數(shù)的記號。
fig? = plt.figure()
ax? = fig.add_subplot(231)
上述語句,一個新的軸圖會在2行3列的柵格中的第一個位置創(chuàng)建。如果只要創(chuàng)建一個軸圖,可以調(diào)用add_subplot(111)(在1行1列的柵格中的第一個位置創(chuàng)建軸圖)。
這個方法的優(yōu)點是讓matplotlib來計算軸圖的確切位置。默認情況下,add_subplot(111)將軸圖定位在[0.125,0.11,0.775,0.77],或者類似的位置,已經(jīng)為軸圖周圍的標題和刻度(標簽)保留的足夠的空間。
大多數(shù)情況下,在畫板上創(chuàng)建軸圖,最常使用的方法是add_subplot。只有在您需要為軸圖指定確切位置時,才會用到add_axes。
7.1 軸域
我們看一個軸域的例子。

如果您需要在任意位置創(chuàng)建軸圖,調(diào)用figure的add_axes函數(shù)添加軸域。該函數(shù)有一個列表參數(shù)[left, bottom,
width, height],分別為軸域在圖形中的左下角位置以及長度和寬度。這些參數(shù)以圖形的坐標體系為參數(shù),范圍在0到1之間。(0, 0)表示Figure的左下角位置,(1, 1)表示Figure的全部長度和寬度。
因此以下語句段,在(0.1,
0.1)位置處添加一個長和寬均為0.8的軸域。
ax1? = fig.add_axes([0.1,0.1,.8,.8])
ax1.set_xticks([])
ax1.set_yticks([])
ax1.text(0.6,? 0.6, 'axes([0.1,0.1,.8,.8])', ha='center', va='center', size=16,alpha=.5)
以下語句段,在(0.2, 0.2)位置處添加一個長和寬均為0.3的軸域。
ax2? = fig.add_axes([0.2,0.2,.3,.3])
ax2.set_xticks([])
ax2.set_yticks([])
ax2.text(0.5,? 0.5, 'axes([0.2,0.2,.3,.3])', ha='center', va='center', size=12, alpha=.5)
7.2 子域
我們再看一個子域的例子。

這個例子通過GridSpec機制添加子圖,我們先定義一個3×3的柵格。
G? = gridspec.GridSpec(3, 3)
第一個子圖位于第一行,占據(jù)所有三個列,因此行號指定為0,列號用冒號表示所有列。
ax1? = fig.add_subplot(G[0, :])
ax1.set_xticks([])
ax1.set_yticks([])
ax1.text(0.5,? 0.5, ',ha='center', va='center', size=20, alpha=.5)
第一個子圖位于第二行,占據(jù)前面兩個列,因此行號指定為1,列號用冒號-1表示倒數(shù)第1列之前的所有列,即第一列和第二列。
ax2? = fig.add_subplot(G[1,:-1])
ax2.set_xticks([])
ax2.set_yticks([])
ax2.text(0.5,? 0.5, ',ha='center', va='center', size=20, alpha=.5)
第三個子圖占據(jù)第二行和第三行,位于最后一個列,因此行號用1冒號表示第1列之后的所有列,即第二列和第三列,列號指定為-1表示倒數(shù)第1列,即第三列。
ax3? = fig.add_subplot(G[1:, -1])
ax3.set_xticks([])
ax3.set_yticks([])
ax3.text(0.5,? 0.5, ',ha='center', va='center', size=20, alpha=.5)
第四個子圖位于第三行第一列,因此行號用-1表示倒數(shù)第1行,即第三行,列號指定為0。
ax4? = fig.add_subplot(G[-1,0])
ax4.set_xticks([])
ax4.set_yticks([])
ax4.text(0.5,? 0.5, ',ha='center', va='center', size=20, alpha=.5)
第五個子圖位于第三行第二列,因此行號用-1表示倒數(shù)第1行,即第三行,列號用-2表示倒數(shù)第2列,即第二列。
ax5? = fig.add_subplot(G[-1,-2])
ax5.set_xticks([])
ax5.set_yticks([])
ax5.text(0.5,? 0.5, ',ha='center', va='center', size=20, alpha=.5)
八、圖的形狀
8.1 普通圖
前面介紹過許國普通圖的例子,這里不再贅述。
8.2 散點圖

在深度學習中,線性回歸算法是基礎的一環(huán)。我們需要訓練出一條直線能夠擬合如上圖中的數(shù)據(jù)。
首先生成模擬數(shù)據(jù)。
x = np.arange(0, 2.0, 0.1)?? #生成一個0到50的序列
y = x * 0.1 + 0.3 + np.random.normal(0.0,? 0.03, 20)
接下來,以散點的方式呈現(xiàn)上面的數(shù)據(jù)。
ax.scatter(x,y,? label='Sample Data')
ax.set_title('Linear
? Regression',fontsize=20) # 添加標題,并設置字體大小
ax.set_xlabel('X
? Axis', fontsize=15) # 添加x軸,并設置字體大小
ax.set_ylabel('Y
? Axis', fontsize=15) # 添加y軸,并設置字體大小
ax.set_ylim(0.2,? 0.6)
8.3 三維圖
Matplotlib通過axes3d模塊支持三維圖。
import? matplotlib.pyplot as plt
from? matplotlib import cm
from? mpl_toolkits.mplot3d import axes3d
fig? = plt.figure()
ax? = fig.gca(projection='3d')
X,? Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X,? Y, Z, rstride=8, cstride=8, alpha=0.3)
cset? = ax.contourf(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset? = ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset? = ax.contourf(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-40,? 40)
ax.set_ylabel('Y')
ax.set_ylim(-40,? 40)
ax.set_zlabel('Z')
ax.set_zlim(-100,? 100)
plt.show()

8.4 等高圖
Matplotlib支持繪制等高圖。
import? numpy as np
import? matplotlib.pyplot as plt
#
? 定義等高線高度函數(shù)
def? f(x, y):
??? return (1 - x / 2 + x ** 5 + y ** 3) *? np.exp(- x ** 2 - y ** 2)
#
? 數(shù)據(jù)數(shù)目
n? = 256
#
? 定義x, y
x? = np.linspace(-3, 3, n)
y? = np.linspace(-3, 3, n)
#
? 生成網(wǎng)格數(shù)據(jù)
X,? Y = np.meshgrid(x, y)
#
? 填充等高線的顏色, 8是等高線分為幾部分
plt.contourf(X,? Y, f(X, Y), 8, alpha = 0.75, cmap = plt.cm.hot)
#
? 繪制等高線
C? = plt.contour(X, Y, f(X, Y), 8, colors = 'black')
#
? 繪制等高線數(shù)據(jù)
plt.clabel(C,? inline = True, fontsize = 10)
#
? 去除坐標軸
plt.xticks(())
plt.yticks(())
plt.show()

九、動畫
9.1 二維動畫
Matplotlab支持生成二維動畫。例如,我們可以將在曲線上某點P的求導做成一個動畫,以更形象地闡述導數(shù)的概念,提高教學效果。

9.2 三維動畫
我們通過動畫的方式來呈現(xiàn)一個三維圖形的效果。
首先導入需要的包。
#? First import everthing you need
import? numpy as np
from? matplotlib import pyplot as plt
from? matplotlib import animation
from? mpl_toolkits.mplot3d import Axes3D
接下來準備數(shù)據(jù)。
x? = np.linspace(-10, 10, 101)
y? = x
x,? y = np.meshgrid(x, y)
z = x? ** 2 + y ** 2
創(chuàng)建畫板和畫紙。
#? Create a figure and a 3D Axes
fig? = plt.figure()
ax =? Axes3D(fig)
畫出動畫的初始圖。
#? Create an init function and the animate functions.
#? Both are explained in the tutorial. Since we are changing
#? the the elevation and azimuth and no objects are really
#? changed on the plot we don't have to return anything from
#? the init and animate function. (return value is explained
#? in the tutorial.
def? init():
??? ax.plot_surface(x, y, z, rstride=1,? cstride=1)
??? return fig,
定義動畫的動態(tài)函數(shù)。
def? animate(i):
??? print(i)
??? ax.view_init(elev=30., azim=i)
??? return fig,
按固定套路生成動畫對象。
#? Animate
anim =? animation.FuncAnimation(fig, animate, init_func=init, frames=360,? interval=20, blit=True)
將動畫保存在文件中。需要說明的是,必須在環(huán)境中安裝ffmpeg模塊,才能進行保存。
#? Save
anim.save('3danim.mp4')
最后,我們可以打開保存的文件查看動畫效果。

十、Matplotlib可視化應用
Matplotlib是深度學習一個行之有效的手段。首先,它可以實現(xiàn)訓練數(shù)據(jù)的可視化,無論是監(jiān)督學習的分類問題和回歸問題,還是無監(jiān)督學習的聚類問題,都可以幫助我們設計有效的模型進行訓練。例如,對于鳶尾花分類問題,我們將數(shù)據(jù)如下圖可視化,就很清除地知道可以使用感知機或線性回歸算法進行處理了。

在深度學習中,Matplotlib還可以實現(xiàn)訓練過程的可視化,從而判斷出訓練算法或訓練參數(shù)是否需要調(diào)整,以及在什么時候可以停止訓練。這里需要一張訓練過程收斂和不收斂的圖進行比較。
此外,深度學習在平面幾何和立體幾何中能夠幫助呈現(xiàn)動畫效果,從而起到輔助教學和求解的作用。例如,下面是一道經(jīng)典的平面幾何試題。
已知:如圖,O是半圓的圓心,C、E是圓上的兩點,CD⊥AB,EF⊥AB,EG⊥CO,求證:CD=GF。

就這道題而言,我們可以將C固定,讓E在圓上變動,更直觀地感知GF的變化。用Matplotlib可視化出來的動畫效果如下:

下面是一個立體幾何的例子。
如圖,AD與BC是四面體ABCD中相互垂直的棱,BC=2,若AD=2c,且AB+BD=AC+CD=2a,其中a、c為常數(shù),則四面體的體積的最大值是____。

雖然不完美,我們還是可以用Matplotlib畫出在BC變化的情況下,四面體的變化情況,從而推測出四面體體積的最大值應該出現(xiàn)在中間位置,為求解提供一定的方向。

下面是另一個立體幾何的例子。
如圖,在四棱錐S-ABCD中,底面ABCD為正方形,側棱SD底面ABCD,E、F分別是AB、SC的中點。
[if !supportLists](1)?????? [endif]求證:EF//平面SAD
[if !supportLists](2)?????? [endif]設SD=2CD,求二面角A-EF-D的大小。

我們可以用Matplotlib畫出上述題目的形狀,然后手動調(diào)整它的觀察位置和角度,全方位了解上述立體圖形。


上面第一個圖為題中的正視圖,我們拖動鼠標旋轉,在轉到第二個圖示,我們發(fā)現(xiàn),似乎兩條紅邊是相等的,這可以通過計算證明。然后就可以找到題中的關鍵點,即線段EF的中點。從而最終得到此題的答案。
結束語
本文介紹了Python的繪圖模塊Matplotlib以及在深度學習等方面的應用。需要知道,Matplotlib僅僅是一個工具,我們掌握它,為我們要實現(xiàn)的目標更好的服務。