news 2026/11/2 12:30:46

从一行NumPy到微型Transformer:程序员手写大模型核心机制的实战笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从一行NumPy到微型Transformer:程序员手写大模型核心机制的实战笔记

最近整理自己的学习笔记时,总有人问我一个问题:程序员想搞懂大模型 LLM,到底该从哪里下手?我的回答往往让不少人意外——先别急着去啃那些动辄几百兆的框架,先老老实实把一行 NumPy 写明白。我自己就是沿着"一行 NumPy → 一个微型 Transformer → 看懂真实 LLM 的论文和代码"这条路走过来的,中间绕了不少弯,但也沉淀出了一些很值得分享的心得。这篇笔记不是教科书,也不是大厂面经汇编,而是我作为一名普通程序员,从零接触 LLM 的真实记录:我会讲清楚为什么基础要从这里打、核心概念能拆成哪几个最小单元、以及自己手写一个"看得见摸得着"的微型模型时踩进过的那些坑。如果你和我一样,不是科班出身、半路转行,或者只是被 AI 热潮推着走的普通开发,这篇内容应该能帮你在 LLM 的学习地图上少走一段弯路。

1. 为什么从 NumPy 开始:先甩掉"调包侠"的焦虑

1.1 一行 NumPy 能推倒出什么

很多人刚开始学大模型,总是直接打开 Transformer 论文,或者一头扎进 PyTorch 源码,结果被矩阵维度、梯度回传、多头注意力这些名词劝退。我觉得一个更平滑的路径是先理解 NumPy。原因很朴素:LLM 的底层本质就是高维矩阵的乘法和变换,而 NumPy 恰好能让你用最少的语法糖,看到矩阵运算最原始的样子。

举个例子,一行output = np.dot(weights, input)就能把"一个向量经过线性层"这件事讲清楚。input是形状为(d_in,)的输入向量,weights是(d_out, d_in)的权重矩阵,点乘结果就是(d_out,)的输出向量。Transformer 里的 FFN、Attention 打分、Embedding 检索,本质上都是这类操作的花式组合。

也经常有人问我 NumPy 和 Python 原生 list 比,到底快在哪。其实核心有三点:第一,NumPy 数组在内存里是连续存储的,不像 list 那样存的是对象的引用,所以缓存命中率高;第二,NumPy 的底层计算是 C 语言实现的,循环被内建函数吞掉了;第三,NumPy 的广播机制让你不用写嵌套 for 循环,表达力更强。比如a + b,只要两个数组在某个维度上可以对齐,NumPy 会自动扩展运算,这在处理批量数据时非常关键。

可别小瞧这一点。以我的经验,理解不了广播机制,后面写注意力分数矩阵时会反复遇到维度错误。如果能在 NumPy 阶段把reshape、transpose、broadcast这几个动作练熟,再做 LLM 的代码阅读会轻松很多。

1.2 从矩阵思维到模型思维:两种学习方式的差别

我还想谈谈学习方法的问题。程序员里有两类人:一类是"调包侠",一类是"抄码党"。调包侠习惯pip install xxx然后直接调用,遇到报错就复制到搜索引擎里碰运气;抄码党则喜欢把 GitHub 上的项目原封不动拉下来跑一遍,觉得自己"已经懂了"。这两种方式都不是完全没用,但学 LLM 时很容易形成"会用 API 但不理解原理"的尴尬状态。

我更推荐的是"用矩阵思维去拆模型"。什么意思?就是你看到一个模型代码时,第一反应不是去看它用了哪个框架的方法,而是问:这里输入是什么形状?输出是什么形状?中间经历了哪些矩阵乘法?这些乘法在数学上表达了什么含义?

举例来说,HuggingFace Transformers 库里的BertModel前向传播代码,对新手来说密密麻麻全是类和方法。但如果你在 NumPy 阶段亲手写过scores = query @ key.T,再看源码里的torch.matmul(query, key.transpose(-1, -2)),你就能立刻意识到:这不就是在算 Query 和 Key 的点积相似度吗?所以,学习路径不是"混沌地看大神代码",而是"用最小实现建立直觉,再反推工业实现"。这个顺序颠倒,痛苦程度完全不同。

2. 拆解 LLM 的核心形态:从数据到 Token 再到参数

