【深度学习笔记】05 线性回归

news2024/11/26 14:55:28

线性回归

线性回归基于几个简单的假设:
首先,假设自变量 x \mathbf{x} x和因变量 y y y之间的关系是线性的,
y y y可以表示为 x \mathbf{x} x中元素的加权和,这里通常允许包含观测值的一些噪声;
其次,我们假设任何噪声都比较正常,如噪声遵循正态分布。

为了解释线性回归,我们举一个实际的例子:
我们希望根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)。
为了开发一个能预测房价的模型,我们需要收集一个真实的数据集。
这个数据集包括了房屋的销售价格、面积和房龄。
在机器学习的术语中,该数据集称为训练数据集(training data set)
训练集(training set)。
每行数据(比如一次房屋交易相对应的数据)称为样本(sample),
也可以称为数据点(data point)或数据样本(data instance)。
我们把试图预测的目标(比如预测房屋价格)称为标签(label)或目标(target)。
预测所依据的自变量(面积和房龄)称为特征(feature)或协变量(covariate)。

通常,我们使用 n n n来表示数据集中的样本数。
对索引为 i i i的样本,其输入表示为 x ( i ) = [ x 1 ( i ) , x 2 ( i ) ] ⊤ \mathbf{x}^{(i)} = [x_1^{(i)}, x_2^{(i)}]^\top x(i)=[x1(i),x2(i)]
其对应的标签是 y ( i ) y^{(i)} y(i)

线性模型

线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下面的式子:

p r i c e = w a r e a ⋅ a r e a + w a g e ⋅ a g e + b . \mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b. price=wareaarea+wageage+b.
:eqlabel:eq_price-area

:eqref:eq_price-area中的 w a r e a w_{\mathrm{area}} warea w a g e w_{\mathrm{age}} wage
称为权重(weight),权重决定了每个特征对我们预测值的影响。
b b b称为偏置(bias)、偏移量(offset)或截距(intercept)。
偏置是指当所有特征都取值为0时,预测值应该为多少。
即使现实中不会有任何房子的面积是0或房龄正好是0年,我们仍然需要偏置项。
如果没有偏置项,我们模型的表达能力将受到限制。
严格来说, :eqref:eq_price-area是输入特征的一个
仿射变换(affine transformation)。
仿射变换的特点是通过加权和对特征进行线性变换(linear transformation),
并通过偏置项来进行平移(translation)。

给定一个数据集,我们的目标是寻找模型的权重 w \mathbf{w} w和偏置 b b b
使得根据模型做出的预测大体符合数据里的真实价格。
输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。

而在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。
当我们的输入包含 d d d个特征时,我们将预测结果 y ^ \hat{y} y^
(通常使用“尖角”符号表示 y y y的估计值)表示为:

y ^ = w 1 x 1 + . . . + w d x d + b . \hat{y} = w_1 x_1 + ... + w_d x_d + b. y^=w1x1+...+wdxd+b.

将所有特征放到向量 x ∈ R d \mathbf{x} \in \mathbb{R}^d xRd中,
并将所有权重放到向量 w ∈ R d \mathbf{w} \in \mathbb{R}^d wRd中,
我们可以用点积形式来简洁地表达模型:

y ^ = w ⊤ x + b . \hat{y} = \mathbf{w}^\top \mathbf{x} + b. y^=wx+b.
:eqlabel:eq_linreg-y

在 :eqref:eq_linreg-y中,
向量 x \mathbf{x} x对应于单个数据样本的特征。
用符号表示的矩阵 X ∈ R n × d \mathbf{X} \in \mathbb{R}^{n \times d} XRn×d
可以很方便地引用我们整个数据集的 n n n个样本。
其中, X \mathbf{X} X的每一行是一个样本,每一列是一种特征。

对于特征集合 X \mathbf{X} X,预测值 y ^ ∈ R n \hat{\mathbf{y}} \in \mathbb{R}^n y^Rn
可以通过矩阵-向量乘法表示为:

y ^ = X w + b {\hat{\mathbf{y}}} = \mathbf{X} \mathbf{w} + b y^=Xw+b

这个过程中的求和将使用广播机制。

解析解

线性回归刚好是一个很简单的优化问题。
与我们将在本书中所讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来,
这类解叫作解析解(analytical solution)。
首先,我们将偏置 b b b合并到参数 w \mathbf{w} w中,合并方法是在包含所有参数的矩阵中附加一列。
我们的预测问题是最小化 ∥ y − X w ∥ 2 \|\mathbf{y} - \mathbf{X}\mathbf{w}\|^2 yXw2
这在损失平面上只有一个临界点,这个临界点对应于整个区域的损失极小点。
将损失关于 w \mathbf{w} w的导数设为0,得到解析解:

w ∗ = ( X ⊤ X ) − 1 X ⊤ y . \mathbf{w}^* = (\mathbf X^\top \mathbf X)^{-1}\mathbf X^\top \mathbf{y}. w=(XX)1Xy.

像线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。
解析解可以进行很好的数学分析,但解析解对问题的限制很严格,导致它无法广泛应用在深度学习里。

随机梯度下降

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值)
关于模型参数的导数(在这里也可以称为梯度)。
但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。
因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本,
这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。

在每次迭代中,我们首先随机抽样一个小批量 B \mathcal{B} B
它是由固定数量的训练样本组成的。
然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。
最后,我们将梯度乘以一个预先确定的正数 η \eta η,并从当前参数的值中减掉。

我们用下面的数学公式来表示这一更新过程( ∂ \partial 表示偏导数):

( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) . (\mathbf{w},b) \leftarrow (\mathbf{w},b) - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{(\mathbf{w},b)} l^{(i)}(\mathbf{w},b). (w,b)(w,b)BηiB(w,b)l(i)(w,b).

算法的步骤如下:
(1)初始化模型参数的值,如随机初始化;
(2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。
对于平方损失和仿射变换,我们可以明确地写成如下形式:

w ← w − η ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) = w − η ∣ B ∣ ∑ i ∈ B x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) , b ← b − η ∣ B ∣ ∑ i ∈ B ∂ b l ( i ) ( w , b ) = b − η ∣ B ∣ ∑ i ∈ B ( w ⊤ x ( i ) + b − y ( i ) ) . \begin{aligned} \mathbf{w} &\leftarrow \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right),\\ b &\leftarrow b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b) = b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right). \end{aligned} wbwBηiBwl(i)(w,b)=wBηiBx(i)(wx(i)+by(i)),bBηiBbl(i)(w,b)=bBηiB(wx(i)+by(i)).
:eqlabel:eq_linreg_batch_update

公式 :eqref:eq_linreg_batch_update中的 w \mathbf{w} w x \mathbf{x} x都是向量。

∣ B ∣ |\mathcal{B}| B表示每个小批量中的样本数,这也称为批量大小(batch size)。
η \eta η表示学习率(learning rate)。

批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。
这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。
调参(hyperparameter tuning)是选择超参数的过程。
超参数通常是我们根据训练迭代结果来调整的,
而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。

线性回归的从零开始实现

从零开始实现整个方法,包括数据流水线、模型、损失函数和小批量随机梯度下降优化器。

%matplotlib inline
import random
import torch
from d2l import torch as d2l

生成数据集

生成一个包含1000个样本的数据集,
每个样本包含从标准正态分布中采样的2个特征。
我们的合成数据集是一个矩阵 X ∈ R 1000 × 2 \mathbf{X}\in \mathbb{R}^{1000 \times 2} XR1000×2

我们使用线性模型参数 w = [ 2 , − 3.4 ] ⊤ \mathbf{w} = [2, -3.4]^\top w=[2,3.4] b = 4.2 b = 4.2 b=4.2
和噪声项 ϵ \epsilon ϵ生成数据集及其标签:

y = X w + b + ϵ . \mathbf{y}= \mathbf{X} \mathbf{w} + b + \mathbf\epsilon. y=Xw+b+ϵ.

ϵ \epsilon ϵ可以视为模型预测和标签时的潜在观测误差。
在这里我们认为标准假设成立,即 ϵ \epsilon ϵ服从均值为0的正态分布。
为了简化问题,我们将标准差设为0.01。
下面的代码生成合成数据集。

def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))
    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)  

features中的每一行都包含一个二维数据样本,labels中的每一行都包含一维标签值(一个标量)

print('features:', features[0], '\nlabel:', labels[0])
features: tensor([-0.4836, -0.8441]) 
label: tensor([6.1063])

通过生成第二个特征features[:, (1)]和labels的散点图,可以直观观察到两者之间的线性关系

d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);

在这里插入图片描述

读取数据集

定义一个data_iter函数,
该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量

每个小批量包含一组特征和标签。

def data_iter(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]

读取第一个小批量数据样本并打印。
每个批量的特征维度显示批量大小和输入特征数。
同样的,批量的标签形状与batch_size相等。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
tensor([[ 0.3747,  0.7438],
        [-0.9089, -1.8827],
        [ 1.7131,  0.8056],
        [ 0.8595,  1.3511],
        [-1.8953, -0.4136],
        [-0.1327, -0.5880],
        [ 0.6790, -0.2707],
        [-0.6167, -1.1107],
        [-0.4787, -0.1805],
        [-0.5738, -0.6744]]) 
 tensor([[2.4371],
        [8.7851],
        [4.8822],
        [1.3283],
        [1.8363],
        [5.9220],
        [6.4880],
        [6.7299],
        [3.8554],
        [5.3370]])

当我们运行迭代时,我们会连续地获得不同的小批量,直至遍历完整个数据集。
上面实现的迭代对教学来说很好,但它的执行效率很低,可能会在实际问题上陷入麻烦。
例如,它要求我们将所有数据加载到内存中,并执行大量的随机内存访问。
在深度学习框架中实现的内置迭代器效率要高得多,
它可以处理存储在文件中的数据和数据流提供的数据。

初始化模型参数

通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,
并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2, 1), requires_grad = True)
b = torch.zeros(1, requires_grad = True)

定义模型

定义模型,将模型的输入和参数同模型的输出关联起来。

要计算线性模型的输出,只需计算输入特征 X \mathbf{X} X和模型权重 w \mathbf{w} w的矩阵-向量乘法后加上偏置 b b b
注意,上面的 X w \mathbf{Xw} Xw是一个向量,而 b b b是一个标量。

def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b

定义损失函数

因为需要计算损失函数的梯度,所以我们应该先定义损失函数。
这里我们使用平方损失函数。
在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

定义优化算法

在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。
接下来,朝着减少损失的方向更新我们的参数。

下面的函数实现小批量随机梯度下降更新。
该函数接受模型参数集合、学习速率和批量大小作为输入。每
一步更新的大小由学习速率lr决定。
因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size
来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。

def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

训练

在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。
计算完损失后,我们开始反向传播,存储每个参数的梯度。
最后,我们调用优化算法sgd来更新模型参数。

概括一下,我们将执行以下循环:

  • 初始化参数
  • 重复以下训练,直到完成
    • 计算梯度 g ← ∂ ( w , b ) 1 ∣ B ∣ ∑ i ∈ B l ( x ( i ) , y ( i ) , w , b ) \mathbf{g} \leftarrow \partial_{(\mathbf{w},b)} \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} l(\mathbf{x}^{(i)}, y^{(i)}, \mathbf{w}, b) g(w,b)B1iBl(x(i),y(i),w,b)
    • 更新参数 ( w , b ) ← ( w , b ) − η g (\mathbf{w}, b) \leftarrow (\mathbf{w}, b) - \eta \mathbf{g} (w,b)(w,b)ηg

