当前位置: 首页 > article >正文

【鼠鼠学AI代码合集#8】线性神经网络

深度学习中的矢量化加速

在深度学习模型的训练过程中,处理大量数据的效率对模型性能至关重要。为此,我们通常希望能够在一次操作中处理整个小批量的样本,而不是逐一处理每个样本。这种优化方式称为矢量化。通过矢量化,我们可以充分利用底层硬件的并行处理能力,从而极大地加速计算过程。相较于传统的逐元素计算,矢量化能够减少开销,并使代码更加简洁。

为什么矢量化如此重要?

在Python中使用for循环进行逐元素操作虽然直观,但效率往往不高,尤其是在处理大规模数据时。Python本身是解释型语言,每次执行一条指令时,Python解释器会带来较大的开销。如果我们能利用底层的线性代数库(例如深度学习框架的内置库),就可以将这些高开销的操作批量化,从而极大提高运行效率。

通过示例对比矢量化与逐元素操作

为了说明矢量化的优势,我们通过一个简单的向量相加示例进行对比。我们将在两个全为1的10000维向量上进行逐元素相加,并分别采用for循环和矢量化两种方式来计算。

首先,我们引入所需的库,并定义一个简单的计时器类来测量不同方法的运行时间。

import time
import torch
import numpy as np

# 计时器类
class Timer:
    """记录多次运行时间"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """启动计时器"""
        self.tik = time.time()

    def stop(self):
        """停止计时器并将时间记录在列表中"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """返回平均时间"""
        return sum(self.times) / len(self.times)

    def sum(self):
        """返回时间总和"""
        return sum(self.times)

    def cumsum(self):
        """返回累计时间"""
        return np.array(self.times).cumsum().tolist()

接下来,我们定义两个大小为10000的全为1的张量向量,并分别通过for循环和矢量化进行相加计算。

n = 10000
a = torch.ones(n)
b = torch.ones(n)
方法一:使用for循环逐元素相加
c = torch.zeros(n)
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
print(f'For循环执行时间:{timer.stop():.5f} 秒')
方法二:使用矢量化操作
timer.start()
d = a + b
print(f'矢量化操作执行时间:{timer.stop():.5f} 秒')

性能对比

运行结果表明,矢量化操作比for循环快得多:

  • For循环执行时间:0.16749 秒
  • 矢量化执行时间:0.00042 秒

通过这组对比,可以看到矢量化代码的速度比for循环快了两个数量级。原因在于for循环只能逐元素进行加法操作,而矢量化操作则可以一次性批量处理整个向量的加法。深度学习框架(如PyTorch、MXNet、TensorFlow等)背后的线性代数库高度优化,通常会利用CPU或GPU的并行计算能力,这使得矢量化操作具有显著的速度优势。

矢量化的其他优势

  1. 并行计算:矢量化操作允许在现代CPU和GPU上进行高效的并行计算。与之相比,for循环只能串行处理元素。
  2. 减少解释器开销:for循环每次迭代都需要调用Python解释器,而矢量化操作将大量的计算交给底层优化的库完成,减少了解释器的频繁调用,进而提高效率。
  3. 简洁易读的代码:矢量化不仅加速了计算过程,还使代码更加简洁。复杂的循环逻辑可以被更简单的张量运算替代,代码可维护性和可读性均有所提升。
  4. 减少出错风险:矢量化代码减少了手动编写循环和逐元素操作的需求,从而降低了编写和调试代码时出错的几率。

总结

矢量化是一种通过批量处理数据来加速计算的技术,尤其在深度学习模型训练中至关重要。相比于逐元素的for循环,矢量化可以充分利用硬件的并行能力,大幅提高计算效率。在深度学习框架中(如PyTorch、MXNet、TensorFlow等),矢量化操作已经被广泛应用,以确保训练和推理的速度能够满足大规模数据的需求。

为了在日常开发中提升代码性能,务必养成使用矢量化操作的习惯。无论是矩阵运算、向量操作还是批量数据处理,矢量化都能带来显著的速度优势和代码质量提升。

参考代码

import time
import torch
import numpy as np

class Timer:
    """记录多次运行时间"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """启动计时器"""
        self.tik = time.time()

    def stop(self):
        """停止计时器并将时间记录在列表中"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

n = 10000
a = torch.ones(n)
b = torch.ones(n)

# For循环实现逐元素加法
c = torch.zeros(n)
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
print(f'For循环执行时间:{timer.stop():.5f} 秒')

# 矢量化加法
timer.start()
d = a + b
print(f'矢量化操作执行时间:{timer.stop():.5f} 秒')

通过这一示例,希望您能更加直观地理解矢量化带来的巨大加速效果,并在实践中应用这一技术提升深度学习模型的训练效率。