2.1 LLM 是什么:把它当作一个"概率大师"

大语言模型这个词现在被用得泛滥,但真问一句"它到底在做什么",很多程序员也未必能一句话说清。我个人的理解是:LLM 本质上是一个超大的条件概率预测器。它根据你输入的一段文本,预测下一个 Token 的概率分布,然后根据这个概率分布去采样,生成下一个 Token;再把新生成的 Token 拼到输入后面,继续预测下一个,如此循环,就产生了连贯的回答。

这个"自回归"过程听起来不难,但难点在于模型要在这个循环里存储足够多的"世界知识"。训练阶段,模型从海量文本里学到的并不只是语法,而是一些可以映射为人类常识的统计规律。这也是为什么 LLM 能写代码、能做数学题、能聊天,因为它们其实是在"精炼"训练数据中的文本规律,而不是真的像人一样去理解世界。

我用一个生活化的类比帮助自己记忆:LLM 像一个读过亿万本书、但失忆了的图书管理员。他回答问题时,并不是去书架里翻出一段话背给你,而是根据你问的问题,凭"模糊的语感"组合出一段最像样的回答。训练数据决定了他肚子里有多少料,模型参数决定了他"组合语感"的能力上限。

2.2 Token、Embedding 与位置编码:三大基础概念

要继续学下去,必须把三个基础概念彻底吃透:Token、Embedding 和位置编码。

Token 是文本被切碎后的最小单元。它可能是完整单词,也可能是子词,甚至是一个单字符。比如"unbelievable"在 BPE 这类算法下可能被切成"un"、"believ"、"able"几个子词。Token 化(Tokenizer)是 LLM 的第一步,也是很多人忽略的一步。我一开始总觉得分词没必要单独研究,直到自己尝试实现一个字符级模型,才意识到"模型看到的从来不是文本,而是数字 ID"这件事有多重要。不同的分词器会直接影响模型能处理的词汇量和压缩效率。

Embedding 则负责把 Token ID 映射成一个高维向量。例如token_id 12可能会被映射成一个 768 维的向量,这个向量里包含"该 Token 的语义特征"。相似的 Token,向量在空间里往往距离更近。在 NumPy 中,这个操作其实就是embedding_matrix[token_ids],一个典型的查表操作。

位置编码容易被新手忽略,但它是 Transformer 能用起来的支柱之一。因为 Attention 本身对位置不敏感,它只会看"哪些 Token 之间关系强",不会自动知道"第 3 个 Token 和第 9 个 Token 谁先谁后"。为了弥补这一点,Transformer 加入位置编码,把位置信息叠加到 Embedding 上。理解了这个细节,你就不会惊讶于为什么模型有时候对长文本中不同位置的同类内容会产生混淆了。

2.3 注意力机制中的 Query、Key、Value:从"我在找什么"说起

说到 Attention,网上已经有大量讲解,从"翻译对齐"到"注意力热力图"都有。但我自己真正记住的,是那个热词里非常凝练的概括:Key 是"我是谁",Query 是"我在找什么",Value 是"我能提供什么"。

这个类比特别适合程序员理解。你可以把一段文本中的每个 Token 想象成公司里的人。Key 就是每个人的名牌,上面写着岗位和履历;Query 是你现在想找的人,比如"我需要一个懂 Python 的人";Value 是这个人的实际能力,比如他能写出什么样的代码。Attention 要做的,就是用你的 Query 去和每个人的 Key 做匹配度打分,然后把匹配度高的人的 Value 按权重融合起来。

用 NumPy 写一下这个打分过程,你就能直观看到 shape 流转:

import numpy as np def softmax(x): # 防止数值溢出,先减去最大值 e_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) return e_x / np.sum(e_x, axis=-1, keepdims=True) # 假设有三个 token,每个 token 的 embedding 维度是 4 embeddings = np.random.randn(3, 4) d_k = embeddings.shape[-1] # 在简化自注意力里,我们用同一个矩阵充当 Q、K、V Q = embeddings K = embeddings V = embeddings # 打分矩阵 shape: (3, 3) scores = Q @ K.T / np.sqrt(d_k) weights = softmax(scores) # 加权聚合 shape: (3, 4) output = weights @ V print(output.shape)