在每个迭代周期(epoch)中,我们使用data_iter函数遍历整个数据集,
并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。
这里的迭代周期个数num_epochs和学习率lr都是超参数,分别设为3和0.03。

lr = 0.03
num_epochs = 3
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)  # X和y的小批量损失
        # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[w,b]的梯度
        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}')
epoch 1, loss 0.041500
epoch 2, loss 0.000147
epoch 3, loss 0.000047
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
w的估计误差: tensor([ 0.0002, -0.0003], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0002], grad_fn=<RsubBackward1>)

线性回归的简洁实现

使用PyTorch框架来实现线性回归模型

生成数据集

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

读取数据集

调用框架中现有的API来读取数据。将features和labels作为API的参数传递,并通过数据迭代器指定batch_size。此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个PyTorch数据迭代器"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)

为了验证是否正常工作,读取并打印第一个小批量样本。

使用iter构造Python迭代器,并使用next从迭代器中获取第一项。

next(iter(data_iter))
[tensor([[ 0.3532, -0.6057],
         [ 1.6997, -1.6114],
         [ 1.3135,  3.0438],
         [-1.0064, -1.3555],
         [ 1.6724,  0.7461],
         [ 0.3855, -1.5162],
         [ 0.7502,  0.5924],
         [ 0.8864, -0.1364],
         [ 2.0878, -2.4125],
         [ 0.4963,  1.4179]]),
 tensor([[ 6.9696],
         [13.0706],
         [-3.5134],
         [ 6.7924],
         [ 5.0087],
         [10.1182],
         [ 3.6684],
         [ 6.4485],
         [16.5720],
         [ 0.3795]])]

定义模型

对于标准深度学习模型,可以使用框架的预定义好的层。

首先定义一个模型变量net,它是一个Sequential类的实例。

Sequential类将多个层串联在一起。当给定输入数据时,Sequential实例将数据传入到第一层,然后将第一层的输出作为第二层的输入,以此类推。

在PyTorch中,全连接层在Linear类中定义。值得注意的是,我们将两个参数传递到nn.Linear中,第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

初始化模型参数

在使用net之前,需要初始化模型参数。

深度学习框架通常有预定义的方法来初始化参数。在这里指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。

正如在构造nn.Linear时指定输入和输出尺寸一样,现在能直接访问参数以设定它们的初始值。通过net[0]选择网络中的第一个图层,然后使用weight.data和bias.data方法访问参数。还可以使用替换方法normal_和fill_来重写参数值。

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
tensor([0.])

定义损失函数

计算均方误差使用的是MSELoss类,也成为平方 L 2 L_{2} L2范数。

默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()

定义优化算法

小批量随机梯度下降算法是一种优化神经网络的标准工具,
PyTorch在optim模块中实现了该算法的许多变种。
当我们(实例化一个SGD实例)时,我们要指定优化的参数
(可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。
小批量随机梯度下降只需要设置lr值,这里设置为0.03。

trainer = torch.optim.SGD(net.parameters(), lr = 0.03)

训练

在每个迭代周期里,将完整遍历一次数据集(train_data),
不停地从中获取一个小批量的输入和相应的标签。
对于每一个小批量,会进行以下步骤:

  • 通过调用net(X)生成预测并计算损失l(前向传播)。
  • 通过进行反向传播来计算梯度。
  • 通过调用优化器来更新模型参数。

为了更好的衡量训练效果,计算每个迭代周期后的损失,并打印它来监控训练过程。

num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X), y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {1:f}')
epoch 1, loss 1.000000
epoch 2, loss 1.000000
epoch 3, loss 1.000000

