機器學習,深度學習相關概念,及用PyTorch實現(二)——Logistic迴歸模型

Logistic迴歸是一種廣義的迴歸模型,其與多元線性迴歸有着很多相似之處,模型的形式基本相同,雖然也被稱爲迴歸,但是其更多的情況使用在分類問題上,同時又以二分類更爲常用。

 

迴歸和分類:迴歸問題得到的結果是連續的,比如通過學習時間預測成績

分類問題是將數據分成幾類,比如根據郵件信息將郵件分成垃圾郵件和有效郵件兩類

Logistic迴歸的模型形式和線性迴歸一樣,都是y=wx+b, 其中x可以是一個多維的特徵,唯一不同的地方在於Logistic迴歸會對y作用一個logistic函數,將其變爲一種概率的結果。Logistic 函數作爲Logistic迴歸的核心,也被稱爲Sigmoid函數。


Sigmoid函數

可以看到Sigmoid函數的範圍是在0~1之間,所以任何一個值經過了Sigmoid 函數的作用,都會變成0~1之間的一個值,這個值可以形象地理解爲一個概率,比如對於二分類問題,這個值越小就表示屬於第一類,這個值越大就表示屬於第二類。

 


 

 損失函數:

Logistic迴歸使用了Sigmoid函數將結果變到0~1之間,對於任意輸入一個數據,經過Sigmoid之後的結果我們記爲yΛ, 表示這個數據點屬於第二類的概率,那麼其屬於第一類的概率就是1 - yΛ.如果這個數據點屬於第二類,我們希望y越大越好,也就是越靠近1越好,如果這個數據屬於第一類,那麼我們希望1- yΛ越大越好,也就是yΛ越小越好,越靠近0越好,所以我們可以這樣設計我們的loss函數

其中y表示真實的label,只能取{0, 1}這兩個值,因爲y表示經過Logistic迴歸預測之後的結果,是一個0~ 1之間的小數。如果y是0,表示該數據屬於第一-類, 我們希望yΛ越小越好,上面的loss函數變爲

在訓練模型的時候我們希望最小化loss函數,根據log函數的單調性,也就是最小化yΛ,與我們的要求是一致的。而如果y是1,表示該數據屬於第二類,我們希望y^越大越好,同時上面的loss函數變爲

我們希望最小化loss函數也就是最大化y^。

 

PyTorch實現邏輯迴歸模型

 

import torch
from torch.autograd import Variable
import numpy as np
import matplotlib.pyplot as plt

#設定隨機種子
torch.manual_seed(2020)

#從data.txt讀入數據
with open('data.txt','r') as f:
    data_list=[i.split('\n')[0].split(',') for i in f.readlines()]
    data=[(float(i[0]),float(i[1]),float(i[2])) for i in data_list]

#標準化
x0_max=max([i[0] for i in data])
x1_max=max([i[1] for i in data])
data=[(i[0]/x0_max,i[1]/x1_max,i[2]) for i in data]

x0=list(filter(lambda x:x[-1]==0.0,data))#選擇第一類的點
x1=list(filter(lambda x:x[-1]==1.0,data))#選擇第二類的點

plot_x0=[i[0] for i in x0]
plot_y0=[i[1] for i in x0]
plot_x1=[i[0] for i in x1]
plot_y1=[i[1] for i in x1]

plt.plot(plot_x0,plot_y0,'ro',label='x_0')
plt.plot(plot_x1,plot_y1,'bo',label='x_1')
plt.legend(loc='best')
plt.show()

 

 

#定義sigmod函數
def sigmoid(x):
    return 1/(1+np.exp(-x))

#畫出sigmoid的圖像
plot_x=np.arange(-10,10.01,0.01)
plot_y=sigmoid(plot_x)

plt.plot(plot_x,plot_y,'r')
plt.show()

 

 

#畫出sigmoid的圖像
plot_x=np.arange(-10,10.01,0.01)
plot_y=sigmoid(plot_x)