这段代码只有十几行,但它把 Attention 最核心的机制完整呈现出来了。Q代表"我想找什么",K代表"我是什么",Q @ K.T就是所有 Token 两两之间的匹配度,除以sqrt(d_k)是为了把分数拉回合理的数值范围,避免 softmax 进入饱和区。

我建议每个想学 LLM 的程序员都把这段代码亲手敲一遍,然后打印中间结果,观察权重矩阵的变化。你很快会发现,模型在训练过程中学到的东西,本质上就是调整这些矩阵里的数值,让"该被注意到的 Token"得到更高权重。

3. 用 NumPy 手搓一个微型大模型:实操笔记

3.1 先写一个最简单"下一个字符预测器"

真正的 LLM 动辄几十亿参数,我一开始也不敢直接挑战。更务实的做法是先写一个"玩具版本":用一个字符级模型去学习预测下一个字符。这个模型的字典很小,比如只包含'H'、'e'、'l'、'o'、' '、'w'、'r'、'd'这些字符,目标是让模型学到"hello world"这类文本的模式。

最简单的方法是用 bigram 计数模型。统计每两个相邻字符的出现次数,然后根据上一个字符预测下一个字符的概率。虽然是统计方法,但它已经把"预测下一个 Token"这个任务本质体现出来了。

import numpy as np from collections import defaultdict text = "hello world hello world" chars = sorted(list(set(text))) stoi = {s: i for i, s in enumerate(chars)} # 统计 bigram 共现次数 bigram_counts = defaultdict(int) for i in range(len(text) - 1): bigram_counts[(stoi[text[i]], stoi[text[i + 1]])] += 1 # 转为概率矩阵 vocab_size = len(chars) counts = np.zeros((vocab_size, vocab_size)) for (a, b), cnt in bigram_counts.items(): counts[a, b] = cnt probs = counts / counts.sum(axis=1, keepdims=True) print(probs.shape) # (vocab_size, vocab_size)

你看,这个矩阵的每一行,就是"给定上一个字符,下一个字符的概率分布"。假如我们把"字符"换成"Token",把"bigram"换成"极高维度的条件概率建模",再把统计表换成神经网络的参数,这其实就是 LLM 的基本逻辑。我强烈建议按这个顺序入门:先写统计模型理解目标函数,再升级到神经网络。

3.2 用 NumPy 实现 softmax、注意力得分与采样推理

拿到统计概率之后,下一步就是把这个模型换成更"神经"的实现。我建议不要跳过前面的手动实现,直接上 PyTorch,因为这样你会错过观察概率如何从模型输出里长出来的机会。

先实现一个通用的 softmax,我在上一篇笔记里已经给过代码,这里要进一步补充的是采样环节。模型最终输出的是一组 logits(未归一化的分数),我们通过 softmax 把它变成概率分布,然后再根据这个分布采样。

def sample_from_logits(logits, temperature=1.0): # temperature < 1 时分布更尖锐,倾向选择高概率项 # temperature > 1 时分布更均匀,输出更随机 logits = logits / temperature probs = softmax(logits) return np.random.choice(len(probs), p=probs)

temperature是很多新手不太敏感的参数。温度越低,模型越保守,更像"复读机";温度越高,模型越自由,容易产生意外内容。你在调用 OpenAI API 或本地模型时常见到的temperature参数,本质上就是操控这个分布的尖锐程度。

为了做一次真正的张量前向,我后来还实现了一个极简的"自注意力嵌入层":用 embedding 矩阵对字符序列做查表,得到向量序列,然后对序列内部做一次自注意力打分。当时写出来虽然很粗糙,但打印每个中间变量的 shape 后,所有关于"维度到底怎么流动"的困惑都消失了。

3.3 训练一个玩具模型:观察 loss 下降的实感

统计模型不用训练,但神经网络玩具模型还是需要感受一下 loss 下降的。我不建议用 NumPy 手工实现反向传播,因为那会把学习笔记变成数学折磨。更聪明的做法是,用 PyTorch 写一个极小的 Transformer 层,但把"理解形状"的注意力逻辑,仍然用 NumPy 风格的注释标注出来。

比如我当时的练习是:用 PyTorch 内置的nn.Embedding和nn.Linear搭一个单层注意力,在"hello world"这个文本上训练一两个 epoch。虽然模型很小,但训练过程中 loss 从 2.5 一路降到 1.0 的感受,是任何课程都无法替代的"实感"。

这里有个容易误入的坑:不要试图在一个小文本上复现大模型效果。小模型在小数据上可能学到"局部模式",但无法涌现复杂的语言能力。我后来才真正明白,大模型的很多能力是参数规模和数据规模共同堆出来的,这也解释了为什么开源模型社区里会有那么多"版本差异巨大"的模型。

3.4 踩过的坑:维度不匹配、广播陷阱与数值溢出

手写微型模型期间,我记录了一堆错误,至少有三类值得分享。

第一类是维度不匹配。最常见的报错是matmul: Input operand 1 has a mismatch in its core dimension,通常出现在Q @ K.T里忘了转置,或者是 batch 维度多了一层少了一层。解决办法很简单:每次矩阵乘法前,用print(x.shape)把形状打出来,确认(batch, seq, d_k)与(batch, d_k, seq)对齐了再乘。

第二类是广播陷阱。比如我想让(3, 4)的矩阵和(4,)的向量相加,这时 NumPy 的广播规则会把向量自动当成(1, 4),如果语义理解不到位,结果可能跟预期不符。更隐蔽的情况是,某个维度是 1 时,例如(1, 4)与(3, 1)相加,会广播成(3, 4),这时候如果你没注意到这个"隐身维度",调起 bug 来会非常头疼。

第三类是数值溢出。softmax 中的np.exp很容易产生极大值。如果输入 logits 里有1000那样的数值,np.exp(1000)直接溢出为inf。解法就是经典的"减去最大值"技巧,我在上面的代码里也用了。建议所有准备手写 Attention 的人都把这段姿势记牢,它是万无一失的。

4. 从微型模型迈向真实 LLM:框架、模型与工程实践

4.1 为什么最终还要上 PyTorch 和 Transformers

既然用 NumPy 能理解原理,那是不是一直用 NumPy 就够了?当然不是。真实训练一个哪怕很小的模型,也会涉及 GPU 加速、自动求导、优化器管理、分布式训练等复杂工程。NumPy 只是帮助你建立"手感的桥梁",实际上阵还是得靠 PyTorch 和 HuggingFace Transformers。

PyTorch 的核心价值是自动微分。你不用手写 backprop,定义一个loss.backward()就能得到梯度。这并不代表你可以不关心梯度,而是意味着你可以把精力集中在模型架构、数据调度和训练策略上。Transformers 库则进一步封装了几乎所有主流模型架构,让加载预训练权重、微调、推理都变得高效。

我个人的建议是顺序不能反:先用 NumPy 理解 Attention 的 shape 流转,再用 PyTorch 重写一遍相同逻辑,接着打开 Transformers 库源码找到对应的nn.MultiheadAttention或eager attention实现,最后再看动画或论文图解。这个顺序下来,你会获得非常有掌控力的理解。

4.2 真实 LLM 落地:Token、推理服务与 LLM 网关

程序员的视角不能只停留在"模型怎么训练",还要关心"模型怎么用起来"。真实的 LLM 应用不是把模型文件加载进来那么简单,它涉及几个容易忽略的工程概念。

Token 计费与限流是第一个要理解的。所有商业 LLM API 都按 Token 计费,而一次对话的每次流式回应都会动态累积 Token。如果你在网关层不做请求合并和上下文裁剪,费用会快速上涨,响应延迟也会上升。我做过一个测试脚本,连续向模型发送 50 轮对话,Token 消耗量比预想高出近 3 倍,原因就是对话历史没有做截断,模型每轮都要重新处理前面的旧内容。

LLM 网关是另一个我后来才补上的知识点。你可以把它想象成一个"模型路由交换机"。它的职责包括:把同一个业务请求路由到不同模型(如便宜的模型处理简单任务、贵的模型处理复杂任务)、做熔断与重试、记录日志与指标、管理 API Key 等。很多团队一开始图省事直接调用各家 SDK,结果模型切换时改了一堆代码,这其实没必要,网关能把这件事收敛成一次配置变更。

在实际的产品环境里,除了模型本身,你还需要考虑模型输出格式的稳定性。如果下游业务依赖 JSON 输出,只要模型偶尔多一个前后缀字符,解析就会失败。所以现在很多工程方案会引入"结构化输出约束"或后处理解析层,这比单纯换大模型更现实。

4.3 怎么评估一个 LLM:公开榜单与 LLM as Judge

模型选型离不开评估。网上有 Open LLM Leaderboard 这一类的公开榜单,测试集覆盖常识、推理、代码、数学等维度,分数可以用来粗筛模型。但公开榜单也有局限,它测的是通用能力,未必贴合你业务里的真实场景。

有一个越来越常用的方法是 LLM as Judge,也就是让一个更强或中立的大模型来给另一个模型的回答打分。这像极了程序员领域的 code review:一个资深工程师去评判新人的代码,虽然不能保证绝对客观,但效率很高。我最近在做一个"客服回答质量对比"的小实验,把两个模型的输出丢给第三个模型做结构化打分,得到的结果和人工评估的相关性相当高,但成本低得多。

做评测时别忘了加"偏置抑制":比如给模型随机打乱候选顺序,避免它总是选第一个。以及要明确打分维度和评分标准。否则你得到的结果很有可能是"觉得这个答案长所以好"这样的失真偏好。

5. 学习路径与避坑建议:给愿意动手的程序员

5.1 一条可落地的学习时间线:从零基础到能读懂模型代码

如果让我给一个普通程序员制定时间线,我会分成四个阶段,每个阶段一周左右。

第一周:扎实 NumPy 基本功。重点练数组创建、索引、reshape、transpose、broadcast、矩阵乘法,并且理解它与内存、速度的关系。这一周不碰任何 LLM 概念,就是纯数学工具热身。

第二周:理解 Token 和 Embedding。用 Python 写一个简单的字符分词器,再用 NumPy 查表实现 Embedding。可以做一个语义距离的小实验:用预训练好的 embedding 对比"king"和"queen"的余弦相似度。这个阶段会建立"文本转数字,数字变向量"的直觉。

第三周:手写 Attention 前向。按照我前面给的代码,实现 softmax、Q @ K.T、缩放、加权求和。强烈建议打印每一层的 shape,反复推敲维度关系。

第四周:接入 PyTorch 与 Transformers。用AutoModelForCausalLM和AutoTokenizer加载一个小模型,如几十 MB 的微型模型,跑通一次"输入文本 → 输出文本"的完整流程,再做一次简单微调。

这样一个月下来,你至少能读懂 Transformer 前向传播的大部分关键代码,碰到陌生模型时也不会心虚。

5.2 程序员常见误区与调试技巧速查

我把自己和身边朋友踩过的问题整理成了一张表,希望你可以少走弯路。

现象可能原因解决方向
ModuleNotFoundError: No module named 'numpy'环境未安装或虚拟环境混用用pip install numpy,并检查pip list和which python
numpy 版本不匹配安装的库要求特定版本用pip freeze对比,必要时建独立虚拟环境
Transformer 中张量维度对不上Batch/Seq/Embedding 维度没理清在关键节点print(x.shape),画形状流转草图
生成回答全是重复内容Temperature 过低或上下文过长适当调高 temperature 或减少历史 Token
API 返回 JSON 不稳定模型输出格式不受控使用结构化输出约束,添加后处理解析层

调试工具上,我个人最常用的是"形状可视化 + 中间值打印"。很多进阶项目只是 Solver 不懂这些,导致调参像抽盲盒。在 LLM 领域,Debug 的核心原则是:先缩小范围,再打印中间变量。把一行复杂代码拆成多个临时变量,是最简单也最有效的方案。

5.3 一些顺手收藏的学习资源类型

除了具体的代码练习,资源选型也能影响学习效率。我觉得不用收藏几十个教程,只需三类就够。

第一类是交互式可视化网站,直观展示 Transformer 的 Attention 权重变化,比如有一些在线 demo 能让你点选某一 Token,看到它和哪些 Token 的相关性高。第二类是经典论文带读,尤其是Attention Is All You Need以及后续的模型架构演变,边读边对照源码。第三类是开源项目代码,但建议选"最小可运行"的项目,例如单文件实现的 GPT 训练脚本,而不是一上来就看几千行的大仓库。

我在第一轮学习时把大量时间浪费在"收藏从未打开"的资料上,后来强迫自己每周只精读一篇文章、只跑一个小项目,反而收获很大。这个"少而精"的策略,对信息过载时代的程序员特别适用。

还有一个建议:做学习笔记时,与其摘抄代码,不如把每个模块都改写成自己的版本,哪怕是改变量名、改注释、简化掉某些分支。这样代码才会真正变成你的代码。我一直觉得,学 LLM 这件事,动手程度和最终理解深度是正相关的。

写在最后:如果只能提一个经验

如果只能从这篇笔记里带走一个经验,我会说:不要被"大模型"这三个字吓住,它的地基没有想象中那么神秘。一行 NumPy 代码里藏着矩阵的流转,一段十几行的注意力实现里藏着几乎所有 LLM 的核心机制,一个字符级预测器里藏着自回归生成的全部逻辑。把这三个小东西吃透,再去看那些庞然大物时,你的眼里不会是黑箱,而是一层层你可以叫出名字的积木。至少对我来说,从"只会调 API"到"能自己改模型结构、做微调、看懂论文",靠的不是什么天赋,而是一行行代码敲出来的熟悉感。希望这份笔记也能给你同样的底气。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 12:30:44

信奥赛网课选择指南:评估维度与避坑策略

1. 信奥赛网课市场现状与核心痛点 作为国内最具权威性的青少年信息学竞赛&#xff0c;全国青少年信息学奥林匹克竞赛&#xff08;NOI&#xff09;近年来参赛人数年均增长率超过25%。2025赛季报名人数首次突破10万大关&#xff0c;随之而来的是编程网课市场的野蛮生长——目前市…

作者头像 李华
网站建设 2026/11/2 12:30:44

可重构多功能集成光计算芯片:算力瓶颈下的技术路径与工程挑战

这两年只要聊到算力&#xff0c;绕不开的话题就是AI推理和训练对硬件的巨大胃口。电芯片的工艺节点正在逼近物理极限&#xff0c;而大模型的参数量还在往上走&#xff0c;两者之间越拉越大的剪刀差&#xff0c;让很多人重新把目光投向了光计算。口号喊了很多年&#xff0c;但说…

作者头像 李华
网站建设 2026/11/2 12:30:21

政务系统崩溃应急处理与性能优化实战

1. 系统崩溃的紧急时刻 那天早上8:15分&#xff0c;政务大厅刚开门&#xff0c;办事群众已经开始排队。突然&#xff0c;前台工作人员发现所有业务系统都无法登录——这不是普通的卡顿&#xff0c;而是整个市级政务服务平台完全瘫痪。作为运维负责人&#xff0c;我的手机瞬间被…

作者头像 李华
网站建设 2026/11/2 12:30:16

高校科技成果数智化转化:架构设计与实践

1. 高校科技成果转化的现状与痛点 高校作为科技创新的重要源头&#xff0c;每年产生大量具有市场潜力的科研成果。但长期以来&#xff0c;科技成果转化率低、转化周期长的问题一直困扰着学术界和产业界。根据我参与过的多个高校技术转移项目经验&#xff0c;主要存在以下几个核…

作者头像 李华
网站建设 2026/11/2 12:29:15

100V直流升220V交流:单相SPWM逆变Matlab仿真与排查

简介&#xff1a;围绕单相逆变电源的Matlab仿真研究&#xff0c;这份课程设计文档面向电气、自动化等专业学生及电力电子初学者&#xff0c;提供从直流100V输入到220V单相交流输出的完整设计思路与仿真参考。文档共1个doc文件&#xff0c;压缩包约378KB&#xff0c;内容按任务书…

作者头像 李华
网站建设 2026/11/2 12:28:33

51单片机坐姿矫正系统设计:STC89C52多传感器采集与调试

简介&#xff1a;这份文档围绕基于STC89C51/52单片机的坐姿矫正系统展开&#xff0c;面向电子信息、自动化等专业学生及51单片机初学者&#xff0c;也可作为毕业设计选题、课程设计和单片机综合实践的参考。系统集成自动与手动模式&#xff0c;利用光敏电阻和模数转换检测环境光…

作者头像 李华