比较生成数据集的真实参数和通过有限数据训练获得的模型参数

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
w的估计误差: tensor([-0.0001,  0.0005])
b的估计误差: tensor([-0.0008])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1255070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Cloud Gateway 的简单介绍和基本使用

前言 本文主要对Spring Cloud Gateway进行简单的概念介绍&#xff0c;并通过多模块编程的方式进行一个简单的实操。 文章目录 前言1 什么是网关&#xff08;概念&#xff09;2 微服务中的网关2.1 问题12.2 问题2 3 网关作用4 Spring Cloud Gateway组成5 Spring Cloud Gateway基…

51代码审计-PHP框架MVC类上传断点调试

知识点1&#xff0c;文件上传漏洞挖掘 搜索关键字$_FILES phpmvc架构 MVC模式&#xff08;Model-View-Controller&#xff09;是软件工程中的一种软件架构模式。 MVC把软件系统分为三个基本部分&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#…

5.前端--CSS-基本概念【2023.11.26】

1. CSS 语法规范 CSS 规则由两个主要的部分构成&#xff1a;选择器以及一条或多条声明。 属性和属性值之间用英文“:”分开 多个“键值对”之间用英文“;”进行区分 选择器 : 简单来说&#xff0c;就是选择标签用的。 声明 &#xff1a;就是改变样式 2.CSS引入方式 按照 CSS 样…

【C++】多线程(一):std::thread的使用

这篇文章应我朋友的邀请&#xff0c;写一篇文章介绍下C多线程。 编译环境准备 首先确定你的编译器支持std的thread&#xff0c;如果不支持&#xff0c;就会出现诸如“thread找不到”的问题。 以下假设你使用 gnu gcc 编译器&#xff0c;因为 MSVC 的我也不太熟悉。 linux …

【详解二叉树】

&#x1f320;作者&#xff1a;TheMythWS. &#x1f387;座右铭&#xff1a;不走心的努力都是在敷衍自己&#xff0c;让自己所做的选择&#xff0c;熠熠发光。 目录 树形结构 概念 树的示意图 树的基本术语 树的表示 树的应用 二叉树(重点) 二叉树的定义 二叉树的五…

实战oj题——用队列实现栈

前言&#xff1a;Leetcode栈和队列的习题&#xff0c;用两个队列实现栈。 【由于我们是用C语言完成这道题&#xff0c;所以我们要将关于队列的实现代码插入到题中&#xff0c;在创建一个栈&#xff0c;栈里包含两个队列。】 思路&#xff1a;我们用两个队列来实现&#xff0c;因…

java学习part12多态

99-面向对象(进阶)-面向对象的特征三&#xff1a;多态性_哔哩哔哩_bilibili 1.多态&#xff08;仅限方法&#xff09; 父类引用指向子类对象。 调用重写的方法&#xff0c;就会执行子类重写的方法。 编译看引用表面类型&#xff0c;执行看实际变量类型。 2.父子同名属性是否…

FPGA驱动CS4344 VHDL例程

CS4344是一款非常简单的I2S立体声24bit D/A芯片&#xff0c;采样率高达192KHz&#xff0c;相对于ADAU1761复杂的寄存器配置来说&#xff0c;CS4344非常友好&#xff0c;无需配置寄存器&#xff0c;只要按I2S时序输入数据&#xff0c;即可实现立体声输出&#xff0c;且10PIN TSS…

Effective Modern C++(1.顶层const与底层const)

1.顶层const与底层const的定义 const修饰的变量不可以改变&#xff0c;那么他就是顶层const&#xff0c;如&#xff1a; const int a 10; 那么&#xff0c;对于 const int *const p new int(10); 第二个const就是顶层const&#xff0c;因为他修饰的是p&#xff1b;第一个…

Windows TCP 通信测试_1

一、单对单通信测试 应用函数 socket、bind、connect、listen、accept、recv、send&#xff08;win下的函数&#xff09;等 1、客户端demo client.cpp #include<WINSOCK2.H> #include<STDIO.H> #include<iostream> #include<cstring> using namespa…

电商项目高级篇-03 商品上架

商品上架 1、商品上架1.1、设计&#xff1a;宽表设计 1、商品上架 上架的商品才可以在网站展示。 上架的商品需要可以被检索。 1.1、设计&#xff1a;宽表设计 优点&#xff1a;方便检索 缺点&#xff1a;数据冗余 商品数据模型设计&#xff1a; PUT product {"mappi…

HarmonyOS开发(七):构建丰富页面

1、组件状态管理 1.1、概述 在应用中&#xff0c;界面一般都是动态的。界面会根据不同状态展示不一样的效果。 ArkUI作为一种声明式UI&#xff0c;具有状态驱动UI更新的特点&#xff0c;当用户进行界面交互或有外部事件引起状态改变时&#xff0c;状态的变会会触发组件的自动…

【企业微信连接问题】

1、个人可以创建企业微信的企业账号么&#xff1f; 答&#xff1a;可以的&#xff0c;只是没法认证。不过基础的功能还是有的。 注册步骤&#xff1a;企业微信注册步骤 2、集简云链接企业微信&#xff0c;在授权之后&#xff0c;找不到集简云怎么办&#xff1f; 答&#xff1a…

美化wordpress复制文章内容弹出版权提示框的源码代码

通过SweetAlert美化的提示框 将下面代码添加到当前主题模板函数functions.php文件最后即可&#xff1a; function zm_copyright_tips() { echo <link rel"stylesheet" type"text/css" rel"external nofollow" target"_blank" href…

人力资源管理后台 === 权限应用

目录 1.权限应用-拆分静态路由-动态路由 2.权限应用-根据用户权限添加动态路由 3.权限应用-根据权限显示左侧菜单 4.权限应用-退出登录重置路由 5.权限应用-功能权限-按钮权限标识 6.权限应用-自定义指令应用功能权限 7.其他模块-集成 8.首页-基本结构和数字滚动 9.首页…

编译器设计03-后端概述

后端处理概述 后端处理&#xff1a;中间代码生成&#xff0c;目标代码生成&#xff0c;贯穿各个阶段的优化。 后端处理犹如得出中文文章&#xff0c;当阅读完英语文章后&#xff0c;你的脑海中就有清晰的“中间代码”了&#xff0c;想写作的时候就心中有数&#xff0c;核心论…

Sringboot3 讲解

文章目录 前言一、Springboot快速入门1.1 实例1.2 总结&#xff1a;1.2.1 什么是starter启动器1.2.2 SpringBootApplication注解的功效 二、springboot3 统一配置文件1.概述2、属性配置文件使用简单案例3、yaml配置介绍和说明4、批量配置文件的读取5、多环境配置和激活 三、spr…

vue页面表单提交时如何做校验

我们在做新增的时候&#xff0c;新增对话框是要加必填校验的&#xff0c;否则就可能会加空数据或者会产生sql的报错。那么这个校验是如何加的呢&#xff1f;下面我们来说一下。 文章目录 一、必填校验1.1 给form表单绑定一个:rules校验规则&#xff0c;给每个item加上一个prop…

17.找出1000之内的所有完数。

文章目录 前言一、题目描述 二、题目分析 三、解题 程序运行代码 前言 本系列为循环结构编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 一个数如果恰好等于它的因子之和&#xff0c;这个数就称为“完数”。例如&#xff0c;⑥的因子为1、2、3&#xff0c;而…

创建可以离线打包开发的uniapp H5项目

安装node环境 略 安装vue脚手架&#xff0c;在线 npm install -g vue/cli PS&#xff1a;vue-cli已进入维护模式&#xff0c;vue3最新脚手架使用npm init vuelatest安装&#xff0c;安装后使用create-vue替换vue指令&#xff0c;create-vue底层使用vite提升前端开发效率&…