【动手学PaddleX】谁都能学会的基于迁移学习的老人摔倒目标检测

本项目使用PaddleX搭建目标检测模块,在一个精选的数据集上进行初步训练,并在另一个老年人跌倒检测的数据集上进行参数微调,实现了迁移学习的目标检测项目。

1.项目介绍

迁移学习是非常有用的方法,在实际生活中由于场景多样,环境复杂,一些场景复杂或者人体姿态不一的数据集较少,因此直接训练的难度较大,且效果往往不如迁移学习。

2.项目流程

  1. 我们使用两个数据集,一个是精选的精选的跌倒检测数据集Fall detection Dataset,一个是老年人跌倒数据集,在界面左侧数据集标签页可看到。
  2. 检查标签名称,看是否满足我们的需要,如不满足则需要处理数据集将标签统一和格式化,并检查文件夹名称是否符合我们的规范。
  3. 本项目使用的格式是PascalVOC格式

具体流程如下:首先部署开发环境,安装PaddleX;切割数据集、构建数据读取器;搭建PPYolo模型;使用其他场景数据集训练、验证模型;使用跌倒数据集进行微调并验证;使用微调后的模型进行推理。

3.项目环境

本项目的实验环境如下表所示

名称版本功能说明
AIStudioBML Codelab代码实现的基础平台与环境
PaddlePaddle2.3.2在新建项目时选择,2.4.0会出现bug
python3.7默认选择
PaddleX2.1.0PaddleX套件用于搭建模型

4.预训练阶段

利用跌倒检测数据集Fall detection Dataset得到预训练模型,为后续模型的微调做准备。

4.1 环境安装

PaddleX集成飞桨智能视觉领域图像分类、目标检测、语义分割、实例分割任务能力,将深度学习开发全流程从数据准备、模型训练与优化到多端部署端到端打通,并提供统一任务API接口。无需分别安装不同套件,以低代码的形式即可快速完成飞桨全流程开发。

本项目主要采用PaddleX来实现目标预测项目。

In [ ]

# 安装相关包,在持久化文件夹下安装
!pip install pycocotools -t /home/aistudio/external-libraries
!pip install cython -t /home/aistudio/external-libraries
!pip install pyproject -t /home/aistudio/external-libraries
!pip install --user --upgrade pyarrow==11.0.0

# 此处如果使用持久化路径会找不到paddlex命令
!pip install paddlex -i https://pypi.tuna.tsinghua.edu.cn/simple

4.2 数据集预处理

数据集存放在data文件夹下,解压相关数据集并处理成我们想要的格式。

In [ ]

# 解压数据集文件
!unzip -oq /home/aistudio/data/data182907/Fall_Dataset.zip -d Fall_Dataset/
!unzip -oq /home/aistudio/data/data94809/pp_fall.zip -d pp_fall/

此时数据集已解压到相关目录,但不符合PAscalVOC格式

  • Annotations 进行detection 任务时的 标签文件,xml文件形式
  • ImageSets 存放数据集的分割文件,比如train,val,test
  • JPEGImages 存放 .jpg格式的图片文件
  • SegmentationClass 存放 按照class 分割的图片
  • SegmentationObject 存放 按照 object 分割的图片

In [ ]

# 对文件结构做处理
%cd /home/aistudio/Fall_Dataset/Fall_Dataset

!mv /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets/Main/test.txt /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets
!mv /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets/Main/train.txt /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets
!mv /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets/Main/trainval.txt /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets
!mv /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets/Main/val.txt /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets
!mv /home/aistudio/Fall_Dataset/Fall_Dataset/train.txt /home/aistudio/Fall_Dataset/Fall_Dataset/train_list.txt
!mv /home/aistudio/Fall_Dataset/Fall_Dataset/trainval.txt /home/aistudio/Fall_Dataset/Fall_Dataset/val_list.txt
!rm -rf /home/aistudio/Fall_Dataset/Fall_Dataset/ImageSets/Main

