推理攻击-Python案例
1、本文通过推理攻击的方式来估计训练集中每个类别的样本数量、某样本是否在训练集中。
2、一种简单的实现方法:用模型对训练数据标签进行拟合,拟合结果即推理为训练集中的情况。
3、了解这些案例可以帮助我们更好的保护数据隐私。
推理攻击(Inference Attack)是针对机器学习模型的一种攻击方式,攻击者通过查询模型获取关于其训练数据的敏感信息。尽管模型本身不直接暴露训练数据,但通过分析模型的输出,攻击者可以推测出有关训练数据的属性,例如样本分布、类别信息或甚至具体的训练样本。
主要特点:
-
数据泄露:攻击者能够获取训练集中的隐私信息,可能涉及个人身份信息等敏感数据。
-
模型查询:攻击者通过向模型发送输入并观察输出,推测与训练数据相关的特征。
-
风险:这种攻击可能导致数据隐私的泄露,影响数据保护合规性。
-
防御措施:常用的防御方法包括差分隐私、模型蒸馏和限制模型查询等。
推理攻击的研究对于保护机器学习系统中的数据隐私至关重要。
1. 背景
推理攻击的目的是通过观察模型的输出,获取关于训练数据的敏感信息。在这个示例中,我们将使用模型的预测结果来推测CIFAR-10数据集中每个类别的样本数量。
2. 代码实现
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import numpy as np# 检查CUDA是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 数据预处理
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])# 下载并加载 CIFAR-10 数据集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)# 定义模型
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.pool = nn.MaxPool2d(2, 2)self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16 * 5 * 5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, 10)def forward(self, x):x = self.pool(F.relu(self.conv1(x)))x = self.pool(F.relu(self.conv2(x)))x = x.view(-1, 16 * 5 * 5)x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return x# 将模型移动到CUDA设备
net = Net().to(device)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)# 训练模型
for epoch in range(2): # 迭代次数for inputs, labels in trainloader:inputs, labels = inputs.to(device), labels.to(device) # 移动到CUDA设备optimizer.zero_grad() # 清零梯度outputs = net(inputs) # 前向传播loss = criterion(outputs, labels) # 计算损失loss.backward() # 反向传播optimizer.step() # 更新参数# 推理攻击
def inference_attack(model, data_loader):model.eval() # 设置为评估模式class_counts = {i: 0 for i in range(10)} # CIFAR-10有10个类true_counts = {i: 0 for i in range(10)} # 用于真实标签计数with torch.no_grad():for inputs, labels in data_loader:inputs = inputs.to(device) # 移动到CUDA设备outputs = model(inputs)_, predicted = torch.max(outputs, 1) # 获取预测的类别# 统计每个类别的预测次数for label in predicted:class_counts[label.item()] += 1# 统计每个类别的真实标签次数for label in labels:true_counts[label.item()] += 1return class_counts, true_counts# 执行推理攻击
predicted_counts, true_counts = inference_attack(net, trainloader)# 输出结果
print("Predicted class counts from inference attack:", predicted_counts)
print("True class counts from training data:", true_counts)# 计算和输出每个类的比例
total_predicted = sum(predicted_counts.values())
total_true = sum(true_counts.values())print("\nTotal samples predicted:", total_predicted)
print("Total samples true:", total_true)for class_id in range(10):predicted_count = predicted_counts[class_id]true_count = true_counts[class_id]print(f"Class {class_id}: Predicted {predicted_count} samples, True {true_count} samples, "f"Predicted proportion: {predicted_count / total_predicted:.2%}, "f"True proportion: {true_count / total_true:.2%}")
输出
Files already downloaded and verified
Predicted class counts from inference attack: {0: 5465, 1: 5794, 2: 5200, 3: 3754, 4: 5434, 5: 6335, 6: 4841, 7: 4568, 8: 5093, 9: 3516}
True class counts from training data: {0: 5000, 1: 5000, 2: 5000, 3: 5000, 4: 5000, 5: 5000, 6: 5000, 7: 5000, 8: 5000, 9: 5000}Total samples predicted: 50000
Total samples true: 50000
Class 0: Predicted 5465 samples, True 5000 samples, Predicted proportion: 10.93%, True proportion: 10.00%
Class 1: Predicted 5794 samples, True 5000 samples, Predicted proportion: 11.59%, True proportion: 10.00%
Class 2: Predicted 5200 samples, True 5000 samples, Predicted proportion: 10.40%, True proportion: 10.00%
Class 3: Predicted 3754 samples, True 5000 samples, Predicted proportion: 7.51%, True proportion: 10.00%
Class 4: Predicted 5434 samples, True 5000 samples, Predicted proportion: 10.87%, True proportion: 10.00%
Class 5: Predicted 6335 samples, True 5000 samples, Predicted proportion: 12.67%, True proportion: 10.00%
Class 6: Predicted 4841 samples, True 5000 samples, Predicted proportion: 9.68%, True proportion: 10.00%
Class 7: Predicted 4568 samples, True 5000 samples, Predicted proportion: 9.14%, True proportion: 10.00%
Class 8: Predicted 5093 samples, True 5000 samples, Predicted proportion: 10.19%, True proportion: 10.00%
Class 9: Predicted 3516 samples, True 5000 samples, Predicted proportion: 7.03%, True proportion: 10.00%
训练阶段
-
模型定义:首先定义了一个简单的卷积神经网络(CNN),用于分类CIFAR-10图像。
-
数据加载:使用PyTorch的
DataLoader
加载CIFAR-10数据集,并进行必要的预处理。 -
训练过程:
- 模型在训练过程中计算损失,并通过反向传播更新权重。
- 在每个训练步骤中,输入和标签都被移动到CUDA设备以加速计算。
推理攻击阶段
-
设置评估模式:在推理攻击中,使用
model.eval()
将模型设置为评估模式,这样可以禁用dropout等训练时特有的操作。 -
无梯度计算:使用
torch.no_grad()
来禁用梯度计算,减少内存使用并加快推理速度。 -
预测类别:
- 遍历训练数据集中的每个样本,输入到模型中进行前向传播,得到输出。
- 使用
torch.max(outputs, 1)
获取每个样本的预测类别。
-
统计预测次数:
- 创建一个字典
class_counts
来记录每个类别的预测次数。 - 对每个预测结果进行迭代,更新
class_counts
字典中对应类别的计数。
- 创建一个字典
3. 结果分析
-
输出类别计数:在推理攻击完成后,打印每个类别的预测次数。这表示模型认为训练集中每个类别的样本数量。
-
比例计算:计算并输出每个类别在总样本中的比例,以帮助攻击者理解各类别在训练集中的分布。
4. 具体推理过程
推理攻击的核心在于,通过观察模型对训练数据的输出,攻击者可以获取有关训练集的信息。尽管模型并不直接透露训练数据的样本,但通过分析模型的预测分布,攻击者能够推测出某些类的样本数量。
另一个示例
在CIFAR-10数据集上实现更复杂的推理攻击可以包括多种策略,比如利用模型输出的概率分布、对抗性样本生成或者训练集样本的重构等。以下是利用模型的输出概率分布来推测训练样本的存在性,同理。
在这个示例中,我们将利用模型的预测概率来分析某些特定样本是否存在于训练集中。
1. 设置
我们将训练一个CNN模型,并使用模型输出的概率来推测某些特定类别样本的存在。
2. 完整代码
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import numpy as np# 检查CUDA是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 数据预处理
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])# 下载并加载 CIFAR-10 数据集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)# 定义模型
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.pool = nn.MaxPool2d(2, 2)self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16 * 5 * 5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, 10)def forward(self, x):x = self.pool(F.relu(self.conv1(x)))x = self.pool(F.relu(self.conv2(x)))x = x.view(-1, 16 * 5 * 5)x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return x# 将模型移动到CUDA设备
net = Net().to(device)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)# 训练模型
for epoch in range(2): # 迭代次数for inputs, labels in trainloader:inputs, labels = inputs.to(device), labels.to(device) # 移动到CUDA设备optimizer.zero_grad() # 清零梯度outputs = net(inputs) # 前向传播loss = criterion(outputs, labels) # 计算损失loss.backward() # 反向传播optimizer.step() # 更新参数# 推理攻击:检查某些样本是否在训练集中
def inference_attack(model, data_loader, sample_images):model.eval() # 设置为评估模式existence_scores = []with torch.no_grad():for image in sample_images:image = image.to(device).unsqueeze(0) # 增加batch维度output = model(image)probabilities = F.softmax(output, dim=1)existence_scores.append(probabilities.cpu().numpy())return existence_scores# 选择一些样本进行推理攻击
sample_indices = [0, 1, 2, 3, 4] # 假设你想检查前五个训练样本
sample_images = [trainset[i][0] for i in sample_indices]# 执行推理攻击
scores = inference_attack(net, trainloader, sample_images)# 输出结果
for i, score in enumerate(scores):print(f"Sample {i} existence scores: {score}")
输出
Files already downloaded and verified
Sample 0 existence scores: [[1.4911070e-03 7.6075867e-03 2.7023258e-02 1.3261433e-01 1.9510817e-025.9721380e-02 7.2566289e-01 1.8510185e-02 5.5387925e-04 7.3045860e-03]]
Sample 1 existence scores: [[1.1406993e-03 2.6581092e-02 5.5561360e-04 7.9178403e-04 2.6659523e-053.2436097e-04 9.5068201e-05 4.9321837e-04 4.1092065e-04 9.6958053e-01]]
Sample 2 existence scores: [[0.09460393 0.03179372 0.10372082 0.18358988 0.06053074 0.115103030.04988525 0.0965076 0.14990555 0.11435943]]
Sample 3 existence scores: [[3.7419258e-03 3.7223320e-03 2.3206087e-02 9.1478322e-03 1.6883773e-016.7729452e-03 7.8114969e-01 1.5515537e-03 7.0034160e-04 1.1696027e-03]]
Sample 4 existence scores: [[3.3676695e-02 8.6378354e-01 1.4160476e-03 8.6853321e-04 1.3602857e-032.0280134e-04 5.6550006e-04 1.4694600e-04 5.3967834e-03 9.2582904e-02]]
这些结果表示模型对每个特定样本的“存在性得分”,即模型对于这些图像属于各个类别的预测概率。每个样本的输出是一个概率分布,表示模型对其归属不同类别的信心。以下是如何解读这些结果:
3.结果解释
-
概率分布:每个样本的输出都是一个包含10个元素的数组,表示它属于CIFAR-10中每个类别的概率。数组的每个元素对应一个类别(例如,飞机、汽车等)。
-
具体样本分析:
- Sample 0:
- 模型认为该样本最可能属于“类别 6”(可能是鸟),其概率为约 72.57%。
- 其他类别的概率相对较低,模型对该样本的分类比较自信。
- Sample 1:
- 模型认为该样本最可能属于“类别 9”(可能是汽车),概率接近 96.96%。
- 这是一个非常高的置信度,表明该样本几乎确定属于该类别。
- Sample 2:
- 概率分布相对均匀,最大值为 18.36%,表明模型对该样本的分类不太自信。
- Sample 3:
- 模型认为该样本最可能属于“类别 6”(可能是鸟),概率为约 78.11%。
- Sample 4:
- 对于该样本,模型认为最可能的类别是“类别 1”(可能是汽车),概率为约 86.38%。
- Sample 0:
这些“存在性得分”可以用来分析模型的分类信心。较高的概率表明模型对某个类别的强烈信心,而较低的概率可能意味着模型不确定,甚至可能表示该样本在训练集中并不存在或被错误分类。
4.应用场景
-
模型性能分析:通过分析这些得分,可以评估模型对特定类别的识别能力。
-
数据集覆盖性:如果某些类别的得分普遍较低,可能表示这些类别在训练集中样本较少或质量较差。
-
攻击策略:在推理攻击中,攻击者可以利用这些信息确定哪些样本可能存在于训练集中,从而进行针对性的数据挖掘。
如果您有其他问题或者想深入探讨的内容,请告诉我!
5.代码说明
-
模型训练:与之前一样,首先训练一个CNN模型用于图像分类。
-
推理攻击函数:
inference_attack
函数接收模型、数据加载器和待检查的样本图像。- 使用
softmax
计算每个样本的预测概率分布。 - 将每个样本的概率分布作为“存在性得分”返回。
-
选择样本进行推理:
- 在示例中,选择训练集的前五个样本进行推理攻击。
-
输出存在性得分:显示每个样本的存在性得分,这可以反映模型对这些样本的信心。
6.进一步扩展
可以通过以下方式进一步复杂化推理攻击:
- 对抗样本生成:创建与原始样本相似但经过修改的对抗样本,观察模型如何反应。
- 基于输出的聚类分析:使用模型输出的概率分布进行聚类分析,推测训练集的样本分布。
- 针对特定类别的查询:设计查询来特定地测试模型对某些类别的敏感性。
总结
通过这种方法,攻击者能够对训练数据集中的类别分布进行合理推测,尽管这一过程并不保证百分之百准确,但足以展示模型在某些方面可能泄漏的信息。这种类型的攻击强调了在设计和部署机器学习模型时,保护训练数据隐私的重要性。
相关文章:

推理攻击-Python案例
1、本文通过推理攻击的方式来估计训练集中每个类别的样本数量、某样本是否在训练集中。 2、一种简单的实现方法:用模型对训练数据标签进行拟合,拟合结果即推理为训练集中的情况。 3、了解这些案例可以帮助我们更好的保护数据隐私。 推理攻击(…...

find_box_3d
参数 (ObjectModel3DScene, SideLen1, SideLen2, SideLen3, MinScore, GenParam : GrippingPose, Score, ObjectModel3DBox, BoxInformation) 入参介绍 1,ObjectModel3DScene, 输入的3d模型,这个模型最好是由xyx三通道点…...

Visual Studio2017编译GDAL3.0.2源码过程
一、编译环境 操作系统:Windows 10企业版 编译工具:Visual Studio 2017旗舰版 源码版本:gdal3.0.2 二、生成解决方案 打开Visual Studio 2017的x64本机生成工具,切换到gdal3.0.2源码根目录;执行generate_vcxproj.b…...

计算机网络——email
pop3拉出来 超出ASCII码范围就不让传了 这样就可以传更大的文件...

【Linux】信号知识三把斧——信号的产生、保存和处理
目录 1、关于信号的前置知识 1.1.什么是信号? 1.2.为什么要学习信号? 1.3.如何学习信号? 1.4.一些常见的信号 1.5.信号的处理方式 1.6.为什么每一个进程都可以系统调用? 2.信号的产生 2.1.kill命令产生信号…...

【国庆要来了】基于Leaflet的旅游路线WebGIS可视化实践
前言 转眼2024年的国庆节马上就要来临了,估计很多小伙伴都计划好了旅游路线。金秋十月,不管是选择出门去看看风景,还是选择在家里看人。从自己生活惯了的城市去别人生活惯了的城市,去感受城市烟火、去感受人文风景,为2…...

Element-UI Plus 暗黑主题切换及自定义主题色
1. 暗黑主题切换 在main.js中引入下面文件 import element-plus/theme-chalk/dark/css-vars.css安装 vueuse/core pnpm add vueuse/coreApp.vue 添加下面代码 使用了 useDark() 的页面才会从 localStorage中读取当前主题状态,否则,刷新页面就会恢复默…...

人工智能与机器学习原理精解【31】
文章目录 卷积神经网络CNN定义数学原理与公式计算与定理架构例子例题 全连接层的前馈计算定义数学原理与公式计算过程示例 参考文献 卷积神经网络 CNN 即卷积神经网络(Convolutional Neural Networks),是一类包含卷积计算且具有深度结构的前…...

如何安全地大规模部署 GenAI 应用程序
大型语言模型和其他形式的生成式人工智能(GenAI) 的广泛使用带来了许多组织可能没有意识到的安全风险。幸运的是,网络和安全提供商正在寻找方法来应对这些前所未有的威胁。 随着人工智能越来越深入地融入日常业务流程,它面临着泄露专有信息、提供错误答…...

verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)
在以往采用 FPGA 实现的 FIR 滤波功能,滤波器系数是通过 matlab 计算生成,然后作为固定参数导入到 verilog 程序中,这尽管简单,但灵活性不足。在某些需求下(例如捕获任意给定台站信号)需要随时修改滤波器的…...

OSPF的不规则区域
1.远离骨干非骨干区域 2.不连续骨干 解决方案 tunnel ---点到点GRE 在合法与非ABR间建立隧道,然后将其宣告于OSPF协议中; 缺点:1、周期和触发信息对中间穿越区域造成资源占用(当同一条路由来自不同区域,路由器会先…...

大数据新视界 --大数据大厂之 Ibis:独特架构赋能大数据分析高级抽象层
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

总结TypeScript相关知识
目录 引入认识特点安装使用变量声明类型推导 JS 和 TS 共有类型number类型boolean类型string类型Array类型null和undefined类型object类型symbol类型对象类型函数类型 可选和只读type 和 interface索引签名类型断言非空类型断言类型缩小严格赋值检测现象TS 新增类型字面量类型a…...

pdf怎么编辑修改内容?详细介绍6款pdf编辑器功能
■ pdf怎么编辑修改内容? PDF(Portable Document Format)作为一种广泛使用的文件格式,具有特点包括兼容性强、易于传输、文件安全性高、跨平台性、可读性强、完整性、可搜索性、安全性、可压缩性。 PDF文件本身是不可以直接进行编…...

【Blender Python】4.获取场景对象的几种方式
概述 有时候我们需要获取场景中已经添加或存在的对象。本节就总结在Blender Python中获取场景中对象的一些方法。 通过名称获取 py.data的objects()方法返回一个对象集合,可以使用键名或者下标形式获取具体的对象。 在默认新建的场景中,存在三个对象…...

鸿蒙harmonyos next flutter通信之EventChannel获取ohos系统时间
建立通道 flutter代码: EventChannel eventChannel EventChannel("com.xmg.eventChannel"); ohos代码: //定义eventChannelprivate eventChannel: EventChannel | null null//定义eventSinkprivate eventSink: EventSink | null null//建…...

Python 代码编写规范
本规范旨在为 Python 项目的代码编写提供一致性指南。它遵循 Python 社区的 PEP 8 标准,并结合了通用的编程最佳实践。 1. 编码风格 1.1 缩进 使用 4 个空格 作为缩进,不要使用制表符(Tab)。 def example():if True:print(&quo…...

k8s中pod的管理
一、资源管理 1.概述 说到k8s中的pod,即荚的意思,就不得不先提到k8s中的资源管理,k8s中可以用以下命令查看我们的资源: kubectl api-resources 比如我们现在需要使用k8s开启一个东西,那么k8s通过apiserver去对比etc…...

JavaScript中引用数据类型的浅拷贝
在JavaScript中,数据类型被分为“基本数据类型”和“引用数据类型”两大类。基本数据类型包括数值型、字符型、逻辑型、未定义型(undefined)、空型(null)和ES6新增的Symbol类型,引用数据类型包括数组、对象和函数。 当我们在程序中执行变量赋值操作的时候…...

自闭症寄宿学校陕西:提供综合发展的教育环境
在陕西这片古老而充满希望的土地上,有一所特殊的学校——星贝育园康复中心,它如同一座灯塔,照亮了无数自闭症儿童及其家庭前行的道路。这所全国规模较大的广泛性发育障碍全托寄宿制儿童康复训练机构,不仅以其专业的康复训练和独特…...

JS模块化工具requirejs详解
文章目录 JS模块化工具requirejs详解一、引言二、RequireJS 简介1、什么是 RequireJS2、RequireJS 的优势 三、RequireJS 的使用1、配置 RequireJS1.1、基础配置 2、定义模块3、加载模块 四、总结 JS模块化工具requirejs详解 一、引言 随着前端技术的快速发展,Jav…...

JavaScript中的异步编程:从回调到Promise
在JavaScript中,异步编程是一项至关重要的技能,它允许我们在不阻塞主线程的情况下执行耗时操作,如网络请求、文件读取或定时任务。随着JavaScript的发展,异步编程的模式也在不断演进,从最初的回调函数,到现…...

windows下DockerDesktop命令行方式指定目录安装
windows下DockerDesktop指定目录安装(重新安装) 因为DcokerDesktop占用内存较大, 并且拉去镜像后占用本地空间较多,所以建议安装时就更改默认安装路径和镜像存储路径 这里,展示了从下载到安装的过程: 首先下载DcokerDesktop;找到Docker Desktop Installer.exe 并重命名为 do…...

排查和解决JVM OOM实战
JVM OOM介绍 Java内存区域布局 下面的分析中都是基于JDK 8开始的。关于JMM不过多介绍每个区域的作用。OOM不单只会发生在堆内存,也可能是因为元空间或直接内存泄漏导致OOM,此时在OOM的详细信息中会有不同体现。 Java OOM的类别 java.lang.OutOfMemory…...

【Swift官方文档】7.Swift集合类型
集合类型 使用数组、集合和字典来组织数据。Swift 提供了三种主要的集合类型:数组、集合和字典,用于存储值的集合。数组是有序的值集合。集合是无序的唯一值集合。字典是无序的键值对集合。 Swift 中的数组、集合和字典始终清晰地指明它们可以存储的值…...

QT调用最新的libusb库
一:下载libusb文件 下载最新的库的下载网站:https://libusb.info/ 下载: 解压后目录如下: 二:库文件添加QT中 根据自己的编译器选择库: ①将头文件中添加libusb.h ②源文件中添加libusb-1.0.lib ③添加…...

白嫖EarMaster Pro 7简体中文破解版下载永久激活
EarMaster Pro 7 简体中文破解版功能介绍 俗话说得好,想要成为音乐家,就必须先拥有音乐家的耳朵,相信很多小伙伴都已经具备了一定的音乐素养,或者是说想要进一步得到提升。那我们就必须练好听耳的能力,并且把这种能力…...

使用JavaScript写一个网页端的四则运算器
目录 style(内联样式表部分) body部分 html script 总的代码 网页演示 style(内联样式表部分) <style>body {font-family: Arial, sans-serif;display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #f0f0f0;}.calculator {…...

Linux find命令详解及实用示例
Linux 系统中的 find 命令是一个功能强大的工具,用于在文件系统中搜索文件并执行相应的操作。无论是系统管理员还是普通用户,掌握 find 命令都能极大地提高工作效率。本文将详细介绍 find 命令的用法,并通过多个示例展示其在实际中的应用。 …...

CSS基础-常见属性(二)
6、CSS三大特性 6.1 层叠性 如果样式发生冲突,则按照优先级进行覆盖。 6.2 继承性 元素自动继承其父元素、祖先元素所设置的某些元素,优先继承较近的元素。 6.3 优先级 6.3.1 简单分级 1、内联样式2、ID选择器3、类选择器/属性选择器4、标签名选择器/…...