原文連接: Jack-Cui,cuijiahua.com/blog/2017/1…html
樸素貝葉斯算法是有監督的學習算法,解決的是分類問題,如客戶是否流失、是否值得投資、信用等級評定等多分類問題。該算法的優勢在於簡單易懂、學習效率高、在某些領域的分類問題中可以與決策樹、神經網絡相媲美。但因爲該算法以自變量之間的獨立(條件特徵獨立)性和連續變量的正態性假設爲前提,就會致使算法精度在某種程度上受影響。python
本篇文章將從樸素貝葉斯推斷原理開始講起,經過實例進行輔助講解。最後,使用Python3編程實現一個簡單的言論過濾器。git
樸素貝葉斯是貝葉斯決策理論的一部分,因此在講述樸素貝葉斯以前有必要快速瞭解一下貝葉斯決策理論。github
假設如今咱們有一個數據集,它由兩類數據組成,數據分佈以下圖所示:算法
咱們如今用p1(x,y)表示數據點(x,y)屬於類別1(圖中紅色圓點表示的類別)的機率,用p2(x,y)表示數據點(x,y)屬於類別2(圖中藍色三角形表示的類別)的機率,那麼對於一個新數據點(x,y),能夠用下面的規則來判斷它的類別:編程
也就是說,咱們會選擇高几率對應的類別。這就是貝葉斯決策理論的核心思想,即選擇具備最高几率的決策。已經瞭解了貝葉斯決策理論的核心思想,那麼接下來,就是學習如何計算p1和p2機率。數組
在學習計算p1 和p2機率以前,咱們須要瞭解什麼是條件機率(Condittional probability),就是指在事件B發生的狀況下,事件A發生的機率,用P(A|B)來表示。bash
根據文氏圖,能夠很清楚地看到在事件B發生的狀況下,事件A發生的機率就是P(A∩B)除以P(B)。網絡
所以,app
同理可得,
因此,
即
這就是條件機率的計算公式。
除了條件機率之外,在計算p1和p2的時候,還要用到全機率公式,所以,這裏繼續推導全機率公式。
假定樣本空間S,是兩個事件A與A'的和。
上圖中,紅色部分是事件A,綠色部分是事件A',它們共同構成了樣本空間S。
在這種狀況下,事件B能夠劃分紅兩個部分。
即
在上一節的推導當中,咱們已知
因此,
這就是全機率公式。它的含義是,若是A和A'構成樣本空間的一個劃分,那麼事件B的機率,就等於A和A'的機率分別乘以B對這兩個事件的條件機率之和。
將這個公式代入上一節的條件機率公式,就獲得了條件機率的另外一種寫法:
對條件機率公式進行變形,能夠獲得以下形式:
咱們把P(A)稱爲"先驗機率"(Prior probability),即在B事件發生以前,咱們對A事件機率的一個判斷。
P(A|B)稱爲"後驗機率"(Posterior probability),即在B事件發生以後,咱們對A事件機率的從新評估。
P(B|A)/P(B)稱爲"可能性函數"(Likelyhood),這是一個調整因子,使得預估機率更接近真實機率。
因此,條件機率能夠理解成下面的式子:
後驗機率 = 先驗機率 x 調整因子
複製代碼
這就是貝葉斯推斷的含義。咱們先預估一個"先驗機率",而後加入實驗結果,看這個實驗究竟是加強仍是削弱了"先驗機率",由此獲得更接近事實的"後驗機率"。
在這裏,若是"可能性函數"P(B|A)/P(B)>1,意味着"先驗機率"被加強,事件A的發生的可能性變大;若是"可能性函數"=1,意味着B事件無助於判斷事件A的可能性;若是"可能性函數"<1,意味着"先驗機率"被削弱,事件A的可能性變小。
爲了加深對貝葉斯推斷的理解,咱們舉一個例子。
兩個如出一轍的碗,一號碗有30顆水果糖和10顆巧克力糖,二號碗有水果糖和巧克力糖各20顆。如今隨機選擇一個碗,從中摸出一顆糖,發現是水果糖。請問這顆水果糖來自一號碗的機率有多大?
咱們假定,H1表示一號碗,H2表示二號碗。因爲這兩個碗是同樣的,因此P(H1)=P(H2),也就是說,在取出水果糖以前,這兩個碗被選中的機率相同。所以,P(H1)=0.5,咱們把這個機率就叫作"先驗機率",即沒有作實驗以前,來自一號碗的機率是0.5。
再假定,E表示水果糖,因此問題就變成了在已知E的狀況下,來自一號碗的機率有多大,即求P(H1|E)。咱們把這個機率叫作"後驗機率",即在E事件發生以後,對P(H1)的修正。
根據條件機率公式,獲得
已知,P(H1)等於0.5,P(E|H1)爲一號碗中取出水果糖的機率,等於30÷(30+10)=0.75,那麼求出P(E)就能夠獲得答案。根據全機率公式,
因此,
將數字代入原方程,獲得
這代表,來自一號碗的機率是0.6。也就是說,取出水果糖以後,H1事件的可能性獲得了加強。
同時再思考一個問題,在使用該算法的時候,若是不須要知道具體的類別機率,即上面P(H1|E)=0.6,只須要知道所屬類別,即來自一號碗,咱們有必要計算P(E)這個全機率嗎?要知道咱們只須要比較 P(H1|E)和P(H2|E)的大小,找到那個最大的機率就能夠。既然如此,二者的分母都是相同的,那咱們只須要比較分子便可。即比較P(E|H1)P(H1)和P(E|H2)P(H2)的大小,因此爲了減小計算量,全機率公式在實際編程中能夠不使用。
理解了貝葉斯推斷,那麼讓咱們繼續看看樸素貝葉斯。貝葉斯和樸素貝葉斯的概念是不一樣的,區別就在於「樸素」二字,樸素貝葉斯對條件個機率分佈作了條件獨立性的假設。 好比下面的公式,假設有n個特徵:
因爲每一個特徵都是獨立的,咱們能夠進一步拆分公式 :
這樣咱們就能夠進行計算了。若是有些迷糊,讓咱們從一個例子開始講起,你會看到貝葉斯分類器很好懂,一點都不難。
某個醫院早上來了六個門診的病人,他們的狀況以下表所示:
如今又來了第七個病人,是一個打噴嚏的建築工人。請問他患上感冒的機率有多大?
根據貝葉斯定理:
可得:
根據樸素貝葉斯條件獨立性的假設可知,"打噴嚏"和"建築工人"這兩個特徵是獨立的,所以,上面的等式就變成了
這裏能夠計算:
所以,這個打噴嚏的建築工人,有66%的機率是得了感冒。同理,能夠計算這個病人患上過敏或腦震盪的機率。比較這幾個機率,就能夠知道他最可能得什麼病。
這就是貝葉斯分類器的基本方法:在統計資料的基礎上,依據某些特徵,計算各個類別的機率,從而實現分類。
一樣,在編程的時候,若是不須要求出所屬類別的具體機率,P(打噴嚏) = 0.5和P(建築工人) = 0.33的機率是能夠不用求的。
說了這麼多,沒點實踐編程怎麼行?
以在線社區留言爲例。爲了避免影響社區的發展,咱們要屏蔽侮辱性的言論,因此要構建一個快速過濾器,若是某條留言使用了負面或者侮辱性的語言,那麼就將該留言標誌爲內容不當。過濾這類內容是一個很常見的需求。對此問題創建兩個類型:侮辱類和非侮辱類,使用1和0分別表示。
咱們把文本當作單詞向量或者詞條向量,也就是說將句子轉換爲向量。考慮出現全部文檔中的單詞,再決定將哪些單詞歸入詞彙表或者說所要的詞聚集合,而後必需要將每一篇文檔轉換爲詞彙表上的向量。簡單起見,咱們先假設已經將本文切分完畢,存放到列表中,並對詞彙向量進行分類標註。編寫代碼以下:
# -*- coding: UTF-8 -*-
""" 函數說明:建立實驗樣本 Parameters: 無 Returns: postingList - 實驗樣本切分的詞條 classVec - 類別標籤向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #切分的詞條
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0,1,0,1,0,1] #類別標籤向量,1表明侮辱性詞彙,0表明不是
return postingList,classVec
if __name__ == '__main__':
postingLIst, classVec = loadDataSet()
for each in postingLIst:
print(each)
print(classVec)
複製代碼
從運行結果能夠看出,咱們已經將postingList是存放詞條列表中,classVec是存放每一個詞條的所屬類別,1表明侮辱類 ,0表明非侮辱類。
繼續編寫代碼,前面咱們已經說過咱們要先建立一個詞彙表,並將切分好的詞條轉換爲詞條向量。
# -*- coding: UTF-8 -*-
""" 函數說明:建立實驗樣本 Parameters: 無 Returns: postingList - 實驗樣本切分的詞條 classVec - 類別標籤向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #切分的詞條
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0,1,0,1,0,1] #類別標籤向量,1表明侮辱性詞彙,0表明不是
return postingList,classVec
""" 函數說明:根據vocabList詞彙表,將inputSet向量化,向量的每一個元素爲1或0 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的詞條列表 Returns: returnVec - 文檔向量,詞集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一個其中所含元素都爲0的向量
for word in inputSet: #遍歷每一個詞條
if word in vocabList: #若是詞條存在於詞彙表中,則置1
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文檔向量
""" 函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表 Parameters: dataSet - 整理的樣本數據集 Returns: vocabSet - 返回不重複的詞條列表,也就是詞彙表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def createVocabList(dataSet):
vocabSet = set([]) #建立一個空的不重複列表
for document in dataSet:
vocabSet = vocabSet | set(document) #取並集
return list(vocabSet)
if __name__ == '__main__':
postingList, classVec = loadDataSet()
print('postingList:\n',postingList)
myVocabList = createVocabList(postingList)
print('myVocabList:\n',myVocabList)
trainMat = []
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
print('trainMat:\n', trainMat)
複製代碼
從運行結果能夠看出,postingList是原始的詞條列表,myVocabList是詞彙表。myVocabList是全部單詞出現的集合,沒有重複的元素。詞彙表是用來幹什麼的?沒錯,它是用來將詞條向量化的,一個單詞在詞彙表中出現過一次,那麼就在相應位置記做1,若是沒有出現就在相應位置記做0。trainMat是全部的詞條向量組成的列表。它裏面存放的是根據myVocabList向量化的詞條向量。
咱們已經獲得了詞條向量。接下來,咱們就能夠經過詞條向量訓練樸素貝葉斯分類器。
# -*- coding: UTF-8 -*-
import numpy as np
""" 函數說明:建立實驗樣本 Parameters: 無 Returns: postingList - 實驗樣本切分的詞條 classVec - 類別標籤向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #切分的詞條
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0,1,0,1,0,1] #類別標籤向量,1表明侮辱性詞彙,0表明不是
return postingList,classVec
""" 函數說明:根據vocabList詞彙表,將inputSet向量化,向量的每一個元素爲1或0 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的詞條列表 Returns: returnVec - 文檔向量,詞集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一個其中所含元素都爲0的向量
for word in inputSet: #遍歷每一個詞條
if word in vocabList: #若是詞條存在於詞彙表中,則置1
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文檔向量
""" 函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表 Parameters: dataSet - 整理的樣本數據集 Returns: vocabSet - 返回不重複的詞條列表,也就是詞彙表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def createVocabList(dataSet):
vocabSet = set([]) #建立一個空的不重複列表
for document in dataSet:
vocabSet = vocabSet | set(document) #取並集
return list(vocabSet)
""" 函數說明:樸素貝葉斯分類器訓練函數 Parameters: trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣 trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec Returns: p0Vect - 非侮辱類的條件機率數組 p1Vect - 侮辱類的條件機率數組 pAbusive - 文檔屬於侮辱類的機率 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix) #計算訓練的文檔數目
numWords = len(trainMatrix[0]) #計算每篇文檔的詞條數
pAbusive = sum(trainCategory)/float(numTrainDocs) #文檔屬於侮辱類的機率
p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #建立numpy.zeros數組,詞條出現數初始化爲0
p0Denom = 0.0; p1Denom = 0.0 #分母初始化爲0
for i in range(numTrainDocs):
if trainCategory[i] == 1: #統計屬於侮辱類的條件機率所需的數據,即P(w0|1),P(w1|1),P(w2|1)···
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #統計屬於非侮辱類的條件機率所需的數據,即P(w0|0),P(w1|0),P(w2|0)···
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num/p1Denom
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive #返回屬於侮辱類的條件機率數組,屬於非侮辱類的條件機率數組,文檔屬於侮辱類的機率
if __name__ == '__main__':
postingList, classVec = loadDataSet()
myVocabList = createVocabList(postingList)
print('myVocabList:\n', myVocabList)
trainMat = []
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, classVec)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('classVec:\n', classVec)
print('pAb:\n', pAb)
複製代碼
運行結果以下,p0V存放的是每一個單詞屬於類別0,也就是非侮辱類詞彙的機率。好比p0V的倒數第6個機率,就是stupid這個單詞屬於非侮辱類的機率爲0。同理,p1V的倒數第6個機率,就是stupid這個單詞屬於侮辱類的機率爲0.15789474,也就是約等於15.79%的機率。咱們知道stupid的中文意思是蠢貨,難聽點的叫法就是傻逼。顯而易見,這個單詞屬於侮辱類。pAb是全部侮辱類的樣本佔全部樣本的機率,從classVec中能夠看出,一用有3個侮辱類,3個非侮辱類。因此侮辱類的機率是0.5。所以p0V存放的就是P(非侮辱類 | him) = 0.0833,P(非侮辱類 | is) = 0.0417,一直到P(非侮辱類 | dog) = 0.0417,這些單詞的條件機率。同理,p1V存放的就是各個單詞屬於侮辱類的條件機率。pAb就是先驗機率。
已經訓練好分類器,接下來,使用分類器進行分類。
# -*- coding: UTF-8 -*-
import numpy as np
from functools import reduce
""" 函數說明:建立實驗樣本 Parameters: 無 Returns: postingList - 實驗樣本切分的詞條 classVec - 類別標籤向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #切分的詞條
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0,1,0,1,0,1] #類別標籤向量,1表明侮辱性詞彙,0表明不是
return postingList,classVec #返回實驗樣本切分的詞條和類別標籤向量
""" 函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表 Parameters: dataSet - 整理的樣本數據集 Returns: vocabSet - 返回不重複的詞條列表,也就是詞彙表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def createVocabList(dataSet):
vocabSet = set([]) #建立一個空的不重複列表
for document in dataSet:
vocabSet = vocabSet | set(document) #取並集
return list(vocabSet)
""" 函數說明:根據vocabList詞彙表,將inputSet向量化,向量的每一個元素爲1或0 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的詞條列表 Returns: returnVec - 文檔向量,詞集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一個其中所含元素都爲0的向量
for word in inputSet: #遍歷每一個詞條
if word in vocabList: #若是詞條存在於詞彙表中,則置1
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文檔向量
""" 函數說明:樸素貝葉斯分類器訓練函數 Parameters: trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣 trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec Returns: p0Vect - 非侮辱類的條件機率數組 p1Vect - 侮辱類的條件機率數組 pAbusive - 文檔屬於侮辱類的機率 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix) #計算訓練的文檔數目
numWords = len(trainMatrix[0]) #計算每篇文檔的詞條數
pAbusive = sum(trainCategory)/float(numTrainDocs) #文檔屬於侮辱類的機率
p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #建立numpy.zeros數組,
p0Denom = 0.0; p1Denom = 0.0 #分母初始化爲0.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: #統計屬於侮辱類的條件機率所需的數據,即P(w0|1),P(w1|1),P(w2|1)···
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #統計屬於非侮辱類的條件機率所需的數據,即P(w0|0),P(w1|0),P(w2|0)···
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num/p1Denom #相除
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive #返回屬於侮辱類的條件機率數組,屬於非侮辱類的條件機率數組,文檔屬於侮辱類的機率
""" 函數說明:樸素貝葉斯分類器分類函數 Parameters: vec2Classify - 待分類的詞條數組 p0Vec - 侮辱類的條件機率數組 p1Vec -非侮辱類的條件機率數組 pClass1 - 文檔屬於侮辱類的機率 Returns: 0 - 屬於非侮辱類 1 - 屬於侮辱類 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = reduce(lambda x,y:x*y, vec2Classify * p1Vec) * pClass1 #對應元素相乘
p0 = reduce(lambda x,y:x*y, vec2Classify * p0Vec) * (1.0 - pClass1)
print('p0:',p0)
print('p1:',p1)
if p1 > p0:
return 1
else:
return 0
""" 函數說明:測試樸素貝葉斯分類器 Parameters: 無 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
def testingNB():
listOPosts,listClasses = loadDataSet() #建立實驗樣本
myVocabList = createVocabList(listOPosts) #建立詞彙表
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #將實驗樣本向量化
p0V,p1V,pAb = trainNB0(np.array(trainMat),np.array(listClasses)) #訓練樸素貝葉斯分類器
testEntry = ['love', 'my', 'dalmation'] #測試樣本1
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'屬於侮辱類') #執行分類並打印分類結果
else:
print(testEntry,'屬於非侮辱類') #執行分類並打印分類結果
testEntry = ['stupid', 'garbage'] #測試樣本2
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'屬於侮辱類') #執行分類並打印分類結果
else:
print(testEntry,'屬於非侮辱類') #執行分類並打印分類結果
if __name__ == '__main__':
testingNB()
複製代碼
咱們測試了兩個詞條,在使用分類器前,也須要對詞條向量化,而後使用classifyNB()函數,用樸素貝葉斯公式,計算詞條向量屬於侮辱類和非侮辱類的機率。運行結果以下:
你會發現,這樣寫的算法沒法進行分類,p0和p1的計算結果都是0,顯然結果錯誤。這是爲何呢?下一篇文章繼續講解~
樸素貝葉斯推斷的一些優勢:
樸素貝葉斯推斷的一些缺點:
其它:
PS: 若是以爲本篇本章對您有所幫助,歡迎關注、評論、贊!
本文出現的全部代碼和數據集,都可在個人github上下載,歡迎Follow、Star:github.com/Jack-Cheris…
圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。 公開課地址: ke.qq.com/course/3627…
加入python學習討論羣 78486745 ,獲取資料,和廣大羣友一塊兒學習。