plt.plot(plot_x,plot_y,'r')
plt.show()

x_data=Variable(x_data)
y_data=Variable(y_data)

# 在PyTorch當中,不需要我們自己寫Sigmoid的函數,PyTorch 已經用底層的C++語言爲我們寫好了一
# 些常用的函數,不僅方便我們使用,同時速度上比我們自己實現的更快,穩定性更好

#定義logistic迴歸模型
w=Variable(torch.randn(2,1),requires_grad=True)
b=Variable(torch.zeros(1),requires_grad=True)

#在更新之前畫出分類效果
w0=w[0].data[0].numpy()
w1=w[1].data[0].numpy()
b0=b.data[0].numpy()

plot_x=np.arange(0.2,1,0.01)
plot_y=(-w0 * plot_x-b0)/w1

plt.plot(plot_x,plot_y,'g',label="cutting line")
plt.plot(plot_x0,plot_y0,'ro',label="x_0")
plt.plot(plot_x1,plot_y1,'bo',label="x_1")
plt.legend(loc="best")
plt.show()

 分類結果是混亂的。

 

 

import torch
from torch.autograd import Variable
import numpy as np
import matplotlib.pyplot as plt
import torch.nn.functional as F

#設定隨機種子
torch.manual_seed(2020)

#從data.txt讀入數據
with open('data.txt','r') as f:
    data_list=[i.split('\n')[0].split(',') for i in f.readlines()]
    data=[(float(i[0]),float(i[1]),float(i[2])) for i in data_list]

#標準化
x0_max=max([i[0] for i in data])
x1_max=max([i[1] for i in data])
data=[(i[0]/x0_max,i[1]/x1_max,i[2]) for i in data]

x0=list(filter(lambda x:x[-1]==0.0,data))#選擇第一類的點
x1=list(filter(lambda x:x[-1]==1.0,data))#選擇第二類的點

plot_x0=[i[0] for i in x0]
plot_y0=[i[1] for i in x0]
plot_x1=[i[0] for i in x1]
plot_y1=[i[1] for i in x1]

plt.plot(plot_x0,plot_y0,'ro',label='x_0')
plt.plot(plot_x1,plot_y1,'bo',label='x_1')
plt.legend(loc='best')
plt.show()


#接下來將數據轉化爲Numpy的類型,接着轉換到Tensor爲之後的訓練做準備
np_data=np.array(data,dtype='float32')#轉換成numpy array
x_data=torch.from_numpy(np_data[:,0:2])#轉換成Tensor,大小是【100,2】
y_data=torch.from_numpy(np_data[:,-1]).unsqueeze(1)#轉換成Tensor,大小是【100,2】

#定義sigmod函數
def sigmoid(x):
    return 1/(1+np.exp(-x))

#畫出sigmoid的圖像
plot_x=np.arange(-10,10.01,0.01)
plot_y=sigmoid(plot_x)

plt.plot(plot_x,plot_y,'r')
plt.show()

x_data=Variable(x_data)
y_data=Variable(y_data)

# 在PyTorch當中,不需要我們自己寫Sigmoid的函數,PyTorch 已經用底層的C++語言爲我們寫好了一
# 些常用的函數,不僅方便我們使用,同時速度上比我們自己實現的更快,穩定性更好

#定義logistic迴歸模型
w=Variable(torch.randn(2,1),requires_grad=True)
b=Variable(torch.zeros(1),requires_grad=True)

def logistic_regression(x):
    return F.sigmoid(torch.mm(x,w)+b)

#在更新之前畫出分類效果
w0=w[0].data[0].numpy()
w1=w[1].data[0].numpy()
b0=b.data[0].numpy()

plot_x=np.arange(0.2,1,0.01)
plot_y=(-w0 * plot_x-b0)/w1

plt.plot(plot_x,plot_y,'g',label="cutting line")
plt.plot(plot_x0,plot_y0,'ro',label="x_0")
plt.plot(plot_x1,plot_y1,'bo',label="x_1")
plt.legend(loc="best")
plt.show()


