前幾天一直在研究 Python 爬蟲技術,只爲從互聯網上獲取數據集。
本文就是利用前幾天學到的爬蟲知識使用 Python 爬取天氣數據集,並做的一期討論日期與最低氣溫能是否是最高氣溫的影響因素,進而判斷能否精確預測第二天的天氣情況。
由於本文開始寫作與5月9日,當天想預測第二天也就是5月10日的氣溫數據,但由於內容較多,到10日下午才寫完。所以數據預測的內容有些“陳舊”,還請讀者多多包涵。
目錄
模型二:基於LinearRegression實現的多變量線性迴歸模型
模型十一:使用LogisticRegression進行邏輯迴歸模型
模型十三:基於scipy.optimize優化運算庫實現對數機率迴歸模型
1 天氣數據集爬取
爬取思路:確定目標(目標網站:大同歷史天氣預報 2020年5月份)
請求網頁(第三方庫 requests)
解析網頁(數據提取)
保存數據(這裏以 .csv 格式存儲到本地)
因爲之前作過爬蟲方面的文章,且本文重點放在數據可視化及預測上,故具體爬蟲思路不在這裏一一贅述。
讀者可參考下方代碼中的註釋進一步理解,亦可參考我發表過的文章,這裏給出鏈接:
①Python爬蟲:10行代碼真正實現“可見即可爬”
②正則表達式心中有,還愁爬蟲之路不好走?
import requests
from bs4 import BeautifulSoup
import pandas as pd
def get_data(url):
# 請求網頁(第三方 requests)
resp = requests.get(url)
# 對於獲取到的 HTML 二進制文件進行 'gbk' 轉碼成字符串文件
html = resp.content.decode('gbk')
# 通過第三方庫 BeautifulSoup 縮小查找範圍(同樣作用的包庫還有re模塊、xpath等)
soup = BeautifulSoup(html,'html.parser')
# 獲取 HTML 中所有<tr>…</tr>標籤,因爲我們需要的數據全部在此標籤中存放
tr_list = soup.find_all('tr')
# 初始化日期dates、氣候contains、溫度temp值
dates,contains,temp = [],[],[]
for data in tr_list[1:]: # 不要表頭
# 數據值拆分,方便進一步處理(這裏可以將獲得的列表輸出[已註釋],不理解的讀者可運行查看)
sub_data = data.text.split()
# print(sub_data)
# 觀察上一步獲得的列表,這裏只想要獲得列表中第二個和第三個值,採用切片法獲取
dates.append(sub_data[0])
contains.append(','.join(sub_data[1:3]))
# print(contains)
# 同理採用切片方式獲取列表中的最高、最低氣溫
temp.append(','.join(sub_data[3:6]))
# print(temp)
# 使用 _data 表存放日期、天氣狀況、氣溫表頭及其值
_data = pd.DataFrame()
# 分別將對應值傳入 _data 表中
_data['日期'] = dates
_data['天氣狀況'] = contains
_data['氣溫'] = temp
return _data
# 爬取目標網頁(大同市2020年5月份天氣[網站:天氣後報])
data_5_month = get_data('http://www.tianqihoubao.com/lishi/datong/month/202005.html')
# 拼接所有表並重新設置行索引(若不進行此步操作,可能或出現多個標籤相同的值)
data = pd.concat([data_5_month]).reset_index(drop = True)
# 將 _data 表以 .csv 格式存入指定文件夾中,並設置轉碼格式防止亂花(注:此轉碼格式可與 HTML 二進制轉字符串的轉碼格式不同)
data.to_csv('F:/DaTong5Mouth.csv',encoding='utf-8')
2 數據可視化
數據可視化用到了可視化工具
其要點包含有:讀取數據、數據清洗、數據處理、可視化工具的使用。
# 數據可視化
from matplotlib import pyplot as plt
import pandas as pd
# 解決顯示中文問題
plt.rcParams['font.sans-serif'] = ['SimHei']
# 第一步:數據讀取
data = pd.read_csv('F:/DaTong5Mouth.csv')
# 第二步:數據處理(由於我們知道文本內容,不存在髒數據,故忽略數據清理步驟)
data['最高氣溫'] = data['氣溫'].str.split('/',expand=True)[0]
data['最低氣溫'] = data['氣溫'].str.split('/',expand=True)[1]
data['最高氣溫'] = data['最高氣溫'].map(lambda x:x.replace('℃,',''))
data['最低氣溫'] = data['最低氣溫'].map(lambda x:x.replace('℃,',''))
dates = data['日期']
highs = data['最高氣溫']
lows = data['最低氣溫']
# 畫圖(折線圖)
# 設置畫布大小及比例
fig = plt.figure(dpi=128,figsize=(10,6))
# 設置最高溫最低溫線條顏色及寬度等信息
L1,=plt.plot(dates,lows,label='最低氣溫')
L2,=plt.plot(dates,highs,label='最高氣溫')
plt.legend(handles=[L1,L2],labels=['最高氣溫','最低氣溫'], loc='best')# 添加圖例
# 圖表格式
# 設置圖形格式
plt.title('2020年5月上旬大同天氣',fontsize=25) # 字體大小設置爲25
plt.xlabel('日期',fontsize=10) # x軸顯示“日期”,字體大小設置爲10
fig.autofmt_xdate() # 繪製斜的日期標籤,避免重疊
plt.ylabel('氣溫',fontsize=10) # y軸顯示“氣溫”,字體大小設置爲10
plt.tick_params(axis='both',which='major',labelsize=10)
# plt.plot(highs,lows,label = '最高氣溫')
# 修改刻度
plt.xticks(dates[::1]) # 由於數據不多,將每天的數據全部顯示出來
# 顯示折線圖
plt.show()
3 模型預測數據
3.1 單變量線性迴歸
模型一:單變量線性迴歸模型
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 解決中文問題(若沒有此步驟,表名字及橫縱座標中的漢語將無法顯示[具體會顯示矩形小方格])
plt.rcParams['font.sans-serif'] = ['SimHei']
# 將數據從上一步存入的 .csv 格式文件中讀取
data = pd.read_csv(r'F:\DaTong5Mouth.csv')
# 由於最高氣溫與最低氣溫中有 / 分隔,故將其分開,即“氣溫”列由一列變爲兩列——“最高氣溫”和“最低氣溫”
data['最高氣溫'] = data['氣溫'].str.split('/',expand=True)[0]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最高氣溫'] = data['最高氣溫'].map(lambda x:x.replace('℃,',''))
# 日次操作同理,這裏不再贅述
data['日期'] = data['日期'].map(lambda x:x.replace('2020年05月0',''))
data['日期'] = data['日期'].map(lambda x:x.replace('日',''))
# 不理解的小夥伴可運行下兩行代碼查看運行結果(這裏先註釋掉了)
# print(data['日期'])
# print(data['最高氣溫'])
def initPlot():
# 先準備好一塊畫布
plt.figure()
# 生成圖表的名字
plt.title('2020年5月上旬大同天氣')
# 橫座標名字
plt.xlabel('日期')
# 縱座標名字
plt.ylabel('當日最高氣溫')
# 表內有柵格(不想要柵格把此行註釋掉即可)
plt.grid(True)
return plt
plt = initPlot() # 畫圖
# 傳入對應日期及其最高氣溫參數
xTrain = np.array([1,2,3,4,5,6,7,8,9])
yTrain = np.array([33,35,28,20,26,27,23,22,22])
# k是黑色,.是以點作爲圖上顯示
plt.plot(xTrain, yTrain, 'k.')
# 將圖顯示出來
plt.show()
可以看到:
- 最高氣溫隨着日期的變化,大致呈現線性變化(最近氣溫下降);
- 如果根據現有的訓練數據能夠擬合出一條直線,使之與這些訓練數據的各點都比較接近,那麼根據該直線,就可以計算出在10號或者11號的溫度情況(氣溫受到影響因素較多,故這裏僅預測爲數不多的數據)
解決方案:
- 採用Python scikit-learn庫中提供的sklearn.linear_model.LinearRegression對象來進行線性擬合。
- 根據判別函數,繪製擬合直線,並同時顯示訓練數據點。
- 擬合的直線較好的穿過訓練數據,根據新擬合的直線,可以方便的求出最近日期下對應的最高氣溫(預測結果)。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
# 解決中文問題(若沒有此步驟,表名字及橫縱座標中的漢語將無法顯示[具體會顯示矩形小方格])
plt.rcParams['font.sans-serif'] = ['SimHei']
# 將數據從上一步存入的 .csv 格式文件中讀取
data = pd.read_csv(r'F:\DaTong5Mouth.csv')
# 由於最高氣溫與最低氣溫中有 / 分隔,故將其分開,即“氣溫”列由一列變爲兩列——“最高氣溫”和“最低氣溫”
data['最高氣溫'] = data['氣溫'].str.split('/',expand=True)[0]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最高氣溫'] = data['最高氣溫'].map(lambda x:x.replace('℃,',''))
# 日次操作同理,這裏不再贅述
data['日期'] = data['日期'].map(lambda x:x.replace('2020年05月0',''))
data['日期'] = data['日期'].map(lambda x:x.replace('日',''))
# 不理解的小夥伴可運行下兩行代碼查看運行結果(這裏先註釋掉了)
# print(data['日期'])
# print(data['最高氣溫'])
# 傳入對應日期及其最高氣溫參數
# # 應以矩陣形式表達(對於單變量,矩陣就是列向量形式)
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis]
# 爲方便理解,也轉換成列向量
yTrain = np.array([33,35,28,20,26,27,23,22,22])
# 創建模型對象
model = LinearRegression()
# 根據訓練數據擬合出直線(以得到假設函數)
hypothesis = model.fit(xTrain, yTrain)
# 截距
print("theta0=", hypothesis.intercept_)
# 斜率
print("theta1=", hypothesis.coef_)
# 預測2020年5月10日的最高氣溫
print("預測2020年5月10日的最高氣溫:", model.predict([[10]]))
# 也可以批量預測多個日期的氣溫,注意要以列向量形式表達(有餘數據集量少,故間隔時間長氣溫可能有較大差異)
# 此處僅利用模型表示,不代表真實值(假設要預測10號、11號、12號的天氣)
xNew = np.array([0,10, 11, 12])[:, np.newaxis]
yNew = model.predict(xNew)
print("預測新數據:", xNew)
print("預測結果:", yNew)
def initPlot():
# 先準備好一塊畫布
plt.figure()
# 生成圖表的名字
plt.title('2020年5月上旬大同天氣')
# 橫座標名字
plt.xlabel('日期')
# 縱座標名字
plt.ylabel('當日最高氣溫')
# 表內有柵格(不想要柵格把此行註釋掉即可)
plt.grid(True)
return plt
plt = initPlot() # 畫圖
# k是黑色,.是以點作爲圖上顯示
plt.plot(xTrain, yTrain, 'k.')
# 畫出通過這些點的連續直線
plt.plot(xNew, yNew, 'g--')
# 將圖顯示出來
plt.show()
模型評價:
擬合出來的判別函數效果如何:對訓練數據的貼合度如何?對新數據的預測準確度如何?
可通過殘差(residuals)和R方(r-squared)判斷, 在Python中如何對單變量線性迴歸模型的效果進行評估
- 手動計算
假設hpyTrain代表針對訓練數據的預測最高氣溫值,hpyTest代表針對測試數據的預測最高氣溫值- 訓練數據殘差平方和:
ssResTrain = sum((hpyTrain - yTrain) ** 2)
- 測試數據殘差平方和:
ssResTest = sum((hpyTest - yTest) ** 2)
- 測試數據偏差平方和:
ssTotTest = sum((yTest - np.mean(yTest)) ** 2)
- R方:
Rsquare = 1 - ssResTest / ssTotTest
- 訓練數據殘差平方和:
- LinearRegression對象提供的方法
- 訓練數據殘差平方和:
model._residues
- R方:
model.score(xTest, yTest)
- 訓練數據殘差平方和:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
# 解決中文問題(若沒有此步驟,表名字及橫縱座標中的漢語將無法顯示[具體會顯示矩形小方格])
plt.rcParams['font.sans-serif'] = ['SimHei']
# 將數據從上一步存入的 .csv 格式文件中讀取
data = pd.read_csv(r'F:\DaTong5Mouth.csv')
# 由於最高氣溫與最低氣溫中有 / 分隔,故將其分開,即“氣溫”列由一列變爲兩列——“最高氣溫”和“最低氣溫”
data['最高氣溫'] = data['氣溫'].str.split('/',expand=True)[0]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最高氣溫'] = data['最高氣溫'].map(lambda x:x.replace('℃,',''))
# 日次操作同理,這裏不再贅述
data['日期'] = data['日期'].map(lambda x:x.replace('2020年05月0',''))
data['日期'] = data['日期'].map(lambda x:x.replace('日',''))
# 不理解的小夥伴可運行下兩行代碼查看運行結果(這裏先註釋掉了)
# print(data['日期'])
# print(data['最高氣溫'])
# 傳入對應日期及其最高氣溫參數
# # # 應以矩陣形式表達(對於單變量,矩陣就是列向量形式)
# xTrain = np.array(data['日期'])[:, np.newaxis]
# # 爲方便理解,也轉換成列向量
# yTrain = np.array(data['最高氣溫'])
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
yTrain = np.array([33,35,28,20,26,27,23,22,22]) # 訓練數據(最高氣溫)
xTest = np.array([3,6,9,10,11])[:,np.newaxis] # 測試數據(日期)
yTest = np.array([28,27,22,20,19]) # 測試數據(最高氣溫)
# 創建模型對象
model = LinearRegression()
# 根據訓練數據擬合出直線(以得到假設函數)
hypothesis = model.fit(xTrain, yTrain)
hpyTrain = model.predict(xTrain)
# 針對測試數據進行預測
hpyTest = model.predict(xTest)
# 手動計算訓練數據集殘差
ssResTrain = sum((hpyTrain - yTrain) ** 2)
print(ssResTrain)
# Python計算的訓練數據集殘差
print(model._residues)
# 手動計算測試數據集殘差
ssResTest = sum((hpyTest - yTest) ** 2)
# 手動計算測試數據集y值偏差平方和
ssTotTest = sum((yTest - np.mean(yTest)) ** 2)
# 手動計算R方
Rsquare = 1 - ssResTest / ssTotTest
print(Rsquare)
# Python計算的訓練數據集的R方
print(model.score(xTest, yTest))
# corrcoef函數是在各行元素之間計算相關性,所以x和y都應是行向量
print(np.corrcoef(xTrain.T, yTrain.T)) # 計算訓練數據的相關性
print(np.corrcoef(xTest.T, yTest.T)) # 計算測試數據的相關性
def initPlot():
# 先準備好一塊畫布
plt.figure()
# 生成圖表的名字
plt.title('2020年5月上旬大同天氣')
# 橫座標名字
plt.xlabel('日期')
# 縱座標名字
plt.ylabel('當日最高氣溫')
# 表內有柵格(不想要柵格把此行註釋掉即可)
plt.grid(True)
return plt
plt = initPlot()
plt.plot(xTrain, yTrain, 'r.') # 訓練點數據(紅色)
plt.plot(xTest, yTest, 'b.') # 測試點數據(藍色)
plt.plot(xTrain, hpyTrain, 'g-') # 假設函數直線(綠色)
plt.show()
查看上述擬合效果:
- 紅色爲訓練數據點,藍色爲測試數據點,綠色爲判別函數(擬合直線)
- 計算出的R方爲0.833,效果良
- 計算出訓練數據的相關性爲-0.763,測試數據的相關性爲-0.968。可以發現,根據數據集的不同,日期與最高氣溫之間的相關性波動較大。這也能解釋爲何針對測試數據的R方事實上不夠理想
3.2 多變量線性迴歸
在單變量線性迴歸中,最高氣溫僅與日期有關(嘗試可知,這顯然是極不合理的),按照這一假設,其預測的結果並不令人滿意(R方=0.833)。因此在多變線性迴歸模型中再引入一個新的影響因素:最低氣溫(此處要注意和最高氣溫一樣,計算前先利用 .map 方法將 ℃ 置空,僅將最低氣溫調整成數值,以便能夠進行數值計算)
模型二:基於LinearRegression實現的多變量線性迴歸模型
- 與單變量線性迴歸類似,但要注意訓練數據此時是(是訓練數據條數,是自變量個數)
- 針對測試數據的預測結果,其R方約爲0.466,這時我們發現還沒有單變量量線性迴歸R方值大,說明擬合效果差於單變量線性迴歸。這是什麼問題呢?經過思考,我認爲最高氣溫的影響因素不能拿日期和最低氣溫來衡量,也就是說,最高氣溫的走勢依據情況特殊而複雜,不能單靠日期和最低氣溫等片面的爲數不多的方面來進行擬合。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
# 解決中文問題(若沒有此步驟,表名字及橫縱座標中的漢語將無法顯示[具體會顯示矩形小方格])
plt.rcParams['font.sans-serif'] = ['SimHei']
# 將數據從上一步存入的 .csv 格式文件中讀取
data = pd.read_csv(r'F:\DaTong5Mouth.csv')
# 由於最高氣溫與最低氣溫中有 / 分隔,故將其分開,即“氣溫”列由一列變爲兩列——“最高氣溫”和“最低氣溫”
data['最高氣溫'] = data['氣溫'].str.split('/',expand=True)[0]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最高氣溫'] = data['最高氣溫'].map(lambda x:x.replace('℃,',''))
data['最低氣溫'] = data['氣溫'].str.split('/',expand=True)[1]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最低氣溫'] = data['最低氣溫'].map(lambda x:x.replace('℃,',''))
# 日次操作同理,這裏不再贅述
data['日期'] = data['日期'].map(lambda x:x.replace('2020年05月0',''))
data['日期'] = data['日期'].map(lambda x:x.replace('日',''))
# 不理解的小夥伴可運行下兩行代碼查看運行結果(這裏先註釋掉了)
# print(data['日期'])
# print(data['最高氣溫'])
# print(data['最低氣溫'])
# 傳入對應日期及其最高氣溫參數
# # # 應以矩陣形式表達(對於單變量,矩陣就是列向量形式)
# xTrain = np.array(data['日期'])[:, np.newaxis]
# # 爲方便理解,也轉換成列向量
# yTrain = np.array(data['最高氣溫'])
# 訓練集
xTrain = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) # 無需手動添加Intercept Item項
yTrain = np.array([[33, 8], [35, 9], [28, 4], [20, 4], [26, 6], [27,10], [23,10], [22,7], [22,3]])
# 測試集
xTest = np.array([3, 6, 9, 10, 11])
yTest = np.array([[28, 4], [27, 10], [22, 3], [20, 5], [19, 7]])
# 創建模型對象
model = LinearRegression()
# 根據訓練數據擬合出直線(以得到假設函數)
model.fit(yTrain, xTrain)
# 針對測試數據進行預測
hpyTest = model.predict(yTest)
print("假設函數參數:", model.intercept_, model.coef_)
print("測試數據預測結果與實際結果差異:", hpyTest - xTest)
print("測試數據R方:", model.score(yTest, xTest))
模型三:基於成本函數和梯度下降實現的多變量線性迴歸模型
- 經過模型三的擬合,我們發現R方僅爲0.164,還不如模型二的預測結果呢。而根據理論知識我們知道,這個模型預測結果應該是線性迴歸模型中預測擬合效果較好的一種,低的這個R方值經過思考,可進一步說明最高氣溫的影響因素不僅僅取決於日期和最低氣溫,甚至我們可推斷出可能與日期及最低氣溫值等影響因素無關。
- 通過運行結果發現“50000次循環後,計算仍未收斂”。這說明①在未對自變量歸一化處理的情況下,運算出現異常,無法收斂;②設置了過大的學習速率,會導致計算不收斂。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import bgd_resolver
from sklearn.linear_model import LinearRegression
# 解決中文問題(若沒有此步驟,表名字及橫縱座標中的漢語將無法顯示[具體會顯示矩形小方格])
plt.rcParams['font.sans-serif'] = ['SimHei']
def costFn(theta, X, y): # 成本函數
temp = X.dot(theta) - y
return (temp.T.dot(temp)) / (2 * len(X))
def gradientFn(theta, X, y): # 根據成本函數,分別對x0,x1...xn求導數(梯度)
return (X.T).dot(X.dot(theta) - y) / len(X)
# 將數據從上一步存入的 .csv 格式文件中讀取
data = pd.read_csv(r'F:\DaTong5Mouth.csv')
# 由於最高氣溫與最低氣溫中有 / 分隔,故將其分開,即“氣溫”列由一列變爲兩列——“最高氣溫”和“最低氣溫”
data['最高氣溫'] = data['氣溫'].str.split('/',expand=True)[0]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最高氣溫'] = data['最高氣溫'].map(lambda x:x.replace('℃,',''))
data['最低氣溫'] = data['氣溫'].str.split('/',expand=True)[1]
# 我們要對數值進行分析,所以將多餘的單位 ℃ 從列表中去掉,只保留數值部分
data['最低氣溫'] = data['最低氣溫'].map(lambda x:x.replace('℃,',''))
# 日次操作同理,這裏不再贅述
data['日期'] = data['日期'].map(lambda x:x.replace('2020年05月0',''))
data['日期'] = data['日期'].map(lambda x:x.replace('日',''))
# 不理解的小夥伴可運行下兩行代碼查看運行結果(這裏先註釋掉了)
# print(data['日期'])
# print(data['最高氣溫'])
# print(data['最低氣溫'])
# 傳入對應日期及其最高氣溫參數
# # # 應以矩陣形式表達(對於單變量,矩陣就是列向量形式)
# xTrain = np.array(data['日期'])[:, np.newaxis]
# # 爲方便理解,也轉換成列向量
# yTrain = np.array(data['最高氣溫'])
# 訓練集
xTrain = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) # 無需手動添加Intercept Item項
yTrainData = np.array([[33, 8], [35, 9], [28, 4], [20, 4], [26, 6], [27,10], [23,10], [22,7], [22,3]])
yTrain = np.c_[yTrainData, np.ones(len(yTrainData))]
np.random.seed(0)
init_theta = np.random.randn(yTrain.shape[1])
theta = bgd_resolver.batch_gradient_descent(costFn, gradientFn, init_theta, yTrain, xTrain)
print("theta值", theta)
# 測試集
xTest = np.array([3, 6, 9, 10, 11])
yTestData = np.array([[28, 4], [27, 10], [22, 3], [20, 5], [19, 7]])
yTest = np.c_[yTestData, np.ones(len(yTestData))]
print("測試數據預測值與真實值的差異:", xTest.dot(theta) - xTest)
rsquare = bgd_resolver.batch_gradient_descent_rsquare(theta, yTest, xTest)
print("測試數據R方:", rsquare)
3.3 以"線性迴歸"的方式來擬合高階曲線
這一部分我們分別使用一階曲線(直線)、二階曲線和三階曲線進行擬合,並檢查擬合效果。
在擬合數據點時,一般來說,對於一個自變量的,擬合出來是一條直線;對於兩個自變量的,擬合出來時一個直平面。這種擬合結果是嚴格意義上的“線性”迴歸。但是有時候,採用“曲線”或“曲面”的方式來擬合,能夠對訓練數據產生更逼近的效果。這就是“高階擬合”。
首先,我們查看要擬合的數據
import numpy as np
import matplotlib.pyplot as plt
# 解決中文問題(若沒有此步驟,表名字及橫縱座標中的漢語將無法顯示[具體會顯示矩形小方格])
plt.rcParams['font.sans-serif'] = ['SimHei']
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
yTrain = np.array([33,35,28,20,26,27,23,22,22]) # 訓練數據(最高氣溫)
xTest = np.array([3,6,9,10,11])[:,np.newaxis] # 測試數據(日期)
yTest = np.array([28,27,22,20,19]) # 測試數據(最高氣溫)
plotData = np.array(np.linspace(0, 15, 30))[:,np.newaxis] # 作圖用的數據點
def initPlot():
plt.figure()
plt.title('2020年5月上旬大同天氣')
plt.xlabel('日期')
plt.ylabel('氣溫')
plt.grid(True)
return plt
plt = initPlot()
plt.plot(xTrain, yTrain, 'r.') # 訓練點數據(紅色)
plt.plot(xTest, yTest, 'b.') # 測試點數據(藍色)
plt.show()
模型四:一階線性擬合
from sklearn.linear_model import LinearRegression
# 線性擬合
linearModel = LinearRegression()
linearModel.fit(xTrain, yTrain)
linearModelTrainResult = linearModel.predict(plotData)
# 計算R方
linearModelRSquare = linearModel.score(xTest, yTest)
print("線性擬合R方:", linearModelRSquare)
plt = initPlot()
plt.plot(xTrain, yTrain, 'r.') # 訓練點數據(紅色)
plt.plot(xTest, yTest, 'b.') # 測試點數據(藍色)
plt.plot(plotData, linearModelTrainResult, 'y-') # 線性擬合線
plt.show()
模型五:二階曲線擬合
- PolynomialFeatures.fit_transform提供了將1階數據擴展到高階數據的方法
- 訓練樣本和測試樣本都需要進行擴充
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 二階曲線擬合 theta0 + theta1*x + theta2*x*x x*x => z theta0+theta1*x+theta2*z
quadratic_featurizer = PolynomialFeatures(degree=2)
xTrain_quadratic = quadratic_featurizer.fit_transform(xTrain)
print(xTrain_quadratic) # 查看擴展後的特徵矩陣
quadraticModel = LinearRegression()
quadraticModel.fit(xTrain_quadratic, yTrain)
# 計算R方(針對測試數據)
xTest_quadratic = quadratic_featurizer.fit_transform(xTest)
quadraticModelRSquare = quadraticModel.score(xTest_quadratic, yTest)
print("二階擬合R方:", quadraticModelRSquare)
# 繪圖點也同樣需要進行高階擴充以便使用曲線進行擬合
plotData_quadratic = quadratic_featurizer.fit_transform(plotData)
quadraticModelTrainResult = quadraticModel.predict(plotData_quadratic)
plt = initPlot()
plt.plot(xTrain, yTrain, 'r.') # 訓練點數據(紅色)
plt.plot(xTest, yTest, 'b.') # 測試點數據(藍色)
plt.plot(plotData, quadraticModelTrainResult, 'g-') # 二階擬合線
plt.show()
模型六:三階曲線擬合
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 三階曲線擬合
cubic_featurizer = PolynomialFeatures(degree=3)
xTrain_cubic = cubic_featurizer.fit_transform(xTrain)
cubicModel = LinearRegression()
cubicModel.fit(xTrain_cubic, yTrain)
plotData_cubic = cubic_featurizer.fit_transform(plotData)
cubicModelTrainResult = cubicModel.predict(plotData_cubic)
# 計算R方(針對測試數據)
xTest_cubic = cubic_featurizer.fit_transform(xTest)
cubicModelRSquare = cubicModel.score(xTest_cubic, yTest)
print("三階擬合R方:", cubicModelRSquare)
plt = initPlot()
plt.plot(xTrain, yTrain, 'r.') # 訓練點數據(紅色)
plt.plot(xTest, yTest, 'b.') # 測試點數據(藍色)
plt.plot(plotData, cubicModelTrainResult, 'p-') # 三階擬合線
plt.show()
綜上對比我們發現,一階擬合R方約爲0.833,二階擬合R方約爲0.218,三階擬合R方約爲0.800。很顯然,得到的擬合R方值並不是隨着階數的增高而增大,同前理,說明日期和最低氣溫並不是最高氣溫的影響因素。這正與我們常識所知的結論相吻合。因此,想要預測天氣值就錯綜而複雜,不得片面考慮一個或爲數不多的幾個因素,且不應考慮到與氣溫影響因素無關的影響變量:比如說像上例中所提及的日期、最低氣溫等。
3.4 線性迴歸預測天氣
模型七:線性迴歸預測模型
使用sklearn.linear_model.LinearRegression處理
無需對自變量進行歸一化處理,也能得到一致的結果。針對訓練數據的R方約爲0.583
1:裝載並查看數據信息
import numpy as np
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
yTrain = np.array([33,35,28,20,26,27,23,22,22]) # 訓練數據(最高氣溫)
# 查看天氣統計數據
print("天氣數據統計:")
print("最低:%.2f, 最高:%.2f, 平均:%.2f, 中位數:%.2f, 標準差:%.2f" %
(np.min(yTrain), np.max(yTrain), np.mean(yTrain), np.median(yTrain) ,np.std(yTrain)))
2:使用LinearRegression,沒有進行歸一化預處理
''' 使用LinearRegression,沒有進行歸一化預處理 '''
import numpy as np
from sklearn.linear_model import LinearRegression
train_data = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
train_temp = np.array([33,35,28,20,26,27,23,22,22])[:, np.newaxis] # 訓練數據(最高氣溫)
xTrain = np.array(train_data[:, 0:2])
yTrain = np.array(train_temp[:, -1])
xTrain = np.c_[xTrain, np.ones(len(xTrain))]
model = LinearRegression()
model.fit(xTrain, yTrain)
print("LinearRegression計算R方:", model.score(xTrain, yTrain))
3:使用LinearRegression,進行歸一化預處理
''' 使用LinearRegression,進行歸一化預處理 '''
import numpy as np
from sklearn.linear_model import LinearRegression
def normalizeData(X):
# 每列(每個Feature)分別求出均值和標準差,然後與X的每個元素分別進行操作
return (X - X.mean(axis=0))/X.std(axis=0)
train_data = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
train_temp = np.array([33,35,28,20,26,27,23,22,22])[:, np.newaxis] # 訓練數據(最高氣溫)
xTrain = np.array(train_data[:, 0:2])
yTrain = np.array(train_temp[:, -1])
xTrain = normalizeData(xTrain)
xTrain = np.c_[xTrain, np.ones(len(xTrain))] # 歸一化完成後再添加intercept item列
model = LinearRegression()
model.fit(xTrain, yTrain)
print("LinearRegression計算R方:", model.score(xTrain, yTrain))
使用自定義的批量梯度下降法
- 在未對自變量歸一化處理的情況下,運算可能出現異常,無法收斂,但這裏沒有出現
- 歸一化處理後,能夠得到與LinearRegression類似的結果,即R方值約爲0.582
- 因此,不考慮影響因素合不合理情況下這種預測結果實質上準確率不容樂觀
1:使用自定義BGD,未作歸一化處理(可能無法收斂,但這裏沒有出現無法收斂情況)
''' 使用自定義BGD,未作歸一化處理,可能無法收斂 '''
import numpy as np
import bgd_resolver
def costFn(theta, X, y):
temp = X.dot(theta) - y
return (temp.T.dot(temp)) / (2 * len(X))
def gradientFn(theta, X, y):
return (X.T).dot(X.dot(theta) - y) / len(X)
train_date = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
train_temp = np.array([33,35,28,20,26,27,23,22,22])[:, np.newaxis] # 訓練數據(最高氣溫)
xTrain = np.array(train_date[:, 0:2])
yTrain = np.array(train_temp[:, -1])
xTrain = np.c_[xTrain, np.ones(len(xTrain))]
init_theta = np.random.randn(xTrain.shape[1])
# 如果數據不進行Normalize,則下面的梯度算法有可能不收斂
theta = bgd_resolver.batch_gradient_descent(costFn, gradientFn, init_theta, xTrain, yTrain)
rsquare = bgd_resolver.batch_gradient_descent_rsquare(theta, xTrain, yTrain)
print("梯度下降法計算R方:", rsquare)
2:使用自定義BGD,作歸一化處理
''' 使用自定義BGD,作歸一化處理 '''
import numpy as np
import bgd_resolver
def normalizeData(X):
# 每列(每個Feature)分別求出均值和標準差,然後與X的每個元素分別進行操作
return (X - X.mean(axis=0))/X.std(axis=0)
def costFn(theta, X, y):
temp = X.dot(theta) - y
return (temp.T.dot(temp)) / (2 * len(X))
def gradientFn(theta, X, y):
return (X.T).dot(X.dot(theta) - y) / len(X)
train_date = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
train_temp = np.array([33,35,28,20,26,27,23,22,22])[:, np.newaxis] # 訓練數據(最高氣溫)
xTrain = np.array(train_date[:, 0:2])
yTrain = np.array(train_temp[:, -1])
xTrain = np.c_[xTrain, np.ones(len(xTrain))]
init_theta = np.random.randn(xTrain.shape[1])
# 如果數據不進行Normalize,則下面的梯度算法有可能不收斂
theta = bgd_resolver.batch_gradient_descent(costFn, gradientFn, init_theta, xTrain, yTrain)
rsquare = bgd_resolver.batch_gradient_descent_rsquare(theta, xTrain, yTrain)
print("梯度下降法計算R方:", rsquare)
3.5 線性迴歸的其它計算方法
模型八:基於協方差-方差公式實現的線性迴歸模型
事實上,使用該方法計算出來的判別函數參數,與LinearRegression對象的計算結果一致。
''' 使用協方差-方差公式計算線性迴歸權重參數,並與LinearRegression結果對比 '''
import numpy as np
from sklearn.linear_model import LinearRegression
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
yTrain = np.array([33,35,28,20,26,27,23,22,22]) # 訓練數據(最高氣溫)
model = LinearRegression()
hypothesis = model.fit(xTrain, yTrain)
print("LinearRegression theta1=", hypothesis.coef_)
print("LinearRegression theta0=", hypothesis.intercept_)
# cov函數是在各行元素之間計算協方差,所以x和y都應是行向量
theta1 = np.cov(xTrain.T, yTrain, ddof=1)[1,0] / np.var(xTrain, ddof=1)
theta0 = np.mean(yTrain) - theta1 * np.mean(xTrain)
print("Least Square theta1=", theta1) # 通過最小二乘法公式計算的斜率
print("Least Square theta0=", theta0) # 通過最小二乘法公式計算的截距
模型九:基於成本函數和批量梯度下降算法實現的線性迴歸模型
成本函數:
- 在使用訓練數據來訓練模型時,用於定義判別函數與實際值的誤差。成本函數計算結果越小,說明該模型與訓練數據的匹配程度越高
- 設定了某個模型後,只要給定了成本函數,就可以使用數值方法求出成本函數的最優解(極小值),從而確定判別函數模型中各個係數
梯度下降:
- 梯度下降是迭代法的一種,可以用於求解最小二乘問題(線性和非線性都可以)。在求解機器學習算法的模型參數,即無約束優化問題時
''' 使用批量梯度下降算法優化線性迴歸權重參數 '''
import numpy as np
import matplotlib.pyplot as plt
import bgd_resolver # 來自bgd_resolver.py文件
def costFn(theta, X, y): # 定義線性迴歸的成本函數
temp = X.dot(theta) - y
return temp.dot(temp) / (2*len(X))
def gradientFn(theta, X, y): # 根據成本函數,分別對x0和x1求導數(梯度)
return (X.T).dot(X.dot(theta) - y) / len(X)
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
yTrain = np.array([33,35,28,20,26,27,23,22,22]) # 訓練數據(最高氣溫)
xTrain_ext = np.c_[np.ones(len(xTrain)), xTrain] # 第一列補充0。注意返回的第一個權重參數將對應theta0
np.random.seed(0)
theta_init = np.random.randn(xTrain_ext.shape[1])
theta = bgd_resolver.batch_gradient_descent(costFn, gradientFn, theta_init, xTrain_ext, yTrain, learning_rate=0.005, tolerance=1e-12)
print("BGD theta1=", theta[1])
print("BGD theta0=", theta[0])
def initPlot():
plt.figure()
plt.title('2020.05 WEATHER')
plt.xlabel('date')
plt.ylabel('maximum temperature')
plt.grid(True)
return plt
plt = initPlot()
plt.plot(xTrain, yTrain, 'k.')
plt.plot(xTrain, xTrain_ext.dot(theta), 'g-')
plt.show()
模型十:基於SGDRegressor隨機梯度下降算法的實現
sklearn.linear_model.SGDRegressor對象提供了使用隨機梯度下降算法進行線性迴歸的實現
- 但是SGDRegressor對於數據集較大的情形比較合適,如果樣本較少(例如本例),其效果一般不好
- 可以觀察到,每次運行時,其優化結果並不相同
''' 使用SGDRegressor隨機梯度下降算法優化線性迴歸權重參數 '''
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import SGDRegressor
xTrain = np.array([1,2,3,4,5,6,7,8,9])[:, np.newaxis] # 訓練數據(日期)
yTrain = np.array([33,35,28,20,26,27,23,22,22]) # 訓練數據(最高氣溫)
regressor = SGDRegressor(loss='squared_loss', max_iter=2000)
regressor.fit(xTrain, yTrain)
# 每次運行,得到的結果並不相同
theta0 = regressor.intercept_[0]
theta1 = regressor.coef_[0]
print("SGD theta1=", theta1)
print("SGD theta0=", theta0)
def initPlot():
plt.figure()
plt.title('2020.05 DaTong WEAThER')
plt.xlabel('Date')
plt.ylabel('maximum temperature')
plt.grid(True)
return plt
plt = initPlot()
plt.plot(xTrain, yTrain, 'k.')
plt.plot(xTrain, theta0 + theta1 * xTrain, 'g-')
plt.show()
3.6 對數機率迴歸
查看數據圖像
其中最高氣溫影響因素日期用 + 表示,最低氣溫用 · 表示。
import numpy as np
import matplotlib.pyplot as plt
# 解決中文問題
plt.rcParams['font.sans-serif'] = ['SimHei']
def initPlot():
plt.figure()
plt.title('2020年5月上旬大同天氣')
plt.xlabel('日期')
plt.ylabel('最低溫度')
return plt
plt = initPlot()
factor1 = np.array([1,2,3,4,5,6,7,8,9]) # 從trainData中獲取下標索引第2列(passed)值爲1的所有行的第0列元素
factor2 = np.array([8,9,4,4,6,10,10,7,3])
plt.plot(factor1,'r+')
plt.plot(factor2,'ko')
plt.show()
模型十一:使用LogisticRegression進行邏輯迴歸模型
- 設置邏輯迴歸算法的某些屬性
model = LogisticRegression(solver='lbfgs')
使用lbfgs算法來執行迴歸計算。默認使用liblinear。注意,這兩種算法的結果並不相同 - 執行計算
model.fit(X, y)
- 執行預測
model.predict(newX)
返回值是newX矩陣中每行數據所對應的結果。如果是1,則表示passed;如果是0,則表示unpassed - 獲得模型參數值
theta0 = model.intercept_[0] theta1 = model.coef_[0,0] theta2 = model.coef_[0,1]
- 決策邊界線
- 決策邊界線可視爲兩種類別數據點的分界線。在該分界線的一側,所有數據點都被歸爲passed類(1),另一側的所有數據點都被歸爲unpassed類(0)
- 對於本例來說,決策邊界線是一條直線(在案例2中進行了說明)
''' 使用LogisticRegression進行邏輯迴歸 '''
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
# 解決中文問題
plt.rcParams['font.sans-serif'] = ['SimHei']
xTrain = np.array([[1,8],[2,9],[3,4],[4,6],[5,10],[6,10],[7,10],[8,7],[9,3]])
yTrain = np.array([33,35,28,20,26,27,23,22,22])
# print(xTrain)
model = LogisticRegression(solver='lbfgs') # 使用lbfgs算法。默認是liblinear算法
model.fit(xTrain, yTrain)
newFactors = np.array([[2, 9],[5, 10],[9, 3],[10, 5]])
print("預測結果:")
print(model.predict(newFactors))
# 獲取theta計算結果
theta = np.array([model.intercept_[0], model.coef_[0,0], model.coef_[0,1]])
def initPlot():
plt.figure()
plt.title('2020年5月上旬大同天氣')
plt.xlabel('日期')
plt.ylabel('最低溫度')
return plt
plt = initPlot()
factor1 = np.array([1,2,3,4,5,6,7,8,9]) # 影響因素1:日期
factor2 = np.array([8,9,4,4,6,10,10,7,3]) # 影響因素2:最低氣溫
plt.plot(factor1,'r+')
plt.plot(factor2,'ko')
boundaryX = np.array([1,2,3,4,5,6,7,8,9,10]) # 繪製決策邊界線(每天日期)
boundaryY = -(theta[1] * boundaryX + theta[0]) / theta[2] # 根據決策邊界線的直線公式和x值,計算對應的y值
plt.plot(boundaryX, boundaryY, 'b-')
plt.show()
模型十二:基於成本函數和梯度下降算法進行邏輯迴歸模型
''' 使用梯度下降算法進行邏輯迴歸 '''
import numpy as np
import matplotlib.pyplot as plt
import bgd_resolver
def normalizeData(X, column_mean, column_std):
return (X - column_mean) / column_std
def sigmoid(z):
return 1. / (1 + np.exp(-z))
def costFn(theta, X, y):
temp = sigmoid(X.dot(theta))
cost = -y.dot(np.log(temp)) - (1 - y).dot(np.log(1 - temp))
return cost / len(X)
def gradientFn(theta, X, y):
return xTrain.T.dot(sigmoid(xTrain.dot(theta)) - yTrain) / len(X)
def initPlot():
plt.figure()
plt.title('2020.5 DaTong Weather')
plt.xlabel('Date')
plt.ylabel('Temp')
return plt
xTrain = np.array([[1,8],[2,9],[3,4],[4,6],[5,10],[6,10],[7,10],[8,7],[9,3]])
# 計算訓練數據每列平均值和每列的標準差
xTrain_column_mean = xTrain.mean(axis=0)
xTrain_column_std = xTrain.std(axis=0)
xTrain = normalizeData(xTrain, xTrain_column_mean, xTrain_column_std) # 如果不進行歸一化處理,計算過程中可能產生溢出(但似乎仍可以收斂)
x0 = np.ones(len(xTrain))
xTrain = np.c_[x0, xTrain] # 需手動追加Intercept Item列
yTrain = np.array([33,35,28,20,26,27,23,22,22])
np.random.seed(0)
init_theta = np.random.random(3) # 隨機初始化theta
theta = bgd_resolver.batch_gradient_descent(costFn, gradientFn, init_theta, xTrain, yTrain, 0.005, 0.00001)
# 預測若干數據,也需要先歸一化,使用之前訓練數據的mean和std
newFactors = np.array([[2, 9],[5, 10],[9, 3],[10, 5]])
newFactors = normalizeData(newScores, xTrain_column_mean, xTrain_column_std)
x0 = np.ones(len(newFactors))
newFactors = np.c_[x0, newFactors]
print("預測結果:")
print(sigmoid(newFactors.dot(theta)))
plt = initPlot()
factor1 = np.array([1,2,3,4,5,6,7,8,9]) # 影響因素1:日期
factor2 = np.array([8,9,4,4,6,10,10,7,3]) # 影響因素2:最低氣溫
plt.plot(factor1,'r+')
plt.plot(factor2,'ko')
# 繪製決策邊界線
boundaryX = np.array([1,2,3,4,5,6,7,8,9,10])
# 因爲之前進行了歸一化,因此邊界線上點的x座標也需要先歸一化。x座標對應的列索引是0
normalizedBoundaryX = (boundaryX - xTrain_column_mean[0]) / xTrain_column_std[0]
# 下面計算出來的邊界線上的y座標normalizedBoundaryY是經過歸一化處理的座標
normalizedBoundaryY = (theta[0] * normalizedBoundaryX + theta[1] ) / theta[1]
# boundaryY纔是將歸一化座標還原成正常座標。y座標對應的列索引是1
boundaryY = xTrain_column_std[1] * normalizedBoundaryY + xTrain_column_mean[1]
plt.plot(boundaryX, boundaryY, 'b-')
plt.show()
模型十三:基於scipy.optimize優化運算庫實現對數機率迴歸模型
- 使用minimize庫函數
- 需要提供jac參數,並將其設置爲梯度計算函數
- scipy.optimize庫中提供的算法會比我們自己實現的算法更高效、靈活、全面
- 本例中沒有對數據進行歸一處理,因此導致minimize方法執行過程中溢出(儘管可能也能收斂)。請自行添加歸一化處理功能
''' 使用minimize來優化邏輯迴歸求解 '''
import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize as opt
# 定義全局變量
xTrain = np.array([[1,8],[2,9],[3,4],[4,6],[5,10],[6,10],[7,10],[8,7],[9,3]])
x0 = np.ones(len(xTrain))
xTrain = np.c_[x0, xTrain]
yTrain = np.array([33,35,28,20,26,27,23,22,22])
def sigmoid(z):
return 1. / (1 + np.exp(-z))
# Cost Function以theta爲參數
def costFn(theta, X, y):
temp = sigmoid(xTrain.dot(theta))
cost = -yTrain.dot(np.log(temp)) - (1 - yTrain).dot(np.log(1 - temp))
return cost / len(X)
# Gradient Function以theta爲參數
def gradientFn(theta, X, y):
return xTrain.T.dot(sigmoid(xTrain.dot(theta)) - yTrain) / len(X)
np.random.seed(0)
# 隨機初始化theta,計算過程中可能產生溢出。
# 可以嘗試將init_theta乘以0.01,這樣可以防止計算溢出
init_theta = np.random.random(xTrain.shape[1])
result = opt.minimize(costFn, init_theta, args=(xTrain, yTrain), method='BFGS', jac=gradientFn, options={'disp': True})
theta = result.x # 最小化Cost時的theta
def initPlot():
plt.figure()
plt.title('2020.5 DaTong Weather')
plt.xlabel('Date')
plt.ylabel('Temp')
return plt
plt = initPlot()
factor1 = np.array([1,2,3,4,5,6,7,8,9]) # 影響因素1:日期
factor2 = np.array([8,9,4,4,6,10,10,7,3]) # 影響因素2:最低氣溫
plt.plot(factor1,'r+')
plt.plot(factor2,'ko')
boundaryX = np.array([1,2,3,4,5,6,7,8,9,10]) # 繪製決策邊界線
boundaryY = (theta[1] * boundaryX + theta[0]) / theta[2]
plt.plot(boundaryX,boundaryY, 'b-')
plt.show()
綜上可以觀察到,所有數據點並不明顯分成兩個類別。
線性迴歸主要都是針對訓練數據和計算結果均爲數值的情形。而在本例中,結果不是數值而是某種分類:這裏分成日期和最低氣溫兩類。而且發現,兩類並不顯示有明顯的分界線。這進一步說明最高氣溫的影響因素不是日期和最低氣溫。
4 總結
我們通過數據爬取並用十三種預測模型最終得出結論:最高氣溫的影響因素與日期和最低氣溫毫無關聯(由上可知會出現很荒謬的、與理論不符的結論,進而判斷);而這一結論與我們常識正好相符合,也就說明在此方面,實驗成功!
5 聲明
轉載請註明原文鏈接
感謝閱讀!
由於博主初學這部分知識,難免有疏漏錯誤之處,敬請讀者批評指正!
END