数据的规范化

  1. Min-Max 归一化(Normalization)
1
2
3
4
5
from sklearn.preprocessing import MinMaxScaler

# 公式: X_scaled = (X - X_min) / (X_max - X_min)
scaler = MinMaxScaler()
X_norm = scaler.fit_transform(X)
  • 结果范围:[0, 1](或可指定其他范围)
  • 保留原始分布形状
  • 对异常值敏感(因为依赖最大/最小值)
  1. Z-score 标准化(Standardization)
1
2
3
4
5
from sklearn.preprocessing import StandardScaler

# 公式: X_scaled = (X - μ) / σ
scaler = StandardScaler()
X_std = scaler.fit_transform(X)
  • 结果:均值 ≈ 0,标准差 ≈ 1
  • 不保证固定范围(可能有 -3 到 +3 甚至更大)
  • 对异常值相对稳健

RobustScaler (鲁棒标准化)与Z-score 标准化(Standardization)对比

StandardScaler (Z-score标准化)

1
2
3
4
5
6
7
8
9
公式: (x - 均值) / 标准差

问题: 均值和标准差都会被异常值严重影响!

例如:
正常值: [100, 150, 200, 180, 220] → 均值 = 170
加入异常值: [100, 150, 200, 180, 220, 6445] → 均值 = 1216 ❌

结果: 所有正常值都变成负数,异常值主导了整个标准化过程
RobustScaler (鲁棒标准化)

1
2
3
4
5
6
7
8
9
10
公式: (x - 中位数) / IQR
其中 IQR = Q3 - Q1 (四分位距)

优势: 中位数和IQR不受异常值影响!

例如:
正常值: [100, 150, 200, 180, 220] → 中位数 = 180, IQR = 70
加入异常值: [100, 150, 200, 180, 220, 6445] → 中位数 = 190, IQR = 70 ✅

结果: 异常值不会扭曲正常数据的标准化结果

Apriori算法

Apriori 算法 是一种用于 频繁项集挖掘(Frequent Itemset Mining)关联规则学习(Association Rule Learning) 的经典算法。

Apriori 基于一个非常重要的性质,叫 先验性质(Apriori Property)

如果一个项集是频繁的,那么它的所有子集也一定是频繁的。 反过来:如果一个项集是非频繁的,那么它的所有超集也一定是非频繁的。

这个性质可以用来 剪枝(prune),避免穷举所有可能的组合。

Apriori 的 候选生成规则

当且仅当它们的前 (k-1) 个项相同,两个频繁 k项集才可以连接生成 (k+1)项集。

剪枝规则只有一句话:

对候选 k-项集 c,只要存在任何一个 (k−1)-子集 不在 Lₖₖ₋₁ 里, 就把 c 整集扔掉,不再给它计数。

为什么这样做合法?

基于 Apriori 向下封闭性(频繁项集的所有子集必频繁)。 若 c 哪怕只有一个 (k−1)-子集是非频繁的,c 自己绝对不可能频繁, 所以没必要浪费一次数据库扫描去数它的支持度。

例子

1
2
3
4
5
6
7
transactions = [
['牛奶', '面包', '黄油'],
['牛奶', '面包'],
['牛奶', '可乐'],
['面包', '黄油'],
['牛奶', '面包', '可乐'],
]

我们的目标是找出 频繁项集(比如哪些商品经常一起出现)。

步骤 1:设定最小支持度(min_support)

假设我们设 min_support = 2,意思是:至少出现在 2 个购物篮中才算“频繁”

支持度(Support) = 包含该项集的交易数 / 总交易数
这里我们直接用“出现次数 ≥ 2”来简化。

步骤 2:生成 1-项集(单个商品)

统计每个商品出现次数:

项集 出现次数
{‘牛奶’} 4
{‘面包’} 4
{‘黄油’} 2
{‘可乐’} 2

全部 ≥2 → 都是频繁 1-项集。

步骤 3:生成 2-项集(两两组合)

从频繁 1-项集中两两组合(注意:只组合那些“所有子集都频繁”的):

可能的组合: - {‘牛奶’, ‘面包’} - {‘牛奶’, ‘黄油’} - {‘牛奶’, ‘可乐’} - {‘面包’, ‘黄油’} - {‘面包’, ‘可乐’} - {‘黄油’, ‘可乐’}

现在统计它们在交易中出现的次数:

项集 出现次数
{‘牛奶’, ‘面包’} 3 ✅
{‘牛奶’, ‘黄油’} 1 ❌
{‘牛奶’, ‘可乐’} 2 ✅
{‘面包’, ‘黄油’} 2 ✅
{‘面包’, ‘可乐’} 1 ❌
{‘黄油’, ‘可乐’} 0 ❌

保留 ≥2 的 → 频繁 2-项集: - {‘牛奶’, ‘面包’} - {‘牛奶’, ‘可乐’} - {‘面包’, ‘黄油’}

步骤 4:生成 3-项集

从频繁 2-项集中尝试组合。
比如:{‘牛奶’,‘面包’} 和 {‘牛奶’,‘可乐’} → 可以组合成 {‘牛奶’,‘面包’,‘可乐’}
但必须检查它的所有 2-项子集是否都在频繁 2-项集中:

  • 子集:{‘牛奶’,‘面包’} ✅
  • 子集:{‘牛奶’,‘可乐’} ✅
  • 子集:{‘面包’,‘可乐’} ❌(之前被剪掉了!)

→ 所以 {‘牛奶’,‘面包’,‘可乐’} 不合法,不能生成。

再试:{‘牛奶’,‘面包’} + {‘面包’,‘黄油’} → {‘牛奶’,‘面包’,‘黄油’}
检查子集: - {‘牛奶’,‘面包’} ✅ - {‘牛奶’,‘黄油’} ❌(之前只有1次) - {‘面包’,‘黄油’} ✅

→ 有一个子集不频繁 → 整个 3-项集被剪枝!

结论:没有频繁 3-项集。

算法结束。

FP-growth

FP-growth(Frequent Pattern Growth)算法,它是一种用于频繁项集挖掘的高效算法

  • Apriori 算法:通过逐层生成候选项集(先1项,再2项…),每次都要扫描整个数据库,效率低。
  • FP-growth不生成候选项集,而是构建一种压缩数据结构——FP树(Frequent Pattern Tree),只需扫描数据库 2 次,效率更高!

FP-growth 的核心思想(分两步)

第一步:构建 FP 树(FP-Tree)

  1. 第一次扫描:统计每个单项的支持度,过滤掉低于 min_support 的项。
  2. 对每条事务
    • 只保留频繁项
    • 按支持度从高到低排序
  3. 第二次扫描:将排序后的事务插入 FP 树。

第二步:从 FP 树中挖掘频繁项集

  • 支持度最低的频繁项开始(称为“后缀模式”)
  • 对每个项,找出它的条件模式基(Conditional Pattern Base)
  • 构建条件 FP 树(Conditional FP-Tree)
  • 递归挖掘,直到树为空

补充.4 关联_FP算法_哔哩哔哩_bilibili

模型评价指标

课后第一,二次作业

考虑表1中的候选3-项集,假设hash函数为h(x)=x mod 4,叶节点最大尺寸为2,构造hash树。

表1. 候选3-项集

编号 项集
1 {1, 4, 5}
2 {1, 5, 9}
3 {3, 5, 6}
4 {1, 2, 4}
5 {1, 3, 6}
6 {3, 5, 7}
7 {4, 5, 7}
8 {2, 3, 4}
9 {6, 8, 9}
10 {1, 2, 5}
11 {5, 6, 7}
12 {3, 6, 7}
13 {4, 5, 8}
14 {3, 4, 5}
15 {3, 6, 8}
IMG_20251027_094247

事务集如表2所示,最小支持度阈值是30%

根据表2的事务集,在格结构上对每个结点添加所有符合条件的字母标记:

N:如果该项集被Apriori算法认为不是候选项集。(一个项集不是候选项集有两种可能的原因,一个是它们没有在候选项集产生步骤产生,另一个是它虽然在候选项集产生步骤产生,但是在剪枝步骤被丢掉)

I:如果计算支持度计数后,该候选项集被认为是非频繁的。

表2. 事务集

TID 项集
1 {a, b, d, e}
2 {b, c, d}
3 {a, b, d, e}
4 {a, c, d, e}
5 {b, c, d, e}
6 {b, d, e}
7 {c, d}
8 {a, b, c}
9 {a, d, e}
10 {b, d}
img
IMG_20251029_160558

判断极大频繁项集(M)

极大频繁项集:是频繁的,且没有频繁的真超集

定义:极大频繁项集(Maximal Frequent Itemset)是一个频繁项集,其所有超集都不是频繁的

检查每个频繁项集:

  • ade:超集有 abde, acde, adde(无效), bcde 等,但所有 4-项集都不频繁 → 所以 ade 是极大
  • bde:同理,超集如 abde, bcde 都不频繁 → bde 是极大
  • 其他频繁项集(如 ad):有超集 ade(频繁)→ 所以 ad 不是极大
  • ab:超集 abd(非频繁),abe(非频繁)→ 但 ab 本身频繁,但有没有频繁超集?没有 → 等等!极大要求:没有频繁的超集。abd 支持度=2(非频繁),abe=2(非频繁)→ 所以 ab 没有频繁超集 → 那 ab 是极大?

判断闭频繁项集(C)

闭项集:一个项集 X 是闭的,如果不存在超集 Y ⊃ X 使得 support(Y) = support(X)