#計算
def binary_loss(y_pred,y):
    logits=(y*y_pred.clamp(1e-12).log()+(1-y)*(1-y_pred).clamp(1e-12).log()).mean()
    return -logits

y_pred=logistic_regression(x_data)
loss=binary_loss(y_pred,y_data)
print(loss)

#自動求導並更新參數
loss.backward()

w.data=w.data-0.1*w.grad.data
b.data=b.data-0.1*b.grad.data

#算出一次更新後的loss
y_pred=logistic_regression(x_data)
loss=binary_loss(y_pred,y_data)
print(loss)

#使用torch.optim更新參數
from torch import nn
w=nn.Parameter(torch.randn(2,1))
b=nn.Parameter(torch.zeros(1))

def logistic_regression(x):
    return F.sigmoid(torch.mm(x,w)+b)

optimizer=torch.optim.SGD([w,b],lr=1.)

#進行一百次更新
import time
start=time.time()
for e in range(1000):
    #前向傳播
    y_pred=logistic_regression(x_data)
    loss=binary_loss(y_pred,y_data)#計算loss
    #反向傳播
    optimizer.zero_grad()#使用優化器將梯度歸0
    loss.backward()
    optimizer.step()#使用優化器來更新參數
    #計算正確率
    mask=y_pred.ge(0.5).float()
    acc=(mask==y_data).sum().item()/y_data.shape[0]
    if(e+1)%200==0:
        print('epoch:{},Loss:{:5f},ACC:{:.5f}'.format(e+1,loss.item(),acc))

during=time.time()
print('During Time:{:.3f} s'.format(during))

#畫出更新後的結果
w0=w[0].data[0].numpy()
w1=w[1].data[0].numpy()
b0=b.data[0].numpy()

plot_x=np.arange(0.2,1,0.01)
plot_y=(-w0*plot_x-b0)/w1

plt.plot(plot_x,plot_y,'g',label="cutting line")
plt.plot(plot_x0,plot_y0,'ro',label="x_0")
plt.plot(plot_x1,plot_y1,'bo',label="x_1")
plt.legend(loc="best")
plt.show()

 

 

模型已經能夠將兩類點分開了,

前面使用了自己寫的loss,其實PyTorch已經爲我們寫好了一些常見的loss,比如線性迴歸裏面的loss是nn.MSE(),而Logistic迴歸的二分類loss在PyTorch中是nn. BCEWithLogitsLoss()。


PyTorch爲我們實現的loss函數有兩個好處,第一是方便我們使用,不需要重複造輪子,第二就是其實現是在底層C++語言上的,所以速度上和穩定性上都要比我們自己實現的要好。
另外,PyTorch 出於穩定性考慮,將模型的Sigmoid操作和最後的loss都合在了
nn. BCEWithLogitsLoss(),所以我們使用PyTorch自帶的loss就不需要再加上Sigmoid操作了。
 

 

import torch
from torch.autograd import Variable
import numpy as np
import matplotlib.pyplot as plt
import torch.nn.functional as F

#設定隨機種子
torch.manual_seed(2020)

#從data.txt讀入數據
with open('data.txt','r') as f:
    data_list=[i.split('\n')[0].split(',') for i in f.readlines()]
    data=[(float(i[0]),float(i[1]),float(i[2])) for i in data_list]

#標準化
x0_max=max([i[0] for i in data])
x1_max=max([i[1] for i in data])
data=[(i[0]/x0_max,i[1]/x1_max,i[2]) for i in data]

x0=list(filter(lambda x:x[-1]==0.0,data))#選擇第一類的點
x1=list(filter(lambda x:x[-1]==1.0,data))#選擇第二類的點

plot_x0=[i[0] for i in x0]
plot_y0=[i[1] for i in x0]
plot_x1=[i[0] for i in x1]
plot_y1=[i[1] for i in x1]


#接下來將數據轉化爲Numpy的類型,接着轉換到Tensor爲之後的訓練做準備
np_data=np.array(data,dtype='float32')#轉換成numpy array
x_data=torch.from_numpy(np_data[:,0:2])#轉換成Tensor,大小是【100,2】
y_data=torch.from_numpy(np_data[:,-1]).unsqueeze(1)#轉換成Tensor,大小是【100,2】


x_data=Variable(x_data)
y_data=Variable(y_data)

# 在PyTorch當中,不需要我們自己寫Sigmoid的函數,PyTorch 已經用底層的C++語言爲我們寫好了一
# 些常用的函數,不僅方便我們使用,同時速度上比我們自己實現的更快,穩定性更好

#定義logistic迴歸模型
w=Variable(torch.randn(2,1),requires_grad=True)
b=Variable(torch.zeros(1),requires_grad=True)

def logistic_regression(x):
    return F.sigmoid(torch.mm(x,w)+b)

#在更新之前畫出分類效果
w0=w[0].data[0].numpy()
w1=w[1].data[0].numpy()
b0=b.data[0].numpy()



#計算
def binary_loss(y_pred,y):
    logits=(y*y_pred.clamp(1e-12).log()+(1-y)*(1-y_pred).clamp(1e-12).log()).mean()
    return -logits

y_pred=logistic_regression(x_data)
loss=binary_loss(y_pred,y_data)
print(loss)

from torch import nn
import time
# 使用自帶的loss
criterion = nn.BCEWithLogitsLoss() # 將 sigmoid 和 loss 寫在一層,有更快的速度、更好的穩定性
w = nn.Parameter(torch.randn(2, 1))
b = nn.Parameter(torch.zeros(1))
def logistic_reg(x):
    return torch.mm(x, w) + b
optimizer = torch.optim.SGD([w, b], 1.)
# y_pred = logistic_reg(x_data)
# loss = criterion(y_pred, y_data)
print(loss.data)
# 同樣進行 1000 次更新
start = time.time()
for e in range(1000):
    # 前向傳播
    y_pred = logistic_reg(x_data)
    loss = criterion(y_pred, y_data)
    # 反向傳播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    # 計算正確率
    mask = y_pred.ge(0.5).float()
    acc = (mask == y_data).sum().item() / y_data.shape[0]
    if (e + 1) % 200 == 0:
        print('epoch: {}, Loss: {:.5f}, Acc: {:.5f}'.format(e+1, loss.item(), acc))
during = time.time() - start
print('During Time: {:.3f} s'.format(during))


# 畫出更新之後的結果
w0 = w[0].data[0].cpu().numpy()
w1 = w[1].data[0].cpu().numpy()
b0 = b.data[0].cpu().numpy()
plot_x = np.arange(0.2, 1, 0.01)
plot_y = (-w0 * plot_x - b0) / w1
plt.plot(plot_x, plot_y, 'g', label='cutting line')
plt.plot(plot_x0, plot_y0, 'ro', label='x_0')
plt.plot(plot_x1, plot_y1, 'bo', label='x_1')
plt.legend(loc='best')
plt.show()

 data.txt

34.62365962451697,78.0246928153624,0
30.28671076822607,43.89499752400101,0
35.84740876993872,72.90219802708364,0
60.18259938620976,86.30855209546826,1
79.0327360507101,75.3443764369103,1
45.08327747668339,56.3163717815305,0
61.10666453684766,96.51142588489624,1
75.02474556738889,46.55401354116538,1
76.09878670226257,87.42056971926803,1
84.43281996120035,43.53339331072109,1
95.86155507093572,38.22527805795094,0
75.01365838958247,30.60326323428011,0
82.30705337399482,76.48196330235604,1
69.36458875970939,97.71869196188608,1
39.53833914367223,76.03681085115882,0
53.9710521485623,89.20735013750205,1
69.07014406283025,52.74046973016765,1
67.94685547711617,46.67857410673128,0
70.66150955499435,92.92713789364831,1
76.97878372747498,47.57596364975532,1
67.37202754570876,42.83843832029179,0
89.67677575072079,65.79936592745237,1
50.534788289883,48.85581152764205,0
34.21206097786789,44.20952859866288,0
77.9240914545704,68.9723599933059,1
62.27101367004632,69.95445795447587,1
80.1901807509566,44.82162893218353,1
93.114388797442,38.80067033713209,0
61.83020602312595,50.25610789244621,0
38.78580379679423,64.99568095539578,0
61.379289447425,72.80788731317097,1
85.40451939411645,57.05198397627122,1
52.10797973193984,63.12762376881715,0
52.04540476831827,69.43286012045222,1
40.23689373545111,71.16774802184875,0
54.63510555424817,52.21388588061123,0
33.91550010906887,98.86943574220611,0
64.17698887494485,80.90806058670817,1
74.78925295941542,41.57341522824434,0
34.1836400264419,75.2377203360134,0
83.90239366249155,56.30804621605327,1
51.54772026906181,46.85629026349976,0
94.44336776917852,65.56892160559052,1
82.36875375713919,40.61825515970618,0
51.04775177128865,45.82270145776001,0
62.22267576120188,52.06099194836679,0
77.19303492601364,70.45820000180959,1
97.77159928000232,86.7278223300282,1
62.07306379667647,96.76882412413983,1
91.56497449807442,88.69629254546599,1
79.94481794066932,74.16311935043758,1
99.2725269292572,60.99903099844988,1
90.54671411399852,43.39060180650027,1
34.52451385320009,60.39634245837173,0
50.2864961189907,49.80453881323059,0
49.58667721632031,59.80895099453265,0
97.64563396007767,68.86157272420604,1
32.57720016809309,95.59854761387875,0
74.24869136721598,69.82457122657193,1
71.79646205863379,78.45356224515052,1
75.3956114656803,85.75993667331619,1
35.28611281526193,47.02051394723416,0
56.25381749711624,39.26147251058019,0
30.05882244669796,49.59297386723685,0
44.66826172480893,66.45008614558913,0
66.56089447242954,41.09209807936973,0
40.45755098375164,97.53518548909936,1
49.07256321908844,51.88321182073966,0
80.27957401466998,92.11606081344084,1
66.74671856944039,60.99139402740988,1
32.72283304060323,43.30717306430063,0
64.0393204150601,78.03168802018232,1
72.34649422579923,96.22759296761404,1
60.45788573918959,73.09499809758037,1
58.84095621726802,75.85844831279042,1
99.82785779692128,72.36925193383885,1
47.26426910848174,88.47586499559782,1
50.45815980285988,75.80985952982456,1
60.45555629271532,42.50840943572217,0
82.22666157785568,42.71987853716458,0
88.9138964166533,69.80378889835472,1
94.83450672430196,45.69430680250754,1
67.31925746917527,66.58935317747915,1
57.23870631569862,59.51428198012956,1
80.36675600171273,90.96014789746954,1
68.46852178591112,85.59430710452014,1
42.0754545384731,78.84478600148043,0
75.47770200533905,90.42453899753964,1
78.63542434898018,96.64742716885644,1
52.34800398794107,60.76950525602592,0
94.09433112516793,77.15910509073893,1
90.44855097096364,87.50879176484702,1
55.48216114069585,35.57070347228866,0
74.49269241843041,84.84513684930135,1
89.84580670720979,45.35828361091658,1
83.48916274498238,48.38028579728175,1
42.2617008099817,87.10385094025457,1
99.31500880510394,68.77540947206617,1
55.34001756003703,64.9319380069486,1
74.77589300092767,89.52981289513276,1

可以看到,使用了PyTorch自帶的loss之後,速度有了一定的上升,雖然看上去速度的提升並不多,但是這只是一個小網絡,對於大網絡,使用自帶的loss不管對於穩定性還是速度而言,都有質的飛躍,同時也避免了重複造輪子的困擾。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章