!mv /home/aistudio/Fall_Dataset/Fall_Dataset/label_list.txt /home/aistudio/Fall_Dataset/Fall_Dataset/labels.txt

In [ ]

# 检查一下数据集里的object

from xml.etree import ElementTree as ET
from collections import defaultdict
import sys
import os

sys.path.append('/home/aistudio/external-libraries')

path = '/home/aistudio/Fall_Dataset/Fall_Dataset/Annotations/'
path_pp = '/home/aistudio/pp_fall/Annotations/'

name_dict = defaultdict(set)
for filename in os.listdir(path_pp):
    if not os.path.isdir(path_pp + filename):
        tree = ET.parse(path_pp + filename)
        root = tree.getroot()
        ets = root.findall('object')
        for et in ets:
            name = et.findall('name')[0].text
            name_dict[name].add(filename)
            # 如果有问题可以删除节点
            # if name.text == 'XX':      # 修改标签
            #     name.text='XXX'
            # elif name.text == 'YY':      # 删除无关标签
            #     root.remove(et)

print(name_dict.keys())

In [ ]

!mv /home/aistudio/pp_fall/images /home/aistudio/pp_fall/JPEGImages

# 使用paddlex切分数据集,根据37比例划分出验证集和训练集
# 注意,要确定符合PascalVOC格式才会成功裁剪
# Fall_Dataset无须此命令,可以直接用分割好的数据集
!paddlex --split_dataset --format VOC --dataset_dir /home/aistudio/pp_fall/ --val_value 0.3

我们得到了最终的数据集结构,Annotation和JPEGImages分别存放标签和图像文件;labels.txt存放标签名称;而train_list.txt和val_list.txt分别存放训练集和验证集,这两个txt在第4步搭建数据读取器中作为参数传入。

4.3 数据预处理

定义一些数据增强方法,可以使模型的泛化能力更强而不会过拟合。

数据增强方法有多种,具体选择哪几个需要根据任务特点、数据特征来确定。例如:图像竖直翻转之后会和实际严重不符,增加模型的学习难度而对实际表现没有任何帮助。

In [ ]

# 导入python库,如果遇到pyarrow报错,可以试试重启内核
import numpy as np
import paddlex as pdx
from paddlex import transforms as T

In [ ]

# 设置数据增强的方式
# API说明:https://github.com/PaddlePaddle/PaddleX/blob/develop/docs/apis/transforms/transforms.md
train_transforms = T.Compose([
    T.MixupImage(),            # mixup增强
    T.RandomDistort(),         # 随机调整亮度、对比度、饱和度、色调
    T.RandomExpand(),          # 随机扩张图像
    T.RandomCrop(),            # 随机裁剪图像
    T.RandomHorizontalFlip(),  # 随机水平翻转
    T.BatchRandomResize(       # 训练批次图像的输入大小
        target_sizes=[320, 352, 384, 416, 448, 480, 512, 544, 576, 608]),  # 为了确保大目标和小目标都可以有效学习,我们设置了不通的输入大小
    T.Normalize()              # 对图像进行标准化
])

eval_transforms = T.Compose([
    T.Resize(target_size=480), # 设置验证时输入大小,此处选择了480,平衡了目标大小和计算时间
    T.Normalize()              # 对图像进行标准化
])

4.4 构建数据读取器

利用准备好的数据构建之后要用的数据集变量,准备了训练集和验证集,所以定义了两个。

In [ ]

%cd
# 定义数据集
# 定义训练和验证所用的数据集
train_dataset = pdx.datasets.VOCDetection(
    data_dir='pp_fall',                   # 数据集存放的路径
    file_list='pp_fall/train_list.txt',   # 训练集划分的txt文件
    label_list='pp_fall/labels.txt',      # 训练数据的类别文件
    transforms=train_transforms,            # 定义数据转换
    shuffle=True)                           # 随机打乱数据集

# 验证数据集的构建和上面训练的一样
eval_dataset = pdx.datasets.VOCDetection(
    data_dir='pp_fall',                   # 数据集存放的路径 
    file_list='pp_fall/val_list.txt',     # 训练集划分的txt文件
    label_list='pp_fall/labels.txt',      # 训练数据的类别文件
    transforms=eval_transforms,             # 定义数据转换
    shuffle=False)                          # 验证数据集没有必要打乱