即:它的支持度严格大于所有超集的支持度(或者没有超集具有相同支持度)。

以单项集为例:

单项集:

  • a (5):超集 ad(4), ae(4), ab(3), ade(4) → 所有支持度 <5 → 没有超集支持度=5 → a 是闭
  • b (7):超集 bd(6), be(4), ab(3), bde(4) → 都 <7 → b 是闭
  • c (5):超集 bc(3), cd(4) → 都 <5 → c 是闭
  • d (9):超集 ad(4), bd(6), cd(4), de(6), ade(4), bde(4) → 都 <9 → d 是闭
  • e (6):超集 ae(4), be(4), de(6) → 注意:de 支持度=6,等于 e 的支持度!
    • e ⊂ de,且 support(e)=support(de)=6 → 所以 e 不是闭

课后第三次作业

假设最小支持度阈值为40%,基于以下事务集写出使用FP-growth挖掘频繁项集的过程。

表1. 事务集

TID 项集
1 {a,b,d,e}
2 {b,c,d}
3 {a,b,d,e}
4 {a,c,d,e}
5 {b,c,d,e}
image-20251117112729200
image-20251117112739932

条件 FP 树(Conditional FP-tree)是 FP-growth 算法在“递归”阶段用来压缩“条件模式基”的一棵小 FP 树。 它的构建过程与初始 FP 树几乎一样,只是输入数据换成了“条件模式基”,并且再扫一遍、删低计数、排序、插入即可。下面用 “以 e 为后缀” 的例子把每一步都写出来,你就能完全复现。

一、准备:条件模式基(Conditional Pattern Base)

从主 FP 树里提取所有以 e 结尾的路径,并记录该路径的出现次数:

路径(删去 e 本身) 该路径计数
d b a 2
d a c 1
d b c 1

这就是“e 的条件模式基”。

二、第一次扫描:算单项计数

把上面 3 条记录拆成单项累加:

  • d: 2+1+1 = 4
  • b: 2+0+1 = 3
  • a: 2+1+0 = 3
  • c: 0+1+1 = 2

最小支持度计数仍是 2,因此全部保留(若某项 <2 则直接丢弃)。

三、第二次扫描:排序 + 插入

  1. 排序规则:按全局频繁 1-项集的降序排(即 d≻b≻a≻c≻e)。 因此每条记录内部重新排序:
原路径 排序后路径 计数
d b a d b a 2
d a c d a c 1
d b c d b c 1
  1. 逐条插入,构建“e 的条件 FP 树”:
  • 插入 d b a(计数 2) 根 → d(2) → b(2) → a(2)
  • 插入 d a c(计数 1) 根 → d(3) → a(1) → c(1)
  • 插入 d b c(计数 1) 根 → d(4) → b(3) → c(1)

最终得到的条件 FP 树文字表示:

1
2
3
4
5
6
7
null
└── d(4)
├── b(3)
│ ├── a(2)
│ └── c(1)
└── a(1)
└── c(1)

用户和权限管理

创建四个系统用户

为系统添加以下四个用户:

  • alice
  • bob
  • john
  • mike
1
2
3
useradd -d /home/bob -m bob
useradd -d /home/john -m john
useradd -d /home/mike -m mike

为四个用户设置密码

1
2
3
4
passwd alice
passwd bob
passwd john
passwd mike

创建共享目录

/home 目录下创建一个名为 work 的共享目录:

1
mkdir /home/work

创建用户组并添加成员

创建一个名为 workgroup 的用户组:

1
groupadd workgroup

将用户 alice, bob, john 加入该组:

1
2
3
4
usermod -a -G workgroup alice   # 添加为附加组
usermod -g workgroup alice # 设置为 alice 的主组(primary group)
usermod -a -G workgroup bob
usermod -a -G workgroup john
  • 主组(Primary Group):每个用户有且只有一个,是用户创建文件时默认继承的组
  • 附加组(Supplementary Group):用户可以有多个,用于额外获得某些组的权限,但不会影响新建文件的默认组

查看某个组(group)的信息

1
getent group workgroup

修改共享目录的所有权

/home/work 目录的属主改为 alice,属组改为 workgroup

1
chown alice:workgroup /home/work

修改 work 目录的权限

  • 属组内的用户(workgroup 组成员)→ 完全访问权限(rwx)
  • 属组外的用户 → 没有访问权限(—)
1
chmod ug+rwx,o-rwx /home/work
  • ug+rwx:用户(user)和组(group)都加上读、写、执行权限
  • o-rwx:其他人(others)去掉所有权限(读、写、执行)

尝试以 bob 用户身份在 work 目录下创建文件

切换到 bob 用户:

1
su - bob

创建文件:

1
touch /home/work/bob.txt

查看是否成功:

1
ls -l /home/work/bob.txt

尝试以 john 的身份查看或修改 bob.txt

1
2
3
4
su - john
cat /home/work/bob.txt #查看文件内容
echo "hello" >> /home/work/bob.txt
ls -l /home/work/bob.txt #查看权限

尝试以 mike 的身份查看或修改 bob.txt

1
2
3
4
su - mike
cat /home/work/bob.txt
echo "mike tried" >> /home/work/bob.txt
cd /home/work

进程管理与调试

编写 badproc.sh 脚本

Shell 程序(Shell Script) 就是一系列 Shell 命令的集合,写在一个文件里,可以像程序一样自动、批量、重复执行

1
2
3
4
5
6
7
8
#!/bin/bash
while echo "I'm making files!"
do
mkdir adir
cd adir
touch afile
sleep 10s
done

#!/bin/bash → 指定用 bash 解释器执行

while echo "..."无限循环,每次循环前先打印一句话(等价于 while true; do ... done

循环体内:

  • 创建目录 adir
  • 进入该目录
  • 创建文件 afile
  • 睡眠 10 秒 → 避免太快占满磁盘

添加可执行权限

1
2
3
chmod +x badproc.sh
#验证
ls -l badproc.sh

在后台运行脚本

1
./badproc.sh &

查看进程号

1
ps aux | grep badproc

杀死进程

1
kill 12345

删除脚本运行时创建的目录和文件

1
rm -rf adir

创建源文件 fork.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
pid_t pid;

/* fork another process */
pid = fork();
if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed\n");
exit(-1);
}
else if (pid == 0) { /* child process */
printf("This is child process, pid=%d\n", getpid());
execl("/bin/ls", "ls", NULL); // 执行 ls 命令
printf("Child process finished\n"); // 这句不会打印,除非 execl 失败
}
else { /* parent process */
printf("This is parent process, pid=%d\n", getpid());
wait(NULL); // 等待子进程结束
printf("Child Complete\n");
exit(0);
}
}

编译程序(带调试信息)

1
gcc -g -o fork fork.c

先运行一次,看看效果

1
./fork

gdb 调试 fork 程序

1
2
3
4
5
6
gdb ./fork
#在 fork() 调用前设置
(gdb) set follow-fork-mode child
#设置断点和 catch exec
break main
catch exec

Linux编程环境熟悉

C++编译

创建一个名为 helloworld.cpp 的文件,nano helloworld.cpp

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main(void)
{
cout << "Hello world" << endl;
return 0;
}

Ctrl+O 保存,再按 Ctrl+X 退出 nano

1
2
3
4
#编译程序
g++ -o hello helloworld.cpp
#运行程序
./hello

创建小型函数库

创建源文件 fred.cbill.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* fred.c */
#include <stdio.h>

void fred(int arg)
{
printf("fred: we passed %d\n", arg);
}

/* bill.c */
#include <stdio.h>

void bill(char *arg)
{
printf("bill: we passed %s\n", arg);
}

编译成目标文件(.o)

1
gcc -c bill.c fred.c

创建头文件 lib.h

1
2
3
/* lib.h */
void bill(char *);
void fred(int);

创建主程序 program.c

1
2
3
4
5
6
7
8
9
/* program.c */
#include <stdlib.h>
#include "lib.h" // 引入我们自己写的头文件

int main()
{
bill("Hello World");
exit(0); // 正常退出
}

编译主程序(只编译,不链接)

1
gcc -c program.c

创建静态库 libfoo.a

使用 ar 命令把 bill.ofred.o 打包成一个静态库:

  • ar:archive 工具,用于创建静态库
  • c:创建新库
  • r:将文件插入到库中(如果不存在则添加)
  • v:显示详细信息
1
ar crv libfoo.a bill.o fred.o

链接主程序和静态库

现在我们要把 program.olibfoo.a 链接起来,生成最终可执行文件 program

1
gcc -o program program.o libfoo.a

运行程序

1
./program

作业一

1

题目:

在某个计算机系统中有一台输入机和一台打印机,现有两道程序投入运行,且程序A先运行,程序B后开始运行。

  • 程序A的运行轨迹为:
    计算50ms,打印100ms,再计算50ms,打印100ms。

  • 程序B的运行轨迹为:
    计算50ms,输入80ms,再计算100ms,结束。

问题:

  1. 两道程序运行时,CPU是否空闲等待?____(有空闲等待 / 无空闲等待)
    若有,在哪段时间内等待?_ms - _ms(两个空,第一个填起始时间,第二个填结束时间,仅数字)

  2. 程序A,B是否有等待CPU的情况?
    A:(有等待 / 无等待)
    B:
    (有等待 / 无等待)
    若有,指出发生等待的时刻(若无等待,空格填0)
    (若有两段以上等待时间,用“/”标表示不同等待时间,如0/30和20/40表示0ms-20ms是第一段空闲;30ms-40ms是第二段空闲)
    A:_ms - _ms
    B:___ms - ___ms

