——淇澳岛候鸟鉴定的算法设计
每年 10 月到次年 4 月,这片红树林是候鸟南下路上的第一个大驿站——而在成千上万只鸟里,有一种少到一只都不能认错
广东珠海淇澳—担杆岛省级自然保护区就在珠海高新区淇澳岛的西北部。它入选"广东十佳观鸟胜地",也正好卡在"东亚—澳大利西亚"这条全球候鸟迁徙通道上。
🌱 1998 年这里只剩 32 公顷原生红树林,互花米草几乎占满了滩涂。清草、种树、守了二十多年,才长回今天的规模。
退潮时,红树林浅滩上密密麻麻全是觅食的鸟。下面这六种,是保护区记录里最常出现的面孔——点一下,看图鉴卡片:
除此之外还常见:池鹭、泽鹬、黑翅长脚鹬、红脚鹬、红嘴鸥,以及成百上千只排成一条"黑线"的野鸭(琵嘴鸭、凤头潜鸭、赤颈鸭、罗纹鸭)。赶上迁徙高峰,一片滩涂上能同时落下几千只。
有些鸟,多到你懒得数;有些鸟,少到一只都不能错。
黑脸琵鹭目前正处在"数量创新高、但远没到安全"的状态——它是全球濒危物种保护里少见的成功案例,但恢复高度依赖人为持续守护。
鉴定出黑脸琵鹭并且准确监测数量就非常重要。
名字就是它的长相:脸是黑的,嘴像琵琶。
觅食时它把汤匙嘴伸进浅水,左右扫动"捞"鱼虾——这个动作是它在滩涂上最好认的招牌。
⚠️ 别被"黑脸"骗了:那不是羽毛,是裸露的皮肤——按羽色算,它仍然是全白。后面讲"羽色编码"时还会碰到。
人眼能做到,但要一只一只看、一只一只比对。一天看几十只可以,几千只就不行了。
而这个时候,监测站的相机其实一直在拍。
数据已经堆在硬盘里,缺的是"鉴定"这一步
保护区在滩涂边架了高清相机和红外相机,涨潮落潮都在拍。鸟一落下来,照片就进了硬盘。
卡点就在这儿:拍照自动化了,测量也开始自动化了,唯独"看一眼,说出它是什么鸟"这一步,还得靠人。
💡 有些地方已经在做"鸟脸识别"了,但那是第六章的机器学习。今天我们要做的,是这件事里能被算出来的那一半。
相机配上测量模块后,一只鸟落下来,会自动生成这样一条记录:
| 编号 | 体长 | 喙长 | 喙形 | 羽色 | 物种=? |
|---|---|---|---|---|---|
| B001 | 74.0 | 17.0 | 2 | 1 | 空着 |
| B002 | 58.0 | 8.0 | 1 | 1 | 空着 |
| B003 | 44.0 | 7.5 | 3 | 3 | 空着 |
前四列是机器测出来的数,最后一列是我们要填的答案。程序要干的活,就是把这四列数字变成第五列。
机器读不懂"汤匙""雪白",它只认 2 和 1。所以这些形状和颜色,必须先被编成号码——这一步叫编码。
按最保守的估计:这 1 500 条记录,一条平均只花 1 分钟(举镜、比对、写名字),派 2 个人:
不吃不喝、不休息、一次都不看错,也要干到半夜。而这只是今天的量——明天早上,硬盘里又躺着一模一样多的一批。
人会累、会看走眼、会下班;相机不会。所以这一列空着,不是人不努力,是这份活本身不该由人来做。
要让机器替我们把那一列填上——这就是接下来要做的事。
计算机不会猜,它只会照着你说的做
概念有四个要点:
这两件事你早就会做。但要交给一个什么都不会的人(或者一台计算机)去做,就得把每一步都写死:算法,就是把"会做"翻译成"说清楚"。泡面那六步、走路那五步,都是算法。
判断一段话是不是算法,就看下面五条——必须同时满足,少一条计算机就做不了:
🎯 对照我们的项目:输入是 4 个特征,输出是物种名,最多问 5 个问题就出结果(有穷),规则写死了不会有第二种答案(确定),每一步都能量、能比(可行)。
每段话下面点一下你的判断,看看对不对:
手上只有 4 个数字和一张表,怎么定它是谁?
鉴定这件事,说到底就是在这两张表之间来回比——左边是相机自动记下来的一行行数字,右边是"这些数字长成什么样才算哪种鸟"的标准答案。
| 编号 | 体长 | 喙长 | 喙形 | 羽色 | 物种=? |
|---|---|---|---|---|---|
| B001 | 74.0 | 17.0 | 2 | 1 | 空着 |
| B002 | 58.0 | 8.0 | 1 | 1 | 空着 |
| B003 | 44.0 | 7.5 | 3 | 3 | 空着 |
前四列是机器测出来的数,最后一列空着——那正是程序要填的答案。
| 物种 | 体长 cm | 喙长 cm | 喙形 | 羽色 | 关键特征 |
|---|---|---|---|---|---|
| 白鹭 | 55–65 | 7–9 | 1 | 1 | 黑嘴黑腿黄脚趾 |
| 中白鹭 | 65–72 | 8–10 | 1 | 1 | 跟白鹭几乎一样,大一号 |
| 大白鹭 | 85–100 | 10–12 | 1 | 1 | 个大,黄嘴黑腿,S 形脖 |
| 苍鹭 | 90–100 | 11–13 | 1 | 2 | 灰身白脖,一撮黑冠羽 |
| 黑脸琵鹭 | 70–78 | 16–18 | 2 | 1 | 汤匙嘴,黑脸;国家一级 |
| 反嘴鹬 | 42–46 | 7–8 | 3 | 3 | 嘴往上翘,黑白相间 |
| 青脚鹬 | 30–35 | 5–6 | 1 | 2 | 上灰下白,青绿腿 |
📌 喙长这一列这次用不上:它和体长几乎同步变化,帮不上忙——特征不是越多越好,够用就行。
机器读不懂"汤匙""雪白",它只认 2 和 1。所以这些形状和颜色,必须先被编成号码——这一步叫编码。
那问题来了:拿 B001 那一行(74 / 17 / 2 / 1),你怎么定它是谁?
一步只做一件事,一句一句排下去:
✍️ 三条硬规矩:一步只做一件事;每步开头是动词;不许出现"大概""可能""感觉"。
这 8 句话,同学一看就懂,照着走也能得出答案。可对着上面那张表仔细看,问题就露出来了。
一句话:自然语言适合“想”和“说”,不适合“执行”——它是最好的草稿纸,却不能直接当施工图。
菱形出两条线,箭头指出下一步该往哪走
这是全世界通用的"算法文字",六个图形各管一件事:
最后那个小圆圈叫连接点:流程图一页画不下、或者有根线要跨很远,就在断开的两头各画一个同样的圆圈,写上同一个标记(比如都写 A),表示"这两处其实是同一条线"。它不是一个操作,只是个接线记号。
一个小道理:判断框长成菱形——一个入口、两个出口(是 / 否),别的框都是一个入口一个出口。
图形在左,功能在右——配对了,中间会连出一条同色的线。
图形是零件,零件怎么装?全世界只有三种装法——再复杂的算法,掰开看都是这三块拼起来的。
从上到下,一步接一步,走完就停。
生活版:泡面的步骤。
一个条件,两条路:是走这边,否走那边,走完再并回同一条线。
生活版:出门前看天带不带伞。
做完一遍别急着走,回头再问一次条件:没够就再来一遍,够了才往下。
生活版:刷步数,没到一万步就继续走。
左边就是刚才那 8 句话,一句一句点开,右边对应的那一块就亮起来——点完,整张图也就齐了:
🟢 左边那条绿色虚线,就是「循环」的回边——20 条记录,这张图要跑 20 遍。右边那条竖线则是「汇合」:五个「是」不管走哪条,最后都要回到同一个「输出」。
🎯 三个关键:① 每个菱形都出两条线;② 五个「是」不管走哪条,最后都汇到同一个"输出";③ 那条绿色虚线是循环,没有它,程序只会认一只鸟。
打开 《淇澳岛候鸟鉴定算法流程图画布》,把我们设计的淇澳岛候鸟鉴定算法画成流程图,画完导出、提交。
三条底线:菱形一定出两条线 · 阈值旁边写依据 · 图上有开始也有结束。
图是给人看的,程序是给机器跑的——中间还差一层过渡
还是那 8 句话、还是上一页那张图。伪代码没有统一格式,同一条算法,可以写成两种样子:
BEGIN WHILE 还有未鉴定的记录 READ L, B, S, C IF S = 2 THEN 物种 ← "黑脸琵鹭" ELSE IF L ≥ 85 AND C = 2 THEN 物种 ← "苍鹭" ELSE IF L ≥ 85 AND C = 1 THEN 物种 ← "大白鹭" ELSE IF L ≥ 50 AND C = 1 THEN 物种 ← "白鹭" ELSE IF S = 3 THEN 物种 ← "反嘴鹬" ELSE 物种 ← "青脚鹬" END IF PRINT 物种 END WHILE END
读入一条待鉴定的记录 取出体长 L、喙长 B、喙形 S、羽色 C 如果 喙形是扁匙状(S = 2) 这只鸟是黑脸琵鹭 否则如果 体长不小于 85 且 羽色是灰(C = 2) 这只鸟是苍鹭 否则如果 体长不小于 85 且 羽色是白(C = 1) 这只鸟是大白鹭 否则如果 体长不小于 50 且 羽色是白(C = 1) 这只鸟是白鹭 否则如果 喙形细长上翘(S = 3) 这只鸟是反嘴鹬 否则 这只鸟是青脚鹬 输出 这只鸟的名字 还有记录吗?有就回到“读入”再来一遍
👀 两栏一一对应,只是穿的衣服不同:五个菱形 → 五次“如果”判断;绿色回边 → 循环(左边 WHILE … END WHILE,右边就一句“回到‘读入’再来一遍”);矩形 → 记下物种名。
两种写法里都不再用“转到第 8 步”这种方式指路。左边用 IF / END IF 划边界、用 WHILE … END WHILE 圈循环;右边连循环都只写一句“回到读入再来一遍”,边界全交给“如果……否则……”和缩进。骨架可以自己定,只要每一步都没有歧义。
一句话:伪代码是写代码前的最后一张草稿——比自然语言严谨,比流程图省事。
同一条候鸟鉴定算法,三种写法都走了一遍。放在一起看,各自的位置就清楚了:
| 🗣️ 自然语言 | 🗺️ 流程图 | 📝 伪代码 | |
|---|---|---|---|
| 长什么样 | 一步一步的话 | 框 + 箭头 | 半中半英的"代码草稿" |
| 最擅长 | 说清思路、随时改 | 分支和循环一眼看全 | 结构最接近程序 |
| 最短板 | 结构不直观、边界含糊 | 画和改都费时间 | 没有统一标准,机器不能跑 |
| 什么时候用 | 想方案、讨论时 | 讲清整体流程、查漏时 | 动笔写代码之前 |
连起来就是一条线:自然语言 → 流程图 → 伪代码 → 程序。每往后一步,机器能读懂的多一点,人一眼看全的少一点。所以三种写法不是三选一,是同一件事的三个阶段。
图是画给人看的,程序是写给机器执行的
CPU 里全是开关,它只认"通 / 断"两种状态,写出来就是 1 和 0。那我们是怎么办到"用人话指挥计算机"的?答案是一层一层的翻译。编程语言就这样分成了三代——越往下越像机器,越往上越像人:
让电脑在屏幕上打出一行"黑脸琵鹭",三种语言的写法差多少(机器语言只截了一段示意):
# ① 机器语言:一串 0 和 1,人眼基本读不懂 10111000 00000001 00000000 10111011 00000010 00000000 ... # ② 汇编语言:给指令起了名字,但还是"一条一行" MOV AH, 09H LEA DX, MSG INT 21H # ③ 高级语言(Python):一句话说完 print("黑脸琵鹭")
三代语言干的活完全一样,区别只在"谁来翻译、翻译多少"。越往上,同一件事写的字越少、越不容易出错——这就是今天几乎没人用 0/1 写程序的原因。
| 方式 | 打个比方 | 特点 |
|---|---|---|
| 编译 C、C++ | 把整本书一次翻完,装订成册再交给你 | 先整体翻成 0/1,生成 .exe,以后直接跑,快;改一个字要重新翻整本。 |
| 解释 Python | 同声传译,你说一句他翻一句 | 边读边翻边执行,改完存盘按 F5 立刻见效,适合边写边试;但每次运行都要重翻一遍。 |
我们用的 Python 就是解释型的高级语言:句子几乎像英语,新手能很快上手;"库"又特别多,人工智能领域大量用它。你敲的每一行,IDLE 都会替你翻译成 0/1 交给 CPU。
所以你上节课画的流程图,不是白画的——它就是程序的"施工图"。这节课你做的事,叫翻译。
点左边一块图形,右边就长出对应的那几行代码
左边是你画的流程图,右边是从这张图翻译出来的程序。同一件事,两种写法。现在右边被盖住了——点左边一个图形,就去揭开它对应的那几行代码。像搭积木一样,一块一块把程序搭出来。
# ① 先打个招呼print("淇澳岛候鸟鉴定助手 v1.0")print("喙形:1尖直 2扁匙 3上翘")print("羽色:1全白 2灰 3黑白") while True: # 回边的开关 body = float(input("体长(cm):")) beak = float(input("喙长(cm):")) shape = int(input("喙形(1/2/3):")) color = int(input("羽色(1/2/3):")) if shape == 2: name = "黑脸琵鹭" elif body >= 85 and color == 2: name = "苍鹭" elif body >= 85 and color == 1: name = "大白鹭" elif body >= 50 and color == 1: name = "白鹭" elif shape == 3: name = "反嘴鹬" else: name = "青脚鹬" print("判为:", name) again = input("再测一条?(y/n):") if again != "y": break print("测试结束")
| 规矩 | 为什么 |
|---|---|
| 缩进用 4 个空格 | Python 里缩进不是排版,是语法。缩进就是告诉电脑"这一段归上面那个 if / while 管"。Tab 和空格混用会直接报错。 |
| = 是"给",== 是"问" | name = "白鹭" 是给;name == "白鹭" 是问。写错不报错,但逻辑全变。 |
| 标点必须是英文 | 中文的 ( : " , 一定报错。写代码时把输入法切到英文。 |
# 缩进对了 if shape == 2: name = "黑脸琵鹭" # ← 有缩进:归 if 管 print(name) # ← 没缩进:不归 if 管
填完 5 个空,让它跑通、存好
整段程序分四块:第 1 块打招呼、第 2 块读数据、第 4 块输出结果,都不用动;只有第 3 块的五处空要你填。
# ===== 1. 打招呼 + 说清编码(不用改)===== print("淇澳岛候鸟鉴定助手 v1.0") print("喙形:1尖直 2扁匙 3上翘") print("羽色:1全白 2灰 3黑白") while True: # ===== 2. 读入一条记录(不用改)===== body = float(input("体长(cm):")) beak = float(input("喙长(cm):")) shape = int(input("喙形(1/2/3):")) color = int(input("羽色(1/2/3):")) # ===== 3. 逐级判断(五个空,你来填)===== if shape == ① ?: # 扁匙状 name = "黑脸琵鹭" elif body >= ② ? and color == ③ ?: # 大型 + 灰 name = "苍鹭" elif body >= 85 and color == 1: name = "大白鹭" elif body >= ④ ? and color == 1: # 中型 + 白 name = "白鹭" elif shape == ⑤ ?: # 细长上翘 name = "反嘴鹬" else: name = "青脚鹬" # ===== 4. 输出 + 问要不要再测(不用改)===== print("判为:", name) again = input("再测一条?(y/n):") if again != "y": break
✍️ 每填一个空就按一次 F5,看结果有没有变。程序会说话,它会把自己的判断喊出来。
先自己做,卡住了再点下面的选项验证——也可以直接点上面代码里的 ① ? 跳下来。
假如 ① 那个空你填成了 0(正确是 2)。程序不报错、照样跑,只是——黑脸琵鹭再也认不出来了:
| 你输入的数据 | 应该判为 | ① 填错后判为 |
|---|---|---|
| A11 74 / 17 / 2 / 1 | 黑脸琵鹭 | 白鹭 |
| A12 76 / 18 / 2 / 1 | 黑脸琵鹭 | 白鹭 |
| A13 72 / 16.5 / 2 / 1 | 黑脸琵鹭 | 白鹭 |
为什么?因为 shape == 0 这个条件永远不成立,程序就一路往下走,最后被"体长≥50 且全白 → 白鹭"收走了——一只国家一级保护动物,被记成了普通白鹭。
所以填完空别急着关程序:填一个空 → 敲一组数据 → 看结果 → 不对就回头改那个空 → 再跑一遍。这个循环,就是编程最真实的样子,没有一个程序是一次写对的。
把 5 个空填完、跑通、保存好,这个程序就算完成了。文件名和格式按下面三条来:
保护区专家已经人工鉴定过,看你的程序能对几条
上节课你的程序只在你手上过了几条。这一次,它要吃 20 条真实记录——数据得你一条一条喂给它——就像保护区的工作人员,把野外记录本上的数字敲进电脑。
💡 这 20 条记录,专家已经一条一条人工鉴定过了。也就是说,你的程序正在被"考试"。
程序跑起来会一个问题一个问题地停下来等你——敲完一个数、按回车,它才问下一个。而它只认数字,所以敲的时候注意三点:
💡 报错了不要慌:按 F5 重新运行,从头再来一遍就行。这次敲对就好。
表上的一行,就是程序要的 4 个数字。顺序不能换:体长 → 喙长 → 喙形 → 羽色。
| A05 | 95.0 | 12.0 | 1 | 2 |
| 编号 | 体长 | 喙长 | 喙形 | 羽色 |
体长(cm) :95 喙长(cm) :12 喙形(1/2/3):1 羽色(1/2/3):2 >>> 判为: 苍鹭 再测一条?(y/n):y
👀 注意:编号 A05 不用输入——编号只是给人看的,程序只看那 4 个特征。
📌 顺便想一想:这才 20 条。真实监测一年几万条记录,如果全靠人手输入、人眼看图,会怎样?
这 20 条是保护区专家已经人工鉴定过的记录。照表从上往下,一条一条输进程序:
| 编号 | 体长 | 喙长 | 喙形 | 羽色 | 备注 |
|---|---|---|---|---|---|
| A01 | 58.0 | 8.0 | 1 | 1 | |
| A02 | 62.0 | 8.5 | 1 | 1 | |
| A03 | 55.0 | 7.5 | 1 | 1 | |
| A04 | 60.0 | 8.0 | 1 | 1 | |
| A05 | 95.0 | 12.0 | 1 | 2 | |
| A06 | 92.0 | 11.5 | 1 | 2 | |
| A07 | 98.0 | 12.5 | 1 | 2 | |
| A08 | 90.0 | 11.0 | 1 | 1 | |
| A09 | 88.0 | 10.5 | 1 | 1 | |
| A10 | 96.0 | 12.0 | 1 | 1 | |
| A11 | 74.0 | 17.0 | 2 | 1 | 喙长特别长 |
| A12 | 76.0 | 18.0 | 2 | 1 | |
| A13 | 72.0 | 16.5 | 2 | 1 | |
| A14 | 44.0 | 7.5 | 3 | 3 | |
| A15 | 45.0 | 8.0 | 3 | 3 | |
| A16 | 43.0 | 7.0 | 3 | 3 | |
| A17 | 32.0 | 5.5 | 1 | 2 | |
| A18 | 34.0 | 6.0 | 1 | 2 | |
| A19 | 68.0 | 9.0 | 1 | 1 | ⬅ 注意它 |
| A20 | 70.0 | 9.5 | 1 | 1 | ⬅ 注意它 |
把你判对的条数填进去(0–20):
表上有四行"错因分析",勾一个、写一句:
这张表,就是下一幕"查错"的证据。
从"结果错了",倒推到"哪一步错了"
| 编号 | 体长 | 喙长 | 喙形 | 羽色 | 你的程序判为 | 专家鉴定 |
|---|---|---|---|---|---|---|
| A19 | 68.0 | 9.0 | 1 | 1 | 白鹭 | 中白鹭 |
| A20 | 70.0 | 9.5 | 1 | 1 | 白鹭 | 中白鹭 |
其余 18 条全对。18 ÷ 20 = 90%——不错,但这两条错得很有价值。
为什么?我们一步一步倒推:
在"白鹭"那条规则之前,插一条新的:
elif body >= 65 and color == 1: # 全白,且 ≥65cm name = "中白鹭"
依据还是那张表:白鹭 55–65,中白鹭 65–72——阈值 65 就取在两段区间的交界处。
💡 为什么放后面就不行?因为 elif 的规矩是:前面有一条成立,后面就不看了。A19 早被"白鹭"那条收走了,永远轮不到"中白鹭"。
| 版本 | 改了什么 | 判对 | 准确率 |
|---|---|---|---|
| v1.0 | 五条规则 | 18 / 20 | 90% |
| v1.1 | 加一条"≥65 且全白 → 中白鹭" | 20 / 20 | 100% |
算法不是一次写对的,是一轮一轮改对的。
你的程序认得 7 种鸟,淇澳岛有 200 多种
| 我们的规则判定 | 机器学习 / 深度学习 | |
|---|---|---|
| 规则谁定 | 人——我们一条条写出来的 | 机器——从大量图片里自己"学"出来 |
| 需要什么 | 几个阈值、一张特征表 | 几万张标注好的图片、大量算力 |
| 优点 | 看得懂、改得动、不用联网 | 准、能直接处理照片、能认几百种 |
| 缺点 | 只认几种、要靠人工量特征 | 黑箱、要数据、错了不好改 |
所以,我们这节课做的不是 AI,而是 AI 之前的那一步:把人的经验,写成机器能执行的规则。
🔭 到了第六章,我们会看到:机器是怎么自己从数据里"学"出这些规则的。
一次答对得越多,说明这节课的"概念"越扎实
今天你完成的不只是一个程序,而是一条完整的链路:
发现需求 → 抽象特征 → 设计算法 → 写成程序 → 测试优化
这条路,和你以后遇到任何"用电脑解决真实问题"的路,是同一条。
📌 下课前提交三样东西:算法设计单 + .py 程序 + 测试记录表