多层感知机------pytorch与paddle实现多层感知机
本文将深入探讨多层感知机的理论基础,并通过PyTorch和PaddlePaddle两个深度学习框架来展示如何实现多层感知机模型。我们将首先介绍多层感知机、优化的基本概念,这些数学工具是理解和实现多层感知机的基础。通过PyTorch和PaddlePaddle的代码示例,我们将展示如何设计、训练和评估一个多层感知机模型,从而让读者能够直观地理解并掌握这两种框架在机器学习问题中的应用。
本文部分为torch框架以及部分理论分析,paddle框架对应代码可见多层感知机paddle
python
import torch
print("pytorch version:",torch.__version__)
pytorch version: 2.3.0+cu118
多层感知机(MLP,也称为神经网络)与线性模型相比,具有以下几个显著的优势:
-
非线性建模能力:线性模型,如线性回归或逻辑回归,仅能够学习输入和输出之间的线性关系。然而,在现实世界中,许多问题和数据的关系是非线性的。多层感知机通过引入激活函数(如Sigmoid、ReLU等),能够在神经元之间创建非线性关系,从而能够捕捉和模拟更复杂的非线性模式。
-
强大的表征学习能力:多层感知机通过多层网络结构,能够学习到输入数据的层次化特征表示。每一层都可以被视为对输入数据进行的一种非线性变换,通过逐层传递,网络可以逐渐抽取出更高级、更抽象的特征,这有助于模型处理复杂的任务。
-
自动特征提取:在传统的机器学习模型中,特征工程是一个重要的步骤,需要人工设计和选择特征。然而,多层感知机具有自动学习和提取有用特征的能力。通过训练,网络可以自动发现数据中的重要特征,并据此进行预测和分类,从而减少了特征工程的依赖。
-
强大的泛化能力:由于多层感知机能够学习到数据的复杂非线性关系,并且具有自动特征提取的能力,因此它通常具有很好的泛化性能。这意味着训练好的模型能够较好地处理未见过的数据,这是机器学习模型的重要性能之一。
-
适应性强:多层感知机可以处理各种类型的数据,包括图像、文本、音频等。通过调整网络结构和参数,可以灵活地适应不同的学习任务和数据集。
-
持续优化和改进:多层感知机可以通过不同的优化算法(如梯度下降法)进行训练和调整,以不断改进模型的性能。此外,随着深度学习技术的不断发展,多层感知机的结构和训练方法也在不断优化和改进,使其在各种任务中取得更好的性能。
多层感知机(MLP)原理
多层感知机(Multilayer Perceptron, MLP)是一种前馈神经网络,由输入层、一个或多个隐藏层和输出层组成。每一层由若干个神经元构成,每个神经元执行线性变换和非线性激活。
网络结构
设:
- 输入向量为 x = [ x 1 , x 2 , ... , x n ] T \mathbf{x} = [x_1, x_2, \ldots, x_n]^T x=[x1,x2,...,xn]T
- 权重矩阵为 W ( l ) \mathbf{W}^{(l)} W(l) 和偏置向量为 b ( l ) \mathbf{b}^{(l)} b(l)
- 激活函数为 ϕ ( ⋅ ) \phi(\cdot) ϕ(⋅)
第 l l l 层的输出 h ( l ) \mathbf{h}^{(l)} h(l) 可以表示为:
h ( l ) = ϕ ( W ( l ) h ( l − 1 ) + b ( l ) ) \mathbf{h}^{(l)} = \phi(\mathbf{W}^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)}) h(l)=ϕ(W(l)h(l−1)+b(l))
其中, h ( 0 ) = x \mathbf{h}^{(0)} = \mathbf{x} h(0)=x 表示输入层的输出。
每一层的计算过程包括线性变换和非线性变换:
- 线性变换 :
a ( l ) = W ( l ) h ( l − 1 ) + b ( l ) \mathbf{a}^{(l)} = \mathbf{W}^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)} a(l)=W(l)h(l−1)+b(l) - 非线性变换(激活函数) :
h ( l ) = ϕ ( a ( l ) ) \mathbf{h}^{(l)} = \phi(\mathbf{a}^{(l)}) h(l)=ϕ(a(l))
前向传播
前向传播是指从输入层到输出层的计算过程。通过前向传播可以得到网络的输出 y ^ \hat{\mathbf{y}} y^。
对于一个三层的网络(输入层、一个隐藏层、输出层),前向传播的计算过程如下:
-
输入层到隐藏层:
a ( 1 ) = W ( 1 ) x + b ( 1 ) \mathbf{a}^{(1)} = \mathbf{W}^{(1)} \mathbf{x} + \mathbf{b}^{(1)} a(1)=W(1)x+b(1)
h ( 1 ) = ϕ ( a ( 1 ) ) \mathbf{h}^{(1)} = \phi(\mathbf{a}^{(1)}) h(1)=ϕ(a(1)) -
隐藏层到输出层:
a ( 2 ) = W ( 2 ) h ( 1 ) + b ( 2 ) \mathbf{a}^{(2)} = \mathbf{W}^{(2)} \mathbf{h}^{(1)} + \mathbf{b}^{(2)} a(2)=W(2)h(1)+b(2)
y ^ = ϕ ( a ( 2 ) ) \hat{\mathbf{y}} = \phi(\mathbf{a}^{(2)}) y^=ϕ(a(2))
损失函数
损失函数 L ( y , y ^ ) L(\mathbf{y}, \hat{\mathbf{y}}) L(y,y^) 用于衡量预测值 y ^ \hat{\mathbf{y}} y^ 和目标值 y \mathbf{y} y 之间的差异。常用的损失函数有均方误差和交叉熵损失。
对于均方误差:
L ( y , y ^ ) = 1 2 ∥ y − y ^ ∥ 2 L(\mathbf{y}, \hat{\mathbf{y}}) = \frac{1}{2} \|\mathbf{y} - \hat{\mathbf{y}}\|^2 L(y,y^)=21∥y−y^∥2
反向传播
反向传播(Backpropagation)是通过计算损失函数相对于各层参数的梯度,从而更新网络参数以最小化损失函数的过程。
反向传播的关键步骤如下:
-
计算输出层的误差 :
δ ( L ) = ∂ L ∂ a ( L ) = ( y ^ − y ) ⊙ ϕ ′ ( a ( L ) ) \delta^{(L)} = \frac{\partial L}{\partial \mathbf{a}^{(L)}} = (\hat{\mathbf{y}} - \mathbf{y}) \odot \phi'(\mathbf{a}^{(L)}) δ(L)=∂a(L)∂L=(y^−y)⊙ϕ′(a(L)) -
计算隐藏层的误差 :
δ ( l ) = ( W ( l + 1 ) ) T δ ( l + 1 ) ⊙ ϕ ′ ( a ( l ) ) \delta^{(l)} = (\mathbf{W}^{(l+1)})^T \delta^{(l+1)} \odot \phi'(\mathbf{a}^{(l)}) δ(l)=(W(l+1))Tδ(l+1)⊙ϕ′(a(l))其中, ⊙ \odot ⊙ 表示元素逐个相乘, ϕ ′ ( a ( l ) ) \phi'(\mathbf{a}^{(l)}) ϕ′(a(l)) 是激活函数的导数。
-
计算梯度 :
∂ L ∂ W ( l ) = δ ( l ) ( h ( l − 1 ) ) T \frac{\partial L}{\partial \mathbf{W}^{(l)}} = \delta^{(l)} (\mathbf{h}^{(l-1)})^T ∂W(l)∂L=δ(l)(h(l−1))T
∂ L ∂ b ( l ) = δ ( l ) \frac{\partial L}{\partial \mathbf{b}^{(l)}} = \delta^{(l)} ∂b(l)∂L=δ(l) -
更新权重 :
使用梯度下降法,学习率为 η \eta η:
W ( l ) ← W ( l ) − η ∂ L ∂ W ( l ) \mathbf{W}^{(l)} \leftarrow \mathbf{W}^{(l)} - \eta \frac{\partial L}{\partial \mathbf{W}^{(l)}} W(l)←W(l)−η∂W(l)∂L
b ( l ) ← b ( l ) − η ∂ L ∂ b ( l ) \mathbf{b}^{(l)} \leftarrow \mathbf{b}^{(l)} - \eta \frac{\partial L}{\partial \mathbf{b}^{(l)}} b(l)←b(l)−η∂b(l)∂L
通过反复进行以上步骤,网络的参数会逐渐调整,以最小化损失函数,从而提高模型的预测准确性。
激活函数
激活函数在神经网络中起着至关重要的作用,它们决定了神经网络的非线性特性和表达能力。注意,激活函数不会改变输入输出的形状,它只对每一个元素进行运算。以下是激活函数的主要作用和用途:
1. 引入非线性
神经网络的核心计算包括线性变换(矩阵乘法和加法)和非线性变换(激活函数)。如果没有激活函数,整个网络就只是线性变换的叠加,无论有多少层,最终也只是输入的线性变换,无法处理复杂的非线性问题。
通过引入非线性激活函数,神经网络能够逼近任意复杂的函数,从而具有更强的表达能力。
2. 提供特征转换
激活函数可以将线性变换的输出映射到不同的特征空间,从而使得神经网络能够捕捉输入数据的复杂特征。每一层的激活函数都对输入进行某种形式的特征转换,使得后续层能够更好地学习和提取特征。
3. 保持梯度流
在反向传播过程中,激活函数的选择会影响梯度的传播。如果激活函数的导数为0,梯度将无法传播,导致网络无法训练。适当的激活函数可以避免梯度消失和梯度爆炸问题,使得梯度能够顺利传播。
常用的激活函数
- Sigmoid 函数
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1- 优点:输出范围在 (0, 1) 之间,便于处理概率问题。
- 缺点:容易导致梯度消失问题,特别是在深层网络中。
python
# 测试sigmoid函数
x_input = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) # 输入
y_output = torch.sigmoid(x_input) # 输出
import matplotlib.pyplot as plt
plt.plot(x_input.detach().numpy(), y_output.detach().numpy()) # 绘制图像
- Tanh 函数
tanh ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+e−xex−e−x- 优点:输出范围在 (-1, 1) 之间,相对于 Sigmoid 函数,梯度消失问题较少。
- 缺点:仍然可能出现梯度消失问题。
python
# 测试Tanh函数
x_input = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) # 输入
y_output = torch.tanh(x_input) # 输出
plt.plot(x_input.detach().numpy(), y_output.detach().numpy()) # 绘制图像
- ReLU 函数
ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)- 优点:计算简单,高效,能够缓解梯度消失问题。
- 缺点:在训练过程中,部分神经元可能会"死亡"(即长时间输出为0),导致梯度无法更新。
python
# 测试ReLU函数
x_input = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) # 输入
y_output = torch.relu(x_input) # 输出
plt.plot(x_input.detach().numpy(), y_output.detach().numpy()) # 绘制图像
- Leaky ReLU 函数
Leaky ReLU ( x ) = { x if x ≥ 0 α x if x < 0 \text{Leaky ReLU}(x) = \begin{cases} x & \text{if } x \geq 0 \\ \alpha x & \text{if } x < 0 \end{cases} Leaky ReLU(x)={xαxif x≥0if x<0- 优点:解决 ReLU 函数的神经元"死亡"问题。
- 缺点 :引入了一个需要调节的参数 α \alpha α。
python
# 测试Leaky ReLU函数
x_input = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) # 输入
y_output = torch.nn.functional.leaky_relu(x_input, negative_slope=0.01) # 输出
plt.plot(x_input.detach().numpy(), y_output.detach().numpy()) # 绘制图像
- Softmax 函数
Softmax ( x i ) = e x i ∑ j e x j \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}} Softmax(xi)=∑jexjexi- 优点:常用于分类问题的输出层,将输入映射为概率分布。
- 缺点:计算开销较大,容易出现数值不稳定问题。
python
# 测试Softmax函数
x_input = torch.randn(3, requires_grad=True) # 输入
y_output = torch.nn.functional.softmax(x_input, dim=0) # 输出
x_input, y_output
(tensor([ 0.5489, -1.2444, 1.6401], requires_grad=True),
tensor([0.2413, 0.0402, 0.7186], grad_fn=<SoftmaxBackward0>))
激活函数的选择
- 隐藏层:通常选择 ReLU 或其变种(如 Leaky ReLU、Parametric ReLU)作为隐藏层的激活函数,因为它们能有效缓解梯度消失问题。
- 输出层 :根据具体任务选择合适的激活函数。
- 分类问题:使用 softmax 函数将输出映射为概率分布。
- 回归问题:使用线性函数或没有激活函数。
- 二分类问题:使用 sigmoid 函数。
手动实现多层感知机
接下来,我们将手动设计一个多层感知机模型,并实现前向传播和反向传播算法。我们利用面向对象编程的方法,结合深度学习库进行设计。
python
# 手动实现一个三层感知机模型,并实现前向传播和反向传播算法
class Perceptron(torch.nn.Module):
def __init__(self, input_size, output_size, hidden_size=10):
super(Perceptron, self).__init__()
self.W1 = torch.nn.Parameter(torch.randn(input_size, hidden_size))
self.b1 = torch.nn.Parameter(torch.randn(hidden_size))
self.W2 = torch.nn.Parameter(torch.randn(hidden_size, output_size))
self.b2 = torch.nn.Parameter(torch.randn(output_size))
def forward(self, x):
# 前向传播
x = torch.matmul(x, self.W1) + self.b1
x = torch.relu(x) # 激活函数
x = torch.matmul(x, self.W2) + self.b2
return x
接下来让我们测试一下该模型的输入输出
python
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 选择设备
x_input = torch.randn(1, 10).to(device)
model = Perceptron(input_size=10, output_size=1).to(device) # 实例化模型
y_output = model(x_input) # 前向传播
x_input, y_output, x_input.shape, y_output.shape
(tensor([[-2.0067, -0.1632, -0.4871, -1.9595, 0.1449, 0.4248, 0.1240, -2.3717,
-0.5491, 0.0733]], device='cuda:0'),
tensor([[-0.2158]], device='cuda:0', grad_fn=<AddBackward0>),
torch.Size([1, 10]),
torch.Size([1, 1]))
接下来,我们导入一个California housing数据,用于训练测试多层感知机
python
from sklearn.model_selection import train_test_split
from sklearn import datasets
# 加载California housing数据集
California = datasets.fetch_california_housing()
X = torch.tensor(California.data, dtype=torch.float32)
y = torch.tensor(California.target, dtype=torch.float32)
python
# 将数据放置在迭代器里边
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, features, labels):
self.features = features
self.labels = labels
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
return self.features[idx], self.labels[idx]
def create_data_loaders(features, labels, batch_size=32, test_size=0.2, random_state=42):
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=test_size, random_state=random_state)
# 创建Dataset对象
train_dataset = CustomDataset(X_train, y_train)
test_dataset = CustomDataset(X_test, y_test)
# 创建DataLoader对象
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
return train_loader, test_loader
train_loader, test_loader = create_data_loaders(X, y, batch_size=64)
python
# 进行训练
model = Perceptron(input_size=8, output_size=1).to(device) # 实例化模型
criterion = torch.nn.MSELoss() # 定义损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 定义优化器
num_epochs = 100 # 定义训练轮数
for epoch in range(num_epochs):
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device) # 将输入和标签都转为GPU上的张量
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels.view(-1, 1)) # 计算损失
# 反向传播和优化
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 更新权重
if (epoch+1) % 10 == 0: # 每10轮输出一次损失
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# 进行测试
model.eval() # 设置模型为评估模式
with torch.no_grad(): # 关闭梯度计算
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
# 计算损失
loss = criterion(outputs, labels.view(-1, 1))
# 输出损失
print(f'Test Loss: {loss.item():.4f}')
break
Epoch [100/100], Loss: 0.7461
Test Loss: 1.9505
从上述过程中可以看到损失在不断减小,这证明模型在不断优化。然而观察X数据不难发现,X各维度之间的数值范围差异较大,这可能会导致模型在训练过程中收敛速度过慢。因此,我们可以对数据进行预处理,将数据缩放到一个较小的范围内。
python
import numpy as np
class Preprocessor:
def __init__(self):
self.min_values = None
self.scale_factors = None
def normalize(self, data):
"""
对输入数据进行归一化处理。
data: numpy数组或类似结构,其中每一列是一个特征。
"""
# 确保输入是numpy数组
data = np.asarray(data)
# 检查是否已经拟合过数据,如果没有,则先拟合
if self.min_values is None or self.scale_factors is None:
self.fit(data)
# 对数据进行归一化处理
normalized_data = (data - self.min_values) * self.scale_factors
return normalized_data
def denormalize(self, normalized_data):
"""
对归一化后的数据进行反归一化处理。
normalized_data: 已经归一化处理的数据。
"""
# 确保输入是numpy数组
normalized_data = np.asarray(normalized_data)
# 反归一化数据
original_data = normalized_data / self.scale_factors + self.min_values
return original_data
def fit(self, data):
"""
计算每个特征的最小值和比例因子,用于后续的归一化和反归一化。
data: numpy数组或类似结构,其中每一列是一个特征。
"""
# 确保输入是numpy数组
data = np.asarray(data)
# 计算每个特征(列)的最小值
self.min_values = np.min(data, axis=0)
# 计算每个特征(列)的比例因子
ranges = np.max(data, axis=0) - self.min_values
# 避免除以零错误,如果范围是零,则设置为1
self.scale_factors = np.where(ranges == 0, 1, 1.0 / ranges)
python
data_all = np.concatenate((X.numpy(), y.reshape(-1, 1).numpy()), axis=1)
# 这样,我们在data_all中,前8列是特征量,最后一列是目标变量
preprocessor = Preprocessor()
# 归一化
data_all_normalized = preprocessor.normalize(data_all)
train_loader, test_loader = create_data_loaders(data_all_normalized[:, :8], data_all_normalized[:, 8:], batch_size=256) # 划分数据集
再次进行训练
python
# 进行训练
model = Perceptron(input_size=8, output_size=1).to(device) # 实例化模型
criterion = torch.nn.MSELoss() # 定义损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 定义优化器
num_epochs = 100 # 定义训练轮数
for epoch in range(num_epochs):
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device) # 将输入和标签都转为GPU上的张量
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels.view(-1, 1)) # 计算损失
# 反向传播和优化
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 更新权重
if (epoch+1) % 10 == 0: # 每10轮输出一次损失
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
Epoch [100/100], Loss: 0.0198
python
# 在测试集上反归一化后计算损失值
model.eval() # 设置模型为评估模式
with torch.no_grad(): # 关闭梯度计算
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
# 反归一化
outputs = torch.cat([inputs, outputs], dim=1)
labels = torch.cat([inputs, labels], dim=1)
outputs = preprocessor.denormalize(outputs.cpu().numpy())
labels = preprocessor.denormalize(labels.cpu().numpy())
outputs = torch.tensor(outputs[:, 8:])
labels = torch.tensor(labels[:, 8:])
# 计算损失
loss = criterion(outputs, labels)
# 输出损失
print(f'Test Loss: {loss.item():.4f}')
break
Test Loss: 0.4513
可见,当进行数据归一化操作后,在测试集上计算损失值时,我们能够得到一个更准确的结果。
多层感知机的简洁实现
接下来,我们将使用深度学习库来实现一个多层感知机(MLP)。
python
class MLP(torch.nn.Module):
def __init__(self, input_size, output_size):
super(MLP, self).__init__()
self.fc1 = torch.nn.Linear(input_size, 64) # 第一个全连接层
self.relu = torch.nn.ReLU() # 激活函数
self.fc2 = torch.nn.Linear(64, 32) # 第二个全连接层
self.fc3 = torch.nn.Linear(32, output_size) # 输出层
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
out = self.relu(out)
out = self.fc3(out)
return out
python
# 进行训练
model = MLP(input_size=8, output_size=1).to(device) # 实例化模型
criterion = torch.nn.MSELoss() # 定义损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 定义优化器
num_epochs = 100 # 定义训练轮数
for epoch in range(num_epochs):
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device) # 将输入和标签都转为GPU上的张量
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels.view(-1, 1)) # 计算损失
# 反向传播和优化
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 更新权重
if (epoch+1) % 10 == 0: # 每10轮输出一次损失
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# 在测试集上反归一化后计算损失值
model.eval() # 设置模型为评估模式
with torch.no_grad(): # 关闭梯度计算
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
# 反归一化
outputs = torch.cat([inputs, outputs], dim=1)
labels = torch.cat([inputs, labels], dim=1)
outputs = preprocessor.denormalize(outputs.cpu().numpy())
labels = preprocessor.denormalize(labels.cpu().numpy())
outputs = torch.tensor(outputs[:, 8:])
labels = torch.tensor(labels[:, 8:])
# 计算损失
loss = criterion(outputs, labels)
# 输出损失
print(f'Test Loss: {loss.item():.4f}')
break
Epoch [100/100], Loss: 0.0092
Test Loss: 0.2960
可以看到,该模型在测试集上具有较好的精度。
如何查看模型中各个层的参数?
python
# 输出模型参数
for name, param in model.named_parameters():
print(name, param.size(), param)
fc1.weight torch.Size([64, 8]) Parameter containing:
tensor([[ 5.9963e-02, -1.2951e-01, 3.3004e-01, -6.4406e-01, -1.5640e-01,
2.2107e+00, -5.6948e-02, 2.5146e-01],
[-4.5391e-04, -9.9010e-03, -2.3830e-01, -6.3069e-01, -7.3112e-02,
2.0470e+00, -7.5371e-01, 3.7635e-01],
[ 3.1990e-01, 1.1859e-01, -3.7902e-01, 4.0402e-02, 1.3464e-01,
-2.5400e+00, -2.1360e-01, -1.8944e-01],
[-1.4829e-01, 2.2241e-02, -1.0245e-01, 8.3118e-02, -2.0893e-02,
-3.0669e-01, -1.6792e-01, 1.0734e-01],
[ 2.1901e-01, 2.6178e-01, -1.5173e-01, 3.1913e-01, 6.7116e-02,
-2.3314e+00, -3.6791e-01, -2.4033e-01],
[ 1.4052e-01, -2.5938e-01, -2.0852e-01, 1.4840e-01, -1.6884e-01,
-3.1774e-01, -3.2745e-01, 3.9544e-02],
[-1.7030e-01, -4.4217e-02, 9.1638e-02, 2.7099e-01, -4.2391e-01,
7.4819e-01, 5.3356e-01, -4.4860e-01],
[ 4.3131e-02, 2.1485e-01, 2.6122e-01, -7.4331e-01, -5.2609e-02,
2.1875e+00, 1.1119e-02, 3.0754e-01],
[ 2.6907e-01, -7.1278e-02, 3.2248e-01, -2.6660e-01, 2.5241e-01,
-2.7023e-02, 1.8220e-02, -9.8531e-02],
[-2.9577e-01, 8.4138e-02, -3.3766e-01, -2.9619e-01, 6.7604e-01,
9.6375e-02, 5.2879e-02, 8.2767e-02],
[ 4.0947e-01, 1.3636e-01, 1.4701e-01, -3.0416e-03, 2.1767e-01,
-4.0451e-01, -3.1511e-01, 2.9062e-01],
[-1.4752e-01, -1.0831e-01, 6.3059e-01, -4.3024e-01, 3.3149e-01,
2.5970e+00, 1.1379e-01, -2.2972e-02],
[ 5.5812e-02, 1.4190e-01, -7.1937e-02, 5.7625e-01, -8.5844e-03,
-2.9726e+00, 2.3468e-01, -2.3603e-01],
[-2.5956e-01, -6.1126e-02, 3.5483e-01, 4.5786e-02, 2.5733e-01,
2.8030e+00, 2.2291e-01, 4.3359e-01],
[ 2.0319e-02, -6.5945e-02, 5.9983e-01, 6.9049e-03, 3.8476e-02,
1.3101e+00, 5.7008e-01, -5.9376e-02],
[-1.7866e-01, 5.0750e-02, 1.9929e-01, 3.6623e-01, 2.1968e-01,
-2.8747e+00, -2.6265e-01, -1.6045e-01],
[-1.0732e-01, -6.8081e-03, 7.8717e-02, -7.2350e-01, 2.2509e-01,
1.9988e+00, -2.4664e-01, 3.4477e-02],
[ 2.7521e-01, 6.6210e-02, 2.0175e-01, 4.2885e-01, 2.1302e-01,
-3.7735e-01, -2.2829e-01, -5.8691e-01],
[-6.4057e-02, -1.4805e-01, -7.5222e-02, -2.3404e-01, -9.3495e-02,
6.3712e-01, 4.2683e-01, -1.9973e-01],
[ 1.1996e-01, -4.4584e-02, -1.9401e-01, 6.9470e-02, -5.2231e-02,
-2.8368e+00, 2.6145e-01, -2.2030e-01],
[-2.1307e-01, 8.9632e-02, 1.6201e-01, -6.3820e-01, 3.0604e-01,
2.7722e+00, -7.7116e-03, 4.3692e-01],
[-2.1850e-01, -5.3631e-02, -5.6718e-01, 3.7211e-01, 2.5336e-01,
-2.3765e+00, -1.3956e-01, 3.6741e-02],
[-1.6725e-01, -1.4787e-01, -3.6086e-01, 1.3463e-01, -1.7634e-01,
-5.0807e+00, -7.1087e-01, 1.4662e-01],
[ 4.1411e-01, -8.8909e-02, -2.4068e-01, -4.2563e-01, -4.8087e-01,
1.3794e+00, 2.6202e-01, 6.3577e-04],
[-1.1647e-01, 2.1374e-01, -1.0698e-01, 4.0261e-02, -4.8291e-02,
3.1532e+00, 4.3473e-01, 1.8457e-01],
[-3.6455e-02, -1.8041e-01, 3.5412e-02, -3.8085e-01, -3.1285e-01,
2.2650e+00, 2.3070e-01, 5.5429e-02],
[ 1.6819e-01, 3.5746e-01, -5.8832e-01, 6.0785e-01, 7.3570e-01,
-2.8588e+00, -3.5661e-01, 2.4908e-01],
[-2.8624e-01, 1.8556e-01, -4.0856e-02, -4.3665e-01, 4.9720e-01,
9.5392e-01, 5.8115e-02, -5.3322e-02],
[ 3.2759e-01, 1.1277e-01, 4.6923e-01, 1.6316e-01, 4.1933e-02,
-1.4664e-03, 4.7685e-01, -4.5640e-01],
[-8.9424e-02, 8.7827e-02, 6.1598e-01, -3.6580e-01, -3.0954e-01,
1.3709e+00, 1.7898e-01, 3.6116e-02],
[-2.7086e-02, 3.5023e-01, 1.5394e-01, 2.4118e-01, 5.7401e-01,
5.9230e-01, -4.9719e-02, -7.4549e-02],
[ 4.4577e-01, 1.0150e-01, -1.5237e-01, 3.3439e-01, 1.2389e-01,
-1.1036e+00, -2.3712e-01, 2.7857e-01],
[ 5.2767e-01, -3.2813e-02, 3.3722e-01, -9.9640e-02, 1.9462e-01,
4.7116e-01, 6.6307e-02, 1.1742e-01],
[-3.1703e-01, -5.8650e-02, 7.5725e-02, -3.4895e-01, -1.5825e-01,
1.5588e+00, 2.7813e-01, 3.0240e-01],
[-1.8139e-01, -1.0638e-01, 5.7428e-02, -7.6722e-02, -2.8519e-01,
-2.6657e+00, 5.5721e-01, -1.2076e-01],
[-9.8105e-02, 1.5827e-01, -2.2377e-01, 5.1781e-01, 3.1894e-01,
-1.5423e+00, 1.3837e-01, 5.6877e-02],
[ 9.7676e-02, 2.4911e-01, -2.3112e-01, 5.3457e-01, -1.9941e-01,
-3.8266e+00, -1.3983e-01, -1.9091e-02],
[-9.9744e-02, -1.0237e-01, -2.0786e-01, 1.3165e-01, 2.2671e-01,
-2.5914e-02, 2.8715e-01, 1.2580e-01],
[-1.2713e-01, 6.1162e-02, -2.6426e-01, 2.5603e-01, 4.9883e-02,
1.3036e-02, -2.5706e-01, -1.8542e-01],
[ 3.1691e-01, 4.1629e-02, -2.8150e-02, 2.6666e-01, 3.0893e-01,
-1.7161e+00, 7.9539e-02, -1.5481e-01],
[-3.3982e-01, -2.1779e-01, -4.4203e-01, 4.7306e-01, -8.6353e-02,
-2.3857e+00, -4.2481e-02, -1.4364e-01],
[ 5.5675e-02, 2.6535e-01, 2.8725e-01, 2.8819e-01, 1.4178e-01,
-1.3665e+00, 1.3446e-01, -3.2450e-01],
[-3.9652e-01, 2.6417e-01, 3.6779e-03, 4.2897e-01, 3.1731e-01,
-2.5695e+00, 1.1523e-01, -3.5958e-01],
[ 1.3601e-01, 2.9462e-02, -4.4992e-01, 4.0183e-01, -4.6031e-01,
-3.9781e+00, -3.6116e-01, 3.4088e-01],
[ 1.5606e-01, -2.6027e-01, 2.6326e-01, -2.0661e-01, -1.5855e-01,
7.7796e-02, -3.1296e-01, 1.8486e-01],
[-2.5821e-01, 1.0872e-02, 1.8136e-01, 4.6435e-01, 2.1279e-01,
-4.1825e+00, -1.1724e-01, -1.7379e-01],
[-1.0077e-01, 9.8824e-02, 1.7599e-01, 2.4945e-01, 2.3315e-01,
2.5714e-01, -2.4893e-01, -3.3822e-01],
[ 1.0923e-01, -1.5452e-01, 3.4690e-01, -1.5318e-01, 3.1165e-01,
-3.2168e-01, -3.3849e-01, -3.0056e-01],
[-1.3560e-01, -2.0219e-01, 8.6806e-02, -4.6732e-01, 1.1725e-01,
2.9789e+00, 2.6419e-01, 8.7402e-02],
[-3.0730e-01, 3.0410e-01, 4.1923e-02, -3.2088e-01, 1.8378e-01,
4.9170e-01, -6.3524e-01, -7.8987e-03],
[-2.5714e-01, -1.4192e-01, -4.9422e-02, 1.1391e-01, 3.3820e-01,
2.9824e+00, 1.6188e-01, 2.1308e-01],
[-2.0380e-01, 6.3577e-02, 1.2460e-01, 1.6975e-01, 9.7793e-02,
-1.5833e-01, 1.5209e-01, -3.1441e-01],
[-9.1623e-02, -1.7417e-01, -1.6255e-01, 2.7875e-01, 2.7782e-01,
-5.5260e-01, -1.8611e-01, -5.9402e-02],
[ 1.0699e-02, 1.1451e-01, -1.1893e-01, 1.0515e-01, -1.6081e-01,
-2.4413e-01, -3.3146e-01, -1.0497e-01],
[ 3.6821e-01, -3.1712e-01, 5.9105e-01, -4.2825e-01, 2.4143e-01,
-1.0427e+00, -1.4272e-01, 5.5061e-02],
[-1.6993e-01, 3.6370e-01, 3.7196e-01, -1.6263e-01, -1.2023e-01,
1.5227e+00, 2.6424e-01, 2.2263e-01],
[ 3.3328e-01, 1.8456e-01, -5.7902e-01, 7.7323e-01, 9.5315e-02,
-1.9005e+00, -3.3347e-01, -3.7411e-01],
[ 3.5044e-01, -2.5484e-02, 1.7411e-01, 2.2476e-01, 1.4351e-01,
-1.8346e+00, 1.2620e-01, 1.1369e-01],
[-1.1702e-02, -1.8642e-01, 9.1827e-02, -3.1908e-01, 3.8766e-02,
-2.4801e-01, -3.4863e-01, -2.7193e-01],
[ 2.9982e-01, -2.9155e-01, 4.4988e-01, 8.1985e-02, 8.4160e-02,
-1.8739e+00, -2.8928e-01, -1.8806e-01],
[-1.0200e-02, -2.1327e-03, -1.4754e-01, -4.8645e-02, -4.6562e-02,
1.7648e+00, 2.8605e-01, 1.5265e-01],
[ 3.7230e-03, -2.4316e-01, -4.8784e-02, -2.9336e-01, 3.8575e-02,
2.3470e+00, 3.2508e-01, -7.1018e-04],
[ 2.0979e-01, -2.1333e-01, 3.3617e-04, 6.5052e-01, 3.5065e-02,
-3.6263e+00, 4.0505e-02, 1.0837e-01],
[-9.9868e-02, 7.0659e-02, 5.3053e-01, -2.8916e-01, -2.9689e-01,
2.5725e+00, 2.9866e-01, -6.9361e-02]], device='cuda:0',
requires_grad=True)
fc1.bias torch.Size([64]) Parameter containing:
tensor([ 0.1158, -0.0710, 0.2583, -0.1872, 0.1605, -0.1956, -0.1404, -0.1737,
-0.3392, -0.1915, -0.0004, 0.1966, 0.1245, -0.0565, -0.0509, 0.2485,
0.1391, 0.1317, 0.1442, 0.1778, -0.0249, 0.3053, 0.2449, -0.0332,
-0.0931, 0.1427, -0.2241, 0.0169, -0.1114, 0.2325, -0.2992, -0.3723,
-0.2598, 0.2683, 0.0137, -0.0720, 0.1453, 0.0531, -0.0966, 0.0492,
0.3713, 0.1682, 0.3056, -0.0586, -0.2791, 0.3361, -0.1102, -0.2492,
0.1715, -0.2114, 0.0612, -0.3136, 0.3381, -0.1302, -0.0316, -0.3116,
0.1594, -0.1124, 0.1757, 0.3872, 0.0453, 0.2220, 0.1309, 0.0039],
device='cuda:0', requires_grad=True)
fc2.weight torch.Size([32, 64]) Parameter containing:
tensor([[-0.1063, -0.9803, -0.0205, ..., -0.1749, 0.1336, -0.2833],
[ 0.1824, 0.6983, -0.0118, ..., 0.1891, -0.0980, 0.2231],
[-0.0242, 0.1250, 0.0661, ..., -0.0287, 0.0658, -0.1014],
...,
[-0.0677, -0.8538, 0.1530, ..., -0.0380, 0.0761, -0.3277],
[ 0.0463, -0.5943, -0.0317, ..., 0.1262, 0.0482, -0.0372],
[-0.0383, -0.4868, 0.0477, ..., -0.0958, 0.1510, -0.0724]],
device='cuda:0', requires_grad=True)
fc2.bias torch.Size([32]) Parameter containing:
tensor([-0.1517, -0.0637, -0.0837, -0.0199, 0.0850, 0.0448, 0.0739, 0.0209,
0.0010, 0.0407, -0.1647, 0.0374, -0.0871, 0.0810, 0.0192, -0.2060,
0.0076, 0.1045, 0.0702, -0.0277, 0.0796, -0.1113, -0.0805, -0.1220,
0.0544, -0.0106, -0.0746, -0.1021, -0.0458, -0.0519, 0.0423, 0.1260],
device='cuda:0', requires_grad=True)
fc3.weight torch.Size([1, 32]) Parameter containing:
tensor([[-0.7225, -0.2847, -0.0815, -0.2057, 0.3262, 0.5847, -0.0273, 0.7122,
-0.1339, -0.2212, -0.6980, -0.0749, 0.0896, -0.2534, -0.1314, -0.1289,
0.6482, 0.1621, -0.1046, 0.5350, 0.2226, -0.0982, -0.1349, -0.0438,
0.6045, 0.1590, 0.7351, 0.1297, 0.0504, 0.5038, 0.1726, 0.2327]],
device='cuda:0', requires_grad=True)
fc3.bias torch.Size([1]) Parameter containing:
tensor([0.1875], device='cuda:0', requires_grad=True)