IMG_20250917_090110

2

题目:

在单CPU和两台I/O设备(I1和I2)的多道程序设计环境下,同时投入三个作业运行,其执行轨迹如下:

  • Job1:I2(30ms),CPU(10ms),I1(30ms),CPU(10ms)
  • Job2:I1(20ms),CPU(20ms),I2(40ms)
  • Job3:CPU(30ms),I1(20ms)

已知条件:

  • CPU、I1、I2可以并行工作;
  • 优先级从高到低依次为:Job1、Job2、Job3;
  • 优先级高的作业可以抢占优先级低的作业(抢占式调度);
  • 所有作业同时投入运行。

问题:

  1. 每个作业从投入到完成分别所需的时间。
    • Job1:____ ms
    • Job2:____ ms
    • Job3:____ ms
  2. 从作业投入到完成,CPU的利用率是:
    • ____ / ____ = ____%(保留小数点后两位)
  3. I/O设备利用率:
    • I1 利用率:____ / ____ = ____%(保留小数点后两位)
    • I2 利用率:____ / ____ = ____%(保留小数点后两位)
IMG_20250917_090158

3

题目:

在单机系统中,有同时到达的两个程序A、B,若每个程序单独运行,则使用CPU,DEV1(设备1)、DEV2(设备2)的顺序和时间如下表所示。

运行情况 程序A 程序B
CPU 25 20
DEV1 39 50
CPU 20 20
DEV2 20 20
CPU 20 10
DEV1 30 20
CPU 20 45

给定条件:

  1. DEV1和DEV2是不同的I/O设备,它们能够同时工作。
  2. 程序B优先级高于程序A,非抢占式。当程序A占用CPU时,即使程序B需要使用CPU,也不能打断程序A的执行,而应等待。
  3. 当使用CPU之后控制转向I/O设备,或者使用I/O设备之后控制转向CPU,由控制程序执行中断处理,但这段处理时间可以忽略不计。

问题:

  1. 哪个程序先结束?(A / B)
  2. 程序全部执行结束需要多少时间?(____ ms)
  3. 程序全部执行完毕时,CPU利用率是多少?(____%)
  4. A程序等待CPU的累积时间是多少?(____ ms)
  5. B程序等待CPU的累积时间是多少?(____ ms)
IMG_20250917_090145

作业二

1

image-20251010094314155
IMG_20251010_101637
IMG_20251010_101643

2

image-202510101021044143.

IMG_20251010_103349

3

image-20251010110615738
IMG_20251010_110541

作业三

1

有一个盒子里混装了数量相等的黑白围棋子,现在利用自动分拣系统把黑子、白子分开,设分拣系统有两个进程P1和P2,其中进程P1拣白子,进程P2拣黑子。规定每个进程每次拣一子;当一个进程在拣时,不允许另一个进程去拣;当一个进程拣了一子时,必须让另一个进程去拣。试写出进程P1和P2能够正确并发执行的程序。

image-20251111211928061

2

请用信号量和PV操作解决以下问题:桌上有一只盘子,最多可以容纳两个水果,每次仅能放入或取出一个水果。爸爸专向盘子中放苹果(apple),妈妈专向盘子中放橘子(orange),两个儿子专等吃盘子中的桔子,两个女儿专等吃盘子里的苹果。写出爸爸(father)、妈妈(mother)、儿子(son)和女儿(daughter)进程及所需定义的变量和信号量。用PV操作实现爸爸、妈妈、儿子、女儿间的同步与互斥关系。

image-20251111211937533

3

设当前的系统状态如下,此时Available=(1,1,2)

进程 Claim (R1, R2, R3) Allocation (R1, R2, R3) Available (R1, R2, R3)
P1 3, 2, 2 1, 0, 0 1, 1, 2
P2 6, 1, 3 5, 1, 1
P3 3, 1, 4 2, 1, 1
P4 4, 2, 2 0, 0, 2
image-20251111211949933

作业四

1

数组int A[100][100];元素按行存储,在虚拟系统中,采用LRU淘汰算法,一个进程有3页内存空间,每页可以存放200个整数,其中第1页存放程序,假定程序已在内容中,问:

A程序缺页次数为:____次;

B程序缺页次数为:____次。

程序A:

for(int i=0; i<100; i++)
   for(int j=0; j<100; j++)
        A[i,j]=0;

程序B:

for(int j=0;j<100;j++)
   for(int i=0;i<100;i++)   
        A[i,j]=0;

程序 A 分析

代码逻辑

1
2
3
for(int i=0; i<100; i++)    // 外层循环:行
for(int j=0; j<100; j++) // 内层循环:列
A[i][j]=0;

访问顺序:A[0][0], A0, …, A[0][99], A[1][0], …

这是按行访问,与数组的按行存储顺序一致。

缺页计算过程

  1. 访问第0、1行:都在虚拟页0中。
    • 第一次访问 A[0][0] 时,内存为空,发生 1次缺页,调入页0。
    • 后续访问第0行和第1行的剩余199个元素时,都在页0中,直接命中(不缺页)。
  2. 访问第2、3行:都在虚拟页1中。
    • 访问 A[2][0] 时,页1不在内存,发生 1次缺页,调入页1。
    • 后续元素全部命中。
  3. 以此类推
    • 程序按照顺序访问:页0 页1 页2 … 页49。
    • 由于我们有2个物理块,LRU算法会淘汰最久未使用的旧页面,但因为访问是单向顺序向前的,每一页只需要调入一次

结果:共有50个页面,每页调入一次。

Total = 50 次缺页。

程序 B 分析

代码逻辑

1
2
3
for(int j=0; j<100; j++)    // 外层循环:列
for(int i=0; i<100; i++) // 内层循环:行
A[i][j]=0;

访问顺序:A[0][0], A[1][0], A[2][0], …

这是按列访问,即“跳跃式”访问。

缺页计算过程

  1. 分析内层循环(i 从 0 到 99,处理第 j 列)
    • i=0, 1: 访问第0、1行 需要 虚拟页0
    • i=2, 3: 访问第2、3行 需要 虚拟页1
    • i=98, 99: 访问第98、99行 需要 虚拟页49
    • 一轮内循环(i=0~99)会依次访问:页0, 页1, 页2, …, 页49。
  2. 内存状态与置换
    • 我们只有 2个 物理块用于数据。
    • 当访问页0、页1时,填满内存。
    • 当需要页2时,根据LRU,淘汰页0。
    • 当需要页3时,根据LRU,淘汰页1。
    • 因为循环访问的页面数量(50页)远大于物理内存(2页),导致内存中的页面不断被替换(颠簸/Thrashing现象)。
    • 结论:在处理每一列(内层循环)时,需要访问所有50个页面。因为内存存不下,这 50个页面每一次都需要重新从磁盘调入
    • 单列(一次外层循环)产生的缺页次数 = 50次
  3. 结合外层循环
    • 外层循环 j 从 0 到 99,共执行 100次
    • 每次外层循环都要重新把这50个页面遍历一遍。由于上一轮留下的页面(最后访问的页48、49)对下一轮开头需要的页(页0)没有帮助。
    • 总缺页次数 = 每列缺页数 × 列数
    • Total = 50 × 100 = 5000

结果5000 次缺页。

2

在一个请求分页虚存管理系统中,一个程序运行的页面走向是:1 2 3 4 2 1 5 6 2 1 2 3 7 6 3 2 1 2 3 6

分别用FIFO、OPT、和LRU算法,对于分配给程序3个页框的情况,求出缺页异常次数和缺页中断率:

(1)FIFO:缺页异常次数:_____次, 缺页中断率:______%.

(2)OPT:缺页异常次数:_____次, 缺页中断率:______%.

(3)LRU:缺页异常次数:_____次, 缺页中断率:______%.

image-20251205102751318

3

一个页式虚拟存储管理系统中的用户空间为1024KB,页面大小为4KB,内存空间为512KB。已知用户的10、11、12、13号虚页分得的内存页框号为62、78、25、36,试将逻辑地址0BEBCH转换为对应的物理地址: _______H。

名词 英文 所在空间 本质 通俗解释(类比)
页面 (Page) Page 逻辑/虚拟空间 (程序里) 程序被切分成的“块” 客人 (要住店的人)
页号 (Page No.) VPN 逻辑/虚拟空间 页面的编号/索引 客人的身份证号
页框 (Page Frame) Page Frame 物理内存 (硬件里) 内存条被切分成的“格” 酒店的房间 (物理存在的空间)
页块 (Block) Physical Block 物理内存 完全等同于“页框” (别名) 完全等同于“房间”
页框号 (PFN) PFN 物理内存 页框的物理编号 房间号码 (如 302 号房)
页表 (Page Table) Page Table 内存中 (系统管理) 映射表 (记录对应关系) 前台登记簿 (记录谁住哪个房间)
image-20251205104211485

前言

本文讲解matlab与opencv对图像处理的基础操作,代码会有matlab与python两版,可对比学习。

读写

读入图像

matlab

1
I = imread('cameraman.jpg');

python

1
I = cv2.imread('cameraman.jpg', cv2.IMREAD_COLOR)

cv2.IMREAD_COLOR = 强制读成 3 通道 BGR 彩色图。

存入图像

matlab

1
imwrite(J,'cameramanC.jpg');

python

1
cv2.imwrite('cameramanC.jpg', J)

读图并转 double

matlab