正态分布

import math
import numpy as np
import matplotlib.pyplot as plt

def normal(x, mu, sigma):
    p = 1 / math.sqrt(2 * math.pi * sigma**2)
    return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)


x = np.arange(-7, 7, 0.01)
params = [(0, 1), (0, 2), (3, 1)]

for mu, sigma in params:
    plt.plot(x, normal(x, mu, sigma), label=f'mean {mu}, std {sigma}')

plt.xlabel('x')
plt.ylabel('p(x)')
plt.legend()
plt.show()

线性回归的从零开始实现

引言

线性回归是一种基础而重要的机器学习算法,通过建立特征与目标变量之间的线性关系进行预测。尽管现代深度学习框架可以简化实现过程,从零开始构建线性回归模型有助于深入理解其工作原理。

1. 生成数据集

为了测试我们的线性回归模型,我们首先需要生成一个带噪声的线性数据集。以下是生成数据集的函数:

import torch

def synthetic_data(w, b, num_examples):
    """生成y=Xw+b+噪声
    w: 模型的权重参数
    b: 模型的偏置
    num_examples: 生成的样本数量
    """
    # 生成特征矩阵X,服从标准正态分布
    X = torch.normal(0, 1, (num_examples, len(w)))
    # 计算标签y,添加线性噪声
    y = torch.matmul(X, w) + b  # 计算线性关系
    y += torch.normal(0, 0.01, y.shape)  # 添加噪声
    return X, y.reshape((-1, 1))  # 返回特征和标签

我们设置真实的权重和偏置,并生成1000个样本:

true_w = torch.tensor([2, -3.4])  # 真实权重
true_b = 4.2  # 真实偏置
features, labels = synthetic_data(true_w, true_b, 1000)  # 生成数据集

2. 可视化数据集

为了直观观察特征与标签之间的关系,我们可以绘制散点图:

import matplotlib.pyplot as plt

plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), s=1)
plt.xlabel('Feature 1')  # X轴标签
plt.ylabel('Label')  # Y轴标签
plt.title('Scatter plot of Features vs Labels')  # 图表标题
plt.show()  # 显示图表

3. 读取数据集

训练模型时,我们需要以小批量的方式读取数据。以下是实现这一功能的 data_iter 函数:

import random

def data_iter(batch_size, features, labels):
    """生成小批量数据
    batch_size: 批量大小
    features: 特征矩阵
    labels: 标签向量
    """
    num_examples = len(features)  # 获取样本数量
    indices = list(range(num_examples))  # 创建索引列表
    random.shuffle(indices)  # 随机打乱索引顺序
    for i in range(0, num_examples, batch_size):
        # 生成当前批量的索引
        batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]  # 返回当前批量的特征和标签

4. 初始化模型参数

接下来,我们需要初始化模型的参数。权重从均值为0、标准差为0.01的正态分布中随机初始化,而偏置初始化为0:

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)  # 权重参数
b = torch.zeros(1, requires_grad=True)  # 偏置参数

5. 定义模型

线性回归模型的输出由输入特征与权重的矩阵乘法后加上偏置计算而来:

def linreg(X, w, b):
    """线性回归模型
    X: 输入特征
    w: 权重参数
    b: 偏置参数
    """
    return torch.matmul(X, w) + b  # 返回预测结果

6. 定义损失函数

我们使用均方损失函数来评估模型的预测能力:

def squared_loss(y_hat, y):
    """均方损失
    y_hat: 预测值
    y: 真实值
    """
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2  # 计算损失

7. 定义优化算法

我们实现小批量随机梯度下降(SGD)来更新模型参数:

def sgd(params, lr, batch_size):
    """小批量随机梯度下降
    params: 要更新的参数列表
    lr: 学习率
    batch_size: 批量大小
    """
    with torch.no_grad():  # 不需要计算梯度
        for param in params:
            param -= lr * param.grad / batch_size  # 更新参数
            param.grad.zero_()  # 清除梯度

8. 训练模型

最后,我们将所有组件结合起来,进行模型训练。以下是主要的训练循环:

lr = 0.03  # 学习率
num_epochs = 3  # 训练轮数
batch_size = 10  # 设置批量大小
net = linreg  # 模型
loss = squared_loss  # 损失函数

for epoch in range(num_epochs):  # 训练每一轮
    for X, y in data_iter(batch_size, features, labels):  # 读取小批量数据
        l = loss(net(X, w, b), y)  # 计算当前批量的损失
        l.sum().backward()  # 反向传播计算梯度
        sgd([w, b], lr, batch_size)  # 更新模型参数
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)  # 计算整个数据集的损失
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')  # 打印损失

9. 评估结果

