(1)單層正向回歸網絡
b | x1 | x2 | z |
1 | 0 | 0 | -0.2 |
1 | 1 | 0 | -0.05 |
1 | 0 | 1 | -0.05 |
1 | 1 | 1 | 0.1 |
接下來我們用代碼實現這組線性回歸數據
import torch
x = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
z = torch.tensor([-0.2, -0.05, -0.05, 0.1])
w = torch.tensor([-0.2,0.15,0.15])
def LinearR(x,w):zhat = torch.mv(x,w)return zhat
zhat = LinearR(x,w)
zhat
下面對這段代碼做一個詳細說明:
(1)tensor的定義
這里我們需要定義成浮點數的原因,是因為torch中有很多函數不接受兩個類型不同的參數傳入,例如這里的mv函數,如果我們省略浮點的定義,就會出現報錯如下:
這種靜態的編程可以說既是pytorch的優點也是缺點,優點在于如果省略了對傳入參數的檢查處理,就會在運行速度上快很多,尤其是傳入數據量大的時候,對每一個數據進行檢查是一個很慢的過程。缺點在于你需要人為記住哪些函數是可以不同類型哪些不可以,pytorch沒有一個統一的標準,唯一的辦法就只有自己多試幾次,所以建議大家在定義tensor的時候就默認定義成為float32
另外由于很多pytorch函數不接受一維張量,所以這里我們在定義的時候也養成兩個[[的二維張量的習慣
(2)精度問題
如果我們想要查看運行結果是否和答案一致的時候,我們運行下面代碼會發現奇怪的現象:
那就是為什么后面三個會是false,這其實就是精度的問題了。
所以當我們人為把精度調高到30位的時候就可以發現問題了:
當數據量大的時候,會因為精度問題產生較大的誤差,如果想要控制誤差大小,可以使用64位浮點數,唯一的缺點就是會比較占用內存。如果我們想要忽略較小的誤差可以使用下面這個函數:
通過shift+tab快捷鍵來查看函數參數使用,我們也可以通過調整rtol和atol控制誤差大小,可以看到這里的默認參數已經挺小的了
(2)torch.nn.Linear
在使用之前,我們先給出pytorch的架構圖,包含了常用的類和庫
我們先給出代碼再進行解釋:
import torch
X = torch.tensor([[0,0],[1,0],[0,1],[1,1]], dtype = torch.float32)
output = torch.nn.Linear(2,1)
zhat = output(X)
zhat
首先nn.Linear是一個類,在這里代表了輸出層,所以使用output作為變量名,output的這一行相當于是類的實例化過程。里面輸入的參數2和1分別指上一層的神經元個數和這一層的神經元個數。
由于nn.Module的子類會在實例化的同時自動生成隨機的w和b,所以這里我們上一層就只有兩個x,又由于這是回歸類模型所以輸出層只有一個。我們也可以調用查看隨機生成的數:
如果我們不想要bias可以設置成false:output = torch.nn.Linear(2,1,bias = False)
如果我們不想都每次都隨機生成不同的權重,可以設置隨機數種子:torch.random.manual_seed(250)
(3)邏輯回歸
我們學習過線性回歸之后,都知道線性回歸能夠擬合直線的線性關系,但是現實生活中的數據關系往往不是線性的,所以為了更好的擬合曲線關系,統計學家就在線性回歸的方程兩邊加上了聯系函數,這種回歸也被叫做廣義線性回歸。而在探索聯系函數的過程中,就發現了sigmoid函數。
我們可以運行下面代碼來畫出sigmoid圖像:
import numpy as np
import matplotlib.pyplot as plt# 定義Sigmoid函數
def sigmoid(x):return 1 / (1 + np.exp(-x))# 生成x軸數據
x = np.linspace(-10, 10, 400)# 計算y軸數據
y = sigmoid(x)# 繪圖
plt.figure(figsize=(8, 6))
plt.plot(x, y, label="Sigmoid Function", color='b')
plt.title("Sigmoid Function")
plt.xlabel("x")
plt.ylabel("sigmoid(x)")
plt.grid(True)
plt.axhline(0, color='black',linewidth=0.5)
plt.axvline(0, color='black',linewidth=0.5)
plt.legend()
plt.show()
首先,我們可以觀察函數圖像,這是一個將所有數映射到(0,1)之間的函數,x趨于負無窮時,函數值趨近于0,x趨近于正無窮時,函數值趨近于1。
運行下面代碼,我們可以畫出sigmoid的導數圖像:
import numpy as np
import matplotlib.pyplot as plt# 定義Sigmoid函數
def sigmoid(x):return 1 / (1 + np.exp(-x))# 定義Sigmoid函數的導數
def sigmoid_derivative(x):s = sigmoid(x)return s * (1 - s)# 生成x軸數據
x = np.linspace(-10, 10, 400)# 計算Sigmoid函數的導數
y_derivative = sigmoid_derivative(x)# 繪圖
plt.figure(figsize=(8, 6))
plt.plot(x, y_derivative, label="Sigmoid Derivative", color='r')
plt.title("Derivative of Sigmoid Function")
plt.xlabel("x")
plt.ylabel("sigmoid'(x)")
plt.grid(True)
plt.axhline(0, color='black',linewidth=0.5)
plt.axvline(0, color='black',linewidth=0.5)
plt.legend()
plt.show()
可以發現在0處取到的導數值最大,往邊上走迅速減小,所以它可以快速將數據從0的附近排開。這樣的性質,讓sigmoid函數擁有將連續型變量轉化為離散型變量的力量,這也讓它擁有了化回歸類問題為分類問題的能力。
當我們將該函數以對數幾率的形式表達出來的時候,會發現另一個有趣的地方。
我們神奇地發現,讓取對數幾率后所得到的值就是我們線性回歸的z,因為這個性質,在等號兩邊加sigmoid 的算法被稱為“對數幾率回歸”,在英文中就是Logistic Regression,就是邏輯回歸。
此時 和
之和為1,因此它們可以被我們看作是一對正反例發生的概率,即
是某樣本i的標簽被預測為1的概率,而
是i的標簽被預測為0的概率,相比的結果就是樣本i的標簽被預測為1的相對概率。基于這種理解,雖然可能不嚴謹,邏輯回歸、即單層二分類神經網絡返回的結果一般被當成是概率來看待和使用。
(1)與門的實現:
x0 | x1 | x2 | andgate |
1 | 0 | 0 | 0 |
1 | 1 | 0 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([[0],[0],[0],[1]], dtype = torch.float32)
w = torch.tensor([-0.2,0.15,0.15], dtype = torch.float32)
def LogisticR(X,w):zhat = torch.mv(X,w)sigma = torch.sigmoid(zhat)#sigma = 1/(1+torch.exp(-zhat))andhat = torch.tensor([int(x) for x in sigma >= 0.5], dtype = torch.float32)return sigma, andhat
sigma, andhat = LogisticR(X,w)
andhat
這里我們求sigma的時候用的是庫里面的函數,注釋的是手動寫的。
唯一需要額外解釋的是andhat的列表推導式:
sigma >= 0.5
會對 sigma
中的每個元素進行比較,返回一個布爾型張量,并且形狀與 sigma
相同舉個例子,假設 sigma = torch.tensor([0.2, 0.7, 0.5, 0.3])
,那么 sigma >= 0.5
的結果會是:tensor([False, ?True, ?True, False])
接下來是列表推導式部分,它會遍歷 sigma >= 0.5
中的每個布爾值,并將每個布爾值轉化為整數(True
轉化為 1
,False
轉化為 0
)。也就是說,這個列表推導式的功能是將布爾值轉換為二分類的預測值(0 或 1)。
上面的例子通過列表推導式 [int(x) for x in sigma >= 0.5]
,會得到:[0, 1, 1, 0]
當然除了sigmoid還有很多經典的函數,例如sign,ReLU,Tanh等函數,這里我們就不再一一列出來了,用到的時候再說。
(2)torch版本實現
import torch
from torch.nn import functional as F
X = torch.tensor([[0,0],[1,0],[0,1],[1,1]], dtype = torch.float32)
torch.random.manual_seed(250) #人為設置隨機數種子
dense = torch.nn.Linear(2,1)
zhat = dense(X)
sigma = F.sigmoid(zhat)
y = [int(x) for x in sigma > 0.5]
y
(4)softmax
當我們遇到多分類問題的時候,往往使用softmax來解決
Softmax 是一種常用于多分類問題的激活函數,特別是在神經網絡的輸出層,它將一組實數轉換為一個概率分布。Softmax 是“歸一化指數函數”的一種形式,其主要功能是將任意實數向量轉換成一個概率分布,使得每個元素的值介于0和1之間,且所有元素的和為1。這對于多分類問題來說非常重要,因為它可以幫助我們得到不同類別的概率,從而進行分類決策。
可是由于指數的緣故,經常會發生溢出的情況,如下:
所以一般我們不手動自己寫softmax,一般會調用pytorch內置的函數
你可能會疑惑為什么這個函數不會出現溢出的問題,這是因為使用了一點小技巧。
為了避免溢出,我們可以將每個輸入值減去輸入向量的最大值
,這樣可以確保計算中的指數值不會太大,仍然可以保留同樣結果的相對比例。通過以下變換,我們可以得到數值穩定的 Softmax:
另外還需要解釋一點的是這里的0是什么,我們可以通過快捷鍵查看函數的參數發現,第二個參數是維度,也就是說該函數需要你指定沿著哪一個維度進行softmax操作。
這里由于是只有一行,所以就是沿著這一行做softmax,所以維度的索引值為0,下面舉幾個例子:
如果是二維張量dim=1就是對兩個小的張量進行softmax
這意味著 Softmax 沒有對每個樣本獨立處理,而是計算了每個類別在所有樣本中的相對比例。
我們再舉一個大一點的三維張量的例子:
import torch
import torch.nn.functional as F# 輸入一個 3D 張量 (batch_size, channels, height)
z = torch.tensor([[[1.0, 2.0, 3.0, 4.0], # 第一個樣本的 4 個特征值[1.0, 2.0, 3.0, 4.0], # 第二個樣本的 4 個特征值[1.0, 2.0, 3.0, 4.0]], # 第三個樣本的 4 個特征值[[5.0, 6.0, 7.0, 8.0], # 第一個樣本的 4 個特征值[5.0, 6.0, 7.0, 8.0], # 第二個樣本的 4 個特征值[5.0, 6.0, 7.0, 8.0]]] # 第三個樣本的 4 個特征值# 打印輸入張量
print("輸入張量:")
print(z)
print("\n")# 沿著 dim=2 計算 Softmax(計算每個特征維度的概率)
softmax_dim2 = F.softmax(z, dim=2)
print("沿著 dim=2 計算 Softmax:")
print(softmax_dim2)
print("\n")# 沿著 dim=1 計算 Softmax(計算每個類別的概率)
softmax_dim1 = F.softmax(z, dim=1)
print("沿著 dim=1 計算 Softmax:")
print(softmax_dim1)
print("\n")# 沿著 dim=0 計算 Softmax(計算每個樣本的概率)
softmax_dim0 = F.softmax(z, dim=0)
print("沿著 dim=0 計算 Softmax:")
print(softmax_dim0)
由于這是一個shape為(2,3,4)的張量,所以dim=2也就是最后一個維度4的求和,所以是四個元素加起來等于1;dim=1也就是中間維度3,按照列加起來等于1;dim=0第一個維度2,所以是第一個小張量里面的加第二個小張量里面的等于1.