1
Image1 = im2double(imread('lotus.jpg'));
  • imread 把图像读成 0-255 的 uint8
  • im2double 把像素值线性缩放到 0–1 浮点,方便后续计算。

为什么要转成double

如果像素是 0–255,你在代码里写 0.299*r 就永远只用到 0.299×255≈76 灰度级,结果会整体偏暗甚至直接截断。

几级灰度的含义是什么

几级灰度”这句话里的“级”就是“台阶”的意思: 把黑→白这段连续亮度等间隔切成多少份,就有多少个离散台阶,叫多少灰度级

  • 2 级灰度 → 纯黑 + 纯白,一共 2 个台阶(1 bit)
  • 8 级灰度 → 0, 36, 73, 109, 146, 182, 219, 255(3 bit)
  • 256 级灰度 → 0–255,共 256 个台阶(8 bit)

uint8 是“Unsigned Integer, 8 bit”的缩写,含义一句话:

无符号、8 位、整型数字,只能放 0–255 的整数。

图像操作

反色

1
J=255-I;

提取通道

matlab

1
2
3
r = Image1(:,:,1);
g = Image1(:,:,2);
b = Image1(:,:,3);

分别提取rgb三个通道

opencv

1
2
matlab:g = Image1(1,1,2)提取G通道的第一个像素点
opencv:g = image[0,0,1]

opencv是从下标0开始

合并通道

NTSC 标准

1
Y = 0.299*r + 0.587*g + 0.114*b;

0.299、0.587、0.114 是 NTSC 在 1953 年定下的“亮度加权系数”,源于人眼三种视锥细胞对 R、G、B 光谱的灵敏度——绿最亮、红次之、蓝最暗

二值化(阈值 0.3)

1
2
BW = zeros(size(Y));   % 先全黑
BW(Y > 0.3) = 1; % 亮度>0.3 的像素置 1(白)

从RGB颜色空间转换成HSI颜色空间

matlab

1
hsi = rgb2hsv(img);

HSI 颜色空间是把一幅彩色图像的每个像素拆成 3 个独立、且更符合人眼感知习惯的物理量:

  1. H(Hue,色调) 用 0°–360° 的“角度”表示“到底是什么颜色”。 例:0°≈红,120°≈绿,240°≈蓝,绕一圈回到红。
  2. S(Saturation,饱和度) 0–1(或 0–100%)表示“颜色有多鲜艳”。 0 → 灰,1 → 最纯、最艳。
  3. I(Intensity,亮度/强度) 0–1(或 0–255)表示“有多亮”,与颜色本身无关,只反映明暗。
  • RGB 是“机器友好”的立方体坐标,但人眼很难从 (R,G,B) 直接说出“颜色、多艳、多亮”。

matlab基本操作

获取图像行列和通道数

1
[N,M,~]=size(r);

把二维矩阵 r 的“行数”赋给 N,“列数”赋给 M

截取图像

1
newimage=img(H/4:H*3/4,W/4:W*3/4,:);

人脸肤色检测

YCrCb 阈值法

  1. Y “Luma”——亮度(Luminance)。 对应人眼最敏感的黑↔︎白信息,决定了你看到的“明暗”。 计算公式近似:

    1
    Y = 0.299·R + 0.587·G + 0.114·B
  2. Cr “Chroma-red”——红色色度。 表示 红色与亮度的差值Cr = R – Y

  3. Cb “Chroma-blue”——蓝色色度。 表示 蓝色与亮度的差值Cb = B – Y

人眼对 绿色最敏感,对 红、蓝最迟钝。 把“绿色”信息扔掉,只保留 R-YB-Y 两个差值,就能 最小化数据量,同时 还能把颜色还原回来

  • 人类肤色在 YCrCb 颜色空间呈明显的“聚类”特性:无论人种如何,Cb、Cr 两个色度分量都落在狭长带状区域。
  • 选取经验区间(Cr ∈ [133,173],Cb ∈ [77,127])直接做 2D 门限,亮度 Y 不参与判断,因此对光照强度变化不敏感,但对色偏敏感。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def skin_YCrCb(img):
#把一张 BGR 彩色图像从“蓝-绿-红”颜色空间转换到“亮度-红度-蓝度”颜色空间(YCrCb)
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
min_YCrCb = np.array([0, 133, 77])
max_YCrCb = np.array([255, 173, 127])
'''
把落在 [min_YCrCb, max_YCrCb] 立方体内的像素标为 255(白),其余标为 0(黑),返回一张单通道掩膜图。
执行过程(逐像素):
取 ycrcb 的一个像素 (y, cr, cb)
检查是否同时满足
min_Y ≤ y ≤ max_Y
min_Cr ≤ cr ≤ max_Cr
min_Cb ≤ cb ≤ max_Cb
满足 → 输出 255;任一通道不满足 → 输出 0
'''
skin = cv2.inRange(ycrcb, min_YCrCb, max_YCrCb)
return skin

HSV 阈值法

肤色聚类现象 大量统计表明:

  • 不同人种、不同光照 的肤色在 RGB 空间里沿对角线“灰度轴”散开 → 亮度影响大。
  • 转到 HSV 后,Hue 坐标紧紧挤在 0-20° 之间(红-橙-黄),S 中等偏高V 中等偏亮
1
2
3
4
5
6
def skin_HSV(img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
low = np.array([0, 30, 60])
high = np.array([20, 150, 255])
skin = cv2.inRange(hsv, low, high)
return skin

椭圆模型法

原理:将RGB图像转换到YCRCB空间,肤色像素点会聚集到一个椭圆区域。先定义一个椭圆模型,然后将每个RGB像素点转换到YCRCB空间比对是否再椭圆区域,是的话判断为皮肤。

image-20250923091302411
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ---------------- 3. 椭圆模型法 ----------------
def skin_ellipse(img):
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
# 如果本地没有模型图,现场画一张 256×256 查找表
ellipse_model = cv2.imread('ellipse_skin_model.png', 0)
if ellipse_model is None:
ellipse_model = np.zeros((256, 256), dtype=np.uint8)
cv2.ellipse(ellipse_model, (113, 155), (23, 15),
43, 0, 360, 255, -1)
cr = ycrcb[:, :, 1].astype(np.uint16) # 防止溢出
cb = ycrcb[:, :, 2].astype(np.uint16)
indices = cr * 256 + cb
skin = np.take(ellipse_model, indices)
return skin

将彩色图像 Image1 的 R、B 通道互换

1
img_swap = img(:,:,[3 1 2]);

MATLAB 中,读取彩色图像(如用 imread)默认是 RGB 顺序

  • 第1通道:Red(红)
  • 第2通道:Green(绿)
  • 第3通道:Blue(蓝)

但在 OpenCV(Python/C++) 中,图像默认是 BGR 顺序

  • 第1通道:Blue
  • 第2通道:Green
  • 第3通道:Red

使用或操作,进行图像的嵌入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
clear; clc; close all;  % 清空所有变量
% 读取两张图片
background = imread('图片2.png');
butterfly = imread('图片1.png');

% 设置缩放比例
scale = 0.5;
small_butterfly = imresize(butterfly, scale, 'bilinear');

% 获取尺寸
[ih, iw, ~] = size(small_butterfly);
x = 200; y = 400;

% 提取背景中对应区域
bg_patch = background(x:x+ih-1, y:y+iw-1, :);

is_black = any(small_butterfly~=0,3); % 蝴蝶主体区域

%只把背景中“蝴蝶主体对应位置”设为 0
% 获取尺寸
[ih, iw, ~] = size(bg_patch);

% 双重循环遍历每个像素
for i = 1:ih
for j = 1:iw
if is_black(i, j) % 如果该位置是蝴蝶主体(非黑)
bg_patch(i, j, 1) = 0; % R 通道设为 0
bg_patch(i, j, 2) = 0; % G 通道设为 0
bg_patch(i, j, 3) = 0; % B 通道设为 0
end
end
end

%现在做 bitor:0 | butterfly = butterfly,背景黑区 | 0 = 背景
patch_bitor = bitor(bg_patch, small_butterfly);

% 写回背景
background(x:x+ih-1, y:y+iw-1, :) = patch_bitor;

% 显示结果
figure;
subplot(1,3,1), imshow(small_butterfly), title(['缩小后的蝴蝶 (', num2str(scale*100), '%)']);
subplot(1,3,2), imshow(bg_patch), title('处理后的背景块(蝴蝶位置清黑)');
subplot(1,3,3), imshow(background), title('最终合成图(bitor)');
image-20251016114415946

利用单尺度和多尺度 Retinex 增强方法实现图像增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
I = imread('gugong1.jpg');

if ndims(I) == 3
I_gray = rgb2gray(I);
else
I_gray = I;
end

I_double = double(I_gray) + eps;

% SSR
sigma = 15;
L_ssr = log(conv2(I_double, fspecial('gaussian', [31, 31], sigma), 'same'));
R_ssr = log(I_double) - L_ssr;
ssr_img = mat2gray(R_ssr);

% MSR
sigmas = [15, 80, 250];
weights = [0.33, 0.34, 0.33];
R_msr = zeros(size(I_double));
for k = 1:length(sigmas)
sigma = sigmas(k);
kernel_size = round(6 * sigma) + 1;
if mod(kernel_size, 2) == 0
kernel_size = kernel_size + 1;
end
L = log(conv2(I_double, fspecial('gaussian', [kernel_size, kernel_size], sigma), 'same'));
R_msr = R_msr + weights(k) * (log(I_double) - L);
end
msr_img = mat2gray(R_msr);

figure;
subplot(1,3,1); imshow(I); title('Original');
subplot(1,3,2); imshow(ssr_img); title('SSR');
subplot(1,3,3); imshow(msr_img); title('MSR');
image-20251104095521796

直方图均衡化计算

image-20251112090645146
image-20251112090723928
image-20251112090714359

5.4代码

opencv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import numpy as np
import matplotlib.pyplot as plt

# --- Step 1: 构造原始图像 ---
gray_levels = np.array([0, 1/7, 2/7, 3/7, 4/7, 5/7, 6/7, 1]) # 原始灰度值 (0~1)
pixel_counts = np.array([560, 920, 1046, 705, 356, 267, 170, 72])
total_pixels = 64 * 64 # 4096

# 将灰度值映射到整数 0~7 用于图像存储
int_gray_levels = np.round(gray_levels * 7).astype(int) # [0,1,2,3,4,5,6,7]

# 创建图像数组
img = np.zeros(total_pixels, dtype=np.uint8)

start_idx = 0
for i, count in enumerate(pixel_counts):
img[start_idx:start_idx + count] = int_gray_levels[i]
start_idx += count

img = img.reshape((64, 64)) # 重塑为 64x64 图像

# --- Step 2: 直方图均衡化 ---
hist, _ = np.histogram(img, bins=8, range=(0, 8))
cdf = np.cumsum(hist) / total_pixels
mapping = np.round(cdf * 7).astype(int)
equalized_img = mapping[img]

# --- Step 3: 可视化对比 ---
plt.figure(figsize=(8, 4))

plt.subplot(1, 2, 1)
plt.imshow(img, cmap='gray', vmin=0, vmax=7)
plt.title('Original')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(equalized_img, cmap='gray', vmin=0, vmax=7)
plt.title('Equalized')
plt.axis('off')

plt.tight_layout()
plt.show()

landingpage的组成

1.开始界面,大概说明产品的定位和作用,让用户可以快速注册后进入

Grammarly: Free AI Writing Assistance

image-20250914185941887
image-20250914194430975

2.展示产品的特点功能和使用范例:1.特点功能这块可以像manus把使用过程中比较有特点的功能截屏,做成下面这样;2.范例部分结构可以参考manus或者lovart垂直滑动的效果,效果可以参考genspackGenspark - AI 幻灯片

image-20250914190352267
image-20250914192344320

专为演示文稿打造的 Gamma | 利用 AI 立即构建演示文稿 | Gamma

image-20250914194941544

3.用户的声音,价格。用户声音我觉得参考lovart就可以,声音这边也可以使用一个滚动的效果

TRAE - Collaborate with Intelligence

image-20250914193756695
image-20250914200906675

我感觉我们的风格应该还是要简约大气

PPtYoda技术调用

这个项目的核心就是基于python-pptx的这个包,其实跟我们不是很符合。

他们ppt生成的逻辑核心是模板,一定要有pptx模板,并带有每个部分的备注,上传后解析,把ppt的各个结构转化成json存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
file = models.FileField(
upload_to=get_template_path,
storage=ppt_storage,
)
created_at = models.DateTimeField(auto_now_add=True)

cover_template = models.JSONField(null=True, blank=True, default=dict)
toc_template = models.JSONField(null=True, blank=True, default=dict)
chapter_L1_template = models.JSONField(null=True, blank=True, default=dict)
chapter_L2_template = models.JSONField(null=True, blank=True, default=dict)
blank_template = models.JSONField(null=True, blank=True, default=dict)

slide_templates = models.JSONField(null=True, blank=True, default=list)
components = models.JSONField(null=True, blank=True, default=list)
sections = models.JSONField(null=True, blank=True, default=dict)

生成ppt时,也是让大模型生成符合这种规范的json,然后通过python-pptx填入。

这样好处确实是解决了使用python-pptx时,生成的ppt结构混乱的问题,但是这样生成的ppt完全依赖于你输入的模板,灵活性上有所缺陷。

后续任务

1.导出speaknotes-pdf后端

2.生成完整presentation

docker部署

1
2
3
4
5
6
7
8
9
docker pull postgres

docker run -d --name pgsql \
-p 5432:5432 \
-e POSTGRES_PASSWORD=123456 \
-v D:\database\postgresql:/var/lib/postgresql/data \
postgres

docker run -d --name pgsql -p 5432:5432 -e POSTGRES_PASSWORD=123456 -v D:\database\postgresql:/var/lib/postgresql/data postgres

docker 运行postgresql 极限简洁教程 - 刘老六 - 博客园

恢复数据库

使用pg_restore

1
2
3
4
5
6
7
# 1. 删除现有数据库
psql -U postgres -c "DROP DATABASE dvdrental;"

# 2. 重建空数据库
psql -U postgres -c "CREATE DATABASE dvdrental;"

pg_restore --dbname=postgresql://postgres:123456@localhost:5432/dvdrental --no-owner /workspace/dvdrental

以下是数据库导入的原理

  1. toc.dat(目录文件)

作用 :Table of Contents,包含备份的元数据信息

内容 :数据库结构、表定义、索引、约束、函数等的描述

格式 :二进制格式,记录了所有数据库对象的信息

  1. 数据文件(3038.dat, 3040.dat, …)

作用 :存储实际的表数据

命名规则 :数字对应数据库对象的 OID(Object Identifier)

  1. restore.sql(可选的SQL脚本)

作用 :包含恢复数据库的 SQL 命令

内容 :CREATE TABLE、INSERT、索引创建等语句

pg_restore 工作流程

  1. 读取 toc.dat
  2. 创建数据库结构
  3. 创建约束和索引

SQLAlchemy ORM

范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
---
config:
layout: elk
---
erDiagram
零件(Part) ||--o{ 库存(Inventory) : "存放"
库房(Warehouse) ||--o{ 库存(Inventory) : "包含"
供应商(Supplier) ||--o{ 采购(Purchase) : "供应"
零件(Part) ||--o{ 采购(Purchase) : "被采购"
库房(Warehouse) ||--o{ 采购(Purchase) : "接收"
库房(Warehouse) }|--|| 职工(Staff) : "组长"
库房(Warehouse) ||--o{ 职工(Staff) : "雇佣"
零件(Part) {
string part_id PK "零件编号"
string name "名称"
decimal unit_price "单价"
string type "类型"
}
供应商(Supplier) {
string supplier_id PK "供应商编号"
string name "名称"
string address "地址"
string phone "电话"
}
库房(Warehouse) {
string warehouse_id PK "库房号"
string address "地址"
}
职工(Staff) {
string staff_id PK "职工号"
string name "姓名"
string gender "性别"
date hire_date "进厂时间"
string title "职称"
string warehouse_id FK "所属库房"
}
库存(Inventory) {
string warehouse_id PK,FK "库房号"
string part_id PK,FK "零件编号"
int stock_quantity "库存数量"
}
采购(Purchase) {
string purchase_id PK "采购单号"
string part_id FK "零件编号"
string supplier_id FK "供应商编号"
string warehouse_id FK "库房号"
date purchase_date "采购日期"
int quantity "进货数量"
decimal actual_price "实际单价"
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# models.py
from sqlalchemy import (
create_engine, Column, String, Integer, DECIMAL, Date, CHAR,
ForeignKey, CheckConstraint, UniqueConstraint
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

# SQLAlchemy 的基类,所有模型类都需要继承这个 Base
# declarative_base() 创建了一个基类,用于定义数据库表结构的映射
Base = declarative_base()

class Part(Base):
__tablename__ = 'part'

part_id = Column(String(20), primary_key=True)
name = Column(String(100), nullable=False)
unit_price = Column(DECIMAL(10, 2), nullable=False)
type = Column(String(50), nullable=False)

# 约束:单价 >= 0
__table_args__ = (
CheckConstraint(unit_price >= 0, name='check_unit_price_positive'),
)

class Supplier(Base):
__tablename__ = 'supplier'

supplier_id = Column(String(20), primary_key=True)
name = Column(String(100), nullable=False)
address = Column(String(200))
phone = Column(String(20))

class Warehouse(Base):
__tablename__ = 'warehouse'

warehouse_id = Column(String(20), primary_key=True)
address = Column(String(200), nullable=False)

# 可选:未来加组长时在此添加
# leader_staff_id = Column(String(20), ForeignKey('staff.staff_id'), unique=True)

class Staff(Base):
__tablename__ = 'staff'

staff_id = Column(String(20), primary_key=True)
name = Column(String(50), nullable=False)
gender = Column(CHAR(1))
hire_date = Column(Date, nullable=False)
title = Column(String(50))
warehouse_id = Column(String(20), ForeignKey('warehouse.warehouse_id'), nullable=False)

# 约束:性别只能是 M/F
__table_args__ = (
CheckConstraint("gender IN ('M', 'F')", name='check_gender'),
)

class Inventory(Base):
__tablename__ = 'inventory'

warehouse_id = Column(String(20), ForeignKey('warehouse.warehouse_id'), primary_key=True)
part_id = Column(String(20), ForeignKey('part.part_id'), primary_key=True)
stock_quantity = Column(Integer, nullable=False)

# 表级约束:确保库存数量不能为负数
# 当尝试插入或更新为负数时会触发数据库错误
__table_args__ = (
CheckConstraint(stock_quantity >= 0, name='check_stock_non_negative'),
)

class Purchase(Base):
__tablename__ = 'purchase'

purchase_id = Column(String(30), primary_key=True)
part_id = Column(String(20), ForeignKey('part.part_id'), nullable=False)
supplier_id = Column(String(20), ForeignKey('supplier.supplier_id'), nullable=False)
warehouse_id = Column(String(20), ForeignKey('warehouse.warehouse_id'), nullable=False)
purchase_date = Column(Date, nullable=False)
quantity = Column(Integer, nullable=False)
actual_price = Column(DECIMAL(10, 2), nullable=False)

# 表级约束:确保采购业务逻辑的合理性
# - 采购数量必须为正数(不能为零或负数)
# - 实际采购价格必须为正数(不能为零或负数)
__table_args__ = (
CheckConstraint(quantity > 0, name='check_quantity_positive'),
CheckConstraint(actual_price > 0, name='check_price_positive'),
)

建立表

1
DATABASE_URL = "postgresql://postgres:123456@localhost:5432/factory_db"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# create_tables.py
from sqlalchemy import create_engine
from models import Base
from dotenv import load_dotenv
import os

load_dotenv()

# 替换为你的 Docker PostgreSQL 地址
DATABASE_URL = os.getenv("DATABASE_URL")

# 创建数据库引擎,用于连接数据库
# create_engine 会根据 DATABASE_URL 创建对应的数据库连接池
engine = create_engine(DATABASE_URL)
Base.metadata.create_all(engine) # 自动建表!
print("✅ 所有表已创建")

各类 RAG 增强技术

如何提高 RAG 管道的性能 | Milvus 文档

可以通过此GitHub 链接获得本文所列主要方法的简单实现。

我们可以根据 RAG 管道各阶段的作用对不同的 RAG 增强方法进行分类。

  • 查询增强:修改和操作 RAG 输入的查询过程,以便更好地表达或处理查询意图。
  • 增强索引:使用多分块、分步索引或多向索引等技术优化分块索引的创建。
  • 检索器增强:在检索过程中应用优化技术和策略。
  • 生成器增强:在为 LLM 生成提示时调整和优化提示,以提供更好的响应。
  • 增强 RAG 管道:在整个 RAG 管道中动态切换流程,包括使用 Agents 或工具来优化 RAG 管道中的关键步骤。

接下来,我们将介绍每个类别下的具体方法。

查询增强

共有四种方式:假设问题、假设文档嵌入、子查询和回溯提示。接下来我将选取几个具体说明。

HyDE(假设文档嵌入)

HyDE 是假设文档嵌入的缩写。它利用 LLM 制作一个“假设文档*”或虚假*答案,以回应没有上下文信息的用户查询。然后,这个假答案会被转换成向量嵌入,并用于查询向量数据库中最相关的文档块。随后,向量数据库会检索出 Top-K 最相关的文档块,并将它们传送给 LLM 和原始用户查询,从而生成最终答案。

image-20250908101159982

这种方法在解决向量搜索中的跨域不对称问题方面与假设问题技术类似。不过,它也有缺点,如增加了计算成本和生成虚假答案的不确定性。

创建子查询

当用户查询过于复杂时,我们可以使用 LLM 将其分解为更简单的子查询,然后再将其传递给向量数据库和 LLM。让我们来看一个例子。

想象一下,用户会问*“Milvus 和 Zilliz Cloud 在功能上有什么不同?*”这个问题相当复杂,在我们的知识库中可能没有直接的答案。为了解决这个问题,我们可以将其拆分成两个更简单的子查询:

  • 子查询 1:“Milvus 有哪些功能?”
  • 子查询 2:“Zilliz Cloud 有哪些功能?”

有了这些子查询后,我们将它们全部转换成向量嵌入后发送给向量数据库。然后,向量数据库会找出与每个子查询最相关的 Top-K 文档块。最后,LLM 利用这些信息生成更好的答案。

image-20250908122807696

增强索引

增强索引是提高 RAG 应用程序性能的另一种策略。让我们来探讨三种索引增强技术:自动合并文档块,构建分层索引,混合检索和重新排名

构建分层索引

在创建文档索引时,我们可以建立两级索引:一级是文档摘要索引,另一级是文档块索引。向量搜索过程包括两个阶段:首先,我们根据摘要过滤相关文档,随后,我们在这些相关文档中专门检索相应的文档块。

image-20250908123520672

在涉及大量数据或数据分层的情况下,例如图书馆 Collections 中的内容检索,这种方法证明是有益的。

混合检索和重新排名

混合检索和重排技术将一种或多种辅助检索方法与向量相似性检索相结合。然后,Reranker会根据检索结果与用户查询的相关性对检索结果重新排序。

常见的补充检索算法包括基于词频的方法(如BM25)或利用稀疏嵌入的大模型(如SPLADE)。重新排序算法包括 RRF 或更复杂的模型,如Cross-Encoder(类似于 BERT 的架构)。

image-20250908123544207

改进检索器

改进 RAG 系统中的检索器组件也能改进 RAG 应用。让我们来探讨一些增强检索器的有效方法:句子窗口检索,元数据过滤

生成器增强

让我们通过改进 RAG 系统中的生成器来探索更多 RAG 优化技术:压缩 LLM 提示,调整提示中的块顺序

调整提示中的块顺序

在论文Lost in the Middle“中,研究人员观察到,LLMs 在推理过程中经常会忽略给定文档中间的信息。相反,他们往往更依赖于文档开头和结尾的信息。

根据这一观察结果,我们可以调整检索知识块的顺序来提高答案质量:在检索多个知识块时,将置信度相对较低的知识块放在中间,而将置信度相对较高的知识块放在两端。

image-20250908124338278

增强 RAG 管道

我们还可以通过增强整个 RAG 管道来提高 RAG 应用程序的性能。

自我反思

这种方法在人工智能 Agents 中融入了自我反思的概念。那么,这种技术是如何工作的呢?

一些最初检索到的 Top-K 文档块是模棱两可的,可能无法直接回答用户的问题。在这种情况下,我们可以进行第二轮反思,以验证这些文档块是否能真正解决查询问题。

我们可以使用高效的反思方法(如自然语言推理(NLI)模型)进行反思,也可以使用互联网搜索等其他工具进行验证。

image-20250908124655326

使用代理进行查询路由选择

有时,我们不必使用 RAG 系统来回答简单的问题,因为它可能会导致更多的误解和对误导信息的推断。在这种情况下,我们可以在查询阶段使用代理作为路由器。这个 Agents 会评估查询是否需要通过 RAG 管道。如果需要,则启动后续的 RAG 管道;否则,LLM 直接处理查询。

image-20250908124734144

Agents 可以有多种形式,包括 LLM、小型分类模型,甚至是一组规则。

通过根据用户意图路由查询,可以重新定向部分查询,从而显著提高响应时间,并明显减少不必要的噪音。

我们可以将查询路由技术扩展到 RAG 系统内的其他流程,例如确定何时利用网络搜索等工具、进行子查询或搜索图片。这种方法可确保 RAG 系统中的每个步骤都能根据查询的具体要求进行优化,从而提高信息检索的效率和准确性。

奇偶校验法

海明校验码(略有涉及)

原码补码乘除法

布斯算法

布斯算法是一种用于补码乘法的算法。 它的优点是:

  • 直接处理补码(无需转成原码)
  • 减少加减次数(尤其当乘数中有连续1时)
  • 统一处理正负数

💡 传统乘法:遇到1就加被乘数;遇到0就跳过。 布斯算法:看相邻两位的变化,决定是否加/减。

布斯算法观察乘数的相邻两位(包括一个额外的最低位 Q₋₁ = 0),根据以下规则操作:

当前位 Q₀ 前一位 Q₋₁ 操作 原因(数学本质)
0 0 无操作 连续0,无需累加
1 1 无操作 连续1,已在前面处理过
0 1 + 被乘数 从1→0,表示一个“正的块”结束 → 加一次
1 0 − 被乘数 从0→1,表示一个“负的块”开始 → 减一次

布斯算法在右移时,必须使用「算术右移(Arithmetic Right Shift)」,即:

  • 符号位(最高位)保持不变
  • 左边补的是符号位的值(正数补0,负数补1)

加减交替法

原码除法 恢复余数法和不恢复余数法(加减交替法) 计组_哔哩哔哩_bilibili

整数除法处理过程_哔哩哔哩_bilibili

整数不恢复余数除法中,被除数通常要进行位扩展

不恢复余数除法(加减交替法)*中,**整数除法**和*小数除法的核心算法是一样的,都是根据余数的正负决定商位和下一步操作。但它们在初始设置、精度控制、终止条件、结果处理等方面有明显区别。

定点整数的除法 - 知乎

「小白debug」如何用开关造出一台计算机_哔哩哔哩_bilibili

交叉熵

交叉熵(Cross-Entropy)主要用于衡量两个概率分布之间的差异。在分类任务中,它被广泛用作损失函数,来评估模型预测结果与真实标签之间的“不匹配程度”。

  1. 信息量(Information Content)

一个事件 ( x ) 发生的概率为 ( p(x) ),其信息量定义为: [ I(x) = -p(x) ] - 概率越小的事件,发生时带来的信息量越大(比如“太阳从西边升起”)。 - 单位通常是 比特(bit)(以 2 为底)或 纳特(nat)(以自然对数 ( e ) 为底)。

  1. 熵(Entropy)

熵是一个概率分布的平均信息量,表示该分布的不确定性: [ H(p) = -_{x} p(x) p(x) ] - 熵越大,不确定性越高(比如公平硬币的熵比偏硬币大)。

  1. 交叉熵(Cross-Entropy)

现在有两个分布: - 真实分布 ( p(x) )(比如真实标签) - 模型预测分布 ( q(x) )(比如神经网络输出的概率)

交叉熵衡量的是:当我们用分布 ( q ) 来编码来自分布 ( p ) 的事件时,平均需要多少比特

数学定义为: [ H(p, q) = -_{x} p(x) q(x) ]

🔍 注意:交叉熵 ≠ 熵。
- 熵:( H(p) = -p p )
- 交叉熵:( H(p, q) = -p q )

Sigmoid的交叉熵损失函数

Sigmoid 的交叉熵损失函数(通常称为 二元交叉熵损失,Binary Cross-Entropy Loss)是用于二分类问题中,结合 Sigmoid 激活函数使用的损失函数。

  • 在二分类任务中,模型输出一个实数值 ( z )(logit)。

  • 通过 Sigmoid 函数将其映射到概率区间 ([0, 1]): [ = (z) = ] 其中 () 表示预测为正类(标签为 1)的概率。

  • 真实标签 ( y {0, 1} )。

二元交叉熵损失(Binary Cross-Entropy, BCE)

对于单个样本,损失函数定义为:

[ (y, ) = -]

其中: - 若 ( y = 1 ),损失为 ( -() ) - 若 ( y = 0 ),损失为 ( -(1 - ) )

Softmax 的交叉熵损失函数

Softmax 函数

给定 logits 向量 ( = [z_1, z_2, …, z_C] )(C 为类别数),Softmax 输出预测概率:

[ _i = (z_i) = ]

交叉熵损失(单个样本)

真实标签为 one-hot 向量 ( = [y_1, y_2, …, y_C] ),其中只有真实类别位置为 1,其余为 0。

损失函数为:

[ = -_{i=1}^{C} y_i (_i) ]

由于 ( y_i ) 只有一个为 1(设真实类别为 ( c )),上式简化为:

[ = -(_c) = -( ) ]

进一步化简:

[ = -z_c + ( _{j=1}^{C} e^{z_j} ) ]

Sigmoid和Softmax区别

1.Sigmoid(逐元素

对输入 ( x )(标量或向量中的每个元素): [ (x) = (0, 1) ]

📌 如果输入是向量 ( = [x_1, x_2, …, x_n] ),则输出: [ [(x_1), (x_2), …, (x_n)] ] 每个元素独立计算,彼此无关

2. Softmax(整体归一化)

对输入向量 ( = [z_1, z_2, …, z_C] ): [ (z_i) = (0, 1) ]

✅ 满足:( _{i=1}^{C} (z_i) = 1 )

应用场景对比

Sigmoid 适用场景:

多标签分类(Multi-label)

  • 每个样本可属于多个类别(如一张图同时有“猫”和“狗”)
  • 输出 C 个独立概率,每个用 Sigmoid 判断是否属于该类
  • 例如:输出 [0.9, 0.2, 0.8] 表示属于类别 0 和 2

Softmax 适用场景:

多分类问题(Multi-class, 互斥)

  • 每个样本只属于一个类别(如 MNIST 手写数字 0~9)
  • 输出 C 个概率,和为 1,最大值对应预测类别
  • 例如:输出 [0.1, 0.7, 0.2] 表示最可能是类别 1

简单来说

  • “多选一” → Softmax
  • “可多选” 或 “是/否” → Sigmoid

为什么多分类要用 Softmax,而不是对每个类别用 Sigmoid 再取最大值?

问题在于 损失函数

如果你用 Binary Cross-Entropy (BCE)(Sigmoid 的配套损失):

  • 损失 = - [y₁·log(p₁) + y₂·log(p₂) + y₃·log(p₃)]
  • 对于标签 [0, 1, 0],损失只惩罚“狗”的预测(希望 p₂→1),但不惩罚“猫”和“鸟”是否太高
  • 结果:模型可能输出 [0.9, 0.95, 0.8],虽然选对了“狗”,但对其他类也过于自信,泛化差。

Softmax + Cross-Entropy

  • 损失 = -log(p₂)
  • 但因为 p₂ = e^{z₂}/(e^{z₁}+e^{z₂}+e^{z₃})要让 p₂ 变大,必须让 z₂ 相对于 z₁、z₃ 更大
  • 所以模型会主动压制错误类别的 logit,学习更清晰的决策边界。

参考资料

《动手学深度学习》 — 动手学深度学习 2.0.0 documentation

课程安排 - 动手学深度学习课程

第一章作业

1

假设同一套指令集用不同的方法设计了两种机器 M1 和 M2。机器 M1 的时钟周期为 0.8ns,机器 M2 的时钟周期为 1.2ns。某个程序 P 在机器 M1 上运行时的 CPI 为 4,在 M2 上的 CPI 为 2。对于程序 P 来说,哪台机器的执行速度更快?快多少?

image-20251011140015756

2

假定编译器对某段高级语言程序编译生成两种不同的指令序列 S1 和 S2,在时钟频率为 500MHz 的机器 M 上运行,目标指令序列中用到的指令类型有 A、B、C 和 D 四类。每类指令在 M 上的 CPI 和两个指令序列所用的各类指令条数如下表所示。

指令类型 A B C D
各指令的 CPI 1 2 3 4
S1 的指令条数 5 2 2 1
S2 的指令条数 1 1 1 5
image-20251011140100523

3

假定机器 M 在运行程序 P 的过程中,共执行了 500×10⁶ 条浮点数指令、4000×10⁶ 条整数指令、3000×10⁶ 条访存指令、1000×10⁶ 条分支指令,这 4 种指令的 CPI 分别是 2、1、4、1。若要使程序 P 的执行时间减少一半,浮点指令的 CPI 应如何改进?若要使程序 P 的执行时间减少一半,访存指令和分支指令的 CPI 应如何改进?若浮点指令和整数指令的 CPI 减少 20%,访存指令和分支指令的 CPI 减少 40%,则程序 P 的执行时间会减少多少?

image-20251011140106600
image-20251011140123204

第二章作业

1

假定某计算机的总线采用奇校验,每8位数据有一位校验位,若在32位数据线上传输的信息是 8F 3C AB 96H,则对应的4个校验位应为什么? 若接收方收到的数据信息和校验位分别为87 3C AB 96H0101B,则说明发生了什么情况,并给出验证过程。

第一部分:计算原始数据 8F 3C AB 96H 对应的 4 个校验位

前提条件: - 使用奇校验:每个字节(8位)中,1的个数必须是奇数。 - 每8位数据配1位校验位,所以32位数据分成4个字节,对应4个校验位。 - 数据是十六进制:8F 3C AB 96H

我们需要对每一个字节单独计算其奇校验位。

第一步:把每个十六进制字节转换成二进制

  • 8F H = 1000 1111
  • 3C H = 0011 1100
  • AB H = 1010 1011
  • 96 H = 1001 0110

第二步:数每个字节中“1”的个数,然后确定校验位

奇校验规则: 如果当前字节中“1”的个数是偶数,则校验位设为 1,使总数变为奇数;如果是奇数,则校验位设为 0,保持奇数。

我们逐个来看:

  1. 字节 8F = 1000 1111
    • 数“1”:位置0, 4,5,6,7 → 共 5个1 → 是奇数
    • 所以校验位 = 0
  2. 字节 3C = 0011 1100
    • 数“1”:位置2,3,4,5 → 共 4个1 → 是偶数
    • 所以校验位 = 1
  3. 字节 AB = 1010 1011
    • 数“1”:位置0,2,4,6,7 → 共 5个1 → 是奇数
    • 所以校验位 = 0
  4. 字节 96 = 1001 0110
    • 数“1”:位置0,3,5,6 → 共 4个1 → 是偶数
    • 所以校验位 = 1

所以,对应的4个校验位是:0 1 0 1,即 0101B

第二部分:接收方收到的数据是 87 3C AB 96H 和校验位 0101B,发生了什么?验证过程

现在接收方收到: - 数据:87 3C AB 96H - 校验位:0101B

我们怀疑有错误,因为原始发送的是 8F,但收到的是 87 —— 很可能第一个字节出错了!

我们来逐字节验证奇校验是否成立

第一步:将接收到的数据转为二进制

  • 87 H = 1000 0111
  • 3C H = 0011 1100 (没变)
  • AB H = 1010 1011 (没变)
  • 96 H = 1001 0110 (没变)

校验位:0101B → 对应四个字节的校验位分别是:第1字节 0,第2字节 1,第3字节 0,第4字节 1

第二步:验证每个字节 + 校验位 是否满足奇校验

注意:我们验证的是“数据位 + 校验位”一共9位中,1的个数是否为奇数。

  1. 第一字节 87 + 校验位 0
    • 数据位:1000 0111 → “1”的个数:位置0, 5,6,7 → 4个1
    • 加上校验位 0 → 总共还是 4个1 → 是偶数
    • 不满足奇校验!→ 出错!
  2. 第二字节 3C + 校验位 1
    • 数据位:0011 1100 → “1”的个数:4个(偶数)
    • 加上校验位 1 → 总共 4+1=5 → 奇数
    • 正确
  3. 第三字节 AB + 校验位 0
    • 数据位:1010 1011 → “1”的个数:5个(奇数)
    • 加上校验位 0 → 总共 5 → 奇数
    • 正确
  4. 第四字节 96 + 校验位 1
    • 数据位:1001 0110 → “1”的个数:4个(偶数)
    • 加上校验位 1 → 总共 5 → 奇数
    • 正确

结论:只有第一个字节校验失败!说明在传输过程中,第一个字节发生了错误。

进一步分析:哪里出错了?

原始发送的是 8F H = 1000 1111

接收的是 87 H = 1000 0111

对比:

1
2
3
4
原始: 1 0 0 0  1 1 1 1
接收: 1 0 0 0 0 1 1 1

第5位(从左数第5位,或从右数第4位)由1变成了0

所以,第5位发生了翻转错误(bit flip)

🧠 总结

  1. 原始数据 8F 3C AB 96H 的校验位是 0101B
  2. 接收方收到 87 3C AB 96H0101B 后,发现第一个字节校验失败,说明该字节在传输中发生了错误(具体是第5位由1变0)。
  3. 奇校验能检测单个比特错误,但不能纠正它,也不能检测偶数个比特错误。
IMG_20251029_093747

第三章作业

已知 x = 10, y = -6,采用 6位机器数表示。请按如下要求计算并把结果还原成真值。

(1)求 [x + y]补[x - y]补 (2)用原码一位乘法计算 [x × y]原 (3)用布斯算法计算 [x × y]补 (4)用加减交替法计算 [x / y]原 的商和余数

image-20251029194504411
image-20251029185146990
IMG_20251031_171709

第七章作业

1

image-20251126152551121

DRAM 的全称是 Dynamic Random Access Memory,中文叫 动态随机存取存储器

它是计算机中最常用的主存储器(Main Memory / RAM),也就是我们平时常说的“内存条”上的核心芯片。

为什么 DRAM 单元要刷新?(物理原理)

核心原因:漏电。

你可以把 DRAM 的存储单元想象成一个底部有个小针眼的“水桶”(电容),而数据就是“水”(电荷)。

  • 存入数据 “1”: 你给桶里倒满水。
  • 存入数据 “0”: 你把桶倒空。
  • 问题来了: 因为那个小针眼(晶体管的漏电流),满桶的水会慢慢往下漏。
    • 如果你不管它,过一会儿(比如 2ms 后),桶里的水漏干了,原本的 “1” 就变成了 “0”,数据就丢了。

这里最容易混淆的是两个时间概念,必须分清楚:

  1. 最大刷新时间(题目中的 2ms):
    • 这是死线 (Deadline)
    • 意思是:对于每一个水桶,管理员最迟必须每隔 2ms 回来检查一次。如果超过 2ms 没管它,水就漏光了。
  2. 产生刷新信号的间隔(题目要求的答案):
    • 这是管理员处理下一行水桶的频率
    • 关键点: 管理员不能一次性同时刷新几百万个水桶(那样电路会过载,电流太大)。他必须一批一批地(一行一行地)轮流刷新。
adf3c730cec214bcbd0b690a8632d0e1

2

image-20251127234744026

ROM 的全称是 Read-Only Memory,中文叫 只读存储器

核心特点:

  • 字面意思: “Read-Only” 意味着只能读,不能写(或者说在正常工作状态下不能随意写入)。
  • 实际特性(最重要):非易失性 (Non-Volatile)
    • RAM (DRAM/SRAM): 一断电,数据立马消失。
    • ROM: 断电后,数据依然保存。哪怕你把电脑关机放一年,ROM 里的数据也不会丢。

为什么 ROM 总是放在内存地址的最前面(从 0 开始)?

  1. 开机第一件事: 当你按下电脑电源键时,RAM(内存条)里是空的(全是乱码或 0),CPU 无法从 RAM 里读取指令。
  2. 启动引导: CPU 设计为通电后自动去地址 0000H(或其他固定地址)找第一条指令。
  3. 固化程序: 我们必须在这个位置放一个断电也不丢数据的存储器(ROM),里面存着电脑的启动程序(BIOS/UEFI)
    • 它负责检测硬件、初始化系统,然后把操作系统(Windows/Linux)从硬盘搬到 RAM 里。

$\overline{\text{MREQ}} = 0$(即处于低电平/有效状态)时,它的主要作用是通知系统,CPU 当前正打算对内存(RAM 或 ROM)进行访问。

$R/\overline{W}$ (Read / Write Control)

  • 角色:这是 CPU 发出的控制信号(总线信号)。
  • 含义:它告诉整个系统,CPU 当前想要控制数据流向的方向。
  • 状态逻辑
    • 高电平 (1) = Read (读):CPU 准备从总线上接收数据(数据流向:内存 CPU)。
    • 低电平 (0) = Write (写):CPU 准备向总线上发送数据(数据流向:CPU 内存)。

通俗理解:这是 CPU 在喊话:“大家都听好了,我现在是要‘收东西’(Read)还是要‘发东西’(Write)。”

$\overline{WE}$ (Write Enable)

  • 角色:这是 RAM 芯片上的输入引脚(控制端)。
  • 含义:允许写入信号。它决定了内存芯片是否允许将数据总线上的电平记录到存储单元中。
  • 符号含义:顶部的横线表示低电平有效 (Active Low)
  • 状态逻辑
    • 低电平 (0) = 允许写入:内存芯片打开“大门”,将数据总线上的数据存入当前地址指向的单元。
    • 高电平 (1) = 禁止写入:内存芯片处于读取模式(配合其他信号)或待机模式,不会修改内部存储的数据。

通俗理解:这是内存芯片身上的一个开关。只有把这个开关拉下来(变低),内存才会乖乖地把数据“记下来”。

d11ce94cd3db7edfb1cd4a3fa6dadf39

3

image-20251127234735106
91e3e3a11a15b2854641380b834e3326

4

image-20251127234807626
DECFBD2ECFC7C36C6D9BB49DFD0C15C2

5

image-20251127234908094
bc48f7ff158bdc49537455d0a2d5efbd
d4cd56bf63b45d5da02ce2f13eb06edf

6

image-20251127234930226
90b6c6f2d8d12022fa0bfa2d8bbcf775

第四章作业

1

哪些寻址方式下的操作数在寄存器中?哪些在存储器中?

  • 操作数(Operand):指令执行时要处理的数据。
  • 寻址方式(Addressing Mode):指明操作数在哪里(寄存器?内存?立即数?)以及如何找到它。
  • 寄存器(Register):CPU内部的高速存储单元。
  • 存储器(Memory):主存(RAM),速度比寄存器慢,但容量大。
寻址方式 操作数位置 举例
寄存器寻址 ✅ 寄存器 MOV AX, BX
立即寻址 ❌ 不在寄存器/存储器(在指令中) MOV AX, 5
直接寻址 ✅ 存储器 MOV AX, [2000H]
寄存器间接寻址 ✅ 存储器 MOV AX, [BX]
寄存器相对寻址 ✅ 存储器 MOV AX, [BX + 10]
基址变址寻址 ✅ 存储器 MOV AX, [BX + SI]
相对基址变址寻址 ✅ 存储器 MOV AX, [BX + SI + 5]

⚠️ 注意:立即数(Immediate) 不是寄存器也不是存储器,它直接嵌入在指令中。

2

什么是 RISC?

RISC = Reduced Instruction Set Computer(精简指令集计算机)

与之相对的是 CISC(Complex Instruction Set Computer,复杂指令集计算机),比如 Intel x86。

1️⃣ 指令集精简(Reduced Instruction Set)

  • 指令数量少(通常几十到几百条),每条指令功能单一。
  • 指令长度固定(如 32 位),便于流水线处理。

💡 类比:就像厨房里只有几把多功能刀具,每把刀只做一件事,但做得又快又好。

例子:

  • RISC:ADD R1, R2, R3 → 把 R2 和 R3 相加,结果存入 R1
  • CISC:MOV [BX+SI+10], AX → 一条指令完成地址计算+内存写入

2️⃣ 指令执行周期短(Single-Cycle Execution)

  • 大多数指令在一个时钟周期内完成。
  • 通过简化指令和硬件设计实现。

3️⃣ 大量通用寄存器(Large Register File)

  • 寄存器数量多(如 ARM 有 16 个通用寄存器,RISC-V 有 32 个)。
  • 减少对内存的访问,提高速度。

4️⃣ 采用流水线技术(Pipelining)

  • 指令执行分为多个阶段(取指、译码、执行、访存、写回),并行处理。
  • RISC 指令简单统一,非常适合流水线。

5️⃣ 加载/存储架构(Load/Store Architecture)

  • 只有 LOADSTORE 指令可以访问内存。
  • 其他运算指令只能在寄存器之间进行。

3

假定某计算机中有一条转移指令,采用相对寻址方式,共占两字节,第一字节是操作码,第二字节是 相对位移量(用补码表示),CPU每次从内存只能取一字节。假设执行到某转移指令时 PC的内容为200, 执行该转移指令后要求转移到100开始的一段程序执行,则该转移指令第二字节的内容应该是多少?

QQ20251206-170833

4

4.假设地址为1200H的内存单元中的内容为12FCH,地址为12FCH的内存单元的内容为38B8H,而 38B8H单元的内容为88F9H。说明以下各情况下操作数的有效地址是多少? (1)操作数采用变址寻址,变址寄存器的内容为252,指令中给出的形式地址为1200H。 (2)操作数采用一次间接寻址,指令中给出的地址码为1200H。 (3)操作数采用寄存器间接寻址,指令中给出的寄存器编号为8,8号寄存器的内容为1200H。

QQ20251206-170838

5

6.某计算机指令系统采用定长指令字格式,指令字长16位,每个操作数的地址码长6位。指令分二 地址、单地址和零地址3类。若二地址指令有k2条,零地址指令有k0条,则单地址指令最多有多少条?

QQ20251206-170843
0%