任務:利用神經網路完成對二手車交易價格的預測
程式碼解析
匯入庫
import pandas as pd
import numpy as np
from torch import nn, optim
import torch
import matplotlib.pyplot as plt
配置引數
config = {
'epoch': 10,
'batch_size': 512,
'learning_rate': 8e-3,
'device': 'cuda',
"num_cols": ['regDate', 'creatDate', 'power', 'kilometer', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10',
'v_11', 'v_12', 'v_13', 'v_14'],
"cate_cols": ['model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'seller', 'notRepairedDamage']
}
- epoch:指定訓練的週期數,即整個訓練集被用來訓練神經網路的次數
- batch_size:每次更新模型權重時使用的樣本數量
- learning_rate:學習率是梯度下降演算法中的一個重要引數,決定了權重更新的步長。較高的學習率意味著模型權重更新更快,但可能跳過區域性最小值;較低的學習率則可能使訓練過程緩慢
- device:指定用於計算的裝置。如果可用,將使用GPU(通常比CPU快很多)來進行計算。
cuda
是 NVIDIA GPU 的 PyTorch 後端。 - num_cols和cate_cols: 列出了資料集中用於模型訓練的特徵列名。
"num_cols"
包含數值型特徵,如日期、功率、里程等;"cate_cols"
包含分型別特徵,如車型、品牌、車身型別、燃料型別等。
匯入資料
test_data = pd.read_csv('/gemini/data-1/used_car_testB_20200421.csv', sep=' ')
train_data = pd.read_csv('/gemini/data-1/used_car_train_20200313.csv', sep=' ')
合併資料
data = pd.concat([train_data, test_data])
定義One-Hot編碼函式
def oneHotEncode(df, colNames):
for col in colNames:
dummies = pd.get_dummies(df[col], prefix=col)
df = pd.concat([df, dummies],axis=1)
df.drop([col], axis=1, inplace=True)
return df
函式效果:對dataframe中的分類變數進行one-hot編碼,即將每個分類變數都拆分成多個二進位制的列。
eg:
Color | Size | |
---|---|---|
0 | Red | Small |
1 | Blue | Large |
2 | Green | Medium |
3 | Red | Small |
4 | Blue | Medium |
轉化為
index | Color_Blue | Color_Green | Color_Red | Size_Large | Size_Medium | Size_Small |
---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | 0 | 0 | 1 |
1 | 1 | 0 | 0 | 1 | 0 | 0 |
2 | 0 | 1 | 0 | 0 | 1 | 0 |
3 | 0 | 0 | 1 | 0 | 0 | 1 |
4 | 1 | 0 | 0 | 0 | 1 | 0 |
資料預處理
data = data.replace('-', '-1')
data.notRepairedDamage = data.notRepairedDamage.astype('float32')
data.loc[data['power']>600,'power'] = 600
- 處理特殊標記,將'-'轉化為'-1'
- 資料型別轉換,
notRepairedDamage
列的資料型別轉換為 32 位浮點數,確保這一列可以進行數值運算 - 異常值處理,查詢
power
列中大於 600 的所有值,並將這些值統一設定為 600,可以防止極端值對模型訓練產生不良影響。
# 處理離散資料
for col in config['cate_cols']:
data[col] = data[col].fillna('-1')
data = oneHotEncode(data, config['cate_cols'])
# 處理連續資料
for col in config['num_cols']:
data[col] = data[col].fillna(0)
data[col] = (data[col]-data[col].min()) / (data[col].max()-data[col].min())
# 處理(可能)無關資料
data.drop(['name', 'regionCode'], axis=1, inplace=True)
- 處理離散資料:用-1填充缺失值,然後對分類特徵進行one-hot編碼
- 處理連續資料:用0填充缺失值,然後對資料進行歸一化處理,即將數值範圍縮放到 [0, 1] 之間。
- 處理(可能)無關資料:移除
特徵縮放
在使用多指標在綜合評價某事物時,可能存在各指標的數量級和量綱不同導致的資料爆炸或各指標對分析的作用不合理等情況,因此需要在資料預處理時採用特徵縮放的方法來平衡各指標之間的差異,從而最佳化演算法。
如果不進行特徵縮放
假如特徵\(x_1\)的數值是100左右,特徵\(x_2\)的數值是1左右,方程為𝑦=𝑏+𝑤1𝑥1+𝑤2𝑥2,那\(w_1\)對𝑦的影響就更大,對Loss的影響也更大,損失函式關於\(w_1\)的梯度也更大,而損失函式關於\(w_2\)的梯度卻很小,因此兩個特徵就不能使用相同的學習率。
不進行特徵縮放的話,Error Surface就是一個橢圓,梯度下降時不一定是朝著最優點(圓心),速度就慢。
如果進行了特徵縮放,Error Surface會盡可能趨近於圓,因此梯度下降時會一直朝著最優點(圓心),所以速度快。
標準化(Standardization/Z-Score Normalization)
-
特點
使資料的平均值變為0、標準差變為1,不改變資料的分佈型別,數值範圍不一定,消除了資料的量綱差異。
-
假設
標準化假設資料是正態分佈,但這個要求並不十分嚴格,如果資料是正態分佈則該技術會更有效。
-
何時使用
當我們使用的演算法假設資料是正態分佈時,可以使用Standardization,比如線性迴歸、邏輯迴歸、線性判別分析。
因為Standardization使資料平均值為0,也可以在一些假設資料中心為0(zero centric data)的演算法中使用,比如主成分分析(PCA)。
歸一化(Normalization)
-
特點
把資料調整到[0,1],並且消除了資料的量綱差異。
也可以把資料調到[-1,1],在使用SVM和Adaboost時就需要這樣。
-
何時使用
當我們不知道資料分佈時或者我們知道資料不是正態分佈時,這是一個很好的方法。
換種說法就是,當我們使用的演算法沒有假設資料的分佈型別時,就可以使用Normalization,比如K近鄰演算法和人工神經網路。
Mean Normalization
-
定義
\(y=\frac{x-mean(x)}{max(x)-min(x)}\)
-
特點
把資料調到[-1,1],平均值為0
-
何時使用
一些假設資料中心為0(zero centric data)的演算法,比如主成分分析(PCA)。
Min-Max Normalization
-
定義
\(y=\frac{x-min(x)}{max(x)-min(x)}\)
-
特點
把資料調到[0,1]
-
何時使用
當處理具有嚴格數值範圍要求的資料(比如圖片)時,這非常有用。
-
# 暫存處理後的test資料集
test_data = data[pd.isna(data.price)]
test_data.to_csv('./one_hot_testB.csv')
# 刪除test資料(price is nan)
data.reset_index(inplace=True)
train_data = data
train_data = train_data.drop(data[pd.isna(data.price)].index)
# 刪除ID
train_data.drop(['SaleID'], axis=1, inplace=True)
# 打亂
train_data = train_data.sample(frac=1) # 它將返回一個包含所有行的隨機排序版本
# 分離目標
train_target = train_data['price']
train_data.drop(['price', 'index'], axis=1, inplace=True)
# 分離出驗證集,用於觀察擬合情況
validation_data = train_data[:10000] # 選取原始訓練資料集中的前10000項作為驗證集
train_data = train_data[10000:] # 將訓練集更新為原訓練集除前10000項之外的餘項
validation_target = train_target[:10000] # 選取原始訓練目標中的前10000項作為驗證時的評估標準
train_target = train_target[10000:] # 調整訓練集的訓練目標
搭建網路
# 定義網路結構
class Network(nn.Module):
def __init__(self, in_dim, hidden_1, hidden_2, hidden_3, hidden_4):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden_1),
nn.BatchNorm1d(hidden_1),
nn.ReLU(),
nn.Linear(hidden_1, hidden_2),
nn.BatchNorm1d(hidden_2),
nn.ReLU(),
nn.Linear(hidden_2, hidden_3),
nn.BatchNorm1d(hidden_3),
nn.ReLU(),
nn.Linear(hidden_3, hidden_4),
nn.BatchNorm1d(hidden_4),
nn.ReLU(),
nn.Linear(hidden_4, 1)
)
def forward(self, x):
y = self.layers(x)
return y
nn.Linear
是全連線層,負責從輸入層到輸出層的線性變換nn.BatchNorm1d
層用於進行批次歸一化,這有助於加速訓練過程並提高模型的穩定性。nn.ReLU
層是啟用函式,用於引入非線性特性,使得網路能夠學習更復雜的函式對映。
# 定義網路
model = Network(train_data.shape[1], 256, 256, 256, 32)
# 網路的輸入維度由 train_data.shape[1] 確定,即訓練資料的特徵數量。其餘引數分別為隱藏層的大小,分別是 256、256、256 和 32。
model.to(config['device'])
# 使用Xavier初始化權重
for line in model.layers:
if type(line) == nn.Linear:
print(line)
nn.init.xavier_uniform_(line.weight)
進行訓練
# 將資料轉化為tensor,並移動到cpu或cuda上
train_features = torch.tensor(train_data.values, dtype=torch.float32, device=config['device'])
train_num = train_features.shape[0]
train_labels = torch.tensor(train_target.values, dtype=torch.float32, device=config['device'])
validation_features = torch.tensor(validation_data.values, dtype=torch.float32, device=config['device'])
validation_num = validation_features.shape[0]
validation_labels = torch.tensor(validation_target.values, dtype=torch.float32, device=config['device'])
# 定義損失函式和最佳化器
criterion = nn.MSELoss()
criterion.to(config['device'])
optimizer = optim.Adam(model.parameters(), lr=config['learning_rate'])
損失函式
神經網路效能的“惡劣程度”的指標
均方誤差(mean squared error)
-
yk是表示神經網路的輸出,tk表示監督資料,k表示資料的維數。
-
t採用one-hot表示:將正確解標籤表示為1,其他標籤表示為0
-
實現
def mean_squared_error(y, t): return 0.5 * np.sum((y-t)**2)
交叉熵誤差(cross entropy error)
-
yk是神經網路的輸出,tk是正確解標籤。
-
t使用one-hot表示時,實際上只計算
\[E = -log y_k \] -
one-hot時的程式碼
def cross_entropy_error(y, t): delta = 1e-7 return -np.sum(t * np.log(y + delta))
微小值delta:當出現np.log(0)時,np.log(0)會變為負無限大的-inf,這樣一來就會導致後續計算無法進行。作為保護性對策,新增一個微小值可以防止負無限大的發生。
# 開始訓練
mae_list = []
for epoch in range(config['epoch']):
losses = []
# 將模型設定為訓練模式。在訓練模式下,如 Batch Normalization 和 Dropout 等層的行為會有所不同。
model.train()
# mini-batch 迴圈:
for i in range(0, train_num, config['batch_size']):
end = i + config['batch_size']
if i + config['batch_size'] > train_num-1:
end = train_num-1
mini_batch = train_features[i: end]
mini_batch_label = train_labels[i: end]
#使用模型對當前 mini-batch 進行前向傳播,得到預測值。
pred = model(mini_batch)
# 移除預測張量中的大小為 1 的維度。計算損失
pred = pred.squeeze()
loss = criterion(pred, mini_batch_label)
# 檢查損失是否為 NaN,如果是,則停止當前 epoch 的訓練。
if torch.isnan(loss):
break
# 計算平均絕對誤差(Mean Absolute Error, MAE),然後將結果新增到 losses 列表中。
mae = torch.abs(mini_batch_label-pred).sum()/(end-i)
losses.append(mae.item())
# 清零最佳化器的梯度快取。
optimizer.zero_grad()
# 計算損失相對於模型引數的梯度。
loss.backward()
# 根據計算出的梯度更新模型引數。
optimizer.step()
# 將模型設定為評估模式,在評估模式下,Batch Normalization 和 Dropout 等層的行為會有所不同。
model.eval()
# 使用模型對驗證集進行前向傳播,得到預測值。
pred = model(validation_features)
validation_mae = torch.abs(validation_labels-pred.squeeze()).sum().item()/validation_num
mae_list.append((sum(losses)/len(losses), validation_mae))
print(f"epoch:{epoch + 1} MAE: {sum(losses)/len(losses)}, Validation_MAE: {validation_mae}")
torch.save(model, 'model.pth')
整理並儲存結果
test_data = test_data.drop(columns=['SaleID','price'])
test_data = torch.tensor(test_data.values, dtype=torch.float32,device='c
data = pd.read_csv('./one_hot_testB.csv')
data.drop(columns='price')
res = pd.concat([data.SaleID, price], axis=1)uda')
price = pd.DataFrame(pred.detach().cpu().numpy(), columns=['price'])
res.to_csv('submission.csv')