通过比较真实参数与训练得到的参数,我们可以评估训练的效果:

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')  # 打印权重误差
print(f'b的估计误差: {true_b - b}')  # 打印偏置误差

完整代码

import torch
import matplotlib.pyplot as plt

def synthetic_data(w, b, num_examples):
    """生成y=Xw+b+噪声
    w: 模型的权重参数
    b: 模型的偏置
    num_examples: 生成的样本数量
    """
    # 生成特征矩阵X,服从标准正态分布
    X = torch.normal(0, 1, (num_examples, len(w)))
    # 计算标签y,添加线性噪声
    y = torch.matmul(X, w) + b  # 计算线性关系
    y += torch.normal(0, 0.01, y.shape)  # 添加噪声
    return X, y.reshape((-1, 1))  # 返回特征和标签

true_w = torch.tensor([2, -3.4])  # 真实权重
true_b = 4.2  # 真实偏置
features, labels = synthetic_data(true_w, true_b, 1000)  # 生成数据集

plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), s=1)
plt.xlabel('Feature 1')  # X轴标签
plt.ylabel('Label')  # Y轴标签
plt.title('Scatter plot of Features vs Labels')  # 图表标题
plt.show()  # 显示图表


import random

def data_iter(batch_size, features, labels):
    """生成小批量数据
    batch_size: 批量大小
    features: 特征矩阵
    labels: 标签向量
    """
    num_examples = len(features)  # 获取样本数量
    indices = list(range(num_examples))  # 创建索引列表
    random.shuffle(indices)  # 随机打乱索引顺序
    for i in range(0, num_examples, batch_size):
        # 生成当前批量的索引
        batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]  # 返回当前批量的特征和标签

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)  # 权重参数
b = torch.zeros(1, requires_grad=True)  # 偏置参数


def linreg(X, w, b):
    """线性回归模型
    X: 输入特征
    w: 权重参数
    b: 偏置参数
    """
    return torch.matmul(X, w) + b  # 返回预测结果


def squared_loss(y_hat, y):
    """均方损失
    y_hat: 预测值
    y: 真实值
    """
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2  # 计算损失

def sgd(params, lr, batch_size):
    """小批量随机梯度下降
    params: 要更新的参数列表
    lr: 学习率
    batch_size: 批量大小
    """
    with torch.no_grad():  # 不需要计算梯度
        for param in params:
            param -= lr * param.grad / batch_size  # 更新参数
            param.grad.zero_()  # 清除梯度



lr = 0.03  # 学习率
num_epochs = 3  # 训练轮数
batch_size = 10  # 设置批量大小
net = linreg  # 模型
loss = squared_loss  # 损失函数

for epoch in range(num_epochs):  # 训练每一轮
    for X, y in data_iter(batch_size, features, labels):  # 读取小批量数据
        l = loss(net(X, w, b), y)  # 计算当前批量的损失
        l.sum().backward()  # 反向传播计算梯度
        sgd([w, b], lr, batch_size)  # 更新模型参数
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)  # 计算整个数据集的损失
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')  # 打印损失


print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')  # 打印权重误差
print(f'b的估计误差: {true_b - b}')  # 打印偏置误差





http://www.kler.cn/news/362567.html

相关文章:

  • 哥德巴赫猜想渐行渐远
  • pytorch dataloader学习
  • 小程序短链接生成教程
  • 【C++打怪之路Lv11】-- stack、queue和优先级队列
  • word中的内容旋转90度
  • 【Flutter】基础入门:自定义Widget
  • Vue封装组件并发布到npm仓库
  • 【ROS2】Qt和ROS混合编程:多继承QObject和rclcpp::Node
  • LRU算法
  • ATmega128定时器里面的定时器和外部中断配置
  • ElasticSearch基本概念
  • 微软主动出击,“钓”出网络钓鱼者
  • 关于在ubuntu服务器上无法守护长链接命令的问题
  • 自动化数据库管理:如何通过存储过程动态创建 MySQL 对象
  • Python中的字符串修剪:strip()、lstrip() 和 rstrip()
  • 1U服务器和Hyper-V虚拟机使用记录
  • [Linux网络编程]06-I/O多路复用策略---select,poll分析解释,优缺点,实现IO多路复用服务器
  • 设计模式基础知识以及典型设计模式总结(上)
  • Spring Boot驱动的汽车销售网站架构优化
  • ansible playbooks
  • 关于WPF项目降低.Net版本
  • Unity性能优化2【脚本篇】
  • 电脑改ip地址怎么弄?一键操作与多种方法详解
  • 存储过程(SQL)
  • (3) c++基本代码
  • CF-Loss:用于视网膜多分类血管分割和血管特征测量的临床相关特征优化损失函数|文献速递-基于生成模型的数据增强与疾病监测应用