做者|Mauro Di Pietro
編譯|VK
來源|Towards Data Science
python
摘要
在本文中,我將使用NLP和Python解釋如何爲機器學習模型分析文本數據和提取特徵。git
天然語言處理(NLP)是人工智能的一個研究領域,它研究計算機與人類語言之間的相互做用,特別是如何對計算機進行編程以處理和分析大量天然語言數據。github
NLP經常使用於文本數據的分類。文本分類是根據文本數據的內容對其進行分類的問題。文本分類最重要的部分是特徵工程:從原始文本數據爲機器學習模型建立特徵的過程。web
在本文中,我將解釋不一樣的方法來分析文本並提取可用於構建分類模型的特徵。我將介紹一些有用的Python代碼。算法
這些代碼能夠很容易地應用於其餘相似的狀況(只需複製、粘貼、運行),而且我加上了註釋,以便你能夠理解示例(連接到下面的完整代碼)。編程
我將使用「新聞類別數據集」(如下連接),其中向你提供從赫芬頓郵報得到的2012年至2018年的新聞標題,並要求你使用正確的類別對其進行分類。api
https://www.kaggle.com/rmisra/news-category-dataset網絡
特別是,我將經過:app
-
環境設置:導入包並讀取數據。
-
語言檢測:瞭解哪些天然語言數據在其中。
-
文本預處理:文本清理和轉換。
-
長度分析:用不一樣的指標來衡量。
-
情緒分析:判斷一篇文章是正面的仍是負面的。
-
命名實體識別:帶有預約義類別(如人名、組織、位置)的標識文本。
-
詞頻:找出最重要的n個字母。
-
詞向量:把一個字轉換成向量。
-
主題模型:從語料庫中提取主題。
環境設置
首先,我須要導入如下庫。
## 數據 import pandas as pd import collections import json ## 繪圖 import matplotlib.pyplot as plt import seaborn as sns import wordcloud ## 文本處理 import re import nltk ## 語言檢測 import langdetect ## 情感分析 from textblob import TextBlob ## 命名實體識別 import spacy ## 詞頻 from sklearn import feature_extraction, manifold ## word embedding import gensim.downloader as gensim_api ## 主題模型 import gensim
數據集包含在一個json文件中,所以我將首先使用json包將其讀入字典列表,而後將其轉換爲pandas數據幀。
lst_dics = [] with open('data.json', mode='r', errors='ignore') as json_file: for dic in json_file: lst_dics.append( json.loads(dic) ) ## 打印第一個 lst_dics[0]
原始數據集包含30多個類別,但在本教程中,我將使用3個類別的子集:娛樂、政治和技術(Entertainment, Politics, Tech)。
## 建立dtf dtf = pd.DataFrame(lst_dics) ## 篩選類別 dtf = dtf[ dtf["category"].isin(['ENTERTAINMENT','POLITICS','TECH']) ][["category","headline"]] ## 重命名列 dtf = dtf.rename(columns={"category":"y", "headline":"text"}) ## 打印5個隨機行 dtf.sample(5)
爲了理解數據集的組成,我將經過用條形圖顯示標籤頻率來研究單變量分佈(僅一個變量的機率分佈)。
x = "y" fig, ax = plt.subplots() fig.suptitle(x, fontsize=12) dtf[x].reset_index().groupby(x).count().sort_values(by= "index").plot(kind="barh", legend=False, ax=ax).grid(axis='x') plt.show()
數據集是不平衡的:與其餘數據集相比,科技新聞的比例確實很小。這多是建模過程當中的一個問題,對數據集從新採樣可能頗有用。
如今已經設置好了,我將從清理數據開始,而後從原始文本中提取不一樣的細節,並將它們做爲數據幀的新列添加。這些新信息能夠做爲分類模型的潛在特徵。
語言檢測
首先,我想確保我使用的是同一種語言,而且使用langdetect包,這很是簡單。爲了舉例說明,我將在數據集的第一個新聞標題上使用它:
txt = dtf["text"].iloc[0] print(txt, " --> ", langdetect.detect(txt))
咱們爲整個數據集添加一個包含語言信息的列:
dtf['lang'] = dtf["text"].apply(lambda x: langdetect.detect(x) if x.strip() != "" else "") dtf.head()
數據幀如今有一個新列。使用以前的相同代碼,我能夠看到有多少種不一樣的語言:
即便有不一樣的語言,英語也是主要的語言。因此我要用英語過濾新聞。
dtf = dtf[dtf["lang"]=="en"]
文本預處理
數據預處理是準備原始數據以使其適合機器學習模型的階段。對於NLP,這包括文本清理、刪除停用詞、詞幹還原。
文本清理步驟因數據類型和所需任務而異。一般,在文本被標識化以前,字符串被轉換爲小寫,標點符號被刪除。標識化(Tokenization)是將字符串拆分爲字符串列表(或「標識」)的過程。
再以第一條新聞標題爲例:
print("--- original ---") print(txt) print("--- cleaning ---") txt = re.sub(r'[^\w\s]', '', str(txt).lower().strip()) print(txt) print("--- tokenization ---") txt = txt.split() print(txt)
咱們要保留列表中的全部標識嗎?咱們沒有。事實上,咱們想刪除全部不提供額外信息的單詞。
在這個例子中,最重要的詞是「song」,由於它能夠將任何分類模型指向正確的方向。相比之下,像「and」、「for」、「the」這樣的詞並不有用,由於它們可能出如今數據集中幾乎全部的觀察中。
這些是停用詞的例子。停用詞一般指的是語言中最多見的單詞,可是咱們沒有一個通用的停用詞列表。
咱們可使用NLTK(天然語言工具包)爲英語詞彙表建立一個通用停用詞列表,它是一套用於符號和統計天然語言處理的庫和程序。
lst_stopwords = nltk.corpus.stopwords.words("english") lst_stopwords
讓咱們從第一個新聞標題中刪除這些停用詞:
print("--- remove stopwords ---") txt = [word for word in txt if word not in lst_stopwords] print(txt)
咱們須要很是當心的停用詞,由於若是你刪除了錯誤的標識,你可能會失去重要的信息。例如,刪除了「Will」一詞,咱們丟失了此人是Will Smith的信息。
考慮到這一點,在刪除停用詞以前對原始文本進行一些手動修改是頗有用的(例如,將「Will Smith」替換爲「Will_Smith」)。
既然咱們有了全部有用的標識,就能夠應用word轉換了。詞幹化(Stemming)和引理化(Lemmatization)都產生了單詞的詞根形式。
他們的區別在於詞幹可能不是一個實際的單詞,而引理是一個實際的語言單詞(詞幹一般更快)。這些算法都是由NLTK提供的。
繼續示例:
print("--- stemming ---") ps = nltk.stem.porter.PorterStemmer() print([ps.stem(word) for word in txt]) print("--- lemmatisation ---") lem = nltk.stem.wordnet.WordNetLemmatizer() print([lem.lemmatize(word) for word in txt])
如你所見,有些單詞已經改變了:「joins」變成了它的根形式「join」,就像「cups」。另外一方面,「official」只隨着詞幹的變化而變化,詞幹「offici」不是一個詞,而是經過去掉後綴「-al」而產生的。
我將把全部這些預處理步驟放在一個函數中,並將其應用於整個數據集。
''' 預處理. :parameter :param text: string - 包含文本的列的名稱 :param lst_stopwords: list - 要刪除的停用詞列表 :param flg_stemm: bool - 是否應用詞幹 :param flg_lemm: bool - 是否應用引理化 :return cleaned text ''' def utils_preprocess_text(text, flg_stemm=False, flg_lemm=True, lst_stopwords=None): ## 清洗(轉換爲小寫並刪除標點和字符,而後刪除) text = re.sub(r'[^\w\s]', '', str(text).lower().strip()) ## 標識化(從字符串轉換爲列表) lst_text = text.split() ## 刪除停用詞 if lst_stopwords is not None: lst_text = [word for word in lst_text if word not in lst_stopwords] ## 詞幹化 if flg_stemm == True: ps = nltk.stem.porter.PorterStemmer() lst_text = [ps.stem(word) for word in lst_text] ## 引理化 if flg_lemm == True: lem = nltk.stem.wordnet.WordNetLemmatizer() lst_text = [lem.lemmatize(word) for word in lst_text] ## 從列表返回到字符串 text = " ".join(lst_text) return text
請注意,你不該該同時應用詞幹和引理化。在這裏我將使用後者。
dtf["text_clean"] = dtf["text"].apply(lambda x: utils_preprocess_text(x, flg_stemm=False, flg_lemm=True, lst_stopwords))
和之前同樣,我建立了一個新的列:
dtf.head()
print(dtf["text"].iloc[0], " --> ", dtf["text_clean"].iloc[0])
長度分析
查看文本的長度很重要,由於這是一個簡單的計算,能夠提供不少信息。
例如,也許咱們足夠幸運地發現,一個類別系統地比另外一個類別長,而長度只是構建模型所需的惟一特徵。不幸的是,因爲新聞標題的長度類似,狀況並不是如此,但值得一試。
文本數據有幾種長度度量。我將舉幾個例子:
- 字數:統計文本中的標識數(用空格分隔)
- 字符數:將每一個標識的字符數相加
- 句子計數:計算句子的數量(用句點分隔)
- 平均字長:字長之和除以字數(字數/字數)
- 平均句子長度:句子長度之和除以句子數(字數/句子數)
dtf['word_count'] = dtf["text"].apply(lambda x: len(str(x).split(" "))) dtf['char_count'] = dtf["text"].apply(lambda x: sum(len(word) for word in str(x).split(" "))) dtf['sentence_count'] = dtf["text"].apply(lambda x: len(str(x).split("."))) dtf['avg_word_length'] = dtf['char_count'] / dtf['word_count'] dtf['avg_sentence_lenght'] = dtf['word_count'] / dtf['sentence_count'] dtf.head()
讓咱們看看例子:
這些新變量相對於目標的分佈狀況如何?爲了回答這個問題,我將研究二元分佈(兩個變量如何一塊兒影響)。
首先,我將整個觀察結果分紅3個樣本(政治、娛樂、科技),而後比較樣本的直方圖和密度。若是分佈不一樣,那麼變量是可預測的,由於這三組有不一樣的模式。
例如,讓咱們看看字符數是否與目標變量相關:
x, y = "char_count", "y" fig, ax = plt.subplots(nrows=1, ncols=2) fig.suptitle(x, fontsize=12) for i in dtf[y].unique(): sns.distplot(dtf[dtf[y]==i][x], hist=True, kde=False, bins=10, hist_kws={"alpha":0.8}, axlabel="histogram", ax=ax[0]) sns.distplot(dtf[dtf[y]==i][x], hist=False, kde=True, kde_kws={"shade":True}, axlabel="density", ax=ax[1]) ax[0].grid(True) ax[0].legend(dtf[y].unique()) ax[1].grid(True) plt.show()
這三個類別具備類似的長度分佈。在這裏,密度圖很是有用,由於樣本有不一樣的大小。
情感分析
情感分析是經過數字或類來表達文本數據的主觀情感。因爲天然語言的模糊性,計算情感是天然語言處理中最困難的任務之一。
例如,短語「This is so bad that it’s good」有不止一種解釋。一個模型能夠給「好」這個詞分配一個積極的信號,給「壞」這個詞分配一個消極的信號,從而產生一種中性的情緒。這是由於上下文未知。
最好的方法是訓練你本身的情緒模型,使之適合你的數據。當沒有足夠的時間或數據時,可使用預訓練好的模型,好比Textblob和Vader。
- Textblob創建在NLTK的基礎上,是最流行的一種,它能夠給單詞賦予極性,並做爲一個平均值來估計整個文本的情緒。
- 另外一方面,Vader(Valence-aware dictionary and mootion reasoner)是一個基於規則的模型,尤爲適用於社交媒體數據。
我將使用Textblob添加一個情感特徵:
dtf["sentiment"] = dtf[column].apply(lambda x: TextBlob(x).sentiment.polarity) dtf.head()
print(dtf["text"].iloc[0], " --> ", dtf["sentiment"].iloc[0])
分類和情緒之間有規律嗎?
大多數的頭條新聞都是中性的,除了政治新聞偏向於負面,科技新聞偏向於正面。
命名實體識別
命名實體識別(Named entity recognition,NER)是用預約義的類別(如人名、組織、位置、時間表達式、數量等)提取非結構化文本中的命名實體的過程。
訓練一個NER模型是很是耗時的,由於它須要一個很是豐富的數據集。幸運的是有人已經爲咱們作了這項工做。最好的開源NER工具之一是SpaCy。它提供了不一樣的NLP模型,這些模型可以識別多種類型的實體。
我將在咱們一般的標題(未經預處理的原始文本)中使用SpaCy模型en_core_web_lg(網絡數據上訓練的英語的大型模型),給出一個例子:
## 調用 ner = spacy.load("en_core_web_lg") ## 打標籤 txt = dtf["text"].iloc[0] doc = ner(txt) ## 展現結果 spacy.displacy.render(doc, style="ent")
這很酷,可是咱們怎麼能把它變成有用的特徵呢?這就是我要作的:
-
對數據集中的每一個文本觀察運行NER模型,就像我在前面的示例中所作的那樣。
-
對於每一個新聞標題,我將把全部被承認的實體以及同一實體出如今文本中的次數放入一個新的列(稱爲「tags」)。
在這個例子中:
{ (‘Will Smith’, ‘PERSON’):1,
(‘Diplo’, ‘PERSON’):1,
(‘Nicky Jam’, ‘PERSON’):1,
(「The 2018 World Cup’s」, ‘EVENT’):1 } -
而後,我將爲每一個標識類別(Person、Org、Event,…)建立一個新列,並計算每一個標識類別找到的實體數。在上面的例子中,特徵將是
tags_PERSON = 3
tags_EVENT = 1
## 標識文本並將標識導出到列表中 dtf["tags"] = dtf["text"].apply(lambda x: [(tag.text, tag.label_) for tag in ner(x).ents] ) ## utils函數計算列表元素 def utils_lst_count(lst): dic_counter = collections.Counter() for x in lst: dic_counter[x] += 1 dic_counter = collections.OrderedDict( sorted(dic_counter.items(), key=lambda x: x[1], reverse=True)) lst_count = [ {key:value} for key,value in dic_counter.items() ] return lst_count ## 計數 dtf["tags"] = dtf["tags"].apply(lambda x: utils_lst_count(x)) ## utils函數爲每一個標識類別建立新列 def utils_ner_features(lst_dics_tuples, tag): if len(lst_dics_tuples) > 0: tag_type = [] for dic_tuples in lst_dics_tuples: for tuple in dic_tuples: type, n = tuple[1], dic_tuples[tuple] tag_type = tag_type + [type]*n dic_counter = collections.Counter() for x in tag_type: dic_counter[x] += 1 return dic_counter[tag] else: return 0 ## 提取特徵 tags_set = [] for lst in dtf["tags"].tolist(): for dic in lst: for k in dic.keys(): tags_set.append(k[1]) tags_set = list(set(tags_set)) for feature in tags_set: dtf["tags_"+feature] = dtf["tags"].apply(lambda x: utils_ner_features(x, feature)) ## 結果 dtf.head()
如今咱們能夠在標識類型分佈上有一個視圖。以組織標籤(公司和組織)爲例:
爲了更深刻地分析,咱們須要使用在前面的代碼中建立的列「tags」。讓咱們爲標題類別之一繪製最經常使用的標識:
y = "ENTERTAINMENT" tags_list = dtf[dtf["y"]==y]["tags"].sum() map_lst = list(map(lambda x: list(x.keys())[0], tags_list)) dtf_tags = pd.DataFrame(map_lst, columns=['tag','type']) dtf_tags["count"] = 1 dtf_tags = dtf_tags.groupby(['type', 'tag']).count().reset_index().sort_values("count", ascending=False) fig, ax = plt.subplots() fig.suptitle("Top frequent tags", fontsize=12) sns.barplot(x="count", y="tag", hue="type", data=dtf_tags.iloc[:top,:], dodge=False, ax=ax) ax.grid(axis="x") plt.show()
接着介紹NER的另外一個有用的應用程序:你還記得咱們從「Will Smith」的名稱中刪除了「Will」這個單詞的停用詞嗎?解決這個問題的一個有趣的方法是將「Will Smith」替換爲「Will_Smith」,這樣它就不會受到停用詞刪除的影響。
遍歷數據集中的全部文原本更更名稱是不可能的,因此讓咱們使用SpaCy。如咱們所知,SpaCy能夠識別一我的名,所以咱們可使用它來檢測姓名,而後修改字符串。
## 預測 txt = dtf["text"].iloc[0] entities = ner(txt).ents ## 打標籤 tagged_txt = txt for tag in entities: tagged_txt = re.sub(tag.text, "_".join(tag.text.split()), tagged_txt) ## 結果 print(tagged_txt)
詞頻
到目前爲止,咱們已經看到了如何經過分析和處理整個文原本進行特徵工程。
如今,咱們將經過計算n-grams頻率來研究單個單詞的重要性。n-gram是給定文本樣本中n個項的連續序列。當n-gram的大小爲1時,稱爲unigram(大小爲2是一個bigram)。
例如,短語「I like this article」能夠分解爲:
- 4個unigram: 「I」, 「like」, 「this」, 「article」
- 3個bigrams:「I like」, 「like this」, 「this article」
我將以政治新聞爲例說明如何計算unigram和bigrams頻率。
y = "POLITICS" corpus = dtf[dtf["y"]==y]["text_clean"] lst_tokens = nltk.tokenize.word_tokenize(corpus.str.cat(sep=" ")) fig, ax = plt.subplots(nrows=1, ncols=2) fig.suptitle("Most frequent words", fontsize=15) ## unigrams dic_words_freq = nltk.FreqDist(lst_tokens) dtf_uni = pd.DataFrame(dic_words_freq.most_common(), columns=["Word","Freq"]) dtf_uni.set_index("Word").iloc[:top,:].sort_values(by="Freq").plot( kind="barh", title="Unigrams", ax=ax[0], legend=False).grid(axis='x') ax[0].set(ylabel=None) ## bigrams dic_words_freq = nltk.FreqDist(nltk.ngrams(lst_tokens, 2)) dtf_bi = pd.DataFrame(dic_words_freq.most_common(), columns=["Word","Freq"]) dtf_bi["Word"] = dtf_bi["Word"].apply(lambda x: " ".join( string for string in x) ) dtf_bi.set_index("Word").iloc[:top,:].sort_values(by="Freq").plot( kind="barh", title="Bigrams", ax=ax[1], legend=False).grid(axis='x') ax[1].set(ylabel=None) plt.show()
若是有n個字母只出如今一個類別中(即政治新聞中的「Republican」),那麼這些就可能成爲新的特徵。一種更爲費力的方法是對整個語料庫進行向量化,並使用全部的單詞做爲特徵(單詞包方法)。
如今我將向你展現如何在數據幀中添加單詞頻率做爲特徵。咱們只須要Scikit learn中的CountVectorizer,它是Python中最流行的機器學習庫之一。
CountVectorizer將文本文檔集合轉換爲計數矩陣。我將用3個n-grams來舉例:「box office」(常常出如今娛樂圈)、「republican」(常常出如今政界)、「apple」(常常出如今科技界)。
lst_words = ["box office", "republican", "apple"] ## 計數 lst_grams = [len(word.split(" ")) for word in lst_words] vectorizer = feature_extraction.text.CountVectorizer( vocabulary=lst_words, ngram_range=(min(lst_grams),max(lst_grams))) dtf_X = pd.DataFrame(vectorizer.fit_transform(dtf["text_clean"]).todense(), columns=lst_words) ## 將新特徵添加爲列 dtf = pd.concat([dtf, dtf_X.set_index(dtf.index)], axis=1) dtf.head()
可視化相同信息的一個很好的方法是使用word cloud,其中每一個標識的頻率用字體大小和顏色顯示。
wc = wordcloud.WordCloud(background_color='black', max_words=100, max_font_size=35) wc = wc.generate(str(corpus)) fig = plt.figure(num=1) plt.axis('off') plt.imshow(wc, cmap=None) plt.show()
詞向量
最近,NLP領域開發了新的語言模型,這些模型依賴於神經網絡結構,而不是更傳統的n-gram模型。這些新技術是一套語言建模和特徵學習技術,將單詞轉換爲實數向量,所以稱爲詞嵌入。
詞嵌入模型經過構建所選單詞先後出現的標識的機率分佈,將特定單詞映射到向量。這些模型很快變得流行,由於一旦你有了實數而不是字符串,你就能夠執行計算了。例如,要查找相同上下文的單詞,能夠簡單地計算向量距離。
有幾個Python庫可使用這種模型。SpaCy是其中之一,但因爲咱們已經使用過它,我將談論另外一個著名的包:Gensim。
它是使用現代統計機器學習的用於無監督主題模型和天然語言處理的開源庫。使用Gensim,我將加載一個預訓練的GloVe模型。
GloVe是一種無監督學習算法,用於獲取300個單詞的向量表示。
nlp = gensim_api.load("glove-wiki-gigaword-300")
咱們可使用此對象將單詞映射到向量:
word = "love" nlp[word]
nlp[word].shape
如今讓咱們來看看什麼是最接近的詞向量,換句話說,就是大多數出如今類似上下文中的詞。
爲了在二維空間中繪製向量圖,我須要將維數從300降到2。我將使用Scikit learn中的t-分佈隨機鄰居嵌入來實現這一點。
t-SNE是一種可視化高維數據的工具,它將數據點之間的類似性轉換爲聯合機率。
## 找到最近的向量 labels, X, x, y = [], [], [], [] for t in nlp.most_similar(word, topn=20): X.append(nlp[t[0]]) labels.append(t[0]) ## 降維 pca = manifold.TSNE(perplexity=40, n_components=2, init='pca') new_values = pca.fit_transform(X) for value in new_values: x.append(value[0]) y.append(value[1]) ## 繪圖 fig = plt.figure() for i in range(len(x)): plt.scatter(x[i], y[i], c="black") plt.annotate(labels[i], xy=(x[i],y[i]), xytext=(5,2), textcoords='offset points', ha='right', va='bottom') ## 添加中心 plt.scatter(x=0, y=0, c="red") plt.annotate(word, xy=(0,0), xytext=(5,2), textcoords='offset points', ha='right', va='bottom')
主題模型
Genism包專門用於主題模型。主題模型是一種用於發現文檔集合中出現的抽象「主題」的統計模型。
我將展現如何使用LDA(潛Dirichlet分佈)提取主題:它是一個生成統計模型,它容許由未觀察到的組解釋觀察結果集,解釋爲何數據的某些部分是類似的。
基本上,文檔被表示爲潛在主題上的隨機混合,每一個主題的特徵是在單詞上的分佈。
讓咱們看看咱們能夠從科技新聞中提取哪些主題。我須要指定模型必須簇的主題數,我將嘗試使用3:
y = "TECH" corpus = dtf[dtf["y"]==y]["text_clean"] ## 預處理語料庫 lst_corpus = [] for string in corpus: lst_words = string.split() lst_grams = [" ".join(lst_words[i:i + 2]) for i in range(0, len(lst_words), 2)] lst_corpus.append(lst_grams) ## 將單詞映射到id id2word = gensim.corpora.Dictionary(lst_corpus) ## 建立詞典 word:freq dic_corpus = [id2word.doc2bow(word) for word in lst_corpus] ## 訓練LDA lda_model = gensim.models.ldamodel.LdaModel(corpus=dic_corpus, id2word=id2word, num_topics=3, random_state=123, update_every=1, chunksize=100, passes=10, alpha='auto', per_word_topics=True) ## 輸出 lst_dics = [] for i in range(0,3): lst_tuples = lda_model.get_topic_terms(i) for tupla in lst_tuples: lst_dics.append({"topic":i, "id":tupla[0], "word":id2word[tupla[0]], "weight":tupla[1]}) dtf_topics = pd.DataFrame(lst_dics, columns=['topic','id','word','weight']) ## plot fig, ax = plt.subplots() sns.barplot(y="word", x="weight", hue="topic", data=dtf_topics, dodge=False, ax=ax).set_title('Main Topics') ax.set(ylabel="", xlabel="Word Importance") plt.show()
試圖僅用3個主題捕捉6年的內容可能有點困難,但正如咱們所看到的,關於蘋果公司的一切都以同一個主題結束。
結論
本文是演示如何使用NLP分析文本數據併爲機器學習模型提取特徵的教程。
我演示瞭如何檢測數據所使用的語言,以及如何預處理和清除文本。而後我解釋了長度的不一樣度量,用Textblob進行了情緒分析,並使用SpaCy進行命名實體識別。最後,我解釋了Scikit學習的傳統詞頻方法與Gensim的現代語言模型之間的區別。
如今,你已經瞭解了開始處理文本數據的全部NLP基礎知識。
原文連接:https://towardsdatascience.com/text-analysis-feature-engineering-with-nlp-502d6ea9225d
歡迎關注磐創AI博客站:
http://panchuang.net/
sklearn機器學習中文官方文檔:
http://sklearn123.com/
歡迎關注磐創博客資源彙總站:
http://docs.panchuang.net/