4.5 构建模型

我们这里使用了PPYOLOv2,参考了其它的项目。

PPYOLOv2版本在COCO 2017 test-dev上的精度为49.5%;在640x640的输入尺寸下,FPS达到68.9FPS。PP-YOLOv2在同等速度下,精度超越YOLOv5。满足我们的需求,因此选择PPYOLOv2。

In [ ]

# 初始化模型,并进行训练
# 可使用VisualDL查看训练指标,参考https://github.com/PaddlePaddle/PaddleX/tree/release/2.0.0/tutorials/train#visualdl可视化训练指标

# 确定目标类别数
num_classes = len(train_dataset.labels)

# 定义PPYOLOv2模型
model = pdx.det.PPYOLOv2(num_classes=num_classes) # 我们只需要修改类别数即可

接下来可以开始训练了,使用PaddleX快速完成训练,具体参数使用请参考单元格,

In [ ]

# API说明:https://github.com/PaddlePaddle/PaddleX/blob/release/2.0.0/paddlex/cv/models/detector.py
# 各参数介绍与调整说明:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html
model.train(
    num_epochs=5,               # 训练轮次
    train_dataset=train_dataset,  # 导入训练数据集读取器
    eval_dataset=eval_dataset,    # 导入验证数据集读取器
    train_batch_size=16,          # 批大小,在32G显存下可以用16,bs越大训练效果越好
    learning_rate=0.00025,        # 根据批大小和模型复杂度选择合理的学习率
    warmup_steps=500,             # 预热步数,学习率会逐渐增大。预热是为了训练过程能够更加稳定
    warmup_start_lr=0.0,          # 预热起始学习率
    save_interval_epochs=1,       # 每1轮次都保存一次,有验证数据时会进行评估
    log_interval_steps=50,        # 每迭代50次打印一次日志 
    lr_decay_epochs=[35, 65],     # step学习率衰减
    save_dir='output/pp_fall_PPYOLOv2',   # 保存路径
    use_vdl=False,                # 用visuadl进行可视化训练记录,此处没有使用
    # resume_checkpoint='output/PPYOLOv2/epoch_4/',  # 重新训练时请把此处改为上一代的地址,并把下一行的pretrain_weights设为None
    # pretrain_weights=None,
    pretrain_weights='COCO',      # 预训练权重
)

5.模型微调

接下来读取预训练相关模型进行微调,以达到良好的效果。

同样设置数据增强以及数据读取器。

In [ ]

# 设置数据增强的方式
# API说明:https://github.com/PaddlePaddle/PaddleX/blob/develop/docs/apis/transforms/transforms.md
train_transforms = T.Compose([
    T.MixupImage(),                                                     # mixup增强
    T.RandomDistort(),                                                  # 随机调整亮度、对比度、饱和度、色调
    T.RandomExpand(),                                                   # 随机扩张图像
    T.RandomCrop(),                                                     # 随机裁剪图像
    T.RandomHorizontalFlip(),                                           # 随机水平翻转
    T.BatchRandomResize(                                                # 训练批次图像的输入大小
        target_sizes=[320, 352, 384, 416, 448, 480, 512, 544, 576, 608]),  # 为了确保大目标和小目标都可以有效学习,我们设置了不通的输入大小
    T.Normalize()                                                       # 对图像进行标准化
])

eval_transforms = T.Compose([
    T.Resize(target_size=480),                                          # 设置验证时输入大小,此处选择了480,平衡了目标大小和计算时间
    T.Normalize()                                                       # 对图像进行标准化
])

In [ ]

%cd
# 定义数据集
# 定义训练和验证所用的数据集
# API说明:https://github.com/PaddlePaddle/PaddleX/blob/release/2.0.0/paddlex/cv/datasets/voc.py
train_dataset = pdx.datasets.VOCDetection(
    data_dir='Fall_Dataset/Fall_Dataset',                   # 数据集存放的路径
    file_list='Fall_Dataset/Fall_Dataset/train_list.txt',   # 训练集划分的txt文件
    label_list='Fall_Dataset/Fall_Dataset/labels.txt',      # 训练数据的类别文件
    transforms=train_transforms,                     # 定义数据转换
    shuffle=True)                                    # 随机打乱数据集

# 验证数据集的构建和上面训练的一样
eval_dataset = pdx.datasets.VOCDetection(
    data_dir='Fall_Dataset/Fall_Dataset',                   # 数据集存放的路径        
    file_list='Fall_Dataset/Fall_Dataset/val_list.txt',     # 验证集划分的txt文件
    label_list='Fall_Dataset/Fall_Dataset/labels.txt',      # 验证数据的类别文件
    transforms=eval_transforms,                      # 定义数据转换
    shuffle=False)                                   # 验证数据集没有必要打乱

微调之前可以看一下模型的初始效果。

In [ ]

# 先使用预训练模型进行测试
# 载入模型
model = pdx.load_model("output/pp_fall_PPYOLOv2/best_model/")
# 进行验证
model.evaluate(eval_dataset, batch_size=1)

接下来进行模型微调的训练。

此处warmup_steps=68最低设置为68.

In [ ]

# API说明:https://github.com/PaddlePaddle/PaddleX/blob/release/2.0.0/paddlex/cv/models/detector.py
# 各参数介绍与调整说明:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html
model.train(
    num_epochs=6,                 # 训练轮次
    train_dataset=train_dataset,  # 训练数据
    eval_dataset=eval_dataset,    # 验证数据
    train_batch_size=16,          # 批大小,在32G显存下可以用16,但是不稳定,容易内存溢出0
    learning_rate=0.0002,        # 学习率
    warmup_steps=68,             # 预热步数,学习率会逐渐增大
    warmup_start_lr=0.0,          # 预热起始学习率
    save_interval_epochs=1,       # 每1轮次都保存一次,有验证数据时会进行评估
    log_interval_steps=50,        # 每迭代50次打印一次日志 
    lr_decay_epochs=[4],     # step学习率衰减 35, 65
    save_dir='output/Finish_PPYOLOv2',   # 保存路径
    use_vdl=False,                # 用visuadl进行可视化训练记录,此处没有使用
    pretrain_weights='output/pp_fall_PPYOLOv2/best_model/model.pdparams',      # 预训练权重
)

之后载入模型,并且在验证集上进行验证。

In [ ]

# 载入模型
model = pdx.load_model("output/Finish_PPYOLOv2/best_model/")
# 进行验证
model.evaluate(eval_dataset, batch_size=1)

6.模型预测

我们已经完成了模型预训练、微调、模型载入,接下来会进行模型预测。在实际生产中,部署完模型文件后,可以使用下面代码进行推理。

In [ ]

%cd
# 导入必要的库
import glob
import numpy as np
import threading
import time
import random
import os
import base64
import cv2
import json
import paddlex as pdx

# 待预测图片路径
# image_name = 'test/电力高架线路_31_31.jpg'
image_name = 'test/test-2.png'

# 预测模型加载
model = pdx.load_model('output/Finish_PPYOLOv2/best_model')

# 读取图片
img = cv2.imread(image_name)
# 预测图片并获得结果
result = model.predict(img)

# 解析预测结果,并保存到txt中
keep_results = []
areas = []
# 获取txt文件
f = open('result.txt', 'a')

# 计数
count = 0
for dt in np.array(result):      # 遍历结果, 并将结果写入到result.txt
    cname, bbox, score = dt['category'], dt['bbox'], dt['score']     # 获取目标框的种类、坐标、置信度
    if score < 0.5:   # 根据置信度过滤掉置信度较低的目标框
        continue
    keep_results.append(dt)    # 将置信度符合的目标框保存下来
    count += 1
    f.write(str(dt) + '\n')    # 写入txt中
    f.write('\n')

print(count)
f.write("the total number is :" + str(int(count)))
f.close()

# 可视化保存
pdx.det.visualize(
    image_name, result, threshold=0.5, save_dir='./output/PPYOLOv2')

生成的结果图片保存至./output/PPYOLOv2文件夹下,可进行查看。

7.总结提高

本项目基于PaddleX进行了目标检测的开发,且用了迁移学习以达到良好的效果,从自身体会上来说PaddleX确实是个挺好的框架,能够快速的开发想要的模型,强烈推荐!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Maven查看项目中的pom依赖

一&#xff0c;背景 Spring项目上线前进行了安全扫描&#xff0c;一些安全漏洞扫出来了&#xff0c;需要做一些处理。扫描的结果如下&#xff1a; [安装包路径:/usr/local/opr-platform/opr-platform.jar -> BOOT-INF/lib/commons-compress-1.19.jar 当前版本:1.19 存在漏洞…

HTML静态网页成品作业(HTML+CSS)——家乡沅陵介绍网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

加速模型训练 GPU cudnn

GPU的使用 在定义模型时&#xff0c;如果没有特定的GPU设置&#xff0c;会使用 torch.nn.DataParallel 将模型并行化&#xff0c;充分利用多GPU的性能&#xff0c;这在加速训练上有显著影响。 model torch.nn.DataParallel(model).cuda() cudnn 的配置&#xff1a; cudnn.…

R可视化:另类的柱状图

介绍 方格状态的柱状图 加载R包 knitr::opts_chunk$set(echo TRUE, message FALSE, warning FALSE) library(patternplot) library(png) library(ggplot2) library(gridExtra)rm(list ls()) options(stringsAsFactors F)导入数据 data <- read.csv(system.file(&qu…

TM7707 SOP-16 双通道全差分输入的24位A/D转换芯片

TM7707是一款24位A/D转换芯片&#xff0c;主要用于低频测量&#xff0c;并能直接将传感器测量的微小信号进行A/D转换。它具有高分辨率、良好的抗噪声性能&#xff0c;以及低电压和低功耗的特点。这些特性使得TM7707非常适合用于以下应用领域&#xff1a; 1.仪表测量&#xff1a…

idea 中配置 Java 注释模板

引言&#xff1a; 在软件工程中&#xff0c;良好的代码注释习惯对于项目的可维护性和可读性至关重要。IntelliJ IDEA&#xff0c;作为一款强大的Java开发IDE&#xff0c;提供了灵活的注释模板配置功能&#xff0c;帮助开发者快速生成规范的代码注释。本文将详细介绍如何在Inte…

OpenHarmony 入门——初识JS/ArkTS 侧的“JNI” NAPI(一)

引言 在Android中Java可以通过JNI 与C/C 通信&#xff0c;而在OpenHarmony 中前段语言目前是ETS&#xff0c;那么OpenHarmony中的 “JNI” 角色是什么呢&#xff1f; 一、NAPI概述 NAPI全称Native Application Programming Interface&#xff08;最新版的文档已经改为Node-A…

AI炒股:批量下载东方财富choice中的投资数据

工作任务&#xff1a;批量下载东方财富choice中的创投数据 在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;写一个关于键盘鼠标自动化操作的Python脚本&#xff0c;具体步骤如下&#xff1a; 打开东方财富choice软件&#xff0c;软件路径为&#xff1…

企业微信hook接口协议,ipad协议http,语音转文字

语音转文字 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信msgid是int要转文字的语音消息id 请求示例 {"uuid":"a4ea6a39-4b3a-4098-a250-2a07bef57355","msgid":1063645 } 返回示例 {"data&…

开发中的常用快捷键

开发中的常用快捷键 文章目录 开发中的常用快捷键一、window11快捷键二、WebStrom 快捷键三、IDEA快捷键四、vscode快捷键五、eclipse快捷键项目备注 一、window11快捷键 快捷切屏 、来回切屏alttab 二、WebStrom 快捷键 常规快捷键和idea差不多 ttab生成vue模版 htab生成ht…

Apache Impala 4.4.0正式发布了!

历时半年多&#xff0c;Impala 4.4终于发布了&#xff01;本次更新带来了不少新功能&#xff0c;受限于篇幅&#xff0c;这里简要列举一些&#xff0c;后续文章再挑重点的进行介绍。 支持更多Iceberg表上的语句 支持对 Iceberg V2 表的 UPDATE 语句&#xff0c;用来更新已有数…

AI全自动生成视频MoneyPrinterTurbo源码

如今&#xff0c;短视频风靡全球&#xff0c;流量已然成为财富的象征。若能实现短视频的全自动生成&#xff0c;岂不是轻而易举地吸引眼球&#xff0c;进而赚取丰厚收益&#xff1f; MoneyPrinter 这一开源项目便能够自动生成短视频&#xff0c;且质量上乘&#xff0c;绝非那些…

WordPress建网站公司 建易WordPress建站

建易WordPress建网站公司是一家专业从事WordPress网站建设、网站维护、网站托管、运营推广和搜索引擎优化(SEO)等服务的公司。建易WordPress建网站公司提供多种服务&#xff0c;包括模板建站和定制网站&#xff0c;并且明码标价&#xff0c;价格透明&#xff0c;竭诚为全国各地…

安装Lubuntu24.04

Lubuntu24.04安装过程与22.04、20.04等完全一致。 记录 01 02 03 04 05 09 给出提示 10 11 12 13 特点 Lubuntu 22.04的特点主要包括以下几点&#xff1a; 轻量级且高效&#xff1a;Lubuntu作为Ubuntu的一个轻量级分支&#xff0c;专注于为低端电脑、老旧电脑或需要最大限…

2023年信息素养大赛小学组C++智能算法复赛真题

今天给大家分享2023年全国青少年信息素养大赛小学组C智能算法挑战赛复赛里面的一套真题&#xff0c;希望有助于大家了解复赛的难度及备考。 其他真题下载&#xff1a;网盘-真题-信息素养大赛

JUC框架-并发容器源码详解

文章目录 并发容器ConcurrentHashMapJDK 1.7 及之前的实现原理JDK 1.8 及之后的实现原理 CopyOnWriteArrayList & CopyOnWriteArraySet工作原理&#xff08;附源码&#xff09;特点适用场景 ConcurrentLinkedQueue数据结构入队列操作ConcurrentLinkedQueue 特性ConcurrentL…

深入分析 Android Activity (八)

文章目录 深入分析 Android Activity (八)1. Activity 的资源管理1.1 使用资源 ID1.2 动态加载资源1.3 资源的本地化1.4 使用 TypedArray 访问资源 2. Activity 的配置变更处理2.1 在 Manifest 文件中声明配置变更2.2 重写 onConfigurationChanged 方法2.3 保存和恢复实例状态 …

MySQL--InnoDB体系结构

目录 一、物理存储结构 二、表空间 1.数据表空间介绍 2.数据表空间迁移 3.共享表空间 4.临时表空间 5.undo表空间 三、InnoDB内存结构 1.innodb_buffer_pool 2.innodb_log_buffer 四、InnoDB 8.0结构图例 五、InnoDB重要参数 1.redo log刷新磁盘策略 2.刷盘方式&…

联想应用商店开发者常见问题FAQ

Phone/Pad应用常见问题 应用上传FAQ Q. 上传apk包时&#xff0c;提示“该包名已存在”如何处理&#xff1f; A&#xff1a;若应用包名出现冲突&#xff0c;请先核实该账号是否已存在该包名产品&#xff0c;若不在该账号下&#xff0c;请进行应用认领。 Q. 应用是否可以授权…

计算机网络——TCP / IP 网络模型

OSI 七层模型 七层模型是国际标准化的一个网络分层模型&#xff0c;大体结构可以分成七层。每层提供不同的功能。 图片来源 JavaGuide 但是这样七层结构比较复杂&#xff0c;不太实用&#xff0c;所以有了 TCP / IP 模型。 TCP / IP 网络模型 TCP / IP 网络模型可以看作是 O…