前言与导读
欢迎来到语言结构的实践之旅。无论你来自语言学、计算机还是其他专业,这本手册将帮助你把课堂上的概念变成可以运行的程序、可以观察的结果。在这里,你不只是学习者,更是探索者——用代码去验证语言的规律,用结构化思维与大模型对话,在实践中发现属于你自己的洞见。
一、本手册的定位
本实训手册是《语言结构与计算》课程的配套实践教材,旨在帮助学习者将教材中的理论概念转化为可操作的编程实践和实验技能。
教材以"组合与聚合"为核心线索,系统介绍了语言结构的分析方法和计算表示。本手册围绕这条线索,设计了代码实践(章节同步练习)和综合实验(跨章节项目)两类实训内容,覆盖从基础概念到综合应用的完整学习路径。
二、内容结构
本手册包含以下三个部分:
第一部分:代码实践(与教材各章同步)
组合与聚合] --> C2[第二章
形式·内容·功能] C2 --> C3[第三章
语言的层级结构] C3 --> C4[第四章
语言结构表示] end subgraph 方法篇 C5[第五章
组合结构与语料库] --> C6[第六章
聚合结构与知识库] C6 --> C7[第七章
结构的处理与转换] end subgraph 应用篇 C8[第八章
从符号到向量] --> C9[第九章
结构的运用] C9 --> C10[第十章
理性与经验结构] end
图P-1 代码实践章节结构与学习路径
每章的代码实践包含2-4个可运行的Python程序,其中: - LangSC代码:使用课程配套工具包进行结构分析、语料库检索、网格操作 - 纯Python代码:用标准库实现数据结构、算法和概念模拟,加深对原理的理解
第二部分:综合实验(5个跨章节项目)
表P-1 综合实验概览
| 实验 | 主题 | 核心技能 | 对应章节 |
|---|---|---|---|
| 实验一 | 组合结构的分析与表示 | 分词、依存分析、JSON结构化 | 第1、3、4章 |
| 实验二 | 聚合结构的构建与应用 | 本体设计、知识图谱构建、大模型辅助 | 第2、6章 |
| 实验三 | 语料库检索与结构发现 | BCC检索、模式提取 | 第5章 |
| 实验四 | 结构化人机协作 | 显隐互补、提示语设计、智能体三层级结构设计 | 第8、9、10章 |
| 实验五 | 综合项目 | 全流程端到端实践 | 全书 |
第三部分:附录(参考资料)
- 附录A:LangSC使用指南(完整API参考)
- 附录B:延伸阅读(推荐书目与论文)
- 附录C:术语对照表(中英文核心术语)
三、学习路径建议
图P-2 推荐学习流程
推荐顺序: 1. 先完成各章代码实践,确保能运行所有示例 2. 按实验一→二→三→四的顺序完成前四个实验 3. 最后挑战实验五(综合项目),将所有技能串联
四、环境配置
4.1 系统要求
表P-2 系统要求
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows 10+、macOS 10.15+、Ubuntu 18.04+ |
| Python | 3.8 或更高版本 |
| 内存 | 建议 4GB 以上 |
| 编辑器 | VS Code(推荐)、PyCharm 或任意Python IDE |
4.2 LangSC安装
LangSC是本课程的核心工具包,提供语言结构分析的全部功能。
# 方式一:pip安装(推荐)
pip install LangSC
# 方式二:从本地安装(实验环境)
pip install -e /path/to/LangSC
LangSC依赖本地动态链接库:
- Windows:gpflib.dll(安装时自动配置)
- Linux:libgpflib.so
- macOS:libgpflib.dylib
4.3 验证安装
安装完成后,运行以下代码验证:
import json
from LangSC import GPF
gpf = GPF()
# 测试分词
Ret = gpf.Parse("今天天气真好", Structure="Segment")
words = json.loads(Ret)
print("分词结果:", words)
# 测试词性标注
Ret = gpf.Parse("今天天气真好", Structure="POS")
tags = json.loads(Ret)
print("词性标注:", tags)
如果输出了分词和词性标注结果,说明安装成功。
4.4 补充工具(可选)
部分代码实践使用了以下标准库或第三方库:
表P-3 补充工具库
| 库 | 用途 | 安装方式 |
|---|---|---|
json |
JSON解析(Python内置) | 无需安装 |
collections |
数据统计(Python内置) | 无需安装 |
numpy |
向量计算(第八章) | pip install numpy |
gensim |
词向量(第八章,可选) | pip install gensim |
wordcloud |
词云可视化(可选) | pip install wordcloud |
graphviz |
结构图可视化(可选) | pip install graphviz |
五、代码约定
本手册中的代码遵循以下约定:
- LangSC导入:统一使用
from LangSC import GPF - JSON处理:Parse返回JSON字符串,需用
json.loads()转换 - 变量命名:LangSC返回值统一命名为
Ret,与附录A保持一致 - 注释说明:关键步骤均附有注释,解释代码逻辑和LangSC用法
- 输出示例:重要代码段后附有期望输出格式(以注释形式)
Parse返回格式速查
表P-4 Parse返回格式速查
| Structure参数 | 返回类型 | 处理方式 |
|---|---|---|
"Segment" |
["词1", "词2", ...] |
json.loads(Ret) → 遍历字符串列表 |
"POS" |
["词1/词性1", "词2/词性2", ...] |
json.loads(Ret) → tag.split("/") |
"Tree" |
{"Type":"Tree", "Units":[...], ...} |
json.loads(Ret) → 访问字典字段 |
"Dep" |
Web服务JSON | gpf.AddStructure(Ret) → 网格查询 |
六、获取帮助
- API详情:参见附录A《LangSC使用指南》
- 术语查阅:参见附录C《术语对照表》
- 延伸学习:参见附录B《延伸阅读》
代码实践:第一至四章
第一章代码实践
1.7 本章代码实践
本节提供可运行的代码示例,帮助读者动手实践。
1.7.1 组合结构的表示
使用LangSC表示句子的组合结构:
import json
from LangSC import GPF
gpf = GPF()
# 创建句子"学生读书"的组合结构
gpf.SetText("学生读书")
# 添加词单元(AddUnit返回单元ID字符串)
id1 = gpf.AddUnit("学生")
gpf.AddUnitKV(id1, "POS", "n") # 名词
id2 = gpf.AddUnit("读")
gpf.AddUnitKV(id2, "POS", "v") # 动词
id3 = gpf.AddUnit("书")
gpf.AddUnitKV(id3, "POS", "n") # 名词
# 添加组合关系(使用单元ID)
gpf.AddRelation(id2, id1, "SBV") # 主谓关系
gpf.AddRelation(id2, id3, "VOB") # 动宾关系
# 可视化
gpf.ShowRelation("学生读书_关系.png")
输出:
读 (v) [ROOT]
├── 学生 (n) [SBV]
└── 书 (n) [VOB]
1.7.2 聚合类的实现
用Python类实现词性聚合:
# 定义聚合类(词性类别)
class WordClass:
"""词性聚合类"""
def __init__(self, name, words):
self.name = name
self.words = set(words)
def contains(self, word):
"""检查词是否属于该聚合类"""
return word in self.words
def can_substitute(self, word1, word2):
"""替换测试:两个词是否可以相互替换"""
return self.contains(word1) and self.contains(word2)
# 创建聚合类实例
nouns = WordClass("名词", ["学生", "老师", "书", "苹果", "电脑"])
verbs = WordClass("动词", ["读", "写", "吃", "买", "用"])
adjectives = WordClass("形容词", ["大", "小", "好", "新", "快"])
# 替换测试
print(nouns.can_substitute("学生", "老师")) # True
print(nouns.can_substitute("学生", "读")) # False
1.7.3 组合-聚合约束验证
验证组合槽的聚合约束:
def validate_pattern(pattern, words, word_classes):
"""
验证词序列是否符合组合模式
pattern: 如 ["n", "v", "n"] 表示 名词+动词+名词
words: 如 ["学生", "读", "书"]
word_classes: 词性到聚合类的映射
"""
if len(pattern) != len(words):
return False
for pos, word in zip(pattern, words):
if pos not in word_classes:
return False
if not word_classes[pos].contains(word):
return False
return True
# 词性映射
pos_map = {"n": nouns, "v": verbs, "a": adjectives}
# 验证
print(validate_pattern(["n", "v", "n"], ["学生", "读", "书"], pos_map)) # True
print(validate_pattern(["n", "v", "n"], ["学生", "大", "书"], pos_map)) # False
1.7.4 使用LangSC进行句法分析
import json
from LangSC import GPF
# 创建分析器
gpf = GPF()
# 分析句子
text = "聪明的学生认真地完成了作业"
# 查看词性(聚合分类)
print("词性标注:")
Ret = gpf.Parse(text, Structure="POS")
pos_result = json.loads(Ret)
for tag in pos_result:
word, pos = tag.split("/")
print(f" {word}: {pos}")
# 查看句法结构(组合关系)
print("\n依存关系:")
Ret = gpf.Parse(text, Structure="Dep")
# 依存分析返回Web服务的JSON,可直接可视化
gpf.ShowStructure(Ret, "依存结构.png")
# 打印原始JSON查看
print(Ret)
第二章代码实践
2.7 本章代码实践
2.7.1 三平面的程序表示
用Python数据结构表示三平面的分析结果:
from dataclasses import dataclass
from typing import List, Dict, Optional
@dataclass
class FormAnalysis:
"""形式平面分析结果"""
word_order: str # 词序类型(如SVO)
sentence_type: str # 句式类型
constituents: List[Dict] # 句法成分
@dataclass
class ContentAnalysis:
"""内容平面分析结果"""
predicate: str # 谓词/动词
arguments: Dict[str, str] # 论元(角色→填充词)
proposition: str # 命题描述
@dataclass
class FunctionAnalysis:
"""功能平面分析结果"""
speech_act: str # 言语行为类型
topic: Optional[str] # 话题
focus: Optional[str] # 焦点
presuppositions: List[str] # 预设
@dataclass
class ThreePlaneAnalysis:
"""三平面综合分析"""
sentence: str
form: FormAnalysis
content: ContentAnalysis
function: FunctionAnalysis
# 示例:分析"饭我吃了"
analysis = ThreePlaneAnalysis(
sentence="饭我吃了",
form=FormAnalysis(
word_order="OSV(话题前置)",
sentence_type="话题句",
constituents=[
{"text": "饭", "role": "话题/受事"},
{"text": "我", "role": "主语/施事"},
{"text": "吃了", "role": "谓语"}
]
),
content=ContentAnalysis(
predicate="吃",
arguments={"施事": "我", "受事": "饭"},
proposition="我吃了饭"
),
function=FunctionAnalysis(
speech_act="陈述",
topic="饭",
focus="我",
presuppositions=["存在某些饭(被提及的饭)"]
)
)
print(f"句子: {analysis.sentence}")
print(f"形式: {analysis.form.sentence_type}, 词序={analysis.form.word_order}")
print(f"内容: {analysis.content.proposition}")
print(f"功能: 话题={analysis.function.topic}, 焦点={analysis.function.focus}")
2.7.2 使用LangSC进行三平面分析
import json
from LangSC import GPF
# 创建分析器
gpf = GPF()
# 分析句子
text = "小明用钥匙打开了门"
# 形式层分析(词性、依存关系)
print("=== 形式层 ===")
Ret = gpf.Parse(text, Structure="Dep")
# 可视化依存结构
gpf.ShowStructure(Ret, "依存结构.png")
print(Ret) # 打印原始JSON查看结构
# 输出:
# 小明 nr SBV (主语)
# 用 p ADV (状语)
# 钥匙 n POB (介宾)
# 打开 v HED (核心)
# 了 u RAD (助词)
# 门 n VOB (宾语)
# 内容层分析(语义角色)
print("\n=== 内容层 ===")
roles = {"小明": "施事", "钥匙": "工具", "打开": "动作", "门": "受事"}
for word, role in roles.items():
print(f"{role}: {word}")
# 输出:
# 施事: 小明
# 工具: 钥匙
# 动作: 打开
# 受事: 门
# 转换为网格结构表示
gpf.SetText(text)
id1 = gpf.AddUnit("小明")
gpf.AddUnitKV(id1, "POS", "nr")
gpf.AddUnitKV(id1, "Role", "施事")
id2 = gpf.AddUnit("钥匙")
gpf.AddUnitKV(id2, "POS", "n")
gpf.AddUnitKV(id2, "Role", "工具")
id3 = gpf.AddUnit("打开")
gpf.AddUnitKV(id3, "POS", "v")
gpf.AddUnitKV(id3, "Role", "谓词")
id4 = gpf.AddUnit("门")
gpf.AddUnitKV(id4, "POS", "n")
gpf.AddUnitKV(id4, "Role", "受事")
gpf.AddRelation(id3, id1, "SBV")
gpf.AddRelation(id3, id2, "ADV")
gpf.AddRelation(id3, id4, "VOB")
print("\n=== 结构表示 ===")
gpf.ShowGrid("小明用钥匙_网格.png")
gpf.ShowRelation("小明用钥匙_关系.png")
2.7.3 歧义检测与消歧
import json
from LangSC import GPF
from typing import List, Tuple
gpf = GPF()
def detect_structural_ambiguity(sentence: str) -> List[Tuple[str, str]]:
"""检测结构歧义的可能性"""
ambiguities = []
# 分析句子
Ret = gpf.Parse(sentence, Structure="Segment")
tokens = json.loads(Ret)
# 检测"的"字结构歧义
de_positions = [i for i, t in enumerate(tokens) if t == "的"]
for pos in de_positions:
if pos > 0 and pos < len(tokens) - 1:
left = tokens[pos-1]
right = tokens[pos+1]
if pos > 1:
far_left = tokens[pos-2]
ambiguities.append((
f"[{far_left} {left}]的{right}",
f"{far_left} [{left}的{right}]"
))
# 检测数量短语歧义
num_patterns = ["三个人一组", "两个人一间"]
for pattern in num_patterns:
if pattern in sentence:
ambiguities.append((
f"每组三个人",
f"一共三个人分成若干组"
))
return ambiguities
# 示例
sentence = "漂亮的女孩的妈妈"
ambiguities = detect_structural_ambiguity(sentence)
for i, (reading1, reading2) in enumerate(ambiguities, 1):
print(f"歧义{i}:")
print(f" 理解A: {reading1}")
print(f" 理解B: {reading2}")
2.7.4 同义句式转换
import json
from LangSC import GPF
gpf = GPF()
def transform_voice(sentence: str) -> dict:
"""句式转换:主动/被动/把字句"""
# 使用依存分析加载到网格
gpf.SetText(sentence)
Ret = gpf.Parse(sentence, Structure="Dep")
gpf.AddStructure(Ret)
# 通过网格查询提取核心成分
sbv_units = gpf.GetUnit("Rel=SBV")
subject = gpf.GetWord(sbv_units[0]) if sbv_units else None
hed_units = gpf.GetUnit("Rel=HED")
verb = gpf.GetWord(hed_units[0]) if hed_units else None
vob_units = gpf.GetUnit("Rel=VOB")
obj = gpf.GetWord(vob_units[0]) if vob_units else None
if not all([subject, verb, obj]):
return {"error": "无法提取完整的主谓宾结构"}
return {
"原句": sentence,
"主动句": f"{subject}{verb}了{obj}",
"被动句": f"{obj}被{subject}{verb}了",
"把字句": f"{subject}把{obj}{verb}了"
}
# 示例
result = transform_voice("老师批评了小明")
for form, sent in result.items():
print(f"{form}: {sent}")
# 输出:
# 原句: 老师批评了小明
# 主动句: 老师批评了小明
# 被动句: 小明被老师批评了
# 把字句: 老师把小明批评了
第三章代码实践
3.8 本章代码实践
3.8.1 词汇分析
import json
from LangSC import GPF
gpf = GPF()
# 分词与词性标注
text = "人工智能正在改变世界"
Ret = gpf.Parse(text, Structure="POS")
pos_result = json.loads(Ret)
print("分词与词性:")
for tag in pos_result:
word, pos = tag.split("/")
print(f" {word} ({pos})")
# 词义分析(多义词)
print("\n'打'的词性变化:")
sentences = ["他打了一个电话", "请给我一打鸡蛋"]
for sent in sentences:
Ret = gpf.Parse(sent, Structure="POS")
tokens = json.loads(Ret)
for tag in tokens:
word, pos = tag.split("/")
if "打" in word:
print(f" '{sent}' 中的 '{word}': {pos}")
3.8.2 句子结构分析
import json
from LangSC import GPF
gpf = GPF()
text = "聪明的学生认真地完成了作业"
# 依存结构
print("依存关系:")
Ret = gpf.Parse(text, Structure="Dep")
gpf.ShowStructure(Ret, "句子结构.png")
print(Ret) # 查看原始JSON
# 短语结构树
print("\n句法树:")
Ret_tree = gpf.Parse(text, Structure="Tree")
tree_result = json.loads(Ret_tree)
print(json.dumps(tree_result, ensure_ascii=False, indent=2))
gpf.ShowStructure(Ret_tree, "句法树.png")
# 加载到网格中查找特定结构
gpf.SetText(text)
Ret_dep = gpf.Parse(text, Structure="Dep")
gpf.AddStructure(Ret_dep)
# 查找主谓关系
print("\n主谓关系:")
sbv_units = gpf.GetUnit("Rel=SBV")
for unit in sbv_units:
print(f" 主语: {gpf.GetWord(unit)}")
3.8.3 篇章衔接分析
import json
from LangSC import GPF
gpf = GPF()
def analyze_cohesion(sentences):
"""
分析篇章衔接
识别指代、词汇复现等衔接手段
"""
entities = {} # 实体跟踪
for i, sent in enumerate(sentences):
Ret = gpf.Parse(sent, Structure="POS")
tokens = json.loads(Ret)
# 识别命名实体和代词
for tag in tokens:
word, pos = tag.split("/")
if pos == 'r': # 代词
print(f"句{i+1}: 代词 '{word}' - 可能指代前文实体")
elif pos in ['nr', 'ns', 'nt']: # 命名实体
if word in entities:
print(f"句{i+1}: 实体 '{word}' 复现 (首次出现于句{entities[word]})")
else:
entities[word] = i + 1
print(f"句{i+1}: 新实体 '{word}'")
# 示例
text = [
"小明是北京大学的学生。",
"他每天都去图书馆学习。",
"这所大学的图书馆藏书丰富。"
]
analyze_cohesion(text)
3.8.4 模拟编译器前端
"""
简化的表达式解析器
展示词法分析→语法分析的过程
"""
import re
from dataclasses import dataclass
from typing import List, Union
@dataclass
class Token:
type: str
value: str
def lexer(text: str) -> List[Token]:
"""词法分析:将字符串切分为Token"""
tokens = []
patterns = [
(r'\d+', 'NUMBER'),
(r'[+\-*/]', 'OP'),
(r'\(', 'LPAREN'),
(r'\)', 'RPAREN'),
(r'\s+', None), # 跳过空白
]
pos = 0
while pos < len(text):
match = None
for pattern, token_type in patterns:
regex = re.compile(pattern)
match = regex.match(text, pos)
if match:
if token_type:
tokens.append(Token(token_type, match.group()))
pos = match.end()
break
if not match:
raise SyntaxError(f"非法字符: {text[pos]}")
return tokens
@dataclass
class Expr:
"""表达式AST节点"""
pass
@dataclass
class Number(Expr):
value: int
@dataclass
class BinaryOp(Expr):
op: str
left: Expr
right: Expr
def parse(tokens: List[Token]) -> Expr:
"""语法分析:将Token序列构建为AST(支持运算符优先级)"""
pos = 0
def parse_primary():
"""解析基本表达式(数字或括号表达式)"""
nonlocal pos
if pos >= len(tokens):
raise ValueError("意外的输入结束")
token = tokens[pos]
if token.type == 'NUMBER':
pos += 1
return Number(int(token.value))
elif token.type == 'LPAREN':
pos += 1
expr = parse_additive()
if pos >= len(tokens) or tokens[pos].type != 'RPAREN':
raise ValueError("缺少右括号")
pos += 1 # 跳过 )
return expr
else:
raise ValueError(f"意外的Token: {token}")
def parse_multiplicative():
"""解析乘除表达式(高优先级)"""
nonlocal pos
left = parse_primary()
while pos < len(tokens) and tokens[pos].type == 'OP' and tokens[pos].value in ('*', '/'):
op = tokens[pos].value
pos += 1
right = parse_primary()
left = BinaryOp(op, left, right)
return left
def parse_additive():
"""解析加减表达式(低优先级)"""
nonlocal pos
left = parse_multiplicative()
while pos < len(tokens) and tokens[pos].type == 'OP' and tokens[pos].value in ('+', '-'):
op = tokens[pos].value
pos += 1
right = parse_multiplicative()
left = BinaryOp(op, left, right)
return left
return parse_additive()
def evaluate(expr: Expr) -> int:
"""语义分析:计算表达式的值"""
if isinstance(expr, Number):
return expr.value
elif isinstance(expr, BinaryOp):
left = evaluate(expr.left)
right = evaluate(expr.right)
if expr.op == '+': return left + right
if expr.op == '-': return left - right
if expr.op == '*': return left * right
if expr.op == '/': return left // right
# 示例:测试运算符优先级
text = "3 + 5 * 2"
tokens = lexer(text)
print("Token序列:", tokens)
ast = parse(tokens)
print("AST:", ast)
result = evaluate(ast)
print("计算结果:", result) # 输出: 13(乘法优先于加法)
3.8.5 句子类型的结构差异
不同类型的句子具有不同的结构特征。以下用LangSC分析四种基本句型,观察其依存结构差异。
"""
比较四种基本句型的结构特征:陈述、疑问、祈使、感叹
"""
import json
from LangSC import GPF
gpf = GPF()
# 四种句型示例
sentences = {
"陈述句": "他每天认真学习",
"疑问句": "他每天学习什么",
"祈使句": "请认真完成作业",
"感叹句": "这篇文章写得真好"
}
for sent_type, text in sentences.items():
print(f"\n【{sent_type}】{text}")
# 词性标注
Ret = gpf.Parse(text, Structure="POS")
tags = json.loads(Ret)
pos_list = [tag.split("/")[1] for tag in tags]
print(f" 词性序列: {' '.join(pos_list)}")
# 依存分析
Ret = gpf.Parse(text, Structure="Dep")
gpf.AddStructure(Ret)
rels = gpf.GetRelation()
print(f" 依存关系数: {len(rels)}")
for r in rels:
head = gpf.GetUnitKV(r[0], "Word")
sub = gpf.GetUnitKV(r[1], "Word")
print(f" {head} ← {r[2]} ← {sub}")
观察提示:疑问句中常有疑问代词(r),祈使句中常缺少主语,感叹句中常有程度副词(d)。不同句型的依存结构差异反映了语言功能对形式的制约——这正是教材第二章"三平面"理论的体现。
第四章代码实践
4.10 本章代码实践
4.10.1 四种结构的Python实现
# 集合:无序不重复
keywords = {"人工智能", "机器学习", "深度学习"}
print("机器学习" in keywords) # True
print(keywords | {"自然语言处理"}) # 并集
# 序列:有序可重复
tokens = ["我", "喜欢", "自然", "语言", "处理"]
print(tokens[1]) # "喜欢"
print(tokens[:3]) # ["我", "喜欢", "自然"]
# 树:使用嵌套字典表示
syntax_tree = {
"type": "S",
"children": [
{"type": "NP", "word": "学生"},
{"type": "VP", "children": [
{"type": "V", "word": "读"},
{"type": "NP", "word": "书"}
]}
]
}
# 图:使用邻接表表示
dependency_graph = {
"读": [("学生", "SBV"), ("书", "VOB")],
"学生": [],
"书": []
}
4.10.2 JSON与Python对象的转换
import json
from dataclasses import dataclass, asdict
from typing import List
@dataclass
class Token:
word: str
pos: str
head: int
rel: str
@dataclass
class Sentence:
text: str
tokens: List[Token]
# 创建对象
sentence = Sentence(
text="学生读书",
tokens=[
Token("学生", "n", 2, "SBV"),
Token("读", "v", 0, "HED"),
Token("书", "n", 2, "VOB")
]
)
# 序列化为JSON
json_str = json.dumps(asdict(sentence), ensure_ascii=False, indent=2)
print(json_str)
# 从JSON反序列化
data = json.loads(json_str)
restored = Sentence(
text=data["text"],
tokens=[Token(**t) for t in data["tokens"]]
)
print(restored)
4.10.3 使用LangSC的结构表示
import json
from LangSC import GPF
# 创建结构
gpf = GPF()
# 设置文本
gpf.SetText("学生读书")
# 添加单元(AddUnit返回单元ID字符串)
id1 = gpf.AddUnit("学生")
gpf.AddUnitKV(id1, "POS", "n")
id2 = gpf.AddUnit("读")
gpf.AddUnitKV(id2, "POS", "v")
id3 = gpf.AddUnit("书")
gpf.AddUnitKV(id3, "POS", "n")
# 添加关系(使用单元ID)
gpf.AddRelation(id2, id1, "SBV")
gpf.AddRelation(id2, id3, "VOB")
# 导出为不同格式
print("网格可视化:")
gpf.ShowGrid("学生读书_网格.png")
print("关系可视化:")
gpf.ShowRelation("学生读书_关系.png")
# 使用Parse获取依存结构的JSON
Ret = gpf.Parse("学生读书", Structure="Dep")
print("\nJSON格式:")
print(Ret) # 打印原始JSON查看结构
# 可视化解析结果
gpf.ShowStructure(Ret, "学生读书_结构.png")
# 将依存结果加载到网格中进行查询
gpf.AddStructure(Ret)
4.10.4 结构遍历
def traverse_tree(tree, depth=0):
"""深度优先遍历树结构"""
indent = " " * depth
node_type = tree.get("type", "")
word = tree.get("word", "")
if word:
print(f"{indent}{node_type}: {word}")
else:
print(f"{indent}{node_type}")
for child in tree.get("children", []):
traverse_tree(child, depth + 1)
# 遍历句法树
syntax_tree = {
"type": "S",
"children": [
{"type": "NP", "word": "学生"},
{"type": "VP", "children": [
{"type": "V", "word": "读"},
{"type": "NP", "word": "书"}
]}
]
}
traverse_tree(syntax_tree)
4.10.5 XML与JSON的转换
教材中XML与JSON并列出现。以下示例展示如何用Python处理XML格式的结构数据,并与JSON互转。
"""
XML表示语言结构 + XML与JSON互转
"""
import xml.etree.ElementTree as ET
import json
# 1. 用XML表示依存结构
xml_str = """<?xml version="1.0" encoding="UTF-8"?>
<sentence id="s001">
<text>学生读书</text>
<tokens>
<token id="1" pos="n" head="2" deprel="SBV">学生</token>
<token id="2" pos="v" head="0" deprel="HED">读</token>
<token id="3" pos="n" head="2" deprel="VOB">书</token>
</tokens>
</sentence>
"""
# 2. 解析XML
root = ET.fromstring(xml_str)
print("句子:", root.find("text").text)
print("依存关系:")
for token in root.findall(".//token"):
word = token.text
pos = token.get("pos")
head = token.get("head")
deprel = token.get("deprel")
print(f" {word}/{pos} → head={head} ({deprel})")
# 3. XML → JSON 转换
def xml_to_dict(element):
"""将XML的token元素转为字典"""
return {
"id": int(element.get("id")),
"word": element.text,
"pos": element.get("pos"),
"head": int(element.get("head")),
"deprel": element.get("deprel")
}
json_data = {
"text": root.find("text").text,
"tokens": [xml_to_dict(t) for t in root.findall(".//token")]
}
print("\nJSON格式:")
print(json.dumps(json_data, ensure_ascii=False, indent=2))
对比思考:XML用标签和属性表达信息,JSON用键值对。对同一份依存结构,XML更适合文档标注(如TEI格式),JSON更适合程序间数据交换。选择格式时,应根据使用场景决定。
代码实践:第五至七章
第五章代码实践
5.9.1 使用LangSC进行句法分析
import json
from LangSC import GPF
# 创建GPF实例
gpf = GPF()
# 分析句子
text = "年轻的工程师认真地设计新产品"
# 获取分词与词性(Parse返回JSON字符串,需用json.loads解析)
Ret = gpf.Parse(text, Structure="POS")
result = json.loads(Ret)
print("分词与词性:")
for tag in result:
print(f" {tag}", end="") # tag已是"word/pos"格式
print()
# 获取依存关系(返回Web服务JSON,可直接可视化)
Ret = gpf.Parse(text, Structure="Dep")
print("\n依存关系:")
gpf.ShowStructure(Ret, "依存结构.png")
print(Ret) # 打印原始JSON查看
# 获取树形结构
Ret = gpf.Parse(text, Structure="Tree")
tree = json.loads(Ret)
print("\n树形结构:")
print(json.dumps(tree, ensure_ascii=False, indent=2))
5.9.2 使用LangSC检索BCC语料库
import json
from LangSC import GPF
from LangSC import BCC
gpf = GPF()
bcc = BCC("Corpus")
# 基本检索:获取上下文
Ret = bcc.Run("人工智能", Command="Context", Number=10)
results = json.loads(Ret) if isinstance(Ret, str) else Ret
for r in results:
print(r)
print()
# 带词性的检索:查找"很+形容词"
Ret = bcc.Run("很a", Command="Context", Number=10)
results = json.loads(Ret) if isinstance(Ret, str) else Ret
for r in results:
print(r)
# 搭配频率检索
Ret = bcc.Run("学习~", Command="Freq", Number=20)
results = json.loads(Ret) if isinstance(Ret, str) else Ret
print("\n'学习'的搭配词频率:")
for item in results:
print(f" {item}")
5.9.3 构建简单的语料库
import json
from dataclasses import dataclass, asdict
from typing import List, Dict
from LangSC import GPF
@dataclass
class AnnotatedSentence:
"""标注句子"""
id: str
text: str
tokens: List[Dict]
dependencies: str # 依存分析的原始JSON字符串
metadata: Dict
class SimpleCorpus:
"""简单语料库
使用LangSC的GPF实例进行自动标注。
gpf.Parse()返回JSON字符串,包含分词、词性、依存等信息。
"""
def __init__(self):
self.sentences = []
self.gpf = GPF()
def add_text(self, text: str, metadata: Dict = None) -> AnnotatedSentence:
"""添加文本并自动标注
Args:
text: 输入文本
metadata: 可选的元数据字典
Returns:
AnnotatedSentence: 标注后的句子对象
Raises:
ValueError: 当分析失败时抛出
"""
try:
# Parse返回JSON字符串,需用json.loads解析
# POS返回 ["word/pos", ...] 格式
pos_ret = json.loads(self.gpf.Parse(text, Structure="POS"))
# Dep返回原始JSON字符串
dep_json = self.gpf.Parse(text, Structure="Dep")
sentence = AnnotatedSentence(
id=f"sent_{len(self.sentences)+1:04d}",
text=text,
tokens=[{"word": w, "pos": p}
for tag in pos_ret
for w, p in [tag.split("/")]],
dependencies=dep_json, # 保存原始JSON字符串
metadata=metadata or {}
)
self.sentences.append(sentence)
return sentence
except Exception as e:
raise ValueError(f"分析文本失败: {text[:20]}... 错误: {e}")
def search(self, query: str) -> List[AnnotatedSentence]:
"""简单关键词检索"""
return [s for s in self.sentences if query in s.text]
def save(self, path: str):
"""保存为JSON Lines"""
with open(path, 'w', encoding='utf-8') as f:
for s in self.sentences:
f.write(json.dumps(asdict(s), ensure_ascii=False) + '\n')
def load(self, path: str):
"""从JSON Lines加载
Args:
path: 文件路径
Raises:
FileNotFoundError: 文件不存在
json.JSONDecodeError: JSON解析失败
"""
with open(path, 'r', encoding='utf-8') as f:
for i, line in enumerate(f, 1):
try:
data = json.loads(line)
self.sentences.append(AnnotatedSentence(**data))
except json.JSONDecodeError as e:
raise ValueError(f"第{i}行JSON解析失败: {e}")
# 使用示例
corpus = SimpleCorpus()
corpus.add_text("学生认真学习功课", {"source": "教材"})
corpus.add_text("老师耐心讲解知识", {"source": "教材"})
corpus.add_text("人工智能改变世界", {"source": "新闻"})
# 检索
for s in corpus.search("学"):
print(f"[{s.id}] {s.text}")
# 保存
corpus.save("my_corpus.jsonl")
5.9.4 搭配统计
import json
from collections import Counter
from typing import List
from LangSC import GPF
def analyze_collocations(texts: List[str], target_word: str, window: int = 2):
"""
分析目标词的搭配词
texts: 文本列表
target_word: 目标词
window: 窗口大小
"""
gpf = GPF()
left_words = Counter()
right_words = Counter()
for text in texts:
# Parse返回JSON字符串,需用json.loads解析
result = json.loads(gpf.Parse(text, Structure="Segment"))
words = result # Segment返回字符串列表,如 ["word1", "word2", ...]
for i, word in enumerate(words):
if word == target_word:
# 左搭配
for j in range(max(0, i-window), i):
left_words[words[j]] += 1
# 右搭配
for j in range(i+1, min(len(words), i+window+1)):
right_words[words[j]] += 1
return left_words, right_words
# 示例
texts = [
"我喜欢吃苹果",
"他喜欢看电影",
"她很喜欢听音乐",
"大家都喜欢这个老师"
]
left, right = analyze_collocations(texts, "喜欢")
print("'喜欢'的左搭配:", left.most_common(5))
print("'喜欢'的右搭配:", right.most_common(5))
5.9.5 语料库标注体系
教材第五章指出,语料库从"生语料"到"熟语料"的关键步骤是标注。标注需要一套明确的标签体系(标注规范)。本节通过代码展示词性标注集和依存关系标注集的结构,以及如何在实际分析中使用它们。
"""
语料库标注体系:词性标注集与依存关系标注集
"""
import json
from collections import Counter
from LangSC import GPF
gpf = GPF()
# ========= 1. 词性标注集 =========
# LangSC使用北大标注体系,常见标签如下:
pos_tagset = {
"n": "名词", "v": "动词", "a": "形容词",
"d": "副词", "p": "介词", "c": "连词",
"r": "代词", "m": "数词", "q": "量词",
"u": "助词", "w": "标点", "f": "方位词",
"t": "时间词", "s": "处所词", "nr": "人名",
"ns": "地名", "nt": "机构名", "nz": "其他专名",
"vn": "名动词", "an": "名形词",
}
print("【词性标注集(部分)】")
for tag, desc in pos_tagset.items():
print(f" {tag:4s} → {desc}")
# 对句子做词性标注,并映射到中文说明
text = "年轻的工程师在北京认真地设计新产品"
Ret = gpf.Parse(text, Structure="POS")
tags = json.loads(Ret)
print(f"\n句子:{text}")
print("词性标注结果:")
for tag in tags:
word, pos = tag.split("/")
desc = pos_tagset.get(pos, "未知")
print(f" {word:6s} {pos:4s} ({desc})")
# ========= 2. 依存关系标注集 =========
# 常见依存关系标签:
dep_tagset = {
"SBV": "主谓关系", "VOB": "动宾关系",
"ATT": "定中关系", "ADV": "状中关系",
"CMP": "动补关系", "COO": "并列关系",
"POB": "介宾关系", "LAD": "左附加",
"RAD": "右附加", "HED": "核心",
"WP": "标点",
}
print("\n【依存关系标注集(部分)】")
for tag, desc in dep_tagset.items():
print(f" {tag:4s} → {desc}")
# 分析句子的依存关系,映射标签含义
Ret = gpf.Parse(text, Structure="Dep")
gpf.AddStructure(Ret)
rels = gpf.GetRelation()
print(f"\n依存分析结果:")
for r in rels:
head_word = gpf.GetUnitKV(r[0], "Word")
sub_word = gpf.GetUnitKV(r[1], "Word")
rel = r[2]
desc = dep_tagset.get(rel, "未知")
print(f" {sub_word} →({rel}: {desc})→ {head_word}")
# ========= 3. 标注统计:分析标签分布 =========
# 对多个句子统计词性分布,了解语料特点
sentences = [
"学生认真学习功课",
"老师耐心讲解知识",
"科学家仔细研究问题",
"工程师努力开发产品",
]
pos_counter = Counter()
for sent in sentences:
Ret = gpf.Parse(sent, Structure="POS")
tags = json.loads(Ret)
for tag in tags:
_, pos = tag.split("/")
pos_counter[pos] += 1
print("\n【四句话的词性分布统计】")
for pos, count in pos_counter.most_common():
desc = pos_tagset.get(pos, "未知")
bar = "█" * count
print(f" {pos:4s} ({desc:4s}): {bar} {count}")
要点:标注体系是连接"原始文本"与"结构化数据"的桥梁。理解标注集中每个标签的含义,是正确使用语料库分析结果的前提。不同的标注规范(如北大体系 vs Penn Treebank体系)标签不同,使用时需确认当前工具所采用的标注体系。
5.9.6 构建微调数据集并体验大模型微调
教材5.6.5节介绍了大模型训练的四类数据集。本练习聚焦于SFT微调数据集的构建与使用,帮助读者理解从"语料"到"模型能力"的转化过程。
任务一:构建微调数据集
SFT(Supervised Fine-Tuning)微调数据集的标准格式为JSONL——每行一条JSON记录,包含instruction(指令)、input(输入,可选)和output(期望输出)三个字段。以下代码构建一个面向"语言结构分析"任务的微调数据集。
"""
构建SFT微调数据集:以"语言结构分析助手"为例
"""
import json
# SFT数据集的标准格式:每条数据包含instruction和output
# 部分数据集还包含input字段(当任务需要额外输入时)
sft_data = [
{
"instruction": "请对以下句子进行分词和词性标注。",
"input": "学生认真学习功课",
"output": "学生/n 认真/ad 学习/v 功课/n"
},
{
"instruction": "请分析以下句子的主谓宾结构。",
"input": "老师批改作业",
"output": "主语:老师,谓语:批改,宾语:作业"
},
{
"instruction": "请判断以下两个词是否属于同一聚合类,并说明理由。",
"input": "苹果、香蕉",
"output": "是。两者都属于"水果"聚合类,可以在"吃+X"的组合槽中互相替换。"
},
{
"instruction": "请将以下句子转换为依存关系三元组。",
"input": "聪明的学生读了一本好书",
"output": "(学生, ATT, 聪明)、(读, SBV, 学生)、(读, VOB, 书)、(书, ATT, 好)、(书, ATT, 一本)"
},
{
"instruction": "以下句子体现了什么组合关系?请列出主要的搭配。",
"input": "春风轻轻地吹过田野",
"output": "主谓搭配:春风—吹过;状中搭配:轻轻地—吹过;动宾搭配:吹过—田野。"
},
{
"instruction": "请用一句话概括以下文本的主题。",
"input": "语料库是语言使用实例的集合,通过大量真实语料,可以发现语言的统计规律和搭配模式。",
"output": "语料库是用于发现语言统计规律的真实语料集合。"
},
]
# 保存为JSONL格式(每行一条JSON,这是微调数据集的通用格式)
def save_sft_dataset(data, path):
"""保存SFT数据集为JSONL格式"""
with open(path, 'w', encoding='utf-8') as f:
for item in data:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
print(f"已保存 {len(data)} 条数据到 {path}")
save_sft_dataset(sft_data, "language_structure_sft.jsonl")
# 数据质量检查
def validate_sft_data(path):
"""检查SFT数据集的基本质量"""
issues = []
count = 0
with open(path, 'r', encoding='utf-8') as f:
for i, line in enumerate(f, 1):
try:
item = json.loads(line)
except json.JSONDecodeError:
issues.append(f"第{i}条JSON格式错误")
continue
count += 1
if "instruction" not in item:
issues.append(f"第{i}条缺少instruction字段")
if "output" not in item:
issues.append(f"第{i}条缺少output字段")
if len(item.get("output", "")) < 5:
issues.append(f"第{i}条output过短({len(item.get('output', ''))}字)")
if len(item.get("instruction", "")) < 5:
issues.append(f"第{i}条instruction过短")
print(f"共检查 {count} 条数据")
return issues
issues = validate_sft_data("language_structure_sft.jsonl")
if issues:
print("发现问题:")
for issue in issues:
print(f" - {issue}")
else:
print("数据质量检查通过!")
# 查看数据集内容
def preview_sft_data(path, n=3):
"""预览前n条数据"""
with open(path, 'r', encoding='utf-8') as f:
for i, line in enumerate(f, 1):
if i > n:
break
item = json.loads(line)
print(f"[{i}] 指令: {item['instruction']}")
if item.get('input'):
print(f" 输入: {item['input']}")
print(f" 输出: {item['output']}")
print()
preview_sft_data("language_structure_sft.jsonl")
任务二:使用低代码平台体验微调(选做)
目前已有多个低代码平台支持上传数据集、选择基座模型、一键启动微调训练,无需自行配置GPU环境。
| 平台 | 网址 | 特点 |
|---|---|---|
| 魔搭社区(ModelScope) | modelscope.cn | 阿里云支持,中文模型丰富,提供免费算力额度 |
| 百度千帆 | qianfan.cloud.baidu.com | 百度智能云平台,支持文心系列模型微调 |
| 讯飞星火 | xinghuo.xfyun.cn | 科大讯飞平台,面向开发者的微调服务 |
| AutoDL | autodl.com | GPU租用平台,搭配一键训练环境 |
| OpenBayes | openbayes.com | 在线GPU平台,支持Jupyter环境 |
操作步骤:
1. 注册并登录上述任一平台
2. 上传任务一中构建的JSONL数据集(language_structure_sft.jsonl)
3. 选择基座模型(推荐参数量较小的模型,如Qwen2-1.5B、ChatGLM3-6B等)
4. 配置训练参数(初次使用可保持默认,学习率建议2e-5,训练轮次1-3)
5. 启动训练,观察训练过程中loss曲线的变化
6. 训练完成后,用新的语言分析指令测试微调后模型的表现
思考:对比微调前后模型在语言结构分析任务上的表现差异。如果效果不理想,思考可能的原因——是数据量不足、数据质量不高,还是任务本身对模型来说太难?
要点:微调数据集的质量直接决定微调效果。数据不在多而在精——几百条高质量的指令-回答对,往往比几万条低质量数据更有效。这与传统语料库强调的"代表性"和"平衡性"原则是一致的:微调数据集同样需要覆盖目标任务的典型场景,保证指令和回答的准确性。
第六章代码实践
教材第六章的核心论点是"知识库的本体就是聚合的标准"。本体定义了概念分类的依据、层次的组织方式和属性的规范——没有本体,就没有有原则的聚合。以下代码实践依次实现知识的三种结构形式(框架、图谱、分类体系),并展示如何用LangSC访问知识库。通过编程实现这些结构,你将更深刻地理解:本体(6.9.3)提供了模式,决定了框架(6.9.1)该有哪些属性、图谱(6.9.2)该有哪些关系。
6.9.1 框架结构的实现
from dataclasses import dataclass, field
from typing import Dict, List, Any
@dataclass
class Frame:
"""框架:对象-属性结构"""
name: str
slots: Dict[str, Any] = field(default_factory=dict)
def set_slot(self, slot: str, value: Any):
self.slots[slot] = value
def get_slot(self, slot: str, default=None):
return self.slots.get(slot, default)
def to_dict(self):
return {"name": self.name, "slots": self.slots}
# 创建词汇框架
verb_frame = Frame("吃")
verb_frame.set_slot("词性", "动词")
verb_frame.set_slot("配价", 2)
verb_frame.set_slot("主语特征", ["有生命", "能进食"])
verb_frame.set_slot("宾语特征", ["食物", "可食用物"])
verb_frame.set_slot("例句", ["他吃了一个苹果", "孩子吃饭"])
print(verb_frame.to_dict())
6.9.2 知识图谱的实现
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Entity:
"""实体"""
id: str
name: str
type: str
properties: dict = None
@dataclass
class Relation:
"""关系"""
subject: str # 主体实体ID
predicate: str # 关系类型
object: str # 客体实体ID
class KnowledgeGraph:
"""简单知识图谱"""
def __init__(self):
self.entities = {} # id -> Entity
self.relations = [] # List[Relation]
def add_entity(self, entity: Entity):
self.entities[entity.id] = entity
def add_relation(self, relation: Relation):
self.relations.append(relation)
def query_by_subject(self, subject_id: str) -> List[Relation]:
"""查询主体的所有关系"""
return [r for r in self.relations if r.subject == subject_id]
def query_by_predicate(self, predicate: str) -> List[Relation]:
"""查询特定类型的所有关系"""
return [r for r in self.relations if r.predicate == predicate]
def get_related_entities(self, entity_id: str) -> List[Entity]:
"""获取关联的实体"""
related_ids = set()
for r in self.relations:
if r.subject == entity_id:
related_ids.add(r.object)
elif r.object == entity_id:
related_ids.add(r.subject)
return [self.entities[id] for id in related_ids if id in self.entities]
# 构建知识图谱
kg = KnowledgeGraph()
# 添加实体
kg.add_entity(Entity("1", "北京大学", "大学"))
kg.add_entity(Entity("2", "北京", "城市"))
kg.add_entity(Entity("3", "中国", "国家"))
kg.add_entity(Entity("4", "蔡元培", "人物"))
# 添加关系
kg.add_relation(Relation("1", "位于", "2"))
kg.add_relation(Relation("2", "属于", "3"))
kg.add_relation(Relation("4", "曾任校长", "1"))
# 查询
print("北京大学的关系:")
for r in kg.query_by_subject("1"):
obj = kg.entities[r.object]
print(f" {r.predicate} -> {obj.name}")
6.9.3 本体的实现:聚合的标准
教材6.3节指出,本体定义了"实例按什么标准聚合为概念、概念按什么方式组织为层次"——它是聚合操作的先行标准。以下代码实现一个简单的本体类,包含概念定义、层次关系(is-a)、属性约束和实例归属。注意:本体中的每个概念对应一个聚合类别,概念之间的层次关系对应分类体系的组织方式。
from dataclasses import dataclass, field
from typing import List, Optional, Dict
@dataclass
class Concept:
"""本体中的概念(聚合类别)"""
name: str
parent: Optional[str] = None # is-a关系:上位概念
properties: Dict[str, str] = field(default_factory=dict) # 属性约束
instances: List[str] = field(default_factory=list) # 归入该类的实例
class Ontology:
"""本体:定义聚合的标准(概念层次 + 属性约束)"""
def __init__(self):
self.concepts = {}
def add_concept(self, concept: Concept):
self.concepts[concept.name] = concept
def get_parent(self, concept_name: str) -> Optional[Concept]:
concept = self.concepts.get(concept_name)
if concept and concept.parent:
return self.concepts.get(concept.parent)
return None
def get_ancestors(self, concept_name: str) -> List[Concept]:
"""获取所有祖先概念"""
ancestors = []
current = concept_name
while True:
parent = self.get_parent(current)
if parent is None:
break
ancestors.append(parent)
current = parent.name
return ancestors
def get_children(self, concept_name: str) -> List[Concept]:
"""获取直接子概念"""
return [c for c in self.concepts.values()
if c.parent == concept_name]
def is_a(self, concept1: str, concept2: str) -> bool:
"""判断concept1是否是concept2的子概念"""
if concept1 == concept2:
return True
ancestors = self.get_ancestors(concept1)
return any(a.name == concept2 for a in ancestors)
# 构建交通工具概念分类体系
onto = Ontology()
onto.add_concept(Concept("交通工具"))
onto.add_concept(Concept("陆地交通工具", parent="交通工具"))
onto.add_concept(Concept("水上交通工具", parent="交通工具"))
onto.add_concept(Concept("航空交通工具", parent="交通工具"))
onto.add_concept(Concept("汽车", parent="陆地交通工具",
instances=["轿车", "SUV", "卡车"]))
onto.add_concept(Concept("火车", parent="陆地交通工具"))
onto.add_concept(Concept("轮船", parent="水上交通工具"))
onto.add_concept(Concept("飞机", parent="航空交通工具"))
# 查询
print("汽车的祖先概念:")
for ancestor in onto.get_ancestors("汽车"):
print(f" {ancestor.name}")
print("\n汽车是否是交通工具:", onto.is_a("汽车", "交通工具"))
print("汽车是否是航空交通工具:", onto.is_a("汽车", "航空交通工具"))
6.9.4 使用LangSC访问知识库
import json
from LangSC import GPF
# 创建GPF实例
gpf = GPF()
# 使用GetItem获取知识库中的词条列表
items = gpf.GetItem("WordSense")
print("知识库中的词条:")
for item in items:
print(f" {item}")
# 使用GetItemKV查询词的语义信息
word = "苹果"
senses = gpf.GetItemKV("WordSense", word, "definition")
print(f"\n词条: {word}")
print(f"释义: {senses}")
# 查询上位词
hypernym = gpf.GetItemKV("WordSense", word, "hypernym")
print(f"上位词: {hypernym}")
# 查询下位词
hyponyms = gpf.GetItemKV("WordSense", "水果", "hyponyms")
print(f"'水果'的下位词: {hyponyms}")
# 查询同义词
synonyms = gpf.GetItemKV("WordSense", word, "synonyms")
print(f"'苹果'的同义词: {synonyms}")
# 也可以使用AddBCCKV向知识库添加自定义键值对
gpf_corpus = GPF("./corpus")
gpf_corpus.AddBCCKV("自定义类别", "苹果 香蕉 橘子")
print("\n已添加自定义类别数据")
第七章代码实践
7.7 本章代码实践
本节提供综合运用本章方法的完整代码示例。
7.7.1 完整的结构处理流程
"""
完整示例:从文本到结构分析的全流程
"""
import json
from LangSC import GPF
def complete_structure_analysis(text):
"""完整的结构分析流程"""
gpf = GPF()
# 1. 文本分析:分词 + 词性标注
print("=== 1. 分词与词性标注 ===")
Ret = gpf.Parse(text, Structure="POS")
result = json.loads(Ret)
print(json.dumps(result, ensure_ascii=False, indent=2))
# 2. 依存句法分析
print("\n=== 2. 依存分析 ===")
Ret = gpf.Parse(text, Structure="Dep")
dep_result = json.loads(Ret)
print(json.dumps(dep_result, ensure_ascii=False, indent=2))
# 3. 结构统计:词性分布
print("\n=== 3. 结构统计 ===")
Ret = gpf.Parse(text, Structure="POS")
pos_result = json.loads(Ret)
pos_counts = {}
for tag in pos_result:
word, pos = tag.split("/")
pos_counts[pos] = pos_counts.get(pos, 0) + 1
print(f"词性分布: {pos_counts}")
# 4. 结构查询:使用网格操作查找关键模式
print("\n=== 4. 结构模式 ===")
gpf2 = GPF()
gpf2.SetText(text)
Ret = gpf2.Parse(text, Structure="Dep")
gpf2.AddStructure(Ret)
# 查找主谓结构(SBV)
sbv_units = gpf2.GetUnit("Rel=SBV")
for unit in sbv_units:
print(f"主谓: {gpf2.GetWord(unit)}")
# 查找动宾结构(VOB)
vob_units = gpf2.GetUnit("Rel=VOB")
for unit in vob_units:
print(f"动宾: {gpf2.GetWord(unit)}")
# 5. 用网格构建自定义结构
print("\n=== 5. 网格结构构建 ===")
gpf.SetText(text)
# 将分词结果加入网格
Ret = gpf.Parse(text, Structure="POS")
pos_result = json.loads(Ret)
units = []
for tag in pos_result:
word, pos = tag.split("/")
Unit = gpf.AddUnit(word)
gpf.AddUnitKV(Unit, "pos", pos)
units.append(Unit)
# 将依存关系加入网格
Ret = gpf.Parse(text, Structure="Dep")
gpf.AddStructure(Ret) # 依存结构直接加载到网格
Grid = gpf.GetGrid()
print(f"网格: {Grid}")
# 6. 事件提取(基于依存分析结果)
print("\n=== 6. 事件提取 ===")
events = extract_event(gpf, text)
print(f"事件: {events}")
return dep_result
def extract_event(gpf, text):
"""从依存分析结果中提取事件(简化实现)"""
gpf_ev = GPF()
gpf_ev.SetText(text)
Ret = gpf_ev.Parse(text, Structure="Dep")
gpf_ev.AddStructure(Ret)
# 使用网格查询提取事件角色
events = []
sbv_units = gpf_ev.GetUnit("Rel=SBV")
vob_units = gpf_ev.GetUnit("Rel=VOB")
hed_units = gpf_ev.GetUnit("Rel=HED")
event = {}
if hed_units:
event["predicate"] = gpf_ev.GetWord(hed_units[0])
if sbv_units:
event["agent"] = gpf_ev.GetWord(sbv_units[0])
if vob_units:
event["patient"] = gpf_ev.GetWord(vob_units[0])
if event:
events.append(event)
return events
# 运行示例
text = "北京大学的学生在图书馆认真地学习专业知识"
result = complete_structure_analysis(text)
7.7.2 结构比较与差异分析
"""
示例:比较两个句子的结构差异
"""
import json
from LangSC import GPF
def compare_structures(text1, text2):
"""比较两个句子的结构"""
gpf = GPF()
# 分别对两个句子做词性标注
s1_pos = json.loads(gpf.Parse(text1, Structure="POS"))
s2_pos = json.loads(gpf.Parse(text2, Structure="POS"))
print(f"句子1: {text1}")
print(f"句子2: {text2}")
# 比较词性分布
print("\n词性分布比较:")
pos1 = count_pos(s1_pos)
pos2 = count_pos(s2_pos)
all_pos = set(pos1.keys()) | set(pos2.keys())
for pos in sorted(all_pos):
c1 = pos1.get(pos, 0)
c2 = pos2.get(pos, 0)
diff = "=" if c1 == c2 else ("+" if c1 < c2 else "-")
print(f" {pos}: {c1} vs {c2} ({diff})")
# 比较依存结构(使用可视化)
print("\n依存结构可视化:")
Ret1 = gpf.Parse(text1, Structure="Dep")
gpf.ShowStructure(Ret1, "句子1_依存.png")
Ret2 = gpf.Parse(text2, Structure="Dep")
gpf.ShowStructure(Ret2, "句子2_依存.png")
# 结构相似度
similarity = calculate_structure_similarity(s1_pos, s2_pos)
print(f"\n结构相似度: {similarity:.2f}")
def count_pos(pos_result):
"""统计词性分布(Parse POS返回 ["word/pos", ...] 格式)"""
counts = {}
for tag in pos_result:
word, pos = tag.split("/")
counts[pos] = counts.get(pos, 0) + 1
return counts
def calculate_structure_similarity(s1_pos, s2_pos):
"""计算两个结构的相似度(简化版)"""
# 基于词性分布的Jaccard相似度
pos1 = set(count_pos(s1_pos).keys())
pos2 = set(count_pos(s2_pos).keys())
intersection = len(pos1 & pos2)
union = len(pos1 | pos2)
return intersection / union if union > 0 else 0
# 运行示例
compare_structures(
"学生读书",
"老师认真地批改作业"
)
7.7.3 五种结构操作分别演示
教材第七章提出结构处理的五种基本操作。以下用同一个句子分别展示每种操作。
"""
五种结构操作:遍历、查询、对齐、转换、生成
"""
import json
from LangSC import GPF
gpf = GPF()
text = "聪明的学生认真地完成了作业"
# ========= 1. 遍历(Traverse):逐一访问结构中的每个单元 =========
print("【遍历】逐一访问每个词及其词性")
Ret = gpf.Parse(text, Structure="POS")
tags = json.loads(Ret)
for i, tag in enumerate(tags):
word, pos = tag.split("/")
print(f" [{i}] {word} ({pos})")
# ========= 2. 查询(Query):查找符合条件的单元 =========
print("\n【查询】查找所有名词")
Ret = gpf.Parse(text, Structure="Dep")
gpf.AddStructure(Ret)
nouns = gpf.GetUnit("POS=n")
for u in nouns:
word = gpf.GetUnitKV(u, "Word")
print(f" 找到名词: {word}")
# ========= 3. 对齐(Align):建立两个结构之间的对应关系 =========
print("\n【对齐】比较两个句子中对应位置的词性")
text2 = "勤奋的老师仔细地批改了试卷"
Ret1 = gpf.Parse(text, Structure="POS")
Ret2 = gpf.Parse(text2, Structure="POS")
tags1 = json.loads(Ret1)
tags2 = json.loads(Ret2)
# 逐位置对齐比较
for t1, t2 in zip(tags1, tags2):
w1, p1 = t1.split("/")
w2, p2 = t2.split("/")
match = "✓" if p1 == p2 else "✗"
print(f" {w1}({p1}) ↔ {w2}({p2}) {match}")
# ========= 4. 转换(Transform):从一种结构转为另一种 =========
print("\n【转换】从词性标注结果转换为词频统计字典")
from collections import Counter
pos_counter = Counter()
for tag in tags1:
_, pos = tag.split("/")
pos_counter[pos] += 1
print(f" 词性分布: {dict(pos_counter)}")
# ========= 5. 生成(Generate):根据规则自动构建新结构 =========
print("\n【生成】从依存关系自动生成事件三元组")
rels = gpf.GetRelation()
subjects, objects, predicates = [], [], []
for r in rels:
head = gpf.GetUnitKV(r[0], "Word")
sub = gpf.GetUnitKV(r[1], "Word")
if r[2] == "SBV":
subjects.append(sub)
predicates.append(head)
elif r[2] == "VOB":
objects.append(sub)
if subjects and predicates:
obj = objects[0] if objects else "?"
print(f" 事件三元组: ({subjects[0]}, {predicates[0]}, {obj})")
对应关系:遍历=统计、查询=信息提取、对齐=翻译/比较、转换=格式互转、生成=自动写作。这五种操作可以自由组合——先遍历收集信息,再查询特定模式,将结果转换为所需格式,最后生成新的结构化输出。
代码实践:第八至十章
第八章代码实践
本章代码实践的主线:教材第八章提出,结构有两种形态——显式结构(面向人,用符号组织)和隐式结构(面向机器,用向量/参数组织)。两者本质上都是信息压缩的路径,只是压缩的主体不同:人类通过分类、命名、抽象进行压缩,机器通过统计学习、降维、参数化进行压缩。以下代码实践围绕这一核心认识展开:8.8.1-8.8.2演示向量的基本操作和显隐互补;8.8.3说明工具分工;8.8.4用代码体现四组深层对立;8.8.5-8.8.6通过RAG和检索对比,展示显式结构与隐式结构在工程中的协作。
8.8.1 词向量基础操作
# 使用Gensim加载预训练词向量
from gensim.models import KeyedVectors
# 加载中文词向量(需下载预训练模型)
# model = KeyedVectors.load_word2vec_format('chinese_word_vectors.bin', binary=True)
# 模拟词向量(实际使用时替换为真实模型)
import numpy as np
class SimpleWordVectors:
"""简化的词向量演示"""
def __init__(self):
# 模拟一些词向量(实际维度通常是100-300)
self.vectors = {
"国王": np.array([0.5, 0.3, 0.8, 0.1]),
"王后": np.array([0.4, 0.3, 0.8, 0.6]),
"男人": np.array([0.5, 0.2, 0.1, 0.1]),
"女人": np.array([0.4, 0.2, 0.1, 0.6]),
"苹果": np.array([-0.2, 0.8, -0.1, 0.3]),
"香蕉": np.array([-0.1, 0.7, -0.2, 0.3]),
"电脑": np.array([0.8, -0.3, 0.5, -0.2]),
}
def similarity(self, word1: str, word2: str) -> float:
"""计算余弦相似度"""
v1, v2 = self.vectors[word1], self.vectors[word2]
norm1, norm2 = np.linalg.norm(v1), np.linalg.norm(v2)
if norm1 == 0 or norm2 == 0:
return 0.0 # 零向量的相似度定义为0
return np.dot(v1, v2) / (norm1 * norm2)
def most_similar(self, word: str, topn: int = 3) -> list:
"""找最相似的词"""
similarities = []
for other in self.vectors:
if other != word:
sim = self.similarity(word, other)
similarities.append((other, sim))
return sorted(similarities, key=lambda x: -x[1])[:topn]
def analogy(self, a: str, b: str, c: str) -> str:
"""类比推理: a - b + c = ?"""
result_vec = self.vectors[a] - self.vectors[b] + self.vectors[c]
result_norm = np.linalg.norm(result_vec)
if result_norm == 0:
return None # 结果向量为零,无法进行类比
best_word, best_sim = None, -1
for word, vec in self.vectors.items():
if word not in [a, b, c]:
vec_norm = np.linalg.norm(vec)
if vec_norm == 0:
continue # 跳过零向量
sim = np.dot(result_vec, vec) / (result_norm * vec_norm)
if sim > best_sim:
best_word, best_sim = word, sim
return best_word
# 使用示例
wv = SimpleWordVectors()
print("'苹果'和'香蕉'的相似度:", f"{wv.similarity('苹果', '香蕉'):.3f}")
print("'苹果'和'电脑'的相似度:", f"{wv.similarity('苹果', '电脑'):.3f}")
print("'国王'最相似的词:", wv.most_similar("国王"))
print("国王 - 男人 + 女人 =", wv.analogy("国王", "男人", "女人"))
8.8.2 符号与向量的互补
from dataclasses import dataclass
from typing import List, Dict
import numpy as np
@dataclass
class HybridEntity:
"""混合表示:符号+向量"""
id: str
name: str
entity_type: str # 符号属性
properties: Dict[str, str] # 符号属性
embedding: np.ndarray # 向量表示
class HybridKnowledgeBase:
"""混合知识库:结合符号和向量"""
def __init__(self):
self.entities = {}
self.relations = [] # 符号关系
def add_entity(self, entity: HybridEntity):
self.entities[entity.id] = entity
def exact_match(self, name: str) -> HybridEntity:
"""精确匹配(符号方式)"""
for e in self.entities.values():
if e.name == name:
return e
return None
def semantic_search(self, query_embedding: np.ndarray, topk: int = 5) -> List[HybridEntity]:
"""语义搜索(向量方式)"""
query_norm = np.linalg.norm(query_embedding)
if query_norm == 0:
return [] # 查询向量为零,无法搜索
similarities = []
for e in self.entities.values():
e_norm = np.linalg.norm(e.embedding)
if e_norm == 0:
continue # 跳过零向量实体
sim = np.dot(query_embedding, e.embedding) / (query_norm * e_norm)
similarities.append((e, sim))
similarities.sort(key=lambda x: -x[1])
return [e for e, _ in similarities[:topk]]
def hybrid_search(self, query: str, query_embedding: np.ndarray) -> List[HybridEntity]:
"""混合搜索:先精确匹配,再语义扩展"""
# 1. 精确匹配
exact = self.exact_match(query)
if exact:
results = [exact]
# 2. 语义扩展:找相似实体
similar = self.semantic_search(exact.embedding, topk=3)
for e in similar:
if e not in results:
results.append(e)
return results
else:
# 无精确匹配,仅使用语义搜索
return self.semantic_search(query_embedding, topk=5)
# 使用示例
kb = HybridKnowledgeBase()
kb.add_entity(HybridEntity(
"1", "北京大学", "大学",
{"位置": "北京", "创建年份": "1898"},
np.array([0.3, 0.5, 0.7, 0.2])
))
kb.add_entity(HybridEntity(
"2", "清华大学", "大学",
{"位置": "北京", "创建年份": "1911"},
np.array([0.3, 0.5, 0.6, 0.3]) # 相似的向量
))
kb.add_entity(HybridEntity(
"3", "百度", "公司",
{"位置": "北京", "领域": "互联网"},
np.array([0.8, -0.3, 0.2, 0.5])
))
# 精确匹配
print("精确匹配 '北京大学':", kb.exact_match("北京大学").name)
# 语义搜索
query_vec = np.array([0.35, 0.5, 0.65, 0.25]) # 类似"大学"的向量
results = kb.semantic_search(query_vec, topk=2)
print("语义搜索结果:", [e.name for e in results])
8.8.3 向量操作说明
LangSC专注于符号化的语言结构分析(分词、词性标注、句法树、依存关系等),不提供词向量嵌入功能。如需进行向量操作,请使用以下第三方库:
- gensim:加载和使用预训练词向量(Word2Vec、FastText等)
- numpy:向量计算(余弦相似度、向量运算等)
- sentence-transformers:句子级别的语义嵌入
以下示例展示如何结合LangSC的结构分析与gensim的向量操作:
import json
import numpy as np
from LangSC import GPF
# LangSC用于符号化结构分析
gpf = GPF()
text = "学生认真读书"
# 使用LangSC获取分词结果
Ret = gpf.Parse(text, Structure="Segment")
tokens = json.loads(Ret)
print("LangSC分词结果:")
for word in tokens:
print(f" {word}") # Segment返回字符串列表
# 使用LangSC获取依存关系(返回Web服务JSON,可直接可视化)
Ret = gpf.Parse(text, Structure="Dep")
print("\nLangSC依存分析:")
gpf.ShowStructure(Ret, "依存结构.png")
print(Ret) # 打印原始JSON查看
# 使用LangSC构建结构并可视化
gpf.SetText(text)
Unit1 = gpf.AddUnit("学生")
gpf.AddUnitKV(Unit1, "pos", "n")
Unit2 = gpf.AddUnit("读")
gpf.AddUnitKV(Unit2, "pos", "v")
Unit3 = gpf.AddUnit("书")
gpf.AddUnitKV(Unit3, "pos", "n")
gpf.AddRelation(Unit2, Unit1, "SBV")
gpf.AddRelation(Unit2, Unit3, "VOB")
# 向量操作请使用gensim等第三方库,例如:
# from gensim.models import KeyedVectors
# model = KeyedVectors.load_word2vec_format('chinese_vectors.bin', binary=True)
# vec = model['学生']
# sim = model.similarity('学生', '老师')
8.8.4 四组对立的代码体现
教材第八章(8.6节)以"显式结构与隐式结构"作为组织轴心,提出符号与向量之间的四组深层对立——它们是显/隐这一根本区分在不同维度上的体现。以下通过代码直观展示每组对立的差异。
"""
四组对立(教材8.6节):
显/隐是根本框架,以下四组是它在不同维度上的体现——
1. 符号 vs 参数(知识的显隐载体)
2. 离散 vs 连续(表示的显隐特征)
3. 规则 vs 统计(推理的显隐实现)
4. 可解释 vs 不可解释(结果的显隐特征)
"""
import json
import numpy as np
from LangSC import GPF
gpf = GPF()
# ========= 1. 符号 vs 参数:知识的显隐载体 =========
print("【对立一:符号 vs 参数(知识的显隐载体)】")
# 显式结构:知识用符号承载,人可直接读取和修改
knowledge = {"猫": {"上位": "哺乳动物", "特征": ["有毛", "会叫"]}}
print(f" 显式(符号): 猫的上位概念 = {knowledge['猫']['上位']}")
# 隐式结构:知识编码在参数中,人无法直接读出
params = np.random.randn(100) # 模拟模型参数
print(f" 隐式(参数): 参数向量(前5维) = {params[:5].round(2)}")
print(f" → 无法从参数中读出'猫是哺乳动物'")
# ========= 2. 离散 vs 连续:表示的显隐特征 =========
print("\n【对立二:离散 vs 连续(表示的显隐特征)】")
# 离散(显式):词性是明确的类别,人一目了然
Ret = gpf.Parse("我喜欢猫", Structure="POS")
tags = json.loads(Ret)
print(f" 离散标签: {tags}") # 每个词有确定的词性
# 连续(隐式):相似度是0到1之间的实数,需要机器计算
sim = float(np.dot([0.3, 0.8], [0.4, 0.7]) /
(np.linalg.norm([0.3, 0.8]) * np.linalg.norm([0.4, 0.7])))
print(f" 连续相似度: {sim:.4f}") # 没有"相似/不相似"的二元判断
# ========= 3. 规则 vs 统计:推理的显隐实现 =========
print("\n【对立三:规则 vs 统计(推理的显隐实现)】")
# 规则方式(显式推理):明确的判断条件,步步可追溯
def is_noun_phrase(tags_list):
"""规则:形容词+名词 = 名词短语"""
for i in range(len(tags_list) - 1):
w1, p1 = tags_list[i].split("/")
w2, p2 = tags_list[i+1].split("/")
if p1 == "a" and p2 == "n":
return f"{w1}{w2}"
return None
print(f" 规则判断: {is_noun_phrase(tags) or '未找到'}")
# 统计方式(隐式推理):基于频率的判断,结果从数据中涌现
freq = {"猫": 500, "狗": 450, "鱼": 300}
total = sum(freq.values())
probs = {w: f/total for w, f in freq.items()}
print(f" 统计概率: {probs}")
# ========= 4. 可解释 vs 不可解释:结果的显隐特征 =========
print("\n【对立四:可解释 vs 不可解释(结果的显隐特征)】")
# 可解释(显式):每一步推理都有明确依据,人可追溯
Ret = gpf.Parse("学生读书", Structure="Dep")
gpf.AddStructure(Ret)
rels = gpf.GetRelation()
print(" 可解释的依存分析:")
for r in rels:
head = gpf.GetUnitKV(r[0], "Word")
sub = gpf.GetUnitKV(r[1], "Word")
print(f" {sub} →({r[2]})→ {head} ← 显式规则,可追溯")
# 不可解释(隐式):只有结果,推理过程编码在参数中
print(" 不可解释的向量计算:")
print(f" cos(猫,狗) = 0.85 ← 为什么是0.85?无法追溯")
核心启示:以上四组对立都是"显式结构 vs 隐式结构"这一根本区分在不同维度上的体现。显式方法(符号、规则、可解释)面向人,隐式方法(参数、统计、不可解释)面向机器。两者本质上是信息压缩的两种路径——人类主导的压缩可解释但有限,机器主导的压缩广覆盖但不可解释。最佳策略是显隐互补:人提供结构框架,机器填充内容。
8.8.5 构建简单的RAG检索增强生成系统
教材第八章提出显式结构与隐式结构的互补关系。RAG(Retrieval-Augmented Generation,检索增强生成)是这一互补思想在工程中的典型实践——知识库以人类可读的文本存储(显式结构),检索阶段用向量相似度找到相关内容(隐式结构),提示语将检索到的显式知识注入大模型的隐式空间,最后生成连贯回答。这正是"用符号激活向量"(教材8.2.5节)的具体体现。本练习构建一个简化的RAG系统,帮助理解"查询→嵌入→相似性检索→上下文增强→生成"的完整流程。
"""
简化RAG系统:基于向量检索的知识问答
教材8.8节提到向量数据库(Faiss、Milvus、Pinecone)是大模型应用的重要基础设施。
本代码用numpy实现最简版本,帮助理解核心原理。
"""
import numpy as np
from typing import List, Tuple, Dict
class SimpleVectorStore:
"""简单的向量存储(模拟向量数据库)
实际向量数据库使用HNSW、IVF等索引加速检索,
这里用暴力搜索演示核心逻辑:存储向量 + 按相似度检索。
"""
def __init__(self, dimension: int):
self.dimension = dimension
self.vectors = [] # 存储向量
self.documents = [] # 存储对应的文本
self.metadata = [] # 存储元数据
def add(self, text: str, vector: np.ndarray, meta: Dict = None):
"""添加一条文档及其向量表示"""
self.documents.append(text)
self.vectors.append(vector)
self.metadata.append(meta or {})
def search(self, query_vector: np.ndarray, top_k: int = 3) -> List[Tuple[str, float, Dict]]:
"""基于余弦相似度检索最相关的文档"""
if not self.vectors:
return []
query_norm = np.linalg.norm(query_vector)
if query_norm == 0:
return []
results = []
for i, vec in enumerate(self.vectors):
vec_norm = np.linalg.norm(vec)
if vec_norm == 0:
continue
sim = np.dot(query_vector, vec) / (query_norm * vec_norm)
results.append((self.documents[i], float(sim), self.metadata[i]))
results.sort(key=lambda x: -x[1])
return results[:top_k]
def size(self) -> int:
return len(self.documents)
class SimpleEmbedder:
"""简化的文本嵌入器(模拟embedding模型)
实际应用中使用sentence-transformers、OpenAI text-embedding等模型。
这里用关键词特征模拟,帮助理解嵌入的作用:
将文本映射到向量空间,使语义相近的文本距离更近。
"""
def __init__(self):
# 定义特征维度(每个维度对应一个语义主题)
self.topics = ["动物", "水果", "学校", "科技", "地理", "语言", "结构"]
self.topic_words = {
"动物": ["猫", "狗", "鸟", "鱼", "动物", "宠物", "哺乳"],
"水果": ["苹果", "香蕉", "水果", "橘子", "梨", "葡萄"],
"学校": ["学生", "老师", "学习", "大学", "教育", "课程"],
"科技": ["人工智能", "计算机", "大模型", "算法", "编程", "深度学习"],
"地理": ["北京", "上海", "中国", "城市", "首都", "位于"],
"语言": ["自然语言", "词汇", "语法", "句子", "语料", "分词"],
"结构": ["结构", "组合", "聚合", "层次", "关系", "表示"],
}
def embed(self, text: str) -> np.ndarray:
"""将文本转换为向量"""
vector = np.zeros(len(self.topics))
for i, topic in enumerate(self.topics):
for word in self.topic_words[topic]:
if word in text:
vector[i] += 1.0
# 归一化
norm = np.linalg.norm(vector)
if norm > 0:
vector = vector / norm
return vector
class SimpleRAG:
"""简化的RAG系统
完整流程:查询 → 嵌入 → 相似性检索 → 上下文增强 → 构建提示语
"""
def __init__(self):
self.embedder = SimpleEmbedder()
self.store = SimpleVectorStore(dimension=7)
def add_knowledge(self, text: str, source: str = ""):
"""添加知识到向量库"""
vector = self.embedder.embed(text)
self.store.add(text, vector, {"source": source})
def query(self, question: str, top_k: int = 3) -> Dict:
"""RAG查询:检索 + 上下文增强"""
# 第1步:将问题转为向量
query_vec = self.embedder.embed(question)
# 第2步:从向量库中检索相关知识
results = self.store.search(query_vec, top_k=top_k)
# 第3步:构建增强后的上下文
context_parts = []
for doc, sim, meta in results:
source = meta.get("source", "未知")
context_parts.append(f"[{source}] {doc}(相似度: {sim:.2f})")
context = "\n".join(context_parts)
# 第4步:构建发送给大模型的提示语
augmented_prompt = f"""请根据以下参考知识回答问题。
参考知识:
{context}
问题:{question}
请基于参考知识作答,如果参考知识不足以回答,请说明。"""
return {
"question": question,
"retrieved": results,
"augmented_prompt": augmented_prompt,
}
# ===== 使用示例 =====
rag = SimpleRAG()
# 添加知识库文档(模拟将教材内容存入知识库)
knowledge_base = [
("猫是一种常见的家养宠物,属于哺乳动物。", "百科"),
("北京是中国的首都,有着三千多年的历史。", "百科"),
("自然语言处理是人工智能的重要分支,研究计算机如何理解人类语言。", "教材"),
("大模型通过海量语料训练获得语言能力,但无法真正理解语言的深层结构。", "教材"),
("苹果是一种常见的水果,富含维生素C和膳食纤维。", "百科"),
("语料库是语言使用实例的集合,是组合结构的实证来源。", "教材"),
("词向量将离散的词汇符号映射到连续的向量空间中。", "教材"),
("大学生应该培养批判性思维和结构化表达能力。", "教材"),
("Python是一种广泛使用的程序设计语言,适合数据处理和人工智能开发。", "百科"),
("依存分析揭示句子中词与词之间的支配和被支配关系。", "教材"),
]
for text, source in knowledge_base:
rag.add_knowledge(text, source)
print(f"知识库已加载 {rag.store.size()} 条文档\n")
# 测试查询
queries = [
"什么是自然语言处理?",
"语料库有什么作用?",
"北京有什么特别之处?",
"词向量是怎么回事?",
]
for q in queries:
result = rag.query(q, top_k=2)
print(f"问题:{q}")
print("检索到的知识:")
for doc, sim, meta in result["retrieved"]:
print(f" [{meta.get('source', '')}] {doc} (相似度: {sim:.2f})")
print()
要点:RAG系统的核心思想是"检索+生成",体现了显式结构与隐式结构的协作——知识库存储人类可读的文本(显式),检索阶段通过向量相似性找到相关内容(隐式),提示语用符号将检索到的知识注入大模型(用符号激活向量),最后生成连贯回答。这正是教材第八章"显隐互补"的工程体现。实际系统中,嵌入模型(如bge-large-zh、text-embedding-ada-002)和向量数据库(如Faiss、Milvus、Chroma)会替代本例中的简化实现,但核心流程是一致的。
8.8.6 符号检索与向量检索的对比实验
教材第八章的核心命题是显式结构(符号)与隐式结构(向量)的互补。本练习通过同一批知识、同一组查询,分别用显式方式(关键词精确匹配)和隐式方式(语义相似度)进行检索,直观对比两种信息压缩路径在检索任务上的差异。
"""
对比实验:符号检索 vs 向量检索
通过精心设计的查询案例,展示两种检索方式各自的优势和局限。
"""
import numpy as np
from typing import List, Tuple, Dict
class SymbolicSearchEngine:
"""符号检索引擎:基于关键词精确匹配
这是传统信息检索的基本方式,对应教材中的"显式结构":
用离散的词汇符号进行匹配,结果是确定的(匹配/不匹配)。
"""
def __init__(self):
self.documents = []
def add(self, text: str):
"""添加文档"""
self.documents.append(text)
def search(self, query: str, top_k: int = 3) -> List[Tuple[str, int]]:
"""基于字符重叠的关键词检索"""
results = []
for doc in self.documents:
# 计算查询词在文档中出现的次数
score = 0
for char in set(query):
if char in doc and char not in "?,。、的了是在":
score += 1
if score > 0:
results.append((doc, score))
results.sort(key=lambda x: -x[1])
return results[:top_k]
class VectorSearchEngine:
"""向量检索引擎:基于语义相似度
对应教材中的"隐式结构":
将文本映射到向量空间,用余弦相似度衡量语义接近程度。
"""
def __init__(self, embedder):
self.embedder = embedder
self.documents = []
self.vectors = []
def add(self, text: str):
"""添加文档"""
self.documents.append(text)
self.vectors.append(self.embedder.embed(text))
def search(self, query: str, top_k: int = 3) -> List[Tuple[str, float]]:
"""基于余弦相似度的语义检索"""
query_vec = self.embedder.embed(query)
query_norm = np.linalg.norm(query_vec)
if query_norm == 0:
return []
results = []
for i, vec in enumerate(self.vectors):
vec_norm = np.linalg.norm(vec)
if vec_norm == 0:
continue
sim = np.dot(query_vec, vec) / (query_norm * vec_norm)
results.append((self.documents[i], float(sim)))
results.sort(key=lambda x: -x[1])
return results[:top_k]
# ===== 复用8.8.5中的SimpleEmbedder =====
# (实际使用时可将两个练习合并为同一个脚本)
class SimpleEmbedder:
"""简化的文本嵌入器"""
def __init__(self):
self.topics = ["动物", "水果", "学校", "科技", "地理", "语言", "结构"]
self.topic_words = {
"动物": ["猫", "狗", "鸟", "鱼", "动物", "宠物", "哺乳", "捕鼠", "驯化"],
"水果": ["苹果", "香蕉", "水果", "橘子", "梨", "葡萄"],
"学校": ["学生", "老师", "学习", "大学", "教育", "课程", "北京大学", "清华"],
"科技": ["人工智能", "计算机", "大模型", "算法", "编程", "深度学习", "Transformer"],
"地理": ["北京", "上海", "中国", "城市", "首都", "位于", "海淀"],
"语言": ["自然语言", "词汇", "语法", "句子", "语料", "分词", "处理"],
"结构": ["结构", "组合", "聚合", "层次", "关系", "表示", "依存"],
}
def embed(self, text: str) -> np.ndarray:
vector = np.zeros(len(self.topics))
for i, topic in enumerate(self.topics):
for word in self.topic_words[topic]:
if word in text:
vector[i] += 1.0
norm = np.linalg.norm(vector)
if norm > 0:
vector = vector / norm
return vector
# ===== 构建知识库 =====
knowledge = [
"猫是哺乳动物,善于捕鼠,是常见的家养宠物。",
"犬类是人类最早驯化的动物,忠诚且聪明。",
"北京大学成立于1898年,是中国顶尖学府。",
"清华大学位于北京海淀区,以工科著称。",
"自然语言处理是人工智能的重要研究方向。",
"大语言模型基于Transformer架构,通过海量语料训练。",
"依存分析用于揭示句子内部词语之间的结构关系。",
"苹果和香蕉都是常见的水果,营养丰富。",
]
# 初始化两个检索引擎
embedder = SimpleEmbedder()
sym_engine = SymbolicSearchEngine()
vec_engine = VectorSearchEngine(embedder)
for doc in knowledge:
sym_engine.add(doc)
vec_engine.add(doc)
# ===== 设计对比查询 =====
test_cases = [
{
"query": "什么动物会抓老鼠?",
"note": "语义相关但关键词不完全匹配("抓老鼠"vs"捕鼠")",
},
{
"query": "宠物有哪些?",
"note": "高度语义化的查询,关键词匹配较弱",
},
{
"query": "北京有哪些大学?",
"note": "关键词和语义都可匹配的查询",
},
{
"query": "怎么分析句子结构?",
"note": "概念性查询,需要语义理解",
},
]
# ===== 执行对比 =====
print("=" * 60)
print("符号检索 vs 向量检索 对比实验")
print("=" * 60)
for case in test_cases:
query = case["query"]
note = case["note"]
print(f"\n查询:{query}")
print(f"说明:{note}")
print("\n 【符号检索】(关键词匹配):")
sym_results = sym_engine.search(query, top_k=2)
if sym_results:
for text, score in sym_results:
print(f" [匹配度={score}] {text}")
else:
print(" (无匹配结果)")
print("\n 【向量检索】(语义相似度):")
vec_results = vec_engine.search(query, top_k=2)
for text, sim in vec_results:
print(f" [相似度={sim:.2f}] {text}")
print("-" * 60)
print("""
实验总结:
- 符号检索擅长精确匹配:查询词与文档词完全一致时效果好
- 向量检索擅长语义匹配:即使用词不同,语义相近也能找到
- 符号检索的局限:"抓老鼠"和"捕鼠"无法匹配(同义不同词)
- 向量检索的局限:可能引入语义相关但实际不相关的结果
- 最佳实践:两者结合——先用向量检索召回候选集,再用符号规则过滤排序
""")
要点: - 符号检索对应教材中的"显式结构"——离散、精确、可解释,但难以处理"同义不同词"问题(符号的离散性局限,见教材8.1.2) - 向量检索对应教材中的"隐式结构"——连续、模糊、覆盖广,但可能引入不相关结果(隐式结构的不可控性) - 实际工程中(如搜索引擎、RAG系统),两种方式通常结合使用:向量检索负责"召回"(隐式结构的广覆盖优势),符号规则负责"精排"(显式结构的可控优势) - 这正是教材第八章"显隐互补——人提供结构框架,机器填充内容"思想的工程体现
第九章代码实践
9.9 本章代码实践
本章教材的核心是结构化思维和知识注入,并从提示语延伸到智能体的结构设计。本节的代码实践聚焦于用程序实现任务分析、框架注入、结构化表达和智能体结构建模的思维过程,帮助你将教材9.1-9.5节的理论转化为可操作的工具。
9.9.1 任务分析器:语义角色填充
本练习对应教材9.2.2节(任务分析),用Python实现"语义角色分析法"——将一个模糊的任务请求结构化为完整的任务描述。
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class TaskAnalysis:
"""任务的语义角色分析结果(对应教材表9-2)"""
agent: str = "" # 施事:以什么身份/视角
action: str = "" # 动作:具体做什么
patient: str = "" # 受事:对什么做
purpose: str = "" # 目的:为什么做(深层功能)
condition: str = "" # 条件:在什么限制下
manner: str = "" # 方式:以什么形式呈现
criteria: str = "" # 评价标准:怎么算好
def completeness_check(self) -> Dict[str, bool]:
"""检查任务描述的完整性"""
roles = {
"施事(Agent)": self.agent,
"动作(Action)": self.action,
"受事(Patient)": self.patient,
"目的(Purpose)": self.purpose,
"条件(Condition)": self.condition,
"方式(Manner)": self.manner,
"评价标准(Criteria)": self.criteria,
}
return {name: bool(value.strip()) for name, value in roles.items()}
def missing_roles(self) -> List[str]:
"""返回缺失的语义角色"""
check = self.completeness_check()
return [name for name, filled in check.items() if not filled]
def to_structured_description(self) -> str:
"""生成结构化的任务描述"""
parts = []
if self.agent:
parts.append(f"立场:{self.agent}")
if self.purpose:
parts.append(f"目的:{self.purpose}")
if self.patient:
parts.append(f"对象:{self.patient}")
if self.action:
parts.append(f"任务:{self.action}")
if self.condition:
parts.append(f"约束:{self.condition}")
if self.manner:
parts.append(f"输出形式:{self.manner}")
if self.criteria:
parts.append(f"评价标准:{self.criteria}")
return "\n".join(parts)
# === 使用示例 ===
# 原始模糊请求
original_request = "帮我分析一下我们公司的用户流失情况。"
print(f"原始请求:{original_request}\n")
# 经过语义角色分析后的完整任务
analyzed_task = TaskAnalysis(
agent="以数据分析师的视角",
action="识别流失模式、分析流失原因、提出留存建议",
patient="近6个月的月度活跃用户数据",
purpose="用于产品团队周会讨论,指导下季度留存策略",
condition="重点关注付费用户,按用户等级分层分析",
manner="先给结论和关键数据,再展开详细分析",
criteria="能让产品经理快速抓住关键问题并做出行动决策",
)
# 完整性检查
missing = analyzed_task.missing_roles()
if missing:
print(f"⚠ 缺失的语义角色:{', '.join(missing)}")
else:
print("✓ 所有语义角色已填充完整")
print(f"\n结构化任务描述:\n{analyzed_task.to_structured_description()}")
# 对比:一个不完整的任务分析
incomplete_task = TaskAnalysis(
action="分析",
patient="用户流失情况",
)
print(f"\n--- 不完整版本 ---")
print(f"缺失的语义角色:{', '.join(incomplete_task.missing_roles())}")
要点:语义角色分析不是一种"提示语格式",而是一种通用的任务分析方法。同一方法可用于向同事布置工作、撰写需求文档。关键在于想清楚每个角色应该填什么,而不是最终文本的格式。
9.9.2 框架注入对比实验
本练习对应教材9.3.2节(框架注入),用代码构建不同的分析框架,观察框架如何改变任务描述的结构和方向。
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass
class AnalysisFramework:
"""分析框架(对应教材9.3.2节的"框架"概念)"""
name: str
dimensions: List[Dict[str, str]] # 每个维度:{name, weight, indicators}
description: str = ""
def build_task_with_framework(self, context: str, purpose: str) -> str:
"""将框架注入到任务描述中(功能-框架-表达三步法)"""
parts = []
# 第一步:功能定位(上下文+目的)
parts.append(f"背景:{context}")
parts.append(f"目的:{purpose}")
parts.append("")
# 第二步:框架注入
parts.append(f"请按以下分析框架({self.name})逐项分析:")
parts.append("")
for i, dim in enumerate(self.dimensions, 1):
weight_info = f"(权重{dim['weight']})" if dim.get('weight') else ""
parts.append(f"{i}. {dim['name']}{weight_info}")
if dim.get('indicators'):
parts.append(f" 关注指标:{dim['indicators']}")
parts.append("")
# 第三步:输出要求
parts.append("要求:每个维度先给出判断结论,再展开分析依据。")
parts.append("最后给出综合评估和建议。")
return "\n".join(parts)
# === 构建不同的分析框架 ===
# 框架A:通用SWOT框架
swot_framework = AnalysisFramework(
name="SWOT分析",
dimensions=[
{"name": "优势(Strengths)", "indicators": "核心竞争力、资源优势、技术壁垒"},
{"name": "劣势(Weaknesses)", "indicators": "短板、资源不足、能力缺口"},
{"name": "机会(Opportunities)", "indicators": "市场趋势、政策利好、技术红利"},
{"name": "威胁(Threats)", "indicators": "竞争加剧、政策风险、替代品"},
]
)
# 框架B:定制化的教育科技分析框架
edtech_framework = AnalysisFramework(
name="教育科技竞争力评估",
dimensions=[
{"name": "技术壁垒", "weight": "40%", "indicators": "核心算法可替代性、数据资产独特性"},
{"name": "用户粘性", "weight": "30%", "indicators": "切换成本、网络效应、内容沉淀"},
{"name": "政策合规", "weight": "20%", "indicators": "教育政策变化影响、合规成本"},
{"name": "资本效率", "weight": "10%", "indicators": "获客成本趋势、变现效率"},
]
)
# 同一个任务,注入不同框架
context = "我们是一家K12在线教育公司,主要竞品有猿辅导、作业帮、好未来"
purpose = "为Q3战略会议准备竞品分析,支撑产品路线图决策"
print("=" * 60)
print("【版本A:注入通用SWOT框架】")
print("=" * 60)
print(swot_framework.build_task_with_framework(context, purpose))
print("\n")
print("=" * 60)
print("【版本B:注入定制化行业框架】")
print("=" * 60)
print(edtech_framework.build_task_with_framework(context, purpose))
print("\n")
print("=" * 60)
print("【对比分析】")
print("=" * 60)
print("版本A使用通用框架:维度覆盖面广但缺乏行业针对性")
print("版本B使用定制框架:维度聚焦行业核心问题,有权重区分优先级")
print("关键差异:框架来自用户的专业判断,不是模型能自动提供的")
要点:这个实验的核心发现是——同样的任务,注入不同的分析框架会产生完全不同的分析方向。框架的质量取决于你对问题领域的理解深度,而不是提示语的"写法"。你可以将这里构建的框架实际提交给大模型,对比两个版本的输出差异。
9.9.3 功能-框架-表达三步法实践
本练习对应教材9.4.3节(功能-框架-表达三步法),用Python实现完整的三步设计过程,自动生成结构化的任务表达。
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class FunctionSpec:
"""第一步:功能定位(教材9.4.3)"""
audience: str # 为谁做?
purpose: str # 做什么用?
success_criteria: str # 什么标准算好?
@dataclass
class FrameworkSpec:
"""第二步:框架构建(教材9.4.3)"""
task_type: str # 任务类型(表9-3)
framework_dimensions: List[str] # 分析维度
context: str # 上下文信息
examples: List[Dict] = field(default_factory=list) # 示例
constraints_hard: List[str] = field(default_factory=list) # 硬约束
constraints_soft: List[str] = field(default_factory=list) # 软约束
@dataclass
class ThreeStepDesigner:
"""功能-框架-表达三步法设计器"""
function: FunctionSpec
framework: FrameworkSpec
def generate_expression(self) -> str:
"""第三步:按认知原则生成结构化表达"""
sections = []
# 原则一:从已知到未知 → 先背景后任务
sections.append(f"【背景】\n{self.framework.context}")
sections.append(f"\n【目的】\n{self.function.purpose}")
sections.append(f"受众:{self.function.audience}")
# 原则二:从整体到局部 → 先总体框架再逐项
sections.append(f"\n【任务框架】({self.framework.task_type}类任务)")
for i, dim in enumerate(self.framework.framework_dimensions, 1):
sections.append(f" {i}. {dim}")
# 约束条件(区分硬/软)
if self.framework.constraints_hard:
sections.append("\n【必须满足】")
for c in self.framework.constraints_hard:
sections.append(f" - {c}")
if self.framework.constraints_soft:
sections.append("\n【优先满足】")
for c in self.framework.constraints_soft:
sections.append(f" - {c}")
# 示例
if self.framework.examples:
sections.append("\n【示例参考】")
for ex in self.framework.examples:
sections.append(f" 输入:{ex.get('input', '')}")
sections.append(f" 输出:{ex.get('output', '')}")
# 评价标准(放在最后强调)
sections.append(f"\n【评价标准】\n{self.function.success_criteria}")
return "\n".join(sections)
def show_design_process(self):
"""展示完整的三步设计过程"""
print("=" * 60)
print("第一步:功能定位")
print("=" * 60)
print(f" 为谁做?→ {self.function.audience}")
print(f" 做什么用?→ {self.function.purpose}")
print(f" 什么标准算好?→ {self.function.success_criteria}")
print(f"\n{'=' * 60}")
print("第二步:框架构建")
print("=" * 60)
print(f" 任务类型:{self.framework.task_type}")
print(f" 分析维度:")
for dim in self.framework.framework_dimensions:
print(f" - {dim}")
print(f" 硬约束:{self.framework.constraints_hard}")
print(f" 软约束:{self.framework.constraints_soft}")
print(f"\n{'=' * 60}")
print("第三步:结构化表达(按认知原则组织)")
print("=" * 60)
print(self.generate_expression())
# === 使用示例 ===
# 设计一个学术场景的任务
designer = ThreeStepDesigner(
function=FunctionSpec(
audience="自己(正在开题的硕士生)",
purpose="整理NLP少样本学习领域的文献脉络,找出研究空白,支撑开题报告",
success_criteria="能清晰呈现研究脉络,识别出2-3个尚未被充分研究的方向",
),
framework=FrameworkSpec(
task_type="分析",
framework_dimensions=[
"研究主题分类(将文献按研究问题聚类)",
"方法论演进(早期方法 → 当前主流 → 新兴方法)",
"主要发现与共识(领域内达成共识的结论)",
"争议与分歧(尚有不同看法的问题)",
"研究空白(尚未被充分关注的方向)",
],
context="研究领域:NLP少样本学习。时间范围:2020-2025。"
"已读核心文献:GPT-3 (Brown et al.), PET (Schick et al.), "
"LM-BFF (Gao et al.), SetFit (Tunstall et al.)等。",
constraints_hard=[
"分析基于已提供的文献,不编造文献",
"每个论点需指出对应的文献来源",
],
constraints_soft=[
"每个部分用简洁的要点形式",
"重点关注研究空白和可能的创新点",
],
)
)
designer.show_design_process()
要点:三步法的核心不在于最终生成的文本格式,而在于设计过程的完整性和逻辑性。注意代码中的三个步骤是严格有序的——先功能、再框架、最后表达。尝试修改功能定位(如将受众从"硕士生"改为"产品经理"),观察框架和表达如何随之变化。
9.9.4 智能体结构建模:三要素规划
本练习对应教材9.5节(从提示语到智能体),用Python实现"三要素规划法"——将一个多智能体协作系统的设计过程结构化为单元、关系、属性三个层面的规划。
教材9.5.2节指出,设计智能体系统本质上就是三要素的规划过程:确定有哪些单元(智能体),确定它们之间的关系(协作方式),确定每个单元的属性(配置)。下面的代码将这一思路转化为可操作的程序模型。
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from collections import Counter
# ========== 第一步:定义智能体(单元) ==========
@dataclass
class Agent:
"""智能体的结构描述(对应教材表9-Z的四项属性)"""
name: str # 智能体名称
role: str # 角色定义(对应系统提示语)
tools: List[str] = field(default_factory=list) # 可用工具(对应第七章五种操作)
input_schema: Dict = field(default_factory=dict) # 输入格式(对应第四章形式化表示)
output_schema: Dict = field(default_factory=dict) # 输出格式
temperature: float = 0.7 # 生成参数(对应第八章隐式结构)
def describe(self) -> str:
"""输出智能体描述"""
tool_str = "、".join(self.tools) if self.tools else "无"
input_str = ", ".join(self.input_schema.keys()) if self.input_schema else "无"
output_str = ", ".join(self.output_schema.keys()) if self.output_schema else "无"
return (
f"【{self.name}】\n"
f" 角色:{self.role}\n"
f" 工具:{tool_str}\n"
f" 输入:{input_str}\n"
f" 输出:{output_str}\n"
f" Temperature:{self.temperature}"
)
# ========== 第二步:定义智能体之间的关系 ==========
@dataclass
class AgentRelation:
"""智能体之间的关系(对应教材表9-Y的四种关系类型)"""
source: str # 源智能体名称
target: str # 目标智能体名称
relation_type: str # 关系类型:sequential/parallel/hierarchical/conditional
description: str = "" # 关系说明
# 关系类型与教材概念的映射
TYPE_LABELS = {
"sequential": "顺序关系(线性组合)",
"parallel": "并行关系(并列组合)",
"hierarchical": "层次关系(层次组合)",
"conditional": "条件关系(聚合选择)",
}
def describe(self) -> str:
label = self.TYPE_LABELS.get(self.relation_type, self.relation_type)
arrow = "→" if self.relation_type != "parallel" else "‖"
desc = f" 说明:{self.description}" if self.description else ""
return f" {self.source} {arrow} {self.target} [{label}]{desc}"
# ========== 第三步:组装多智能体系统 ==========
class MultiAgentSystem:
"""多智能体系统:三要素的完整规划"""
def __init__(self, name: str, purpose: str):
self.name = name
self.purpose = purpose
self.agents: List[Agent] = [] # 单元
self.relations: List[AgentRelation] = [] # 关系
def add_agent(self, agent: Agent):
"""添加智能体(规划单元)"""
self.agents.append(agent)
def add_relation(self, relation: AgentRelation):
"""添加关系(规划关系)"""
self.relations.append(relation)
def validate(self) -> List[str]:
"""检查系统结构的完整性"""
issues = []
agent_names = {a.name for a in self.agents}
# 检查关系中引用的智能体是否都已定义
for rel in self.relations:
if rel.source not in agent_names:
issues.append(f"关系引用了未定义的智能体:{rel.source}")
if rel.target not in agent_names:
issues.append(f"关系引用了未定义的智能体:{rel.target}")
# 检查是否有孤立的智能体(未参与任何关系)
connected = set()
for rel in self.relations:
connected.add(rel.source)
connected.add(rel.target)
isolated = agent_names - connected
for name in isolated:
issues.append(f"智能体 {name} 未参与任何协作关系")
return issues
def show_structure(self):
"""展示系统结构摘要"""
print(f"{'='*50}")
print(f"系统名称:{self.name}")
print(f"系统目标:{self.purpose}")
print(f"{'='*50}")
# 单元概览
print(f"\n一、单元(共 {len(self.agents)} 个智能体)")
print("-" * 40)
for agent in self.agents:
print(agent.describe())
print()
# 关系概览
print(f"二、关系(共 {len(self.relations)} 条协作关系)")
print("-" * 40)
type_counts = Counter(r.relation_type for r in self.relations)
for rtype, count in type_counts.items():
label = AgentRelation.TYPE_LABELS.get(rtype, rtype)
print(f" {label}:{count} 条")
print()
for rel in self.relations:
print(rel.describe())
# 结构检查
issues = self.validate()
print(f"\n三、结构检查")
print("-" * 40)
if issues:
for issue in issues:
print(f" ⚠ {issue}")
else:
print(" 结构完整,所有智能体均已连接。")
# ========== 示例:文献综述智能体系统 ==========
# --- 单元规划:需要3个智能体 ---
retriever = Agent(
name="检索智能体",
role="根据研究主题,从学术数据库中检索相关文献,筛选并整理文献列表",
tools=["学术搜索", "文献筛选"], # 对应第七章的"查询"操作
input_schema={"research_topic": "str", "keywords": "list", "year_range": "str"},
output_schema={"papers": "list[dict]", "total_found": "int"},
temperature=0.3, # 低温度:检索需要精确,不需要创造性
)
analyzer = Agent(
name="分析智能体",
role="阅读文献列表,按主题分类,提取关键发现,识别研究空白",
tools=["文本分析", "分类归纳"], # 对应第七章的"遍历""对齐"操作
input_schema={"papers": "list[dict]"},
output_schema={"themes": "list", "findings": "list", "gaps": "list"},
temperature=0.5, # 中等温度:需要一定归纳能力
)
writer = Agent(
name="撰写智能体",
role="根据分析结果,按学术规范撰写文献综述,确保论证逻辑清晰",
tools=["文本生成", "格式规范"], # 对应第七章的"生成""转换"操作
input_schema={"themes": "list", "findings": "list", "gaps": "list"},
output_schema={"review_text": "str", "references": "list"},
temperature=0.7, # 较高温度:写作需要表达的灵活性
)
# --- 关系规划:流水线式顺序协作 ---
rel_1 = AgentRelation("检索智能体", "分析智能体", "sequential", "检索结果传递给分析")
rel_2 = AgentRelation("分析智能体", "撰写智能体", "sequential", "分析结果传递给撰写")
# --- 组装系统 ---
system = MultiAgentSystem(
name="文献综述协作系统",
purpose="自动完成某研究主题的文献检索、分类分析和综述撰写"
)
for agent in [retriever, analyzer, writer]:
system.add_agent(agent)
for rel in [rel_1, rel_2]:
system.add_relation(rel)
# --- 展示完整结构 ---
system.show_structure()
要点:这段代码是对教材9.5.2节"三要素视角"的程序化表达——
Agent对应单元,AgentRelation对应关系,Agent的各项字段(role、tools、schema、temperature)对应属性。注意三个智能体的temperature设置不同,这体现了第八章的洞见:隐式结构的参数配置影响生成行为的确定性与创造性。练习:(1) 将上述顺序关系改为层次关系——增加一个"监督智能体"来协调其余三个。系统结构会发生什么变化?(2) 回顾9.9.1的语义角色分析法和9.9.3的三步法,为
analyzer智能体的role字段设计一段更精细的系统提示语。
9.9.5 递归的三要素:从节点到系统的同构性
本练习对应教材9.5.2节(四),验证三要素在步骤层→智能体层→系统层三个粒度上的递归同构。我们在9.9.4的Agent基础上,为智能体增加内部步骤的描述能力,使三个层级都能用同一套三要素框架来分析。
from dataclasses import dataclass, field
from typing import List, Dict
# ========== 步骤层:智能体内部的处理节点 ==========
@dataclass
class AgentStep:
"""智能体内部的一个处理步骤(步骤层的"单元")"""
name: str # 步骤名称
instruction: str # 该步骤的提示语约束(步骤层的"属性")
alternatives: List[str] = field(default_factory=list) # 可替换策略(步骤层的"聚合")
def describe(self) -> str:
alt_str = "、".join(self.alternatives) if self.alternatives else "无"
return (
f" - {self.name}\n"
f" 指令约束:{self.instruction}\n"
f" 备选策略:{alt_str}"
)
# ========== 扩展Agent:增加内部步骤 ==========
@dataclass
class AgentWithSteps:
"""带内部步骤的智能体——展示三要素的递归性"""
name: str
role: str
tools: List[str] = field(default_factory=list)
temperature: float = 0.7
steps: List[AgentStep] = field(default_factory=list) # 内部步骤
step_order: str = "sequential" # 步骤间关系(步骤层的"关系")
def add_step(self, step: AgentStep):
self.steps.append(step)
def show_three_levels(self, system_name: str, system_agents: int):
"""展示三个层级上三要素的同构"""
print(f"\n{'='*55}")
print(f"三层级同构分析:{self.name}")
print(f"{'='*55}")
# 步骤层
print(f"\n【步骤层】({self.name} 内部)")
print(f" 单元:{len(self.steps)} 个处理步骤")
for s in self.steps:
print(s.describe())
print(f" 关系:步骤间为 {self.step_order} 执行")
print(f" 属性:每个步骤的指令约束(见上方)")
# 智能体层
print(f"\n【智能体层】({self.name} 整体)")
print(f" 单元:{self.name}(含 {len(self.steps)} 个内部步骤)")
print(f" 关系:内部步骤的 {self.step_order} 编排")
print(f" 属性:角色={self.role},工具={'、'.join(self.tools)},"
f"temperature={self.temperature}")
# 系统层
print(f"\n【系统层】({system_name})")
print(f" 单元:{system_agents} 个智能体({self.name} 是其中之一)")
print(f" 关系:智能体间的协作编排")
print(f" 属性:各智能体的角色定义、接口规范、全局约束")
# 聚合分析
print(f"\n【聚合维度】")
for s in self.steps:
if s.alternatives:
print(f" 步骤层聚合:{s.name} 可替换为 {'、'.join(s.alternatives)}")
# ========== 示例:分析智能体的内部步骤 ==========
analyzer = AgentWithSteps(
name="分析智能体",
role="阅读文献列表,按主题分类,提取关键发现,识别研究空白",
tools=["文本分析", "分类归纳"],
temperature=0.5,
)
analyzer.add_step(AgentStep(
name="文献预处理",
instruction="提取每篇文献的标题、摘要、关键词,统一为结构化格式",
alternatives=["正则提取", "大模型提取"],
))
analyzer.add_step(AgentStep(
name="主题聚类",
instruction="按研究主题将文献分为3-5个类别,每类给出类别标签",
alternatives=["关键词聚类", "语义相似度聚类", "大模型分类"],
))
analyzer.add_step(AgentStep(
name="发现提取",
instruction="从每个类别中提取核心发现,标注支撑文献编号",
))
analyzer.add_step(AgentStep(
name="空白识别",
instruction="对比各类别的覆盖范围,识别研究不足或矛盾之处",
))
# 展示三层级同构
analyzer.show_three_levels(system_name="文献综述协作系统", system_agents=3)
运行结果:
=======================================================
三层级同构分析:分析智能体
=======================================================
【步骤层】(分析智能体 内部)
单元:4 个处理步骤
- 文献预处理
指令约束:提取每篇文献的标题、摘要、关键词,统一为结构化格式
备选策略:正则提取、大模型提取
- 主题聚类
指令约束:按研究主题将文献分为3-5个类别,每类给出类别标签
备选策略:关键词聚类、语义相似度聚类、大模型分类
- 发现提取
指令约束:从每个类别中提取核心发现,标注支撑文献编号
备选策略:无
- 空白识别
指令约束:对比各类别的覆盖范围,识别研究不足或矛盾之处
备选策略:无
关系:步骤间为 sequential 执行
属性:每个步骤的指令约束(见上方)
【智能体层】(分析智能体 整体)
单元:分析智能体(含 4 个内部步骤)
关系:内部步骤的 sequential 编排
属性:角色=阅读文献列表,按主题分类,提取关键发现,识别研究空白,
工具=文本分析、分类归纳,temperature=0.5
【系统层】(文献综述协作系统)
单元:3 个智能体(分析智能体 是其中之一)
关系:智能体间的协作编排
属性:各智能体的角色定义、接口规范、全局约束
【聚合维度】
步骤层聚合:文献预处理 可替换为 正则提取、大模型提取
步骤层聚合:主题聚类 可替换为 关键词聚类、语义相似度聚类、大模型分类
要点:
AgentStep与Agent的结构是同构的——都有名称(单元标识)、约束/角色(属性)、以及与同层级其他元素的关系。alternatives字段体现了聚合维度:同一位置上的可替换选项。三个层级的代码结构一模一样,只是粒度不同,这正是教材所说的"同一个三要素框架在三个尺度上自相似地重现"。练习:(1) 为"检索智能体"和"撰写智能体"也添加内部步骤,分别调用
show_three_levels(),验证三层级同构是否同样成立。(2) 在"主题聚类"步骤的alternatives中选择一种备选策略,思考这个选择会如何影响后续步骤——这就是聚合选择对组合结构的约束。(3) 观察三个智能体的步骤指令中是否共享了"文献""主题""发现"等术语,思考这些跨层级的共享标签起了什么作用。
第十章代码实践
10.9 本章代码实践
10.9.1 模拟理性推理与经验模式
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple
import random
@dataclass
class Concept:
"""概念表示(理性结构)"""
name: str
features: Dict[str, bool] # 定义特征
parent: Optional[str] = None # 上位概念
class RationalReasoner:
"""理性推理器(符号方式)"""
def __init__(self):
self.concepts = {}
self.rules = []
def define_concept(self, concept: Concept):
"""定义概念"""
self.concepts[concept.name] = concept
def add_rule(self, condition: str, conclusion: str):
"""添加推理规则"""
self.rules.append((condition, conclusion))
def is_instance(self, entity_features: Dict, concept_name: str) -> Tuple[bool, List[str]]:
"""判断实体是否属于概念(可解释的推理)"""
if concept_name not in self.concepts:
return False, ["概念未定义"]
concept = self.concepts[concept_name]
reasons = []
for feature, required_value in concept.features.items():
if feature not in entity_features:
return False, [f"缺少特征: {feature}"]
if entity_features[feature] != required_value:
reasons.append(f"特征不匹配: {feature}应为{required_value}")
return False, reasons
return True, [f"所有特征匹配,是{concept_name}"]
def infer(self, facts: List[str]) -> Tuple[List[str], List[str]]:
"""基于规则的推理(演绎)"""
conclusions = []
explanations = []
for condition, conclusion in self.rules:
if condition in facts:
conclusions.append(conclusion)
explanations.append(f"因为'{condition}',所以'{conclusion}'")
return conclusions, explanations
class EmpiricalPredictor:
"""经验预测器(模拟统计方式)"""
def __init__(self):
self.patterns = {} # 存储观察到的模式
def learn(self, observations: List[Tuple[str, str]]):
"""从观察中学习模式(模拟训练)"""
for context, outcome in observations:
if context not in self.patterns:
self.patterns[context] = {}
self.patterns[context][outcome] = self.patterns[context].get(outcome, 0) + 1
def predict(self, context: str) -> Tuple[str, float, str]:
"""基于模式的预测(模拟推理)"""
if context not in self.patterns:
return "未知", 0.0, "没有相关的训练数据"
outcomes = self.patterns[context]
total = sum(outcomes.values())
best_outcome = max(outcomes, key=outcomes.get)
confidence = outcomes[best_outcome] / total
explanation = f"在{total}次观察中,'{best_outcome}'出现{outcomes[best_outcome]}次"
return best_outcome, confidence, explanation
# 对比演示
print("=== 理性推理 ===")
reasoner = RationalReasoner()
reasoner.define_concept(Concept("哺乳动物", {"胎生": True, "哺乳": True}))
reasoner.define_concept(Concept("猫", {"胎生": True, "哺乳": True, "有胡须": True}, "哺乳动物"))
reasoner.add_rule("X是猫", "X是哺乳动物")
reasoner.add_rule("X是哺乳动物", "X需要食物")
entity = {"胎生": True, "哺乳": True, "有胡须": True}
is_cat, reasons = reasoner.is_instance(entity, "猫")
print(f"判断结果: {'是猫' if is_cat else '不是猫'}")
print(f"推理过程: {reasons}")
conclusions, explanations = reasoner.infer(["X是猫"])
print(f"推导结论: {conclusions}")
print(f"推理链: {explanations}")
print("\n=== 经验预测 ===")
predictor = EmpiricalPredictor()
# 模拟训练数据
observations = [
("天阴多云", "下雨"), ("天阴多云", "下雨"), ("天阴多云", "不下雨"),
("晴空万里", "不下雨"), ("晴空万里", "不下雨"), ("晴空万里", "不下雨"),
]
predictor.learn(observations)
prediction, confidence, explanation = predictor.predict("天阴多云")
print(f"预测: {prediction} (置信度: {confidence:.2f})")
print(f"依据: {explanation}")
10.9.2 人机协作流程模拟
from dataclasses import dataclass
from typing import Callable, List, Any
from enum import Enum
class Role(Enum):
HUMAN = "human"
MACHINE = "machine"
@dataclass
class Task:
"""任务定义"""
description: str
role: Role
input_type: str
output_type: str
validator: Callable[[Any], bool] = None
class HumanMachineWorkflow:
"""人机协作工作流"""
def __init__(self, name: str):
self.name = name
self.tasks = []
self.results = {}
def add_task(self, task: Task):
"""添加任务"""
self.tasks.append(task)
def execute(self, initial_input: Any, human_executor: Callable, machine_executor: Callable):
"""执行工作流"""
current_input = initial_input
for i, task in enumerate(self.tasks):
print(f"\n[步骤{i+1}] {task.description}")
print(f" 执行者: {task.role.value}")
print(f" 输入: {str(current_input)[:100]}...")
if task.role == Role.HUMAN:
result = human_executor(task, current_input)
else:
result = machine_executor(task, current_input)
# 验证结果
if task.validator and not task.validator(result):
print(f" 警告: 结果未通过验证")
self.results[f"step_{i+1}"] = result
current_input = result
print(f" 输出: {str(result)[:100]}...")
return self.results
# 示例:文档生成工作流
def human_define_structure(task, input_data):
"""人类定义文档结构"""
return {
"title": "AI技术趋势报告",
"sections": ["背景介绍", "技术分析", "应用案例", "未来展望"],
"word_count": 2000,
"audience": "技术管理者"
}
def machine_generate_content(task, structure):
"""机器生成内容(模拟)"""
content = {}
for section in structure["sections"]:
content[section] = f"[{section}的生成内容,约{structure['word_count']//4}字]..."
return {"structure": structure, "content": content}
def human_review(task, draft):
"""人类审核"""
review = {
"approved": True,
"comments": ["背景介绍可以更具体", "技术分析部分很好"],
"revised_content": draft["content"]
}
return review
def machine_polish(task, reviewed):
"""机器润色"""
polished = reviewed["revised_content"].copy()
for section in polished:
polished[section] = f"[润色后的{section}内容]"
return polished
# 创建工作流
workflow = HumanMachineWorkflow("文档生成")
workflow.add_task(Task("定义文档结构和要求", Role.HUMAN, "topic", "structure"))
workflow.add_task(Task("生成初稿内容", Role.MACHINE, "structure", "draft"))
workflow.add_task(Task("审核并提供修改意见", Role.HUMAN, "draft", "review"))
workflow.add_task(Task("根据反馈润色定稿", Role.MACHINE, "review", "final"))
# 执行
executors = {
Role.HUMAN: lambda t, i: {
"定义": human_define_structure,
"审核": human_review
}.get(t.description[:2], lambda t,i: i)(t, i),
Role.MACHINE: lambda t, i: {
"生成": machine_generate_content,
"根据": machine_polish
}.get(t.description[:2], lambda t,i: i)(t, i)
}
print("=== 人机协作工作流演示 ===")
results = workflow.execute(
"AI技术发展",
lambda t, i: executors[Role.HUMAN](t, i),
lambda t, i: executors[Role.MACHINE](t, i)
)
10.9.3 结构化思维能力评估
from dataclasses import dataclass
from typing import List, Dict
import json
@dataclass
class StructuredThinkingTest:
"""结构化思维测试"""
question: str
test_type: str # decomposition, classification, formalization
sample_answer: Dict
scoring_rubric: Dict[str, int]
class StructuredThinkingAssessor:
"""结构化思维评估器"""
def __init__(self):
self.tests = []
def add_test(self, test: StructuredThinkingTest):
self.tests.append(test)
def evaluate(self, answer: Dict, test: StructuredThinkingTest) -> Dict:
"""评估答案"""
score = 0
feedback = []
for criterion, points in test.scoring_rubric.items():
if criterion in answer:
if isinstance(answer[criterion], list) and len(answer[criterion]) > 0:
score += points
feedback.append(f"✓ {criterion}: +{points}分")
elif isinstance(answer[criterion], str) and len(answer[criterion]) > 10:
score += points
feedback.append(f"✓ {criterion}: +{points}分")
else:
feedback.append(f"✗ {criterion}: 内容不足")
else:
feedback.append(f"✗ {criterion}: 缺失")
return {
"score": score,
"max_score": sum(test.scoring_rubric.values()),
"feedback": feedback
}
# 示例测试
assessor = StructuredThinkingAssessor()
# 分解能力测试
decomposition_test = StructuredThinkingTest(
question="请将'开发一个在线商城'分解为可执行的子任务",
test_type="decomposition",
sample_answer={
"层次1": ["需求分析", "系统设计", "开发实现", "测试部署"],
"层次2": {
"需求分析": ["用户调研", "功能规划", "需求文档"],
"系统设计": ["架构设计", "数据库设计", "接口设计"],
}
},
scoring_rubric={
"层次1": 20,
"层次2": 30,
"依赖关系": 20,
"时间估计": 15,
"资源需求": 15
}
)
# 用户答案
user_answer = {
"层次1": ["需求分析", "前端开发", "后端开发", "测试"],
"层次2": {
"需求分析": ["市场调研", "用户访谈"],
"前端开发": ["页面设计", "交互实现"]
},
"依赖关系": "需求分析完成后才能开始开发"
}
result = assessor.evaluate(user_answer, decomposition_test)
print("=== 结构化思维评估 ===")
print(f"得分: {result['score']}/{result['max_score']}")
print("评价:")
for fb in result["feedback"]:
print(f" {fb}")
生成性实践任务
以上代码实践以分析性任务为主(给定文本→调用API→观察结果)。以下补充生成性任务——从"观察结构"转向"设计结构",体现第九章"从分析到构造"的跃迁。
生成任务一:结构框架对输出质量的影响实验
目标:设计不同结构化程度的提示语框架,观察大模型输出质量的变化。
import json
# 设计三种不同结构化程度的提示语
prompts = {
"无结构": "分析一下《红楼梦》",
"弱结构": "请从人物、情节、主题三个角度分析《红楼梦》",
"强结构": json.dumps({
"任务": "文学作品分析",
"作品": "《红楼梦》",
"分析框架": {
"人物分析": {
"要求": "选取3个核心人物",
"维度": ["性格特征", "人物关系", "命运走向"]
},
"情节分析": {
"要求": "梳理主线与支线",
"维度": ["核心冲突", "转折事件", "结局走向"]
},
"主题分析": {
"要求": "提炼2-3个核心主题",
"维度": ["表层含义", "深层隐喻", "时代意义"]
}
},
"输出格式": "按上述框架逐项输出,每项200字左右"
}, ensure_ascii=False, indent=2)
}
# 将三种提示语分别提交给大模型,记录输出
# 然后人工评估三种输出在以下维度上的差异:
evaluation_dimensions = {
"完整性": "是否覆盖了所有重要方面",
"结构性": "输出是否层次分明、条理清晰",
"针对性": "是否切题、是否有泛泛而谈",
"一致性": "重复执行时输出是否稳定"
}
print("=== 实验设计 ===")
print("请将以下三种提示语分别提交给大模型:\n")
for level, prompt in prompts.items():
print(f"【{level}】")
print(prompt)
print()
print("=== 评估维度 ===")
for dim, desc in evaluation_dimensions.items():
print(f" {dim}: {desc}")
练习: 1. 将三种提示语提交给大模型,保存三份输出 2. 按四个维度打分(1-5分),记录在评分表中 3. 分析"结构化程度"与"输出质量"的相关性 4. 思考:哪些维度最受结构化程度的影响?为什么?
生成任务二:大模型输出的结构提取与人工对比
目标:从大模型的自由文本输出中提取结构(单元、关系),与人工标注对比。
import json
# 步骤1:让大模型自由生成一段分析文本
free_prompt = "请分析'人工智能对教育的影响',写一段500字的分析。"
# (将此提交给大模型,获取输出文本)
# 步骤2:定义结构提取的schema
extraction_schema = {
"实体类型": ["技术", "应用场景", "影响", "利益相关方"],
"关系类型": ["导致", "应用于", "影响", "促进", "阻碍"],
"属性": ["正面/负面", "短期/长期", "确定性程度"]
}
# 步骤3:让大模型按schema重新组织同一内容
structured_prompt = f"""
请将以下文本中的信息,按照给定的schema提取为结构化JSON:
Schema:
{json.dumps(extraction_schema, ensure_ascii=False, indent=2)}
输出格式:
{{
"实体": [{{"名称": "...", "类型": "...", "属性": {{}}}}],
"关系": [{{"源": "...", "目标": "...", "类型": "...", "属性": {{}}}}]
}}
"""
# 步骤4:人工也做同样的结构提取,与模型结果对比
comparison_table = """
| 对比维度 | 人工提取 | 模型提取 | 差异分析 |
|-----------------|---------|---------|---------|
| 实体数量 | | | |
| 关系数量 | | | |
| 遗漏的关键实体 | | | |
| 错误的关系 | | | |
| 属性标注一致性 | | | |
"""
print("=== 实验流程 ===")
print("1. 获取大模型自由文本输出")
print("2. 人工按schema提取结构")
print("3. 让大模型按同一schema提取结构")
print("4. 对比两份结构,填写对比表")
print(comparison_table)
练习: 1. 完成上述实验流程,提交人工提取和模型提取的两份JSON 2. 分析两者的差异:模型在哪些方面表现好?在哪些方面需要人的判断? 3. 思考:这个实验如何体现了"显隐互补"原则?
实验一:组合结构的分析与表示
一、实验信息
- 实验名称:组合结构的分析与表示
- 对应章节:第一章《组合与聚合》、第三章《语言的层级结构》、第四章《语言结构表示》
- 实验学时:4学时
- 难度等级:★★☆
二、实验目的
- 理解组合结构的层次性(词→短语→句子→篇章)
- 掌握用表格和JSON两种形式表示语言结构的方法
- 理解自然语言与程序语言在组合结构上的相似性
- 初步掌握LangSC的基本使用方法
本实验的理论基础 本实验实践"组合结构的分析与表示"。核心操作是遍历(逐词处理)和查询(依存关系提取)。你将在真实句子中体会结构三要素:词=单元,依存关系=关系,词性=属性。从三侧面看,本实验侧重形式层的结构分析(分词、词性、句法),体会形式化表示如何将隐性的语言直觉转化为显性的数据结构(第四章的"降维"思想)。
三、预备知识
3.1 组合结构的基本概念
组合结构描述的是语言单位如何按一定规则组合成更大的单位:
图实1-1 自然语言与程序语言的组合层次对比
3.2 结构的三要素
任何结构都包含三个基本要素: - 单元(Unit):组成结构的基本部分 - 关系(Relation):单元之间的联系 - 属性(Attribute):单元或关系的特征
3.3 结构表示方法
表格表示:直观、易读,适合展示线性序列和简单属性
JSON表示:结构化、可计算,适合存储和程序处理
四、实验环境
- Python 3.8+
- LangSC库(语言结构分析工具包)
- 文本编辑器或IDE(推荐VS Code或PyCharm)
LangSC安装与配置
LangSC是本课程配套的语言结构分析工具包,提供分词、词性标注、依存分析和语料库检索等功能。
安装方式(请根据实际情况选择):
# 方式一:pip安装(如已发布到PyPI)
pip install LangSC
# 方式二:从本地安装(实验环境)
pip install -e /path/to/LangSC
核心组件:
表实1-1 LangSC核心组件
| 组件 | 功能 | 说明 |
|---|---|---|
GPF |
统一接口 | 提供分析、网格操作、可视化、语料库检索等全部功能 |
基本用法示例:
# 导入LangSC
from LangSC import GPF
import json
# 创建GPF实例
gpf = GPF()
注意:如果LangSC暂未安装,可使用其他NLP工具(如jieba、LTP、HanLP)完成分词和依存分析任务,核心概念和方法是相同的。
五、实验内容
任务一:句子的多层结构分析(基础)
任务描述
对一个中文句子进行多层次的结构分析,包括分词、词性标注和依存分析,并以表格和JSON两种形式输出结果。
实验材料
分析以下句子(任选其一,或自选一个类似复杂度的句子):
- "北京大学的学生在图书馆里认真地阅读专业书籍"
- "人工智能技术正在深刻地改变人们的日常生活"
- "年轻的程序员通过网络课程快速学习新技术"
实验步骤
步骤1:分词与词性标注
使用LangSC进行分词和词性标注:
from LangSC import GPF
import json
gpf = GPF()
sentence = "北京大学的学生在图书馆里认真地阅读专业书籍"
# 分词与词性标注
Ret = gpf.Parse(sentence, Structure="POS")
tokens = json.loads(Ret)
print(tokens)
记录输出结果。
步骤2:依存关系分析
分析词与词之间的依存关系:
# 依存分析
Ret = gpf.Parse(sentence, Structure="Dep")
print(Ret)
步骤3:表格形式输出
将分析结果整理为表格形式:
表实1-2 句子结构分析结果模板
| 序号 | 词语 | 词性 | 词性说明 | 依存关系 | 关系说明 | 中心词序号 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | ||||||
| ... |
词性标记参考: - n:名词 - v:动词 - a:形容词 - d:副词 - p:介词 - u:助词 - m:数词 - q:量词
依存关系参考: - SBV:主谓关系(主语-谓语) - VOB:动宾关系(动词-宾语) - ATT:定中关系(定语-中心语) - ADV:状中关系(状语-中心语) - POB:介宾关系(介词-宾语) - RAD:右附加关系 - HED:核心词
步骤4:JSON形式输出
将分析结果转换为JSON格式:
import json
# 用JSON构建结构化分析结果(也可使用gpf的网格操作功能)
# 添加分析结果(示例)
result = {
"sentence": "北京大学的学生在图书馆里认真地阅读专业书籍",
"tokens": [
{
"id": 1,
"word": "北京大学",
"pos": "n",
"pos_name": "名词",
"dep": "ATT",
"dep_name": "定中关系",
"head": 3
},
# ... 继续添加其他词
],
"analysis": {
"subject": "学生",
"predicate": "阅读",
"object": "书籍",
"modifiers": {
"subject_modifier": "北京大学的",
"predicate_modifier": "认真地",
"object_modifier": "专业",
"location": "在图书馆里"
}
}
}
# 输出格式化JSON
print(json.dumps(result, ensure_ascii=False, indent=2))
步骤5:绘制依存树
用文本形式绘制依存关系树:
图实1-2 句子依存关系树示例
提交要求
- 完整的表格分析结果
- 完整的JSON输出(格式正确、可解析)
- 依存关系树的文本图示
- 分析过程中遇到的问题及解决方法
任务二:程序代码的结构分析(对比)
任务描述
分析一段程序代码的结构,用与任务一相同的方式(表格+JSON)表示,然后对比自然语言和程序语言的结构异同。
实验材料
分析以下Python代码:
def calculate_average(numbers):
"""计算列表中数字的平均值"""
total = sum(numbers)
count = len(numbers)
if count == 0:
return 0
return total / count
实验步骤
步骤1:识别代码的组成单元
列出代码中的各类单元:
表实1-3 代码组成单元分类
| 单元类型 | 具体内容 | 数量 |
|---|---|---|
| 关键字 | def, return, if | |
| 函数名 | ||
| 参数名 | ||
| 变量名 | ||
| 运算符 | ||
| 字面量 | ||
| 内置函数 |
步骤2:分析代码的层次结构
calculate_average"] --> SIG["函数签名"] FD --> DOC["文档字符串"] FD --> BODY["函数体"] SIG --> NAME["函数名: calculate_average"] SIG --> PARAM["参数列表: (numbers)"] DOC --> DOCSTR["'计算列表中数字的平均值'"] BODY --> ASSIGN1["total = sum(numbers)"] BODY --> ASSIGN2["count = len(numbers)"] BODY --> IF["if count == 0"] BODY --> RET["return total / count"]
图实1-3 代码层次结构分析
步骤3:用JSON表示代码结构
{
"type": "function_definition",
"name": "calculate_average",
"parameters": [
{"name": "numbers", "type": "unknown"}
],
"docstring": "计算列表中数字的平均值",
"body": [
{
"type": "assignment",
"target": "total",
"value": {
"type": "function_call",
"function": "sum",
"arguments": ["numbers"]
}
},
// ... 继续完成
]
}
步骤4:对比分析
填写以下对比表格:
表实1-4 自然语言与程序代码结构对比
| 对比维度 | 自然语言句子 | 程序代码 | 分析说明 |
|---|---|---|---|
| 基本单元 | 词(语素组成) | 标识符/关键字 | |
| 组合层次 | 词→短语→句子 | 表达式→语句→函数 | |
| 核心关系 | 主谓、动宾、偏正... | 赋值、调用、控制... | |
| 关系标记 | 词序、虚词、形态 | 运算符、括号、缩进 | |
| 歧义性 | 存在歧义,需语境消解 | 语法强制无歧义 | |
| 递归性 | 从句嵌套 | 函数调用/递归 | |
| 修饰关系 | 定语、状语、补语 | 参数、修饰符、注释 |
步骤5:深入思考
回答以下问题(每题100字左右):
-
自然语言的"主谓宾"结构与程序的"函数(参数)→返回值"结构有什么相似之处?
-
为什么程序语言不允许歧义,而自然语言可以容忍甚至利用歧义?
-
两种语言的"递归"有什么异同?
提交要求
- 完整的代码结构分析(层次图+JSON)
- 填写完整的对比表格
- 三个思考问题的回答
任务三:篇章结构的JSON建模(挑战)
任务描述
分析一篇短文的篇章结构,设计一个合理的JSON Schema来表示篇章结构,并将分析结果填入。本任务可借助大模型辅助完成篇章分析,但JSON Schema必须自己设计。
实验材料
分析以下新闻短文:
【标题】我国新能源汽车产销量连续8年全球第一
【正文】
据工业和信息化部最新数据显示,2023年我国新能源汽车产销量分别达到958.7万辆和949.5万辆,同比分别增长35.8%和37.9%,连续8年位居全球第一。
业内专家表示,这一成绩的取得得益于多方面因素。首先,国家政策的大力支持为行业发展创造了良好环境;其次,电池技术的突破使续航里程大幅提升;此外,充电基础设施的完善也消除了消费者的"里程焦虑"。
展望未来,新能源汽车市场仍有巨大发展空间。预计到2025年,新能源汽车渗透率将超过50%。同时,智能化、网联化将成为下一阶段的发展重点。
实验步骤
步骤1:分析篇章的宏观结构
识别篇章的组成部分和功能:
表实1-5 篇章宏观结构分析
| 部分 | 位置 | 功能 | 内容概要 |
|---|---|---|---|
| 标题 | |||
| 第一段 | |||
| 第二段 | |||
| 第三段 |
步骤2:识别衔接手段
找出篇章中的衔接手段:
表实1-6 篇章衔接手段分析
| 衔接类型 | 实例 | 作用 |
|---|---|---|
| 指代 | "这一成绩"指代... | |
| 连接词 | 首先、其次、此外 | |
| 词汇衔接 | ||
| 时间标记 |
步骤3:设计JSON Schema
设计一个能表示篇章结构的JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "篇章结构",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "篇章标题"
},
"meta": {
"type": "object",
"description": "元信息",
"properties": {
// 你的设计...
}
},
"paragraphs": {
"type": "array",
"description": "段落列表",
"items": {
// 你的设计...
}
},
"cohesion": {
"type": "object",
"description": "衔接关系",
"properties": {
// 你的设计...
}
},
"structure": {
"type": "object",
"description": "宏观结构",
"properties": {
// 你的设计...
}
}
}
}
要求:你的Schema应能表示: - 基本信息(标题、来源、时间等) - 段落划分及每段的功能 - 句子级别的信息(可选) - 关键实体(人物、机构、数据等) - 衔接手段 - 篇章的逻辑结构
步骤4:填充JSON实例
根据你设计的Schema,将篇章分析结果填入:
{
"title": "我国新能源汽车产销量连续8年全球第一",
"meta": {
// 填写...
},
"paragraphs": [
// 填写...
],
// ... 继续填写
}
步骤5:借助大模型验证
将你设计的Schema和待分析的文本提供给大模型,让它按你的格式进行分析。对比大模型的输出与你的分析:
- 大模型是否能正确理解你的Schema?
- 大模型的分析与你的有何异同?
- 你的Schema设计有什么可以改进的地方?
提交要求
- 篇章宏观结构分析表
- 衔接手段分析表
- 你设计的JSON Schema(完整、可用)
- 填充后的JSON实例(符合Schema、内容完整)
- 大模型辅助分析的记录和对比分析
六、思考题
请认真思考并回答以下问题(每题150-200字):
-
有限与无限:为什么说"有限规则可以生成无限结构"?请结合本实验的分析结果,用具体例子说明自然语言和程序语言中的递归性是如何实现这一点的。
-
知识表示:如果要让计算机自动完成任务一的句子分析,需要什么样的"知识"?这些知识可以分为哪几类?应该如何表示和存储?
-
表示方法比较:表格表示和JSON表示各有什么优缺点?在什么场景下应该选择哪种表示方式?请结合实际应用举例说明。
七、评分标准
| 评分项 | 分值 | 评分要点 |
|---|---|---|
| 任务一:句子结构分析 | 30分 | 分词准确(10)、依存分析正确(10)、格式规范(10) |
| 任务二:代码结构对比 | 30分 | 结构识别完整(10)、JSON正确(10)、对比分析深入(10) |
| 任务三:篇章结构建模 | 25分 | Schema设计合理(15)、JSON实例正确(10) |
| 思考题 | 15分 | 回答深入、有见解(每题5分) |
| 自主探索(加分) | +10分 | 超出基本要求的创造性工作,如额外分析、工具对比、独立发现等 |
大模型对话日志:凡使用大模型辅助完成的步骤,请保存完整对话记录(截图或导出文本),作为实验报告附录提交。对话日志是评估人机协作过程的重要依据。
八、参考资料
- 教材第一章"组合与聚合"
- 教材第三章"语言的层级结构"
- 教材第四章"语言结构表示"
- JSON官方规范:https://www.json.org/
- LangSC使用文档
实验一 完
实验二:聚合结构的构建与应用
一、实验信息
- 实验名称:聚合结构的构建与应用
- 对应章节:第一章《组合与聚合》、第六章《聚合结构与知识库》
- 实验学时:4学时
- 难度等级:★★★
二、实验目的
- 理解"聚合需要标准"的核心论点,体会本体作为聚合标准的作用
- 掌握框架结构(对象-属性)的构建方法
- 掌握知识图谱的本体(schema)设计与实例填充
- 体验借助大模型辅助本体构建的人机协作工作流
- 体会不同本体标准如何影响知识库的内容
本实验的理论基础 本实验实践"聚合标准的设计"。核心挑战是你自己选择分类标准(=设计本体),不同的标准会产生不同的知识库——这正是第六章的核心论点。从三侧面看,本实验涉及内容层(语义分类)和功能层(知识服务于什么目的),体会"功能决定内容,内容决定形式"在知识库设计中的具体表现。从三要素看,你将定义概念(单元)、设计层次与语义关系(关系)、规范属性约束(属性)。
三、预备知识
3.1 聚合与标准
第一章指出:"聚合的核心是分类,而分类需要标准"(1.2.3节)。第六章进一步论证:知识库的本体就是聚合的标准。
聚合有两条计算路径: - 分类(自顶向下):预建分类体系,逐一归类实例。例如词性标注需要先有词性标签集。 - 聚类(自底向上):定义相似性目标,自动发现分组。例如从语料中发现分布相似的词。
两条路径的共性是:标准/目标先行。没有标准就没有有原则的聚合。
3.2 本体:聚合的标准
本体(Ontology) 定义了实例按什么标准聚合为概念、概念按什么方式组织为层次。它由三个要素构成,对应导论中的结构三要素:
| 结构三要素 | 在本体中的体现 | 示例 |
|---|---|---|
| 单元 | 概念/类 | 名词、动词;Person、Location |
| 关系 | 层次关系与语义关系 | 实词→名词(is-a);Person-bornIn→Location |
| 属性 | 属性定义与约束 | 能做主语;birthDate: Date |
本体不是知识库的内容,而是知识库的骨架——先设计骨架(本体),再填充内容(实例)。
3.3 三种知识结构
表实2-1 三种知识结构
| 结构类型 | 核心模式 | 适用场景 | 日常类比 |
|---|---|---|---|
| 框架 | 对象-属性-值 | 描述单个对象的属性 | 个人简历 |
| 图谱 | 对象-关系-对象 | 描述对象之间的关系网络 | 朋友关系图 |
| 分类体系 | 概念-层次-继承 | 定义概念的分类层级 | 超市货架分类 |
3.4 知识库与语料库的区别
表实2-2 知识库与语料库的区别
| 维度 | 语料库 | 知识库 |
|---|---|---|
| 存储内容 | 实例(句子、篇章) | 概念(类别、关系) |
| 结构类型 | 组合结构 | 聚合结构 |
| 来源 | 采集真实语料 | 从实例中归纳/设计 |
| 所需前提 | 采样标准(收什么、怎么收) | 本体标准(分什么类、怎么分) |
| 用途 | 发现规律、训练模型 | 提供知识、支持推理 |
四、实验环境
- Python 3.8+
- LangSC库
- LangSC库(JSS模块)(
pip install LangSC,用于任务四,详见附录A) - 大模型API或对话界面(用于任务三)
- 文本编辑器或IDE
五、实验内容
任务一:词汇的框架知识库构建(基础)
任务描述
为一组常用动词构建语法语义框架知识库,记录每个动词的语法属性和搭配信息。通过此任务,体会框架结构的知识组织方式,以及知识库构建需要先确定属性体系(即框架的本体)。
实验材料
选择以下10个动词进行分析:
吃、喝、看、听、说、写、读、买、卖、送
实验步骤
步骤1:设计框架的属性体系(本体)
在填入任何具体知识之前,首先设计词汇框架的属性体系——这就是这个小型知识库的本体:
{
"word": "动词",
"frame": {
"pos": "词性",
"valency": "配价(需要几个必要搭配成分,如主语和宾语)",
"subject": {
"semantic_type": "主语的语义类型",
"constraints": "主语的限制条件"
},
"object": {
"semantic_type": "宾语的语义类型",
"constraints": "宾语的限制条件",
"optional": "宾语是否可省略"
},
"collocations": {
"typical_objects": "常见宾语搭配",
"typical_adverbials": "常见状语搭配",
"typical_complements": "常见补语搭配"
},
"examples": "例句"
}
}
思考:这个属性体系(本体)决定了你的知识库"长什么样"。如果你加入"语体"属性(口语/书面),知识库就会多一个维度的知识。本体设计在先,知识填充在后。
步骤2:分析第一个动词"吃"
手工分析"吃"的框架属性:
表实2-3 动词"吃"的框架属性
| 属性 | 值 | 说明 |
|---|---|---|
| 词性 | v(动词) | |
| 配价 | 2(二价动词) | 需要主语和宾语 |
| 主语语义类型 | 人、动物 | 有生命的实体 |
| 宾语语义类型 | 食物、药物 | 可摄入的物质 |
| 宾语可省略 | 是 | "你吃了吗?" |
步骤3:使用BCC语料库验证和补充
使用LangSC检索BCC语料库,获取搭配信息:
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 检索"吃"的宾语搭配
Ret = bcc.Run("吃n", Command="Context", Number=200) # 检索"吃+名词"
# 统计高频宾语
object_freq = {}
for r in Ret:
# 提取宾语并统计
# ...
# 检索"吃"的状语搭配
Ret2 = bcc.Run("d吃", Command="Context", Number=200) # 检索"副词+吃"
# 检索"吃"的补语搭配
Ret3 = bcc.Run("吃得", Command="Context", Number=200) # 检索"吃得..."
记录检索结果:
表实2-4 动词搭配检索结果
| 搭配类型 | 高频搭配(频次) |
|---|---|
| 典型宾语 | 饭()、菜()、面(__)、... |
| 典型状语 | 慢慢地()、大口()、... |
| 典型补语 | 饱()、完()、... |
步骤4:完成10个动词的框架
按照相同方法,完成其余9个动词的分析。
步骤5:构建完整的知识库JSON
将所有分析结果整合为一个知识库文件:
{
"name": "常用动词框架知识库",
"version": "1.0",
"description": "10个常用动词的语法语义框架",
"ontology": {
"description": "本知识库的属性体系",
"frame_slots": ["pos", "valency", "subject", "object", "collocations", "examples"]
},
"entries": {
"吃": {
"pos": "v",
"valency": 2,
"subject": {
"semantic_type": ["人", "动物"],
"constraints": "有生命的实体"
},
"object": {
"semantic_type": ["食物", "药物"],
"constraints": "可摄入的物质",
"optional": true
},
"collocations": {
"typical_objects": ["饭", "菜", "面", "肉", "水果", "药"],
"typical_adverbials": ["慢慢地", "大口地", "狼吞虎咽地"],
"typical_complements": ["饱", "完", "光", "掉"]
},
"examples": [
"他吃了一碗米饭。",
"小猫在吃鱼。",
"这药一天吃三次。"
]
},
"喝": {
// 继续填写...
}
// ... 其他8个动词
}
}
步骤6:知识库应用测试
编写简单的查询函数,测试知识库的应用:
import json
# 加载知识库
with open('verb_frames.json', 'r', encoding='utf-8') as f:
kb = json.load(f)
def check_collocation(verb, obj):
"""检查动宾搭配是否合理"""
if verb in kb['entries']:
frame = kb['entries'][verb]
if obj in frame['collocations']['typical_objects']:
return f"'{verb}{obj}'是常见搭配"
else:
obj_type = frame['object']['semantic_type']
return f"'{verb}{obj}'不是典型搭配,'{verb}'的宾语通常是{obj_type}类型"
return f"知识库中没有'{verb}'的信息"
# 测试
print(check_collocation("吃", "苹果"))
print(check_collocation("吃", "石头"))
print(check_collocation("喝", "水"))
print(check_collocation("喝", "饭"))
提交要求
- 完整的动词框架知识库JSON文件
- BCC语料库检索记录(至少3个动词的检索过程)
- 知识库应用测试代码和结果
任务二:领域知识图谱的本体设计与构建(进阶)
任务描述
选择一个感兴趣的领域,先设计本体(schema),再填充实例,构建一个小型知识图谱。本任务的核心不在于数据量,而在于本体设计——你需要说明每个设计决策背后的理由。
选题(三选一)
选题A:中国古代诗人关系图谱 - 可能的实体类型:诗人、朝代、诗作、诗歌风格、地点 - 可能的关系类型:属于(朝代)、创作、影响、师承、好友、同时代
选题B:编程语言家族图谱 - 可能的实体类型:编程语言、设计者、公司、编程范式、应用领域 - 可能的关系类型:影响、继承、设计者、发布年份、适用于
选题C:常见疾病-症状图谱 - 可能的实体类型:疾病、症状、治疗方法、药物、科室 - 可能的关系类型:表现为(症状)、治疗方法、常用药物、就诊科室
实验步骤
步骤1:确定范围与目的
回答以下问题(这是本体构建的第一步,参见教材6.4.2):
- 这个知识图谱是为什么目的服务的?(例如:支持问答、推荐、教学……)
- 它需要回答哪些典型问题?(列出3-5个具体问题)
- 哪些信息在范围内,哪些不在范围内?
示例(选题A): - 目的:支持关于中国古代诗人和诗作的问答 - 典型问题:"李白和杜甫什么关系?""唐代有哪些浪漫主义诗人?""《静夜思》是谁写的?" - 范围:中国古代主要诗人(约10-15人)及其代表作、相互关系
步骤2:设计本体(schema)——核心步骤
这是本任务最重要的环节。你需要做出以下设计决策,并说明每个决策的理由:
a) 定义实体类型及层次
{
"entity_types": [
{
"type": "诗人",
"parent": null,
"description": "中国古代诗人",
"design_rationale": "核心实体类型,围绕诗人展开知识组织"
},
{
"type": "朝代",
"parent": null,
"description": "中国历史朝代",
"design_rationale": "时代背景是理解诗人的重要维度"
},
{
"type": "诗作",
"parent": null,
"description": "诗歌作品",
"design_rationale": "诗人的创作成果,连接诗人与风格"
}
// ... 其他实体类型
]
}
需要思考的决策: - 设立几个实体类型?太多则管理复杂,太少则区分不足 - 是否需要子类型?(例如"诗人"是否要分为"诗人/词人"?) - 每个类型设立的理由是什么?
b) 定义属性
为每种实体类型定义属性:
| 实体类型 | 属性名 | 值类型 | 是否必填 | 设计理由 |
|---|---|---|---|---|
| 诗人 | 姓名 | String | 是 | 基本标识 |
| 诗人 | 字 | String | 否 | 古人常以字行 |
| 诗人 | 号 | String | 否 | 部分诗人有号 |
| 诗人 | 生卒年 | String | 否 | 时代定位 |
| 诗人 | 风格 | List[String] | 否 | 聚合分类的关键维度 |
| 诗作 | 标题 | String | 是 | 基本标识 |
| 诗作 | 体裁 | String | 否 | 五言绝句/七言律诗等 |
| 诗作 | 名句 | String | 否 | 最具代表性的诗句 |
c) 定义关系类型
| 关系名称 | 主体类型 | 客体类型 | 说明 | 设计理由 |
|---|---|---|---|---|
| 属于 | 诗人 | 朝代 | 诗人所属时代 | 回答"某人是哪个朝代的" |
| 创作 | 诗人 | 诗作 | 创作关系 | 回答"某人写了什么" |
| 好友 | 诗人 | 诗人 | 友谊关系 | 回答"某人的好友是谁" |
| 影响 | 诗人 | 诗人 | 风格影响 | 回答"谁影响了谁" |
步骤3:填充实体数据
收集至少20个实体:
表实2-5 知识图谱实体数据
| 实体ID | 名称 | 类型 | 属性 |
|---|---|---|---|
| E001 | 李白 | 诗人 | 字:太白, 号:青莲居士, ... |
| E002 | 杜甫 | 诗人 | 字:子美, ... |
| E003 | 唐朝 | 朝代 | 618-907 |
| E004 | 《静夜思》 | 诗作 | 体裁:五言绝句 |
| ... |
步骤4:填充关系数据
收集至少30条关系:
表实2-6 知识图谱关系数据
| 关系ID | 主体 | 关系 | 客体 |
|---|---|---|---|
| R001 | 李白 | 属于 | 唐朝 |
| R002 | 李白 | 创作 | 《静夜思》 |
| R003 | 李白 | 好友 | 杜甫 |
| R004 | 杜甫 | 影响 | 白居易 |
| ... |
步骤5:构建完整的图谱JSON文件
将本体设计和实例数据整合为一个文件:
{
"name": "中国古代诗人关系图谱",
"version": "1.0",
"purpose": "支持关于中国古代诗人和诗作的问答",
"schema": {
"entity_types": [
// 步骤2a的实体类型定义
],
"relation_types": [
// 步骤2c的关系类型定义
]
},
"entities": [
{
"id": "E001",
"name": "李白",
"type": "诗人",
"properties": {
"字": "太白",
"号": "青莲居士",
"生卒年": "701-762",
"籍贯": "陇西成纪",
"风格": ["浪漫主义", "豪放"]
}
}
// ... 更多实体
],
"relations": [
{
"id": "R001",
"subject": "E001",
"relation": "属于",
"object": "E003"
}
// ... 更多关系
]
}
步骤6:实现图谱查询功能
import json
class KnowledgeGraph:
def __init__(self, filepath):
with open(filepath, 'r', encoding='utf-8') as f:
self.data = json.load(f)
self.entities = {e['id']: e for e in self.data['entities']}
self.relations = self.data['relations']
def get_entity(self, name):
"""根据名称查找实体"""
for e in self.data['entities']:
if e['name'] == name:
return e
return None
def query_relations(self, subject=None, relation=None, obj=None):
"""查询关系三元组"""
results = []
for r in self.relations:
match = True
if subject and self.entities[r['subject']]['name'] != subject:
match = False
if relation and r['relation'] != relation:
match = False
if obj and self.entities[r['object']]['name'] != obj:
match = False
if match:
results.append({
'subject': self.entities[r['subject']]['name'],
'relation': r['relation'],
'object': self.entities[r['object']]['name']
})
return results
def find_path(self, start, end, max_depth=3):
"""寻找两个实体之间的路径(BFS)"""
visited = {start}
queue = [[start]]
while queue:
path = queue.pop(0)
if len(path) > max_depth + 1:
break
node = path[-1]
if node == end:
return path
for rel in self.relations:
subj_name = self.entities[rel['subject']]['name']
obj_name = self.entities[rel['object']]['name']
if subj_name == node and obj_name not in visited:
visited.add(obj_name)
queue.append(path + [obj_name])
elif obj_name == node and subj_name not in visited:
visited.add(subj_name)
queue.append(path + [subj_name])
return None
# 使用示例
kg = KnowledgeGraph('poet_kg.json')
# 查询1:与李白同时代的诗人
print("与李白同时代的诗人:")
results = kg.query_relations(relation="属于", obj="唐朝")
poets = [r['subject'] for r in results]
print(poets)
# 查询2:李白的作品
print("\n李白的作品:")
results = kg.query_relations(subject="李白", relation="创作")
for r in results:
print(f" {r['object']}")
# 查询3:李白和白居易的关系路径
print("\n李白到白居易的关系路径:")
path = kg.find_path("李白", "白居易")
print(path)
步骤7:本体设计反思
回答以下问题(纳入实验报告):
- 你在设计本体时做了哪些取舍?(例如:为什么设了某些类型/关系而没设另一些?)
- 如果把知识图谱的目的从"问答"改为"诗歌教学",本体需要做哪些调整?
- 同一个领域,不同的本体设计如何产生不同的知识?
提交要求
- 本体设计文档(包含实体类型、属性、关系的定义及每项的设计理由)
- 完整的图谱JSON文件(至少20个实体、30条关系)
- 查询功能代码及测试结果
- 本体设计反思(回答上述3个问题)
任务三:大模型辅助本体构建(挑战)
任务描述
借助大模型,为一个你不太熟悉的领域设计知识图谱本体(schema),体验"人做顶层决策,大模型做下层扩展"的协作工作流(教材6.5.2节)。本任务的重点不是建成一个完美的本体,而是记录和反思人机协作的全过程。
选题
选择一个你不太熟悉但有兴趣的领域(不同于任务二的选题),例如: - 中医药 - 天文学 - 美食/烹饪 - 电影/影视 - 体育运动 - 其他(自选)
实验步骤
步骤1:确定领域与目标
选择领域,明确知识图谱的目的和典型查询需求。
步骤2:设计初始提示语,请大模型建议本体
设计一个结构化的提示语,让大模型为该领域生成本体方案:
提示语示例:
我需要为[领域]设计一个知识图谱的本体(schema)。
知识图谱的目标:
- 目的:[具体目的]
- 典型查询:[列出3-5个]
请设计:
1. 核心实体类型(6-10个),每个包含:名称、定义、设计理由
2. 每个实体类型的关键属性(3-5个),包含属性名、值类型、是否必填
3. 实体类型之间的关系类型(8-12个),包含:关系名、主体类型、客体类型、说明
4. 实体类型的层次结构(如有的话)
要求:
- 说明每个设计决策的理由
- 指出哪些类别之间的边界可能模糊,需要进一步讨论
记录你发送的完整提示语和大模型的完整输出。
步骤3:审核大模型方案
逐项审核大模型建议的本体,填写审核表:
表实2-7 本体方案审核表
| 序号 | 大模型建议 | 类别 | 你的判断 | 理由 |
|---|---|---|---|---|
| 1 | 实体类型:XX | 实体类型 | 接受/修改/拒绝 | |
| 2 | 属性:XX.yy | 属性 | 接受/修改/拒绝 | |
| 3 | 关系类型:XX | 关系 | 接受/修改/拒绝 | |
| ... |
统计接受率、修改率、拒绝率。
步骤4:迭代修订
根据审核结果,设计新的提示语进行修订:
提示语示例:
基于你之前的建议,我做了以下调整:
1. 保留的部分:[列出]
2. 修改的部分:
- [原建议] → [修改后],理由:[说明]
3. 拒绝的部分:
- [原建议],理由:[说明]
4. 我自己增加的部分:
- [新增内容],理由:[说明]
请基于修改后的版本:
1. 检查一致性:是否存在类别重叠、缺漏、或属性冲突?
2. 建议改进:还有哪些可以优化的地方?
记录大模型的反馈及你的最终决定。
步骤5:生成最终本体并让大模型填充示例
确定最终本体后,让大模型根据本体填充5-10个示例实体和关系:
提示语示例:
以下是最终确定的本体schema:
[粘贴最终本体]
请根据此schema,填充5-10个示例实体和10-15条关系。
要求:
- 严格遵循schema定义的类型和属性
- 每个实体类型至少有1个示例
- 每种关系类型至少有1个示例
- 输出JSON格式
审核大模型填充的实例是否符合本体定义。
步骤6:撰写协作过程报告
回答以下问题(400字以上):
- 在本体设计中,哪些决策你依赖了大模型的建议?哪些决策你必须自己做?为什么?
- 大模型在本体构建中的优势和局限分别是什么?
- 如果不借助大模型,你为这个不熟悉的领域设计本体会遇到什么困难?
- 这个任务体现了怎样的人机分工原则?
提交要求
- 初始提示语(完整内容)
- 大模型的原始输出
- 本体方案审核表(逐项审核)
- 迭代修订的提示语与大模型反馈
- 最终本体schema(JSON格式)
- 大模型填充的示例实例及审核结果
- 协作过程报告(400字以上)
任务四:基于JSS的知识库结构化检索(进阶)
任务描述
前三个任务中,知识库的查询依赖手写Python循环遍历JSON数据。这种方式对于理解概念足够,但无法应对真实的大规模知识库。本任务使用JSS——一个基于JSON数据的高性能搜索引擎,用SQL语法检索知识库,体验从"手动翻阅"到"结构化检索"的质变。
通过本任务,你将理解:
- config.json(索引配置)就是面向检索的"本体"——它决定了数据按什么结构被索引、能以什么方式被查询
- 不同的索引类型(精确匹配、全文检索、数值范围)对应不同的查询需求
- SQL作为结构化查询语言,如何高效地从大规模JSON数据中提取信息
前置条件:需安装LangSC(
pip install LangSC),详见附录A。
实验材料
本任务提供两类数据,学生可选择其一或全部完成:
数据A:诗人知识库(与任务二选题A衔接)
如果你在任务二中选择了选题A,可以将自己构建的知识图谱JSON数据转为JSONL格式使用。此外,课程提供一个预置的诗人数据集(约200条记录),包含诗人、诗作、朝代等信息。
{"id": "P001", "name": "李白", "type": "诗人", "dynasty": "唐", "style": "浪漫主义 豪放", "birth_year": 701, "death_year": 762, "courtesy_name": "太白", "art_name": "青莲居士", "representative": "《将进酒》《静夜思》《蜀道难》"}
{"id": "P002", "name": "杜甫", "type": "诗人", "dynasty": "唐", "style": "现实主义 沉郁顿挫", "birth_year": 712, "death_year": 770, "courtesy_name": "子美", "art_name": "少陵野老", "representative": "《春望》《三吏三别》《登高》"}
{"id": "W001", "name": "静夜思", "type": "诗作", "author": "李白", "dynasty": "唐", "genre": "五言绝句", "theme": "思乡", "famous_line": "举头望明月,低头思故乡"}
数据B:动词知识库(与任务一衔接)
将任务一构建的动词框架知识库转为JSONL格式:
{"id": "V001", "word": "吃", "pos": "v", "valency": 2, "subject_type": "人 动物", "object_type": "食物 药物", "typical_objects": "饭 菜 面 肉 水果", "typical_complements": "饱 完 光 掉", "example": "他吃了一碗米饭"}
{"id": "V002", "word": "喝", "pos": "v", "valency": 2, "subject_type": "人 动物", "object_type": "液体 饮品", "typical_objects": "水 茶 酒 咖啡 牛奶", "typical_complements": "完 光 醉", "example": "她喝了一杯咖啡"}
实验步骤
步骤1:设计cfg配置文件——从本体到索引配置
索引配置是面向检索的"本体"。你需要思考:每个字段应该用什么索引类型?这取决于你打算如何查询这个字段。配置文件命名规则为cfg_<数据文件名>.txt,例如数据文件poets.jsonl对应cfg_poets.txt。
以诗人知识库为例:
{
"table_name": "poets",
"record_format": "jsonl",
"content": {
"id": "id",
"name": "name",
"type": "type",
"dynasty": "dynasty",
"style": "style",
"birth_year": "birth_year",
"death_year": "death_year",
"courtesy_name": "courtesy_name",
"art_name": "art_name",
"representative": "representative"
},
"combin": {
"all_text": ["name", "style", "representative", "courtesy_name", "art_name"]
},
"index": {
"kv": ["id", "type", "dynasty"],
"number": ["birth_year", "death_year"],
"bm25": ["style", "representative", "all_text"],
"affix": ["name"]
}
}
思考并记录(纳入实验报告):
- dynasty为什么用KV索引而不是BM25?(因为朝代是分类标签,需要精确匹配)
- style为什么用BM25?(因为风格描述是文本,需要全文检索)
- birth_year为什么用Number索引?(因为需要数值范围查询)
- all_text组合字段的作用是什么?(跨多个文本字段的统一检索入口)
关键洞察:索引类型的选择,本质上是对字段语义角色的判断——它是分类标签(KV)、描述文本(BM25)、还是数值量度(Number)?这正是本体设计中"属性定义"的检索侧体现。
步骤2:构建索引
from LangSC import JSS
# 自动索引并加载(数据目录下需有poets.jsonl和cfg_poets.txt)
jss = JSS("./data", log_level=1)
步骤3:SQL检索实践
依次完成以下检索任务,记录SQL语句和结果:
a) 精确匹配——按分类检索
# 查找所有唐代诗人
results = jss.Run('SELECT name, style FROM poets WHERE dynasty = "唐"')
for r in results:
print(f" {r['name']}: {r['style']}")
# 查找所有诗作
results = jss.Run('SELECT name, author, genre FROM poets WHERE type = "诗作"')
b) 全文检索——按内容搜索
# 搜索与"浪漫主义"相关的诗人
results = jss.Run('SELECT name, style FROM poets WHERE style LIKE "浪漫主义"')
# 搜索代表作中包含特定关键词的诗人
results = jss.Run('SELECT name, representative FROM poets WHERE representative LIKE "月"')
# 跨字段搜索:在所有文本字段中搜索
results = jss.Run('SELECT name, type FROM poets WHERE all_text LIKE "青莲"')
c) 数值范围——按时间筛选
# 查找出生于700-750年的诗人
results = jss.Run(
'SELECT name, birth_year, death_year FROM poets '
'WHERE birth_year BETWEEN 700 AND 750'
)
# 查找活过70岁的诗人(需组合条件)
results = jss.Run(
'SELECT name, birth_year, death_year FROM poets '
'WHERE type = "诗人" AND death_year > 0'
)
d) 组合条件——多维度筛选
# 唐代 + 现实主义
results = jss.Run(
'SELECT name, style FROM poets '
'WHERE dynasty = "唐" AND style LIKE "现实主义"'
)
# 模糊匹配姓名
results = jss.Run('SELECT name, dynasty FROM poets WHERE name LIKE "%李%"')
e) 统计与随机抽样
# 统计总记录数
results = jss.Run('SELECT COUNT(*) FROM poets')
print(f"知识库总记录数: {results}")
# 随机抽取3条记录
results = jss.Run('SELECT RAND(3) FROM poets')
步骤4:对比手写查询与SQL查询
用同一个查询任务,分别用手写Python和JSS SQL实现,对比两种方式:
import json, time
# 方式一:手写Python遍历
with open('./data/poets.jsonl', 'r', encoding='utf-8') as f:
data = [json.loads(line) for line in f]
start = time.time()
result_manual = [d for d in data if d.get('dynasty') == '唐' and '浪漫' in d.get('style', '')]
time_manual = time.time() - start
print(f"手写遍历: {len(result_manual)} 条, 耗时 {time_manual:.4f}s")
# 方式二:JSS SQL
start = time.time()
result_sql = jss.Run('SELECT name FROM poets WHERE dynasty = "唐" AND style LIKE "浪漫"')
time_sql = time.time() - start
print(f"SQL检索: {len(result_sql)} 条, 耗时 {time_sql:.4f}s")
思考:当数据量从200条增长到20万条时,两种方式的差距会如何变化?结构化索引的价值在什么规模下开始显现?
步骤5:修改索引配置,体验"不同标准,不同检索"
修改cfg_poets.txt,将dynasty字段从KV索引改为BM25索引,重新初始化JSS后测试:
# KV索引下:精确匹配
results = jss.Run('SELECT name FROM poets WHERE dynasty = "唐"') # 精确匹配"唐"
# BM25索引下:全文检索
results = jss.Run('SELECT name FROM poets WHERE dynasty LIKE "唐"') # 全文检索"唐"
观察并记录:索引类型的变化如何影响查询行为?这与教材6.3节"不同标准产生不同知识"有何呼应?
提交要求
- 完整的cfg配置文件,附每个索引选择的理由说明
- 步骤3中所有SQL查询语句及执行结果
- 手写查询与SQL查询的对比记录
- 修改索引配置的实验记录及分析
- 思考总结:cfg配置文件作为"检索本体"与知识图谱schema有何异同?(200字以上)
六、思考题
请认真思考并回答以下问题(每题150-200字):
-
标准与聚合:教材第六章论证"没有标准就没有聚合"。结合你在任务二中设计本体的经验,谈谈你对这一论点的理解。如果没有事先设计schema,直接从数据中"抽取"知识,会出现什么问题?
-
不同标准,不同知识:如果让两个同学分别独立为同一领域设计本体(不相互参考),他们的知识图谱会有什么不同?这些不同说明了什么?
-
人机分工:在大模型辅助本体构建中,哪些工作适合交给大模型,哪些必须由人来完成?这种分工的依据是什么?
七、评分标准
| 评分项 | 分值 | 评分要点 |
|---|---|---|
| 任务一:动词框架知识库 | 20分 | 属性体系设计合理(7)、搭配数据充分(6)、JSON规范(4)、应用测试(3) |
| 任务二:领域知识图谱 | 30分 | 本体设计及理由(13)、数据完整(7)、查询功能(6)、设计反思(4) |
| 任务三:大模型辅助本体构建 | 20分 | 提示语设计(5)、审核过程严谨(5)、迭代修订有效(5)、协作报告有深度(5) |
| 任务四:JSS结构化检索 | 15分 | 索引配置及理由(5)、SQL检索完整(4)、对比分析(3)、总结有深度(3) |
| 思考题 | 15分 | 每题5分,要求有深度、有见解 |
| 自主探索(加分) | +10分 | 超出基本要求的创造性工作,如额外分析、工具对比、独立发现等 |
大模型对话日志:凡使用大模型辅助完成的步骤,请保存完整对话记录(截图或导出文本),作为实验报告附录提交。对话日志是评估人机协作过程的重要依据。
八、参考资料
- 教材第一章"组合与聚合"(1.2.3节:聚合的共性——分类标准)
- 教材第六章"聚合结构与知识库"(6.3节:本体——聚合的标准;6.4节:本体构建方法论;6.5节:大模型与本体构建)
- JSON Schema规范:https://json-schema.org/
- LangSC使用文档(附录A)
- LangSC使用指南(附录A)——JSON结构化搜索引擎(JSS模块),任务四所用工具
实验二 完
实验三:语料库检索与结构发现
一、实验信息
- 实验名称:语料库检索与结构发现
- 对应章节:第五章《组合结构与语料库》
- 实验学时:4学时
- 难度等级:★★★
二、实验目的
- 掌握语料库检索的基本方法和技巧
- 理解"检索式即结构表达"的核心思想
- 学会从语料中发现组合规律(搭配、句式)
- 学会从语料中发现聚合规律(分布、聚类)
- 体验语料库方法与大模型方法的互补
本实验的理论基础 本实验实践"从语料库中发现组合结构"。核心操作是查询(检索匹配模式)和遍历+统计(频率分析)。检索式本身就是对组合结构的形式化表达——你在编写检索式时,其实是在用三要素描述你要查找的模式(什么样的单元、它们之间有什么关系、具备什么属性)。从三侧面看,检索覆盖形式层(句法模式如"把+NP+VP")和内容层(语义搭配如"喝+液体类")。
三、预备知识
3.1 语料库的本质
语料库是组合结构实例的集合: - 每个句子是一个组合结构的实例 - 整个语料库是大量实例的采样 - 语料库反映了语言使用的真实分布
3.2 检索式是结构表达
检索式本身就是对语言结构的形式化描述:
表实3-1 检索式与结构描述对照
| 检索式 | 描述的结构 |
|---|---|
吃 n |
动词"吃"后接名词 |
v得很 |
动词+得+程度副词 |
把 n v |
把字句的基本结构 |
(v)一(v){$1=$2} |
ABAB式动词重叠 |
3.3 从语料发现规律
组合规律:什么词倾向于一起出现 - 搭配:动宾搭配、形名搭配 - 句式:把字句、被字句的使用条件
聚合规律:什么词可以在相同位置替换 - 分布:能进入"很___"的词是形容词 - 聚类:语义相似的词有相似的分布
四、实验环境
- Python 3.8+
- LangSC库(BCC语料库接口)
- 大模型API(用于任务三验证)
- Excel或文本编辑器(统计记录)
BCC语料库检索基础
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 基本检索
Ret = bcc.Run("吃饭", Command="Context", Number=100)
# 带词性的检索
Ret = bcc.Run("v得很", Command="Context", Number=100) # 动词+得+很
# 通配符检索
Ret = bcc.Run("吃*", Command="Context", Number=100) # 吃+任意
# 查看结果
for r in Ret[:5]:
print(r['text'])
print(r['source'])
print('---')
五、实验内容
任务一:搭配模式的发现与统计(基础)
任务描述
研究"V+得+X"结构(述补结构)的搭配模式,发现不同动词后接补语的规律。
背景知识
"V+得+X"是汉语中重要的述补结构: - "跑得快" - 动作方式/结果 - "说得好" - 动作评价 - "累得很" - 状态程度 - "气得发抖" - 状态结果
实验步骤
步骤1:检索"V得"结构
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 检索"动词+得"结构
Ret = bcc.Run("v得", Command="Context", Number=500)
# 保存检索结果
with open('v_de_results.txt', 'w', encoding='utf-8') as f:
for r in Ret:
f.write(r['text'] + '\n')
步骤2:提取高频动词V
统计"V得"结构中的高频动词:
from collections import Counter
# 统计动词频率
verb_counter = Counter()
for r in Ret:
# 提取"得"前面的动词
# 注意:这里需要根据实际返回格式调整
verb = extract_verb(r) # 你需要实现这个函数
if verb:
verb_counter[verb] += 1
# 输出前20个高频动词
print("高频动词TOP20:")
for verb, count in verb_counter.most_common(20):
print(f" {verb}: {count}")
记录结果:
表实3-2 高频动词统计表(TOP20)
| 排名 | 动词 | 频次 | 示例 |
|---|---|---|---|
| 1 | |||
| 2 | |||
| ... | |||
| 20 |
步骤3:分析高频动词的补语分布
选择5个高频动词,分别检索其补语模式:
from LangSC import BCC
bcc = BCC("Corpus")
# 以"跑"为例
results_pao = bcc.Run("跑得", Command="Context", Number=100)
# 统计"跑得"后面的补语
complement_counter = Counter()
for r in results_pao:
complement = extract_complement(r) # 提取补语
if complement:
complement_counter[complement] += 1
print("'跑得'后的高频补语:")
for comp, count in complement_counter.most_common(10):
print(f" {comp}: {count}")
为5个动词分别填写:
动词1:跑
表实3-3 动词"跑"的补语分布
| 补语 | 频次 | 语义类型 |
|---|---|---|
| 快 | 速度 | |
| 慢 | 速度 | |
| 远 | 距离 | |
| ... |
动词2:说
表实3-4 动词"说"的补语分布
| 补语 | 频次 | 语义类型 |
|---|---|---|
(继续填写其他动词)
步骤4:分析补语的语义类型
对发现的补语进行语义分类:
表实3-5 补语语义类型分类
| 语义类型 | 典型补语 | 常见搭配动词 |
|---|---|---|
| 速度 | 快、慢 | 跑、走、开 |
| 程度 | 很、极、厉害 | 累、饿、困 |
| 结果 | 完、好、光 | 吃、做、写 |
| 评价 | 好、对、棒 | 说、做、写 |
| 状态 | 发抖、流泪 | 气、哭、笑 |
| ... |
步骤5:发现规律并总结
回答以下问题:
-
不同类型的动词(动作动词、状态动词),后接的补语有什么不同?
-
补语的语义类型与动词的语义类型之间有什么关联?
-
从搭配频率来看,哪些"V得X"是固定搭配?哪些是自由组合?
提交要求
- 高频动词统计表(TOP20)
- 5个动词的补语分布表
- 补语语义类型分类表
- 规律总结(300字以上)
任务二:句式的分布与功能研究(进阶)
任务描述
对比研究"把"字句和"被"字句,通过语料库检索发现两种句式的使用条件和功能差异。
背景知识
把字句:主语 + 把 + NP + V + ... - "他把书放在桌上" - 强调主语对宾语的处置
被字句:主语 + 被 + (NP) + V + ... - "书被他放在桌上" - 强调主语受到的影响
实验步骤
步骤1:检索两种句式
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 检索把字句
ba_results = bcc.Run("把nv", Command="Context", Number=200)
print(f"把字句检索到 {len(ba_results)} 条")
# 检索被字句
bei_results = bcc.Run("被v", Command="Context", Number=200)
print(f"被字句检索到 {len(bei_results)} 条")
# 保存结果
with open('ba_sentences.txt', 'w', encoding='utf-8') as f:
for r in ba_results:
f.write(r['text'] + '\n')
with open('bei_sentences.txt', 'w', encoding='utf-8') as f:
for r in bei_results:
f.write(r['text'] + '\n')
步骤2:随机抽样并标注
从每种句式中随机抽取50个句子进行详细分析:
import random
# 随机抽取50个把字句
ba_sample = random.sample(ba_results, min(50, len(ba_results)))
# 随机抽取50个被字句
bei_sample = random.sample(bei_results, min(50, len(bei_results)))
步骤3:标注分析维度
为每个句子标注以下信息:
把字句标注表:
表实3-6 把字句标注表
| 编号 | 句子 | 主语语义角色 | 把后NP语义角色 | 动词类型 | 动词后成分 | 语境/功能 |
|---|---|---|---|---|---|---|
| 1 | 他把书放在桌上 | 施事 | 受事 | 位移动词 | 处所补语 | 叙述事件 |
| 2 | ||||||
| ... |
被字句标注表:
表实3-7 被字句标注表
| 编号 | 句子 | 主语语义角色 | 被后NP(如有) | 动词类型 | 句子功能 | 情感色彩 |
|---|---|---|---|---|---|---|
| 1 | 书被他弄丢了 | 受事 | 施事 | 结果动词 | 叙述不幸事件 | 消极 |
| 2 | ||||||
| ... |
语义角色参考: - 施事(做动作的) - 受事(被作用的) - 与事(间接对象) - 工具 - 处所
动词类型参考: - 位移动词(放、搬、拿) - 结果动词(打破、弄丢) - 变化动词(改成、变成) - 消耗动词(吃、喝、用)
步骤4:统计对比
基于标注结果进行统计对比:
表实3-8 把字句与被字句统计对比
| 对比维度 | 把字句(%) | 被字句(%) | 差异分析 |
|---|---|---|---|
| 主语语义角色 | |||
| - 施事 | |||
| - 受事 | |||
| 动词类型分布 | |||
| - 位移动词 | |||
| - 结果动词 | |||
| - ... | |||
| 句子功能 | |||
| - 叙述事件 | |||
| - 表达不满 | |||
| - ... | |||
| 情感色彩 | |||
| - 中性 | |||
| - 消极 | |||
| - 积极 |
步骤5:总结使用规律
基于统计结果,总结"把"字句和"被"字句的使用条件:
## 把字句的使用条件
1. 主语通常是施事,是动作的发出者
2. "把"后的NP通常是受事,是被处置的对象
3. 动词通常是...
4. 常用于表达...
## 被字句的使用条件
1. 主语通常是受事,是被影响的对象
2. 常用于表达...
3. 往往带有...色彩
## 选择依据
当...时,倾向于用把字句
当...时,倾向于用被字句
步骤6:规律验证
设计5个测试句子,根据你总结的规律预测应该用"把"还是"被",然后在语料库中验证:
表实3-9 句式选择规律验证
| 测试情境 | 预测句式 | 预测依据 | 语料库验证 | 验证结果 |
|---|---|---|---|---|
| 表达故意处置某物 | 把字句 | 强调主语的主动性 | 检索相关例句 | ✓/✗ |
| 表达遭受不幸 | 被字句 | 强调主语受影响 | ||
| ... |
提交要求
- 把字句标注表(50条)
- 被字句标注表(50条)
- 统计对比表
- 使用规律总结(500字以上)
- 规律验证记录
任务三:新结构的发现与描写(挑战)
任务描述
在语料库中自主发现一个语言结构模式,进行系统的检索、统计和描写,并借助大模型验证你发现的规律。
选题建议
选题方向A:构式研究 - "X是X,但是Y"(让步转折) - "不是A就是B"(选择) - "连X都/也Y"(极端情况) - "X什么X"(否定/不耐烦)
选题方向B:搭配模式 - "一+量词+名词+的+名词"(如"一脸的无奈") - "形容词+得+不得了" - "动词+来+动词+去"(如"想来想去")
选题方向C:话语标记 - "话说回来" - "说白了" - "不是我说你"
选题方向D:自选 - 你在阅读中注意到的有趣结构 - 网络新兴表达模式
实验步骤
步骤1:确定研究对象
选择一个结构模式,说明选题理由:
## 研究对象
结构模式:_______________
选题理由:_______________
预期发现:_______________
步骤2:设计检索式
根据结构特点设计检索式:
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 示例:检索"X是X,但是Y"
# 可能需要多种检索式组合
# 检索式1
results1 = bcc.Run("是 但是", Command="Context", Number=300)
# 检索式2(更精确)
results2 = bcc.Run("n是n 但是", Command="Context", Number=200)
# 合并去重
all_results = merge_and_filter(results1, results2)
记录你的检索策略:
表实3-10 检索策略记录
| 检索式 | 预期匹配 | 实际匹配数 | 有效率 |
|---|---|---|---|
步骤3:提取有效实例
从检索结果中筛选符合目标结构的实例:
# 筛选有效实例
valid_examples = []
for r in all_results:
if is_valid_pattern(r['text']): # 你需要定义判断标准
valid_examples.append(r['text'])
print(f"有效实例:{len(valid_examples)} 条")
# 保存实例
with open('pattern_examples.txt', 'w', encoding='utf-8') as f:
for ex in valid_examples:
f.write(ex + '\n')
收集至少50个有效实例。
步骤4:分析结构槽位
分析结构中各个位置的填充情况:
以"X是X,但是Y"为例:
X的分布统计:
表实3-11 X槽位的类型分布统计
| X的类型 | 频次 | 比例 | 典型例子 |
|---|---|---|---|
| 名词短语 | |||
| 动词短语 | |||
| 形容词短语 | |||
| 小句 |
X的语义特征:
表实3-12 X槽位的语义特征分布
| 语义特征 | 频次 | 示例 |
|---|---|---|
| 正面评价 | "好是好" | |
| 负面评价 | "贵是贵" | |
| 能力/状态 | "能是能" | |
| ... |
Y的分布统计:
表实3-13 Y槽位与X的关系分布
| Y与X的关系 | 频次 | 示例 |
|---|---|---|
| 转折(相反) | "便宜是便宜,但是质量差" | |
| 限制(条件) | "去是去,但是要早点回来" | |
| 补充(附加) |
步骤5:形式化描写
用JSON格式描述这个结构:
{
"pattern_name": "让步转折构式",
"pattern_form": "[X]是[X],但是[Y]",
"structure": {
"X_slot": {
"position": "重复出现在'是'前后",
"syntactic_type": ["NP", "VP", "AP", "S"],
"semantic_features": "被部分承认的事实或属性",
"constraints": [
"前后两个X必须相同",
"X通常是积极或中性的属性"
]
},
"Y_slot": {
"position": "'但是'之后",
"syntactic_type": ["S"],
"semantic_features": "与X形成对比或限制的内容",
"constraints": [
"Y的内容必须与X相关",
"Y通常表达负面或限制信息"
]
}
},
"function": {
"pragmatic": "先承认X成立,再提出Y作为转折或限制",
"discourse": "让步关系,'虽然X,但是Y'的变体",
"attitude": "说话人认为Y比X更重要"
},
"register": ["口语", "书面语"],
"frequency": "常见",
"variants": [
"[X]是[X],可是[Y]",
"[X]是[X],不过[Y]"
],
"examples": [
{
"text": "便宜是便宜,但是质量不好",
"X": "便宜",
"Y": "质量不好",
"context": "评价商品"
},
{
"text": "他聪明是聪明,但是不用功",
"X": "聪明",
"Y": "不用功",
"context": "评价人"
}
]
}
步骤6:借助大模型验证
设计提示语让大模型生成符合该结构的句子:
## 任务
请生成5个符合"X是X,但是Y"结构的句子。
## 结构说明
- X是一个词或短语,表示被承认的事实或属性
- "是"重复X,表示承认
- Y是转折内容,对X进行限制或提出相反信息
## 示例
- 便宜是便宜,但是质量不好
- 他努力是努力,但是方法不对
## 要求
- 生成的句子要自然、地道
- X和Y之间要有逻辑关联
- 覆盖不同的话题(评价人、评价物、描述情况等)
评估大模型生成的结果:
表实3-14 大模型生成结果评估
| 生成的句子 | 结构正确? | 语义合理? | 自然度(1-5) | 问题分析 |
|---|---|---|---|---|
步骤7:分析大模型的掌握程度
- 大模型生成的句子是否都符合结构?
- 有没有生成不自然的句子?为什么?
- 大模型是否"理解"了这个结构的功能?
- 你发现的规律中,哪些大模型掌握得好?哪些不好?
提交要求
- 选题说明和检索策略
- 有效实例集(至少50条)
- 槽位分布统计表
- 结构的JSON形式化描写
- 大模型验证记录和分析
- 研究报告(800字以上),包含:
- 结构的形式特征
- 结构的功能特征
- 使用条件和限制
- 与大模型验证的对比发现
六、思考题
请认真思考并回答以下问题(每题150-200字):
-
检索式与结构:为什么说"检索式是结构的形式化表达"?请用你在本实验中设计的检索式举例说明,检索式如何体现了你对语言结构的理解。
-
语料库与语法书:从语料库统计中发现的语言规律,与语法书上描述的规则有什么不同?哪种描述更接近语言的真实使用?各有什么优缺点?
-
大模型与结构:通过任务三的验证,你认为大模型是否"学会"了你发现的这个结构?大模型对语言结构的"掌握"与人类语言学家的"理解"有什么区别?
七、评分标准
表实3-15 实验三评分标准
| 评分项 | 分值 | 评分要点 |
|---|---|---|
| 任务一:搭配模式研究 | 25分 | 统计完整(10)、分析合理(10)、规律总结(5) |
| 任务二:句式对比研究 | 30分 | 标注规范(10)、统计准确(10)、规律深入(10) |
| 任务三:新结构发现 | 30分 | 选题有价值(5)、检索有效(5)、描写完整(10)、验证充分(10) |
| 思考题 | 15分 | 每题5分 |
| 自主探索(加分) | +10分 | 超出基本要求的创造性工作,如额外分析、工具对比、独立发现等 |
大模型对话日志:凡使用大模型辅助完成的步骤,请保存完整对话记录(截图或导出文本),作为实验报告附录提交。对话日志是评估人机协作过程的重要依据。
八、参考资料
- 教材第五章"组合结构与语料库"
- BCC语料库在线版:http://bcc.blcu.edu.cn/
- LangSC使用文档
- 《语料库语言学》相关章节
实验三 完
实验四:结构化人机协作
一、实验信息
- 实验名称:结构化人机协作
- 对应章节:第八章《从符号到向量》、第九章《结构的运用:从结构化思维到有效交互》、第十章《理性结构与经验结构》
- 实验学时:4学时
- 难度等级:★★★★
二、实验目的
- 深入理解显式结构(符号)与隐式结构(向量)的本质差异及其互补关系
- 掌握结构化提示语设计的方法和原则
- 体验"人提供结构,机器提供内容"的协作模式
- 培养在大模型时代的结构化思维能力
- 反思人机协作中各自的角色和价值
- 掌握用三要素方法设计智能体协作系统的基本思路
- 理解三要素在步骤层、智能体层、系统层上的递归同构性,体会聚合维度的贯穿作用
本实验的理论基础 本实验实践"显隐互补"原则(第八章)。核心挑战是设计人提供显性结构、模型填充隐性内容的协作方案。你将运用第九章的功能-框架-表达三步法、三要素诊断法和三侧面诊断法,从功能层出发设计提示语和智能体,体会结构化思维如何提升人机交互的质量。在三侧面中,功能层是人的核心价值所在——模型在形式层和内容层能力很强,但功能层高度依赖人的显性指定。
三、预备知识
3.1 显式结构与隐式结构回顾
教材第八章提出,结构有两种形态:显式结构面向人,用符号组织,人可直接读取和设计;隐式结构面向机器,用向量/参数组织,人无法直接读取。两者本质上都是信息压缩——人类主导的压缩(分类、命名、抽象)与机器主导的压缩(统计、降维、参数化)。
表实4-1 显式结构与隐式结构对比
| 维度 | 显式结构(符号) | 隐式结构(向量) |
|---|---|---|
| 面向对象 | 人 | 机器 |
| 表示形式 | 离散符号 | 连续数值 |
| 可读性 | 人可读 | 人不可读 |
| 知识载体 | 符号(可查询) | 参数(难提取) |
| 推理方式 | 规则推理(显式) | 统计计算(隐式) |
| 优势 | 可解释、可控制 | 覆盖广、灵活 |
| 局限 | 覆盖有限、离散 | 黑箱、幻觉 |
| 压缩主体 | 人(分类、命名) | 机器(统计、降维) |
3.2 提示语的本质
提示语不是"魔法咒语",而是结构化的任务描述: - 形式结构:如何组织提示语的文字 - 内容结构:传达什么任务信息 - 功能结构:达成什么交互目的
3.3 人机协作的分工原则
表实4-2 人机协作分工原则
| 人的角色 | 机器的角色 |
|---|---|
| 提供结构框架 | 填充具体内容 |
| 把控质量标准 | 处理规模任务 |
| 承担最终责任 | 发现统计模式 |
| 定义问题和目标 | 执行和生成 |
3.4 从提示语到智能体
教材9.5节指出,随着任务复杂度的提升,人机交互结构也在不断演化。单轮提示语是"函数调用"级别的交互——输入一段文本,输出一段文本。但当任务涉及多个步骤、多种工具、多个视角时,需要升级到"系统架构"级别的交互——设计多个智能体(Agent) 协作完成任务。
什么是智能体?
智能体是具有自主性的功能单元,与传统函数的区别在于:
- 由系统提示(系统提示语) 定义角色和行为规范——决定"它是谁"
- 拥有工具集(Tools) 扩展能力边界——决定"它能做什么"
- 通过JSON格式的输入/输出与其他智能体通信——决定"它怎么协作"
- 受模型参数控制生成行为——决定"它怎么工作"
智能体系统设计 = 三要素规划
设计一个多智能体协作系统,本质上就是教材中三要素(单元/关系/属性)的规划过程:
- 单元:需要几个智能体?各自负责什么功能?
- 关系:智能体之间如何协作?顺序执行还是并行处理?谁监督谁?
- 属性:每个智能体的系统提示语怎么写?能用什么工具?输入输出什么格式?
表实4-3 智能体关系类型与适用场景
| 关系类型 | 结构特征 | 对应概念 | 适用场景 |
|---|---|---|---|
| 顺序关系 | A→B→C 流水线 | 线性组合 | 步骤明确的流程任务 |
| 并行关系 | A、B、C 同时执行→汇聚 | 并列组合 | 可独立处理的子任务 |
| 层次关系 | 监督者→执行者 | 层次组合 | 复杂任务的分解协调 |
| 条件关系 | 根据状态选择路径 | 聚合选择 | 需要判断和分支的任务 |
三要素的递归同构
教材9.5.2节(四)进一步指出,三要素的结构模式不止存在于系统层——它在三个粒度上递归地重现:
| 层级 | 单元 | 关系 | 属性 |
|---|---|---|---|
| 步骤层(智能体内部) | 各处理步骤 | 步骤间的控制流 | 每步的提示语约束 |
| 智能体层 | 一个完整的智能体 | 内部节点的编排 | 系统提示、工具集 |
| 系统层 | 各个智能体 | 协作模式(上表) | 角色定义、接口规范 |
同一个"确定单元→建立关系→配置属性"的过程在每个尺度上重复出现。此外,每个层级都存在聚合维度——步骤层有可替换的处理策略,智能体层有同类角色的互换,系统层有不同编排方案的选择。任务四的步骤2(四)将要求你亲手验证这种同构性。
LangGraph简介
LangGraph是一个智能体编排框架,其核心概念与本书的理论高度对应:
- 节点(Node) = 智能体(单元)——每个节点是一个执行特定功能的智能体
- 边(Edge) = 协作关系——定义智能体之间的执行顺序和条件分支
- 状态(State) = 共享属性——智能体之间通过结构化状态传递信息
本实验的任务四将运用以上概念,设计一个多智能体协作系统。
四、实验环境
- 大模型API或对话界面(如Claude、GPT等)
- Python 3.8+(用于数据处理)
- LangSC库(用于语料库检索)
- LangGraph(可选,用于智能体编排实践):
pip install langgraph langchain-core - 文本编辑器
五、实验内容
任务一:提示语结构化改造(基础)
任务描述
将弱结构提示语改造为强结构提示语,体验结构化表达对大模型输出质量的影响。
实验材料
以下5个弱结构提示语需要改造:
1. "帮我写一篇关于环保的文章"
2. "翻译这段话:人工智能正在改变世界"
3. "这个代码有bug,帮我修一下:[代码]"
4. "总结一下这个会议记录:[会议记录]"
5. "给我推荐几本书"
实验步骤
步骤1:分析弱结构提示语的问题
对每个提示语进行问题分析:
表实4-3 弱结构提示语问题分析
| 提示语 | 缺失的形式结构 | 缺失的内容结构 | 缺失的功能结构 |
|---|---|---|---|
| 1. 写环保文章 | 无层次组织 | 无具体要求 | 目的/受众不明 |
| 2. 翻译 | |||
| 3. 修bug | |||
| 4. 总结会议 | |||
| 5. 推荐书 |
步骤2:改造提示语1(环保文章)
原始版本:
帮我写一篇关于环保的文章
分析问题: - 形式:没有层次,一句话 - 内容:主题模糊,无具体要求 - 功能:不知道文章用途、读者是谁
改造后版本:
## 任务
撰写一篇关于"日常生活中的环保行动"的科普文章
## 目标读者
- 高中生和大学生
- 对环保有基本了解,但缺乏行动指南
## 文章要求
### 结构
1. 引言(100字):用数据或案例说明环保的紧迫性
2. 正文(600字):介绍5个日常可行的环保行动
- 每个行动包括:是什么、为什么重要、怎么做
3. 结语(100字):号召读者从小事做起
### 风格
- 语言通俗易懂,避免专业术语
- 多用具体数据和生活实例
- 语气积极正面,有感染力
### 约束
- 总字数:800±50字
- 不要说教,不要道德绑架
- 每个建议都要具体可操作
## 示例段落风格
"你知道吗?一个普通的塑料袋需要200年才能完全降解。如果我们每人每天少用一个塑料袋,全国一天就能减少14亿个塑料袋的使用。这不是遥远的环保口号,而是你我今天就能做到的改变。"
步骤3:测试改造效果
将原始提示语和改造后提示语分别发送给大模型,记录输出:
表实4-4 提示语改造前后效果对比
| 评估维度 | 原始提示语输出 | 改造后提示语输出 | 改进程度 |
|---|---|---|---|
| 结构完整性 | |||
| 内容针对性 | |||
| 字数控制 | |||
| 风格符合度 | |||
| 可用性 |
步骤4:改造其余4个提示语
按照相同方法改造提示语2-5:
提示语2改造(翻译):
原始:翻译这段话:人工智能正在改变世界
改造后:
## 任务
将以下中文翻译成英文
## 原文
人工智能正在改变世界
## 翻译要求
- 风格:[正式/非正式/学术]
- 用途:[演讲/论文/日常交流]
- 特殊要求:[如有专业术语偏好]
## 输出格式
1. 翻译结果
2. 关键词/术语的翻译说明(如有)
3. 可选的替代翻译(如有多种表达方式)
(继续完成提示语3-5的改造)
步骤5:总结改造原则
基于以上实践,总结提示语结构化改造的原则:
## 提示语结构化改造清单
### 形式结构检查
□ 是否有清晰的层次划分(标题、小节)?
□ 是否使用了列表或编号来组织要点?
□ 是否有足够的空白分隔不同部分?
### 内容结构检查
□ 任务定义是否明确?
□ 是否提供了必要的背景信息?
□ 是否有具体的输出格式要求?
□ 是否提供了示例?
### 功能结构检查
□ 是否说明了任务的目的?
□ 是否明确了目标受众?
□ 是否设定了边界和约束?
□ 是否有质量标准?
提交要求
- 5个提示语的问题分析表
- 5个改造后的完整提示语
- 改造前后效果对比(至少2个提示语的完整对比)
- 提示语结构化改造原则总结
任务二:结构化信息抽取系统(进阶)
任务描述
设计一个完整的人机协作流程,从非结构化文本中抽取结构化信息,体验"人设计结构,机器填充内容,人审核质量"的协作模式。
实验场景
场景选择(二选一):
场景A:招聘信息结构化 - 输入:招聘网站上的职位描述 - 输出:结构化的职位信息
场景B:产品评论分析 - 输入:电商平台的用户评论 - 输出:结构化的评价信息
实验步骤(以场景A为例)
步骤1:收集原始数据
从招聘网站收集10条不同的职位描述(可手动复制或截图转文字):
示例1:
Python开发工程师
薪资:15K-25K·13薪
经验要求:3-5年
学历要求:本科
职位描述:
1. 负责公司核心业务系统的开发和维护
2. 参与技术方案设计和评审
3. 解决系统运行中的技术问题
任职要求:
1. 本科及以上学历,计算机相关专业
2. 3年以上Python开发经验
3. 熟悉Django/Flask框架
4. 有大数据处理经验优先
(收集10条不同类型的招聘信息)
步骤2:设计输出结构(JSON Schema)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "JobPosting",
"type": "object",
"required": ["title", "company", "requirements"],
"properties": {
"title": {
"type": "string",
"description": "职位名称"
},
"company": {
"type": "string",
"description": "公司名称,如未提及填null"
},
"location": {
"type": "string",
"description": "工作地点"
},
"salary": {
"type": "object",
"properties": {
"min": {"type": "number", "description": "最低月薪(K)"},
"max": {"type": "number", "description": "最高月薪(K)"},
"months": {"type": "number", "description": "年薪月数,如13薪填13"}
}
},
"requirements": {
"type": "object",
"properties": {
"education": {
"type": "string",
"enum": ["不限", "大专", "本科", "硕士", "博士"]
},
"experience": {
"type": "string",
"description": "经验要求,如'3-5年'"
},
"skills": {
"type": "array",
"items": {"type": "string"},
"description": "技能要求列表"
},
"preferred": {
"type": "array",
"items": {"type": "string"},
"description": "加分项列表"
}
}
},
"responsibilities": {
"type": "array",
"items": {"type": "string"},
"description": "工作职责列表"
},
"benefits": {
"type": "array",
"items": {"type": "string"},
"description": "福利待遇列表"
}
}
}
步骤3:设计抽取提示语
## 角色
你是一个信息抽取助手,专门从招聘信息中提取结构化数据。
## 任务
从以下招聘信息文本中提取结构化信息,以JSON格式输出。
## 输入文本
"""
[粘贴招聘信息]
"""
## 输出格式
请严格按照以下JSON结构输出:
```json
{
"title": "职位名称",
"company": "公司名称,未提及填null",
"location": "工作地点,未提及填null",
"salary": {
"min": 数字或null,
"max": 数字或null,
"months": 数字或null
},
"requirements": {
"education": "学历要求",
"experience": "经验要求",
"skills": ["技能1", "技能2"],
"preferred": ["加分项1", "加分项2"]
},
"responsibilities": ["职责1", "职责2"],
"benefits": ["福利1", "福利2"]
}
注意事项
- 只抽取文本中明确提到的信息
- 信息未提及时填写null,不要推测或编造
- 薪资单位统一为K(千元),如"15K-25K"提取为min:15, max:25
- 技能和职责按原文分点列出,不要合并或拆分
- 确保输出是有效的JSON格式
**步骤4:执行批量抽取**
用设计的提示语处理10条招聘信息,记录:
**表实4-5 批量抽取执行记录**
| 编号 | 职位简述 | 抽取耗时 | JSON有效性 | 备注 |
|------|----------|----------|------------|------|
| 1 | Python开发 | | ✓/✗ | |
| 2 | | | | |
| ... | | | | |
**步骤5:人工审核与标注**
对每条抽取结果进行人工审核:
**审核表模板**:
**表实4-6 人工审核表**
| 字段 | 抽取值 | 原文依据 | 正确性 | 错误类型 |
|------|--------|----------|--------|----------|
| title | "Python开发工程师" | "Python开发工程师" | ✓ | - |
| salary.min | 15 | "15K-25K" | ✓ | - |
| salary.months | 13 | "13薪" | ✓ | - |
| skills[0] | "Django" | "熟悉Django/Flask框架" | ✓ | - |
| skills[1] | "Flask" | "熟悉Django/Flask框架" | ✓ | - |
| preferred[0] | "有大数据处理经验" | "有大数据处理经验优先" | ✓ | - |
| ... | | | | |
错误类型分类:
- **遗漏**:原文有但未提取
- **错误**:提取的值与原文不符
- **编造**:原文没有但模型捏造
- **格式**:值正确但格式不对
**步骤6:统计准确率**
**表实4-7 各字段抽取准确率统计**
| 字段类型 | 总数 | 正确数 | 准确率 | 主要错误 |
|----------|------|--------|--------|----------|
| 基本信息(title/company/location) | | | | |
| 薪资信息 | | | | |
| 要求信息 | | | | |
| 职责列表 | | | | |
| 福利列表 | | | | |
| **总体** | | | | |
**步骤7:优化迭代**
根据错误分析,改进提示语:
**常见问题及优化策略**:
**表实4-8 常见问题及优化策略**
| 问题 | 原因分析 | 优化策略 |
|------|----------|----------|
| 遗漏技能项 | 描述不够明确 | 增加"列出所有提到的技术栈" |
| 薪资格式不一致 | 未处理特殊格式 | 增加格式转换说明 |
| 编造公司名 | 约束不够强 | 强调"必须是原文明确提到的" |
修改后重新测试2-3条,对比改进效果。
**步骤8:反思人机分工**
填写分工分析表:
**表实4-9 人机分工分析**
| 环节 | 人做了什么 | 机器做了什么 | 为什么这样分工 |
|------|------------|--------------|----------------|
| 结构设计 | 设计JSON Schema | - | 人理解业务需求 |
| 提示语设计 | 编写提示语 | - | 人知道如何表达 |
| 信息抽取 | - | 从文本提取信息 | 机器处理快、规模大 |
| 质量审核 | 检查正确性 | - | 人能判断对错 |
| 迭代优化 | 分析问题、改进提示语 | 重新抽取 | 人发现规律,机器执行 |
#### 提交要求
1. JSON Schema设计(完整、规范)
2. 初版提示语和优化后提示语
3. 10条抽取结果的审核记录
4. 准确率统计表
5. 优化策略和效果对比
6. 人机分工反思(300字以上)
---
### 任务三:人机协作完成语言分析研究(挑战)
#### 任务描述
与大模型协作完成一个小型语言分析研究项目,全程实践"人提供结构和判断,机器提供内容和规模"的协作模式。
#### 选题(三选一)
**选题A:网络流行语分析**
- 研究问题:某个网络流行语(如"绝绝子"、"yyds"、"city不city")的使用模式
- 分析维度:使用语境、语法功能、情感色彩、使用人群
**选题B:中英文表达差异**
- 研究问题:某个语义领域(如"道歉"、"请求"、"拒绝")中英文表达的差异
- 分析维度:句式结构、直接程度、礼貌策略
**选题C:文本类型的结构特征**
- 研究问题:某类文本(如产品评论、新闻标题、学术摘要)的结构特征
- 分析维度:常见句式、词汇特点、信息组织
#### 实验步骤
**步骤1:定义研究问题(人主导)**
填写研究设计表:
```markdown
## 研究设计
### 1. 研究主题
选题:_____________
具体研究对象:_____________
### 2. 研究问题
核心问题:_____________
子问题1:_____________
子问题2:_____________
子问题3:_____________
### 3. 分析框架
分析维度1:_____________(如何操作化)
分析维度2:_____________(如何操作化)
分析维度3:_____________(如何操作化)
### 4. 数据来源
数据类型:_____________
数据来源:_____________
预计数据量:_____________
### 5. 预期发现
假设1:_____________
假设2:_____________
步骤2:数据收集(人机协作)
方式A:使用语料库
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 检索包含目标词/结构的语料
Ret = bcc.Run("研究对象", Command="Context", Number=100)
方式B:网络收集 - 手动收集或用工具抓取 - 确保数据来源的多样性
方式C:请大模型协助
## 任务
帮我收集/生成以下类型的语言材料:
[描述需要的材料类型]
## 要求
- 来源多样(不同平台/场景)
- 风格多样(正式/非正式)
- 数量:20-30条
## 输出格式
编号 | 内容 | 来源/场景
收集至少50条有效数据。
步骤3:设计分析标注体系(人主导)
设计标注维度和标签:
{
"annotation_scheme": {
"dimension_1": {
"name": "语法功能",
"labels": ["作谓语", "作定语", "作状语", "独立使用", "其他"],
"definition": "该词在句中承担的语法角色"
},
"dimension_2": {
"name": "情感色彩",
"labels": ["强烈正面", "一般正面", "中性", "一般负面", "强烈负面"],
"definition": "表达的情感倾向和强度"
},
"dimension_3": {
"name": "使用场景",
"labels": ["日常闲聊", "评价事物", "表达态度", "调侃玩笑", "其他"],
"definition": "该表达的典型使用场景"
}
}
}
步骤4:批量标注(机器为主,人把控)
设计标注提示语:
## 角色
你是一个语言分析助手,帮助研究者对语言材料进行标注。
## 标注体系
[粘贴你设计的标注体系]
## 待标注数据
"""
1. [数据1]
2. [数据2]
...
"""
## 输出格式
请以JSON格式输出每条数据的标注结果:
```json
[
{
"id": 1,
"text": "原文",
"annotations": {
"dimension_1": "标签",
"dimension_2": "标签",
"dimension_3": "标签"
},
"notes": "标注说明(如有疑难)"
}
]
标注原则
- 根据上下文判断,不要孤立看待
- 存在多种可能时,选择最主要的一个
- 不确定时在notes中说明
**步骤5:人工抽检与修正**
从标注结果中抽取20%进行人工检查:
**表实4-10 人工抽检记录**
| 编号 | 原文 | 机器标注 | 人工判断 | 是否一致 | 不一致原因 |
|------|------|----------|----------|----------|------------|
| 1 | | | | ✓/✗ | |
| 2 | | | | | |
| ... | | | | | |
计算一致率:_____________
如果一致率低于80%,需要:
1. 分析不一致的原因
2. 改进标注体系或提示语
3. 重新标注
**步骤6:统计分析(人机协作)**
请大模型协助统计分析:
```markdown
## 任务
对以下标注数据进行统计分析
## 数据
[粘贴标注后的JSON数据]
## 分析要求
1. 各维度的分布统计(频次和百分比)
2. 维度之间的交叉分析(如:不同场景下的情感分布)
3. 发现的主要模式
## 输出格式
使用Markdown表格展示统计结果
步骤7:解释与结论(人主导)
基于统计结果撰写分析:
## 研究发现
### 1. 主要发现
发现1:_____________(数据支持:___%)
发现2:_____________(数据支持:___%)
发现3:_____________(数据支持:___%)
### 2. 模式解释
为什么会出现这些模式?
- 语言学解释:_____________
- 社会文化解释:_____________
### 3. 与预期的对比
假设1验证结果:_____________
假设2验证结果:_____________
### 4. 研究局限
- 数据局限:_____________
- 方法局限:_____________
- 结论的适用范围:_____________
步骤8:撰写研究报告(人主导,机器辅助)
请大模型协助润色(但核心观点和逻辑由人确定):
## 任务
帮我润色以下研究报告的语言表达,使其更加学术规范
## 原稿
[你的报告草稿]
## 要求
1. 保持原意不变
2. 改进语言表达的学术性
3. 不要添加我没有的观点或数据
4. 标出你修改的地方
步骤9:反思人机协作(人主导)
完成反思报告:
## 人机协作反思
### 1. 分工回顾
| 环节 | 人做了什么 | 机器做了什么 | 效果评估 |
|------|------------|--------------|----------|
| 问题定义 | | | |
| 数据收集 | | | |
| 体系设计 | | | |
| 批量标注 | | | |
| 质量把控 | | | |
| 统计分析 | | | |
| 解释结论 | | | |
| 报告撰写 | | | |
### 2. 人的不可替代性
在这个任务中,哪些工作必须由人来做?为什么?
- _____________
- _____________
### 3. 机器的优势
在这个任务中,机器帮助我做了什么?如果没有机器,会有什么不同?
- _____________
- _____________
### 4. 结构化思维的体现
在这个任务中,我使用了哪些"结构化思维"?
- _____________
- _____________
### 5. 对未来的思考
这次实验让我对人机协作有了什么新的认识?
- _____________
提交要求
- 研究设计表
- 收集的原始数据(至少50条)
- 标注体系设计
- 标注提示语和标注结果
- 人工抽检记录
- 统计分析结果
- 研究报告(2000字以上),包含:
- 研究背景与问题
- 研究方法
- 数据分析
- 研究发现
- 讨论与结论
- 人机协作反思报告(500字以上)
- 所有使用的提示语记录
任务四:智能体协作系统设计(拓展)
任务描述
运用教材9.5节的三要素方法,为一个复杂任务设计多智能体协作方案。核心要求是完成结构设计(三要素规划),LangGraph实现为可选的进阶内容。
选题(二选一)
选题A:将任务三的研究改造为多智能体协作版本 - 基于你在任务三中选择的研究课题,设计一个多智能体系统来自动化研究流程 - 需要考虑哪些环节适合由独立的智能体承担,哪些环节需要人来把关
选题B:设计一个"结构化写作助手"多智能体系统 - 目标:辅助完成一篇结构化的分析报告(如产品分析、行业调研、文献综述等) - 需要考虑写作流程中的信息收集、结构规划、内容生成、质量审核等环节
实验步骤
步骤1:需求分析(人主导)
用代码实践9.9.1的语义角色分析法,分析你所选任务的整体需求:
## 任务需求分析
### 总体任务
- 施事(谁来做):_______________
- 动作(做什么):_______________
- 受事(对什么):_______________
- 目的(为什么):_______________
### 子任务识别
列出完成总体任务所需的所有子任务,标注:
1. 子任务名称 + 简要描述
2. 输入是什么?输出是什么?
3. 依赖哪些前序子任务的结果?
4. 需要什么工具或能力?
步骤2:三要素规划(人主导)
这是本任务的核心环节。按照教材9.5.2节的三要素方法,完成以下三层规划:
(一)单元规划——确定智能体
为每个智能体填写设计表:
## 智能体设计表
### 智能体1:_______________
- **角色定义**:(用1-2句话描述,这将成为系统提示语的核心)
- **工具列表**:(该智能体需要使用哪些工具/能力)
- **输入格式**:(接收什么数据?用JSON字段描述)
- **输出格式**:(产出什么数据?用JSON字段描述)
- **Temperature建议**:(高/中/低,并说明理由)
### 智能体2:_______________
...(同上)
(二)关系规划——确定协作结构
画出智能体之间的协作结构图(可手绘或用文字描述),并回答:
- 使用了哪些关系类型?(参见预备知识3.4的表实4-3)
- 为什么选择这种关系结构而不是其他?
- 是否有条件分支(某些情况下跳过或重复某个智能体)?
## 协作结构
### 结构图
(用箭头表示流程,如:检索 → 分析 → 撰写)
### 关系说明
| 源智能体 | 目标智能体 | 关系类型 | 说明 |
|---------|----------|---------|------|
| | | | |
### 设计理由
为什么选择这种结构?(200字以上)
(三)属性配置——为每个智能体撰写系统提示语
运用代码实践9.9.2的框架注入方法和9.9.3的三步法,为每个智能体设计完整的系统提示语。要求:
- 明确角色定位(功能)
- 注入必要的专业框架(框架)
- 用结构化方式组织指令(表达)
(四)递归验证——三层级同构分析
教材9.5.2节(四)指出,三要素在步骤层→智能体层→系统层三个粒度上递归地重现。本环节要求你验证这一同构性,并体会聚合维度在智能体架构中的贯穿作用。
从你在(一)中设计的智能体中选取一个,将其内部工作过程进一步分解为具体的处理步骤,然后用三要素框架分析该智能体的内部结构:
## 递归验证:三层级同构分析
### 选取智能体:_______________
### 步骤层分解
将该智能体的工作过程分解为3-5个处理步骤:
1. 步骤名称:_____ 描述:_____
2. 步骤名称:_____ 描述:_____
3. ...
### 三层级对照表(参照教材表9-8)
| 层级 | 单元是什么? | 关系是什么? | 属性是什么? |
|------|------------|------------|------------|
| **步骤层**(该智能体内部) | | | |
| **智能体层**(该智能体整体) | | | |
| **系统层**(整个协作系统) | | | |
### 聚合分析
1. **步骤层聚合**:该智能体的某个步骤是否存在可替换的备选策略?
举一例说明。
2. **智能体层聚合**:你的系统中哪些智能体属于同一"角色类型"
(如都是"分析器"或"生成器")?它们在什么条件下可以互换?
3. **跨层级术语关联**:从全局任务描述到各智能体的系统提示,
再到具体步骤的指令,有哪些术语/标签是共享的?
这些共享标签在纵向上起到了什么作用?
提示:如果你在(一)中设计了"数据分析智能体",它内部的步骤可能是"数据清洗→特征提取→模式识别→结论生成"。步骤之间的顺序执行是关系,每个步骤的指令约束是属性——同一个三要素框架在更小的尺度上再次出现。
步骤3:方案验证(人机协作)
将你的智能体设计方案提交给大模型,请其评审:
我设计了一个多智能体协作系统,请帮我评审方案的合理性:
[粘贴步骤2的完整设计方案]
请从以下角度评价:
1. 智能体划分是否合理?粒度是否恰当?
2. 协作关系是否能覆盖所有必要的任务流程?
3. 各智能体的输入输出是否能正确对接?
4. 有没有遗漏的环节或冗余的智能体?
根据反馈优化设计,记录: - 大模型指出了哪些问题? - 你做了哪些修改?为什么? - 优化前后方案的关键差异
步骤4(可选):LangGraph实现
本步骤为进阶内容,需要一定的Python编程基础。跳过本步骤不影响实验评分。
安装LangGraph:
pip install langgraph langchain-core
以下是一个LangGraph的参考代码框架,展示如何将三要素规划转化为可运行的智能体系统:
from typing import TypedDict, List
from langgraph.graph import StateGraph, END
# === 定义共享状态(属性)===
class ResearchState(TypedDict):
"""智能体之间通过状态传递信息"""
topic: str # 研究主题
raw_data: List[str] # 原始数据
analysis: str # 分析结果
report: str # 最终报告
current_step: str # 当前步骤
# === 定义智能体节点(单元)===
def data_collector(state: ResearchState) -> ResearchState:
"""数据收集智能体
实际应用中,这里会调用大模型API + 工具
"""
print(f"[数据收集] 正在为主题 '{state['topic']}' 收集数据...")
# 此处调用大模型或工具完成数据收集
state["raw_data"] = ["示例数据1", "示例数据2", "示例数据3"]
state["current_step"] = "collected"
return state
def data_analyzer(state: ResearchState) -> ResearchState:
"""数据分析智能体"""
print(f"[数据分析] 正在分析 {len(state['raw_data'])} 条数据...")
# 此处调用大模型完成分析
state["analysis"] = "分析结果摘要..."
state["current_step"] = "analyzed"
return state
def report_writer(state: ResearchState) -> ResearchState:
"""报告撰写智能体"""
print(f"[报告撰写] 正在根据分析结果撰写报告...")
# 此处调用大模型完成撰写
state["report"] = "完整报告内容..."
state["current_step"] = "completed"
return state
# === 构建协作图(关系)===
workflow = StateGraph(ResearchState)
# 添加节点(单元)
workflow.add_node("collector", data_collector)
workflow.add_node("analyzer", data_analyzer)
workflow.add_node("writer", report_writer)
# 添加边(关系)—— 顺序关系
workflow.set_entry_point("collector")
workflow.add_edge("collector", "analyzer")
workflow.add_edge("analyzer", "writer")
workflow.add_edge("writer", END)
# 编译并运行
app = workflow.compile()
result = app.invoke({
"topic": "网络流行语的语法功能",
"raw_data": [],
"analysis": "",
"report": "",
"current_step": "start"
})
print(f"\n最终状态:{result['current_step']}")
注意:以上代码是框架示例,实际使用时需要在每个节点函数中调用大模型API(如通过langchain的ChatModel)。关键在于理解代码结构与三要素规划的对应关系:
StateGraph=系统、add_node=添加单元、add_edge=定义关系、ResearchState=共享属性。
步骤5:反思
完成以下反思(400字以上):
- 单轮提示语 vs 多智能体:如果用一个长提示语完成同样的任务(不用多智能体),效果会有什么不同?各有什么优劣?
- 粒度选择:你最终设计了几个智能体?如果把智能体数量翻倍(更细粒度)或减半(更粗粒度),会发生什么?什么因素决定了"合适的粒度"?
- 递归同构的体会:在步骤(四)的三层级对照中,你是否确实观察到了三要素在步骤层、智能体层、系统层上的重复出现?三个层级的"单元-关系-属性"有什么共性?又有什么差异?
- 三要素规划的体会:在单元规划、关系规划、属性配置三个环节中,哪个环节最需要人的判断?为什么?
交付物
- 任务需求分析(步骤1)
- 三要素规划完整方案:智能体设计表 + 协作结构图 + 系统提示语(步骤2(一)至(三))
- 三层级同构分析:步骤层分解 + 三层级对照表 + 聚合分析(步骤2(四))
- 大模型评审记录及优化对比(步骤3)
- LangGraph代码及运行结果(步骤4,可选)
- 反思报告(步骤5,400字以上)
任务五:结构诊断——发现大模型输出的结构缺陷(拓展)
本任务的核心能力:在大模型时代,"诊断结构缺陷的能力"可能比"构建结构的能力"更稀缺——因为大模型可以帮你构建,但你需要能判断它构建得对不对。
任务描述
给定大模型生成的结构化输出,运用三要素框架系统地找出结构缺陷并提出修正方案。
实验步骤
步骤1:获取待诊断材料
向大模型提出以下请求,获取其生成的结构化输出:
请为"中国古典诗词"这个领域构建一个小型知识图谱,包含:
- 至少10个实体(诗人、作品、流派、朝代等)
- 实体之间的关系(创作、属于、影响等)
- 每个实体的关键属性
请以JSON格式输出。
步骤2:三要素诊断
用三要素框架逐一审查模型输出:
| 诊断维度 | 检查要点 | 记录发现的问题 |
|---|---|---|
| 单元诊断 | 实体是否遗漏?粒度是否一致?是否有重复? | |
| 关系诊断 | 关系类型是否准确?方向是否正确?是否有遗漏的关键关系? | |
| 属性诊断 | 属性值是否准确?格式是否一致?是否有缺失? |
步骤3:三侧面诊断
从三侧面进一步审查:
| 诊断维度 | 检查要点 | 记录发现的问题 |
|---|---|---|
| 形式层 | JSON格式是否规范?命名是否一致?结构是否统一? | |
| 内容层 | 事实是否准确?分类是否合理?信息是否充分? | |
| 功能层 | 这个知识图谱能回答什么问题?不能回答什么?本体设计是否服务于预期功能? |
步骤4:修正方案
针对诊断出的问题,提出具体修正方案,并让大模型按照你的修正意见重新生成。对比修正前后的质量差异。
提交要求
- 大模型原始输出(完整JSON)
- 三要素诊断记录表(填写上表)
- 三侧面诊断记录表(填写上表)
- 修正方案说明
- 修正后的输出及质量对比
六、思考题
请认真思考并回答以下问题(每题200字以上):
-
提示语设计的关键:通过本实验,你认为设计好提示语的关键是什么?为什么说"大模型时代更需要结构化思维"?大模型自己能设计好提示语吗?
-
人机边界:在人机协作中,什么样的任务适合交给机器?什么样的任务必须由人来做?这个边界会随着技术发展而变化吗?
-
显/隐对立的体现:教材第八章以"显式结构 vs 隐式结构"为组织轴心,提出四组深层对立(符号/参数、概念/向量、推理/计算、确定/模糊)。在本实验中,哪些环节体现了显式结构的优势(如人设计Schema、制定标注体系),哪些环节体现了隐式结构的优势(如大模型批量处理、语义理解)?请举例说明。
-
未来展望:基于本实验的体验,你认为未来5-10年人机协作会如何发展?"结构化思维"的价值会如何变化?人类还需要学习哪些能力?
-
智能体设计与提示语设计:教材9.5节指出,"提示语设计和智能体设计是同一种结构化思维在不同粒度上的应用"。请结合本实验的体验,谈谈你对这句话的理解。在什么情况下应该从单轮提示语升级到多智能体方案?又在什么情况下单轮提示语已经足够?进一步思考:如果三要素在步骤层、智能体层、系统层上都递归出现,那么"设计智能体"和"写一段好的提示语"之间的本质区别是什么?仅仅是粒度不同吗?
七、评分标准
表实4-11 实验四评分标准
| 评分项 | 分值 | 评分要点 |
|---|---|---|
| 任务一:提示语改造 | 15分 | 问题分析准确(5)、改造合理(5)、总结深入(5) |
| 任务二:信息抽取系统 | 25分 | Schema设计(7)、提示语设计(7)、审核规范(5)、优化有效(6) |
| 任务三:协作研究 | 25分 | 设计合理(6)、数据充分(5)、分析深入(7)、反思有深度(7) |
| 任务四:智能体系统设计 | 20分 | 三要素规划完整性(5)、协作结构合理性(5)、三层级同构分析(5)、反思深度(5) |
| 思考题 | 15分 | 每题约3分,有深度、有见解 |
| 自主探索(加分) | +10分 | 超出基本要求的创造性工作,如LangGraph实现、额外分析、工具对比等 |
大模型对话日志:本实验大量使用大模型,请保存所有对话的完整记录(截图或导出文本),作为实验报告附录提交。对话日志是评估提示语设计质量和迭代过程的核心依据。
跨实验数据复用:实验二的本体设计与知识图谱Schema可作为本实验任务二信息抽取的Schema参考或验证基准,鼓励复用前序实验成果。
八、参考资料
- 教材第八章"从符号到向量"
- 教材第九章"结构的运用:从结构化思维到有效交互"(特别是9.5.2节(四)"递归的三要素"和表9-8)
- 教材第十章"理性结构与经验结构"
- Prompt Engineering最佳实践
- LangSC使用文档
- LangGraph官方文档(任务四可选参考)
实验四 完
实验五:综合项目——从语料到知识到应用
一、实验信息
- 实验名称:综合项目——从语料到知识到应用
- 对应章节:第一章至第十章(综合运用)
- 实验学时:8学时
- 难度等级:★★★★★
二、实验目的
- 综合运用组合结构与聚合结构的分析方法,完成端到端的语言分析任务
- 掌握从非结构化语料中提取结构模式的全流程
- 能够构建小型领域知识库,并以结构化数据形式表示
- 熟练运用结构化提示语与大模型协作完成分析报告
- 培养对大模型输出进行多维度批判性评价的能力
- 体会"结构化思维"在真实项目中的贯穿性作用
三、预备知识
3.1 本实验涉及的核心概念
本实验的理论基础 本实验是全书知识的综合运用,要求你完成从"识别结构"到"设计结构"的全过程——对应教材第十章提出的结构化能力五层次模型(识别→表示→操作→判断→设计)。你将综合使用三要素(分析结构成分)、三侧面(切换分析视角)和五操作(执行结构处理),在真实任务中体会这三个核心框架的协同作用。
本实验是一个综合性项目,贯穿全书的核心内容。下表梳理了各阶段与教材章节的对应关系:
表实5-1 项目阶段与教材章节对应关系
| 项目阶段 | 核心概念 | 对应章节 |
|---|---|---|
| 结构提取 | 组合结构(词→短语→句子→篇章) | 第一章《组合与聚合》、第五章《组合结构与语料库》 |
| 结构提取 | 句式模式、篇章结构 | 第三章《语言的层级结构》 |
| 知识构建 | 聚合结构(本体设计、分类体系、知识图谱) | 第二章《形式·内容·功能》、第六章《聚合结构与知识库》 |
| 知识构建 | 结构表示(JSON、表格) | 第四章《语言结构表示》 |
| 大模型协作 | 结构化思维与知识注入 | 第九章《结构的运用:从结构化思维到有效交互》 |
| 大模型协作 | 输出评估与人机分工 | 第十章《理性结构与经验结构》 |
3.2 端到端思维
前四个实验分别侧重于某一技能(结构分析、知识构建、语料库检索、人机协作)。本实验要求将这些技能串联起来,形成一个完整的工作流:
图实5-1 综合项目端到端工作流
每个环节的输出是下一个环节的输入,因此各阶段的结构化程度直接影响最终成果的质量。
3.3 领域选择建议
本实验需要选择一个文本领域作为研究对象。推荐以下领域(也可自选):
表实5-2 推荐研究领域及说明
| 领域 | 适合原因 | 数据获取难度 |
|---|---|---|
| 新闻报道 | 结构规范、模式清晰 | 低 |
| 产品评论 | 情感丰富、结构多样 | 低 |
| 学术摘要 | 格式固定、逻辑性强 | 中 |
| 社交媒体帖子 | 语言生动、变化多端 | 低 |
| 旅游攻略 | 信息密集、实用性强 | 低 |
如果你不确定如何开始,以下三个示范选题提供了更具体的方向,每个选题精心设计以覆盖全书核心概念:
表实5-2a 示范选题
| 选题 | 核心任务 | 覆盖概念 | 难度 |
|---|---|---|---|
| A. 领域术语的结构图谱 | 从一个专业领域(如中医、法律、计算机)的语料中提取术语,构建术语之间的结构关系图谱(上下位、同义、因果等),最终让大模型在你的图谱框架内回答领域问题 | 语料库检索(5章)→本体设计(6章)→知识图谱构建(6章)→大模型辅助扩展(9章) | ★★★★ |
| B. 大模型输出的结构化评测 | 给大模型不同结构化程度的提示语(无结构/弱结构/强结构),对比输出质量,用定量方法验证"结构越明确→输出越可控"的假设 | 三步法(9章)→三要素诊断(9章)→显隐互补(8章)→理性vs经验(10章) | ★★★★★ |
| C. 双语结构对齐 | 选取一个语言现象(如"把"字句、存现句、被动句),对比中英文的结构差异,用对齐操作建立映射,分析大模型在该结构上的翻译表现 | 组合结构(1章)→形式/内容/功能(2章)→对齐操作(7章)→大模型评测(9章) | ★★★★ |
四、实验环境
- Python 3.8+
- LangSC库(语言结构分析工具包)
- 大模型API或对话界面(如Claude、GPT等)
- 文本编辑器或IDE(推荐VS Code或PyCharm)
五、实验内容
本实验分为三个阶段,每个阶段依次递进。
阶段一:结构提取——从语料库中提取结构模式
任务描述
选择一个文本领域,收集样本语料,运用组合结构分析方法提取该领域文本的常见结构模式。
实验步骤
步骤1:确定领域并收集语料
从上述推荐领域中选择一个(或自选),收集10-20篇同类型文本作为样本。
from LangSC import GPF
from LangSC import BCC
gpf = GPF("./corpus")
bcc = BCC("Corpus")
# 示例:检索新闻报道语料
Ret = bcc.Run("领域关键词", Command="Context", Number=20)
也可从网站手动收集。记录每条语料的来源信息:
表实5-3 语料收集清单
| 编号 | 标题/首句 | 来源 | 字数 | 收集日期 |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| ... |
步骤2:篇章层结构分析
对每篇文本标注其宏观结构(段落功能):
表实5-4 篇章层结构分析
| 编号 | 段落数 | 篇章结构模式 | 说明 |
|---|---|---|---|
| 1 | 3 | 导语—展开—总结 | 典型新闻结构 |
| 2 | 4 | 背景—问题—分析—展望 | |
| ... |
统计最常见的篇章结构模式(出现频次及占比)。
步骤3:句子层结构分析
从语料中选取30-50个代表性句子,进行句式分析:
from LangSC import GPF
import json
gpf = GPF()
for sentence in selected_sentences:
Ret = gpf.Parse(sentence, Structure="Segment")
tokens = json.loads(Ret)
Ret = gpf.Parse(sentence, Structure="Dep")
# 记录句式类型和关键结构特征
将句式归类:
| 句式类型 | 结构模式 | 示例 | 出现频次 |
|---|---|---|---|
| 主谓宾 | S + V + O | ||
| 存现句 | 处所 + V + 存在物 | ||
| "把"字句 | S + 把 + O + V | ||
| 其他 |
步骤4:关键关系提取
识别文本中反复出现的语义关系:
| 关系类型 | 表达方式 | 示例 | 频次 |
|---|---|---|---|
| 因果关系 | 因为...所以...、由于...、导致... | ||
| 转折关系 | 但是、然而、不过 | ||
| 并列关系 | 和、以及、同时 | ||
| 时间关系 | 首先...然后...最后... | ||
| 比较关系 | 比、相比、更加 |
步骤5:形成结构模式报告
将以上分析汇总为该领域的"结构模式报告",包括: - 篇章结构的典型模式(附频率统计) - 常见句式及分布 - 高频语义关系及其表达方式 - 该领域文本的结构特征总结(200字以上)
阶段一提交要求
- 语料收集清单(10-20条,附来源信息)
- 篇章结构分析表
- 句式分析统计表
- 关键关系提取表
- 结构模式报告
阶段二:知识构建——设计本体并构建小型领域知识库
任务描述
基于阶段一提取的结构模式,运用聚合结构的方法,先设计领域本体(聚合的标准),再据此构建一个小型领域知识库,并以JSON格式表示。教材第六章指出:"知识库的本体就是聚合的标准"——没有本体,实例就无法有原则地归入概念。因此,本阶段的核心工作是本体设计,而非仅仅罗列实体和关系。
实验步骤
步骤1:实体与概念提取
从语料中提取关键实体和概念,按类别组织:
| 类别 | 实体/概念 | 出现频次 | 示例语境 |
|---|---|---|---|
步骤2:设计本体——聚合的标准
在填充任何实例之前,先设计领域知识库的本体。本体定义了"实例按什么标准归入概念、概念按什么方式组织为层次"(教材6.3节)。
将提取的实体和概念组织为层级分类体系:
关键要求:对每个分类决策说明理由——为什么这样划分而不是另一种方式?分类的依据是功能、属性还是来源?不同的标准会导致不同的分类结果(教材6.3.3节)。
步骤3:定义关系类型
根据阶段一发现的语义关系,定义知识库中的关系类型:
| 关系名称 | 说明 | 示例 | 方向性 |
|---|---|---|---|
| 属于 | 实体属于某类别 | "Python"属于"编程语言" | 有向 |
| 导致 | 因果关系 | "技术进步"导致"效率提升" | 有向 |
| 相关 | 一般关联 | 无向 | |
| ... |
步骤4:设计JSON Schema
为知识库设计完整的JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "领域知识库",
"type": "object",
"properties": {
"domain": {
"type": "string",
"description": "领域名称"
},
"entities": {
"type": "array",
"description": "实体列表",
"items": {
"type": "object",
"required": ["id", "name", "category"],
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"category": {"type": "string"},
"attributes": {
"type": "object",
"description": "实体属性"
}
}
}
},
"categories": {
"type": "array",
"description": "分类体系",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"parent": {"type": "string"},
"description": {"type": "string"}
}
}
},
"relations": {
"type": "array",
"description": "关系列表",
"items": {
"type": "object",
"required": ["type", "source", "target"],
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"evidence": {"type": "string", "description": "语料中的依据"}
}
}
},
"structural_patterns": {
"type": "object",
"description": "阶段一发现的结构模式",
"properties": {
"discourse_patterns": {"type": "array"},
"sentence_patterns": {"type": "array"},
"relation_expressions": {"type": "array"}
}
}
}
}
步骤5:填充知识库实例
按照Schema填入实际数据,确保: - 至少包含20个实体 - 至少包含3层分类 - 至少包含30条关系 - 所有关系都有语料中的依据
步骤6:知识库质量检查
用以下标准自查知识库:
| 检查项 | 通过/不通过 | 说明 |
|---|---|---|
| JSON格式有效(可解析) | ||
| 符合设计的Schema | ||
| 实体无重复 | ||
| 关系的source和target均存在于实体列表中 | ||
| 分类体系无孤立节点 | ||
| 每条关系都有语料依据 |
阶段二提交要求
- 实体与概念提取表
- 本体设计:分类体系图(层级结构)、属性定义、关系类型,每个设计决策附理由说明
- JSON Schema设计(本体的形式化表示)
- 完整的知识库JSON实例(按本体填充)
- 质量检查记录
阶段三:大模型协作——用结构化提示语与大模型协作完成分析报告
任务描述
基于前两个阶段的成果,设计结构化提示语,与大模型协作完成一份领域分析报告,并对大模型输出进行多维度评估。
实验步骤
步骤1:设计结构化提示语
将知识库信息融入提示语,引导大模型生成高质量分析:
## 角色
你是一个[领域]分析专家,擅长基于结构化知识进行深入分析。
## 背景知识
以下是关于[领域]的结构化知识库:
### 核心实体
[从知识库中提取关键实体及其属性]
### 分类体系
[从知识库中提取分类层级]
### 关键关系
[从知识库中提取重要关系]
### 该领域文本的结构特征
[从阶段一的结构模式报告中提取]
## 任务
基于以上知识,撰写一份关于[具体分析主题]的分析报告。
## 报告结构
1. 概述(200字):介绍分析对象和分析目的
2. 现状分析(400字):基于知识库中的实体和关系描述现状
3. 模式发现(400字):基于结构分析发现的规律和特征
4. 深入讨论(300字):对发现的模式进行解释和讨论
5. 结论与建议(200字):总结要点并提出建议
## 要求
- 所有分析必须基于提供的知识库,不要引入外部事实
- 引用具体实体和关系时标注来源
- 保持分析的逻辑连贯性
- 总字数1500字左右
步骤2:执行生成并记录
将提示语发送给大模型,完整记录: - 使用的大模型名称和版本 - 发送的完整提示语 - 大模型的完整输出 - 生成时间
步骤3:多维度评估大模型输出
从以下四个维度对大模型输出进行评估:
维度一:事实准确性
逐条检查报告中的事实陈述是否与知识库一致:
| 编号 | 报告中的陈述 | 知识库中的依据 | 准确性 | 问题描述 |
|---|---|---|---|---|
| 1 | 准确/不准确/无依据 | |||
| 2 | ||||
| ... |
准确率 = 准确陈述数 / 总陈述数 = ______
维度二:逻辑一致性
检查报告的论证逻辑:
| 检查项 | 评分(1-5) | 说明 |
|---|---|---|
| 各段之间的逻辑衔接 | ||
| 论点与论据的匹配度 | ||
| 结论是否从分析中自然得出 | ||
| 是否存在自相矛盾之处 |
维度三:结构完整性
检查报告是否符合提示语中要求的结构:
| 要求的部分 | 是否包含 | 字数是否达标 | 内容是否切题 |
|---|---|---|---|
| 概述 | |||
| 现状分析 | |||
| 模式发现 | |||
| 深入讨论 | |||
| 结论与建议 |
维度四:语言适当性
| 检查项 | 评分(1-5) | 说明 |
|---|---|---|
| 语言是否符合分析报告风格 | ||
| 术语使用是否准确 | ||
| 是否存在空泛、套话 | ||
| 表达是否简洁清晰 |
步骤4:综合评估与改进
汇总四维评估结果:
| 评估维度 | 得分/满分 | 主要问题 | 改进策略 |
|---|---|---|---|
| 事实准确性 | /100 | ||
| 逻辑一致性 | /20 | ||
| 结构完整性 | /15 | ||
| 语言适当性 | /20 | ||
| 综合 |
根据评估结果,修改提示语并重新生成,对比改进效果。
步骤5:撰写最终分析报告
在大模型输出的基础上,由人工进行以下修订: 1. 修正事实错误 2. 补充知识库中的关键信息(大模型可能遗漏) 3. 调整逻辑不通顺之处 4. 增加个人的分析见解 5. 形成最终的分析报告(2000字以上)
标注哪些内容来自大模型、哪些来自人工修订。
阶段三提交要求
- 结构化提示语设计(初版和优化版)
- 大模型完整输出记录
- 四维评估表(事实准确性、逻辑一致性、结构完整性、语言适当性)
- 改进策略和对比分析
- 最终分析报告(标注人机贡献)
六、思考题
请认真思考并回答以下问题(每题200字以上):
-
结构的贯穿作用:回顾三个阶段的工作,"结构"这一概念是如何贯穿始终的?组合结构和聚合结构各自在哪些环节发挥了作用?如果缺少结构化的视角,项目的各阶段会出现什么问题?
-
知识与模型的互补:你构建的知识库(显性知识)与大模型的内部知识(隐性知识)在协作中是如何互补的?特别是在本体设计(聚合标准的制定)这一环节,人和大模型各自的优势是什么?这种互补关系对未来的知识工程有何启示?
-
评估的意义:为什么需要从事实准确性、逻辑一致性、结构完整性、语言适当性四个维度来评估大模型的输出?这四个维度之间有什么内在联系?是否还需要其他维度?
-
端到端的反思:如果让你重新做一次这个项目,你会在哪些环节做出不同的选择?从"语料→知识→应用"的全流程来看,哪个环节对最终成果的影响最大?
七、评分标准
| 评分项 | 分值 | 评分要点 |
|---|---|---|
| 阶段一:结构提取 | 25分 | 语料收集规范(5)、篇章分析完整(5)、句式统计准确(5)、关系提取合理(5)、模式报告有深度(5) |
| 阶段二:知识构建 | 25分 | 实体提取全面(4)、本体设计合理且有理由(7)、Schema设计规范(5)、JSON实例正确(5)、质量检查严格(4) |
| 阶段三:大模型协作 | 30分 | 提示语设计合理(8)、四维评估严谨(8)、改进策略有效(7)、最终报告质量高(7) |
| 思考题 | 20分 | 每题5分,要求有深度、有具体例证、有个人见解 |
| 自主探索(加分) | +10分 | 超出基本要求的创造性工作,如额外分析、工具对比、独立发现等 |
注意:本实验强调全流程的连贯性。各阶段之间应有明确的数据传递关系——阶段一的输出应作为阶段二的输入,阶段二的输出应融入阶段三的提示语。评分时将检查各阶段之间的衔接质量。
大模型对话日志:阶段三涉及大量大模型交互,请保存所有对话的完整记录(截图或导出文本),作为实验报告附录提交。
跨实验数据复用:鼓励复用前序实验的成果——实验一的结构分析方法、实验二的本体设计与知识图谱Schema、实验三的检索策略、实验四的提示语模板均可作为本实验的起点。
八、实验报告模板
# 实验五 综合项目报告
## 基本信息
- 姓名:___________
- 学号:___________
- 选择的文本领域:___________
- 完成日期:___________
## 第一部分:结构提取
### 1.1 语料收集
[语料清单及来源信息]
### 1.2 篇章结构分析
[分析表格及统计结果]
### 1.3 句式分析
[句式归类及频率统计]
### 1.4 关键关系提取
[关系类型及表达方式]
### 1.5 结构模式报告
[该领域文本的结构特征总结,200字以上]
## 第二部分:知识构建
### 2.1 实体与概念
[提取结果]
### 2.2 本体设计
[分类体系(层级结构图)、属性定义、关系类型,每个决策附理由]
### 2.3 JSON Schema
[完整Schema]
### 2.4 知识库实例
[完整JSON,附质量检查记录]
## 第三部分:大模型协作
### 3.1 提示语设计
[初版提示语]
### 3.2 大模型输出
[完整记录]
### 3.3 四维评估
[评估表格及分析]
### 3.4 改进与优化
[优化后的提示语及对比]
### 3.5 最终分析报告
[2000字以上,标注人机贡献]
## 第四部分:思考题
[四道思考题的回答]
## 第五部分:总结与反思
[项目整体反思,300字以上]
- 最大的收获是什么?
- 最大的困难是什么?如何克服的?
- 对"结构化思维"有了什么新的认识?
九、参考资料
- 教材第一章"组合与聚合"
- 教材第二章"形式·内容·功能"
- 教材第三章"语言的层级结构"
- 教材第四章"语言结构表示"
- 教材第五章"组合结构与语料库"
- 教材第六章"聚合结构与知识库"
- 教材第九章"结构的运用:从结构化思维到有效交互"
- 教材第十章"理性结构与经验结构"
- JSON官方规范:https://www.json.org/
- LangSC使用文档
实验五 完
附录A LangSC使用指南
本附录详细介绍LangSC(Language Structure Computing Library,语言结构计算开发包)的功能和使用方法。LangSC是本课程配套的语言结构计算工具包,整合了三个核心模块:
| 模块 | 全称 | 功能 |
|---|---|---|
| GPF | Grid-based Parsing Framework | 基于网格的语言解析框架,支持分词、词性标注、句法分析、结构化标注和可视化 |
| BCC | BCC Corpus | 语料库索引与检索引擎,支持词频统计、上下文检索 |
| JSS | JSON Structured Search | JSON结构化数据搜索引擎,支持SQL查询、多种索引类型 |
三个类完全独立,可以单独使用,也可以组合使用。
注意:LangSC目前处于持续开发中,部分API可能在后续版本中调整。本指南中的代码示例均基于当前版本的实际API编写。
A.1 概述与安装
A.1.1 设计理念
LangSC采用三模块设计——GPF、BCC、JSS各自独立,通过不同类提供结构分析、语料库检索和数据搜索能力。
核心概念:
- 网格(Grid):GPF的核心数据结构。文本被映射为一个二维网格,列对应文本中的字符位置,行对应不同层次的标注单元。所有分析结果最终都可以加载到网格中进行统一管理。
- 单元(Unit):网格中的基本元素,用坐标
(列号,行号)标识,例如(2,1)。每个单元可以横跨多列(如"北京大学"横跨4个字符列),并携带键值对属性(如POS=n)。 - 关系(Relation):两个单元之间的有向关系,表示为
(Head, Sub, Relation)三元组,如"读"与"书"之间的"VOB"关系。
A.1.2 安装与导入
pip install LangSC
依赖包:
| 依赖包 | 说明 |
|---|---|
pywin32 |
Windows 平台必需 |
wordcloud |
词云可视化 |
requests |
HTTP 请求 |
chardet |
文件编码检测 |
基本初始化:
from LangSC import GPF, BCC, JSS
# GPF:结构分析(默认数据路径 ./data)
gpf = GPF()
gpf = GPF("./mydata") # 指定数据路径
# BCC:语料库检索(指定语料数据目录)
bcc = BCC("Corpus")
# JSS:JSON结构化搜索(指定数据目录,自动索引并加载)
jss = JSS("./json")
初始化时,三个类都会自动检测指定数据路径下的索引状态。如果路径下有未索引的资源文件,会自动进行索引构建。索引完成后记录到索引日志文件(IdxLog_GPF.txt、IdxLog_BCC.txt、IdxLog_JSS.txt),下次初始化时通过时间戳比较避免重复索引。
A.1.3 功能概览
| 模块 | 功能域 | 主要方法 | 说明 |
|---|---|---|---|
| GPF | 结构分析 | Parse() |
分词、词性标注、句法树、依存分析等 |
| GPF | 可视化 | ShowStructure(), ShowGrid() |
将结构生成图片 |
| GPF | 网格操作 | SetText(), AddUnit(), AddRelation(), AddStructure() |
构建和操作网格 |
| GPF | 数据表 | GetItem(), GetItemKV() |
查询GPF数据表 |
| GPF | FSA | CallFSA() |
运行有限状态自动机 |
| BCC | 语料检索 | Run(), CallBCC(), AddBCCKV(), GetBCCKV(), ClearBCCKV() |
BCC语料库检索与词表管理 |
| JSS | 数据搜索 | Run(), Terminate() |
JSON数据自动索引与SQL检索 |
A.2 结构分析(Parse)
Parse是GPF最常用的方法,输入文本,返回JSON格式的分析结果。
A.2.1 基本用法
from LangSC import GPF
import json
gpf = GPF()
# Parse的基本调用形式
Ret = gpf.Parse(text, Structure="分析类型")
Parse返回JSON字符串,需要用json.loads()转换为Python对象。
A.2.2 分词(Segment)
将文本切分为词语序列。
gpf = GPF()
Ret = gpf.Parse("今天天气真好", Structure="Segment")
print(Ret)
# 输出JSON字符串,如: ["今天", "天气", "真", "好"]
words = json.loads(Ret)
for w in words:
print(w)
A.2.3 词性标注(POS)
在分词基础上标注每个词的词性。这是Parse的默认分析类型。
Ret = gpf.Parse("今天天气真好", Structure="POS")
print(Ret)
# 输出如: ["今天/t", "天气/n", "真/d", "好/a"]
tags = json.loads(Ret)
for tag in tags:
word, pos = tag.split("/")
print(f"词:{word},词性:{pos}")
常用词性标记:
| 标记 | 含义 | 标记 | 含义 |
|---|---|---|---|
| n | 名词 | v | 动词 |
| a | 形容词 | d | 副词 |
| r | 代词 | p | 介词 |
| u | 助词 | c | 连词 |
| m | 数词 | q | 量词 |
| t | 时间词 | f | 方位词 |
| ns | 地名 | nr | 人名 |
| nt | 机构名 | w | 标点 |
A.2.4 组块分析(Chunk)
将句子切分为更大的句法组块(短语级)。
Ret = gpf.Parse("我们大家今天下午在操场集合", Structure="Chunk")
print(Ret)
# 返回JSON,包含组块划分结果
A.2.5 句法树(Tree)
生成短语结构树。
Ret = gpf.Parse("我们大家今天下午在操场集合", Structure="Tree")
print(Ret)
# 返回包含Type、Units、POS等字段的JSON
# 解析结果
tree = json.loads(Ret)
if tree.get("Units"):
print("句法树:", tree["Units"][0])
Tree返回的JSON结构包含:
- Type:结构类型,如"Tree"
- Units:括号表示法的句法树数组
- POS:词性标注数组
A.2.6 依存分析(Dep)
生成依存句法结构。依存分析通过Web服务完成,返回包含词语、词性和依存关系的JSON。
Ret = gpf.Parse("学生认真地读书", Structure="Dep")
print(Ret)
# 返回包含依存关系的JSON结构
推荐用法:将依存分析结果加载到网格中,通过网格操作进行结构化查询:
gpf.SetText("学生认真地读书")
Ret = gpf.Parse("学生认真地读书", Structure="Dep")
gpf.AddStructure(Ret) # 将依存结构加载到网格
# 通过网格查询依存关系
sbv = gpf.GetUnit("Rel=SBV") # 查找主谓关系
for u in sbv:
print(gpf.GetUnitKV(u, "Word"))
# 可视化依存结构
gpf.ShowStructure(Ret, "dependency.png")
A.2.7 其他参数
# 使用云服务进行分析(默认为本地分析)
Ret = gpf.Parse("这些都是我们爱的人", Structure="POS", Web=True)
# 指定用户词典表
Ret = gpf.Parse("在北京语言大学读书", Structure="Segment", Table="UserDict")
A.2.8 Structure参数汇总
| 参数值 | 功能 | 返回格式 |
|---|---|---|
"Segment" |
分词 | ["词1", "词2", ...] |
"POS" |
词性标注(默认) | ["词1/pos1", "词2/pos2", ...] |
"Chunk" |
组块分析 | 含POS和组块边界的JSON |
"Tree" |
短语结构树 | {"Type":"Tree", "Units":[...], "POS":[...]} |
"Dep" |
依存分析 | 含词语和依存关系的JSON |
A.3 结构可视化(Show)
GPF可以将各种结构生成为图片,支持多种JSON格式的自动识别和可视化。
A.3.1 ShowStructure——显示JSON结构
ShowStructure接受JSON字符串,自动识别结构类型并生成图片。
gpf = GPF()
序列可视化(JSON数组,元素为字符串或对象):
# 词语序列
Json = '["我们", "大家", "喜欢", "上课"]'
gpf.ShowStructure(Json, "seq.png")
# 带词性的序列
Json = '["我们/r", "大家/n", "喜欢/v"]'
gpf.ShowStructure(Json, "pos.png")
# 带属性的序列
Json = '[{"我们":{"POS":"r"}}, "大家", "喜欢/v"]'
gpf.ShowStructure(Json, "rich_seq.png")
树形可视化(JSON对象,值为数组表示子节点):
# 句法树
Json = '{"S":[{"NP":[{"r":"我们"},{"n":"大家"}]},{"VP":{"v":"喜欢"}}]}'
gpf.ShowStructure(Json, "tree.png")
图形可视化(JSON数组,元素为三元组 [头, 尾, 关系]):
# 依存关系图
Json = '[["读","学生","SBV"],["读","书","VOB"],["读","认真","ADV"]]'
gpf.ShowStructure(Json, "dep.png")
# 字典形式的图
Json = '{"学习":[["我","主语"],["在","mod"]]}'
gpf.ShowStructure(Json, "graph.png")
词云可视化(JSON对象,值为数值表示频率):
# 词频词云
Json = '{"发展":140,"经济":70,"推进":66,"建设":63,"加强":53}'
gpf.ShowStructure(Json, "cloud.png")
结构化JSON(Parse返回的带Type字段的结构):
# 直接可视化Parse结果
Ret = gpf.Parse("我们大家今天下午在操场集合", Structure="Tree")
gpf.ShowStructure(Ret, "parse_tree.png")
A.3.2 ShowGrid——显示网格
ShowGrid显示当前网格中的所有单元,可通过参数控制是否同时显示关系。
Line = "我们大家今天下午在操场集合"
gpf = GPF()
Ret = gpf.Parse(Line, Structure="Tree")
gpf.AddStructure(Ret)
# 仅显示网格单元
gpf.ShowGrid("grid.png")
# 同时显示单元之间的关系
gpf.ShowGrid("grid_rel.png", IsShowRel=True)
网格图中不同类型的单元用不同颜色区分: - 灰色:字符单元(Char) - 绿色:词语单元(Word) - 浅蓝:短语单元(Phrase) - 金色:组块单元(Chunk)
A.4 BCC语料库检索
BCC是LangSC的语料库检索引擎,作为独立类使用,支持基于模式的语料检索和统计分析。
A.4.1 基本检索
from LangSC import BCC
bcc = BCC("Corpus") # 指定语料数据目录
# 基本频率检索(默认Command="Freq")
Ret = bcc.Run("a的n")
print(Ret)
# 上下文检索(KWIC格式)
Ret = bcc.Run("a的n", Command="Context", Number=100)
print(Ret)
# 计数
Ret = bcc.Run("a的n", Command="Count")
print(Ret)
A.4.2 BCC检索语言
BCC检索引擎接受一种专用的检索语言,完整格式如下:
[Lua:]查询体{条件}[范围]操作(参数)
各部分说明:
| 部分 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
Lua: |
可选 | 无 | 加上此前缀时生成的脚本带Lua标记 |
| 查询体 | 必填 | — | 检索的核心内容 |
{条件} |
可选 | 空(无条件) | 过滤条件 |
[范围] |
可选 | 空(全语料) | 语料子集限定 |
操作(参数) |
可选 | Context(10,0,100) |
输出形式 |
最简形式只需一个查询体,例如:n喜欢。通过Python参数Command、Number等也可以控制输出形式(见E.4.5)。
查询体
查询体由一个或多个检索单元通过连接符串联而成。
(1)检索单元
检索单元是查询的最小构成单位。词性用英文字母表示(如n名词、v动词、a形容词等),词形用汉字表示。共有6种类型:
| 类型 | 格式 | 示例 | 含义 |
|---|---|---|---|
| 词性+词形 | 词性汉字 |
n喜欢 |
词性为n且词形含"喜欢" |
| 词形+词性 | 汉字词性 |
喜欢v |
词形含"喜欢"且词性为v |
| 纯词形 | 汉字 |
喜欢 |
词形含"喜欢"的任意词 |
| 纯词性 | 词性 |
n |
任意名词 |
| 词性+特征 | 词性[*汉字] |
q[*的] |
特征值含"的"的q类词 |
| 词性+全特征 | 词性[*] |
q[*] |
q类词的所有特征 |
(2)连接符
连接符写在两个检索单元之间,表示它们的位置关系。
| 连接符 | 写法 | 含义 |
|---|---|---|
| 紧邻 | 直接相连或空格 | 两个词线性相邻 |
| 同句 | * |
同一句中,距离不限 |
| 跨句 | ^ |
跨越句子边界 |
示例:
nv → 一个名词紧接一个动词
n*v → 同一句中,名词与动词之间可有其他词
n^v → 名词和动词分属不同句子
(3)修饰符
修饰符附加在检索单元的前后,表示位置约束。
| 修饰符 | 位置 | 含义 | 示例 |
|---|---|---|---|
~ |
前缀 | 句首 | ~n 句首的名词 |
~ |
后缀 | 句尾 | n~ 句尾的名词 |
. |
前缀 | 左定位 | .n |
. |
后缀 | 右定位 | n. |
() |
包裹修饰符 | 层级深度 | (~)n 括号深度为1的句首名词 |
多层括号表示更深的层级:((~))n 表示括号深度为2。
(4)块内查询
方括号表示在同一个词或短语内部查询子结构:词性[内部查询]。*的位置决定边界关系:
| 写法 | 含义 |
|---|---|
v[n喜欢] |
v词内部包含"n喜欢",共享边界 |
v[*n喜欢] |
v词内部包含"n喜欢",共享右边界 |
v[n喜欢*] |
v词内部包含"n喜欢",共享左边界 |
v[*n喜欢*] |
v词内部包含"n喜欢",完全包含 |
条件
花括号内写过滤条件。$1、$2等代表查询体中第1个、第2个检索单元的结果。多个条件用;分隔。
| 写法 | 含义 |
|---|---|
{} |
无条件 |
{$1=$2} |
第1个和第2个检索单元结果相同 |
{$1=[A,B]} |
第1个检索单元结果在集合[A,B]内 |
{$1=$2;$1=[a 怎么]} |
两个条件同时满足 |
范围限制
方括号内写语料范围,每组(起始ID, 结束ID)用;分隔,限定在特定语料子集中检索。
[(0,713)] → 只检索0~713范围
[(0,713);(714,928)] → 检索两个范围的并集
操作
操作决定输出形式。写在检索式末尾,也可通过Python参数Command指定(见E.4.5)。
| 操作 | 参数 | 说明 |
|---|---|---|
Context(窗口大小, 页码, 每页条数) |
均可省略,默认10,0,100 |
上下文浏览 |
Freq(最大条数) |
默认1000 |
频率统计 |
Freq(最大条数, 统计对象, 上下文数) |
完整参数 | 指定统计对象的频率 |
Count() 或 Count(参数) |
— | 计数统计 |
复合查询
两个独立检索式之间用AND或NOT连接(前后须有空格):
查询A AND 查询B → 在B的结果范围内检索A
查询A NOT 查询B → 在排除B的结果后检索A
每个部分都可以带各自的{条件}、[范围]、操作()。
示例:
from LangSC import BCC
bcc = BCC("Corpus")
# 在含"好"的结果范围内,检索句首形容词"美丽"
Ret = bcc.Run("~JJ美丽{$1=[A,B]} AND 好{$1=$2}")
# 排除含"的"的结果,检索名词"喜欢"后接动词
Ret = bcc.Run("n喜欢v{}Context() NOT 的{}")
A.4.3 BCC检索示例
from LangSC import BCC
bcc = BCC("Corpus")
# 1. 查找"形容词+的+名词"模式的高频搭配
Ret = bcc.Run("a的n", Command="Freq", Number=500)
print(Ret)
# 2. 查找"动词+得+形容词"的上下文实例
Ret = bcc.Run("v得a", Command="Context", Number=50)
print(Ret)
# 3. 查找把字句的频率分布
Ret = bcc.Run("把nv", Command="Freq")
print(Ret)
# 4. 查找句尾助词的使用情况
Ret = bcc.Run("u~", Command="Freq", Number=200)
print(Ret)
# 5. 查找句首名词后接动词的模式
Ret = bcc.Run("~nv", Command="Context", Number=100)
print(Ret)
# 6. 限定语料范围的检索
Ret = bcc.Run("n喜欢v{}[(0,713);(714,928)]Context(20,0,10)")
print(Ret)
# 7. 仅获取匹配计数
Ret = bcc.Run("a的n", Command="Count")
print(Ret)
# 8. 分页获取上下文(翻页)
Ret = bcc.Run("a的n", Command="Context", Number=100, PageNo=0) # 第1页
Ret = bcc.Run("a的n", Command="Context", Number=100, PageNo=1) # 第2页
# 9. 按特定目标统计频率
Ret = bcc.Run("a的n", Command="Freq", Target="$1", Number=500)
print(Ret)
# 10. 查看生成的Lua检索脚本
Ret = bcc.Run("a的n", Print="Lua")
print(Ret)
A.4.4 词表管理
可以预先定义词表,在查询条件中引用:
from LangSC import BCC
bcc = BCC("Corpus")
# 添加词表
bcc.AddBCCKV("感觉词", "灵怪 疏松 愚笨")
bcc.AddBCCKV("人称代词", "我 你 他 她 我们 你们 他们")
# 在查询条件中使用词表
Ret = bcc.Run("a的{$1=[感觉词]}", Command="Freq", Number=200000)
print(Ret)
# 查看已定义的词表
KVs = bcc.GetBCCKV()
print(KVs)
# 查看指定词表内容
KVs = bcc.GetBCCKV("感觉词")
print(KVs)
# 清除指定词表
bcc.ClearBCCKV("感觉词")
# 清除所有词表
bcc.ClearBCCKV()
A.4.5 BCC参数详解
Ret = bcc.Run(Query, **参数)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Command / Output |
str | "Freq" |
输出模式:"Freq"频率、"Context"上下文、"Count"计数 |
Number |
int | 100 |
输出结果数量 |
Target |
str | "$Q" |
频率统计的目标(Freq模式) |
WinSize |
int | 20 |
上下文窗口大小(Context模式) |
PageNo |
int | 0 |
分页页码(Context模式,从0开始) |
Print |
str | "" |
设为"Lua"时输出生成的Lua检索脚本 |
Service |
str | "" |
指定远程检索服务 |
当Python参数与检索式中的操作同时指定时,Python参数优先。例如:
# 检索式中写的是Context,但Python参数指定Freq,最终按Freq输出
Ret = bcc.Run("n喜欢v{}Context()", Command="Freq", Number=50)
A.4.6 管理命令
以下命令可直接作为CallBCC()的Query参数传入,不进行语料检索,直接执行管理功能:
| 命令 | 说明 |
|---|---|
AddTag(标签,值) 或 AddKV(键=值) |
添加标签/键值对 |
GetTags() 或 GetKeys() |
获取所有标签名 |
GetTagVal(键名) 或 GetValues(键名) |
获取指定标签的值 |
ClearTag() 或 ClearKV() |
清除所有标签 |
ClearTag(标签名) |
清除指定标签 |
SetMax(数值) |
设置最大检索数 |
SpeedUp(数值) |
设置加速参数 |
AddLimit(起始,结束) |
添加语料范围限制 |
ClearLimit() |
清除所有范围限制 |
这些命令也可以通过Python API的AddBCCKV()、GetBCCKV()、ClearBCCKV()方法实现类似功能。
A.5 网格操作(Grid)
网格是GPF的核心数据结构。通过网格操作,可以手动构建语言结构、查询结构属性、添加自定义标注。
A.5.1 设置文本
gpf = GPF()
# 设置网格的基础文本
gpf.SetText("孩子们都吃撑了")
# 获取当前文本
text = gpf.GetText()
print(text) # "孩子们都吃撑了"
# 获取文本的子串
sub = gpf.GetText(0, 2) # 从位置0到2
A.5.2 添加单元
gpf.SetText("孩子们都吃撑了")
# 方式1:自动定位(推荐)——GPF自动在文本中查找词语位置
Unit1 = gpf.AddUnit("孩子们")
Unit2 = gpf.AddUnit("都")
Unit3 = gpf.AddUnit("吃")
Unit4 = gpf.AddUnit("撑了")
# 方式2:指定结束位置(列号)
Unit1 = gpf.AddUnit("孩子们", 2) # "孩子们"结束于第2列
Unit2 = gpf.AddUnit("都", 3) # "都"结束于第3列
Unit3 = gpf.AddUnit("吃", 4) # "吃"结束于第4列
Unit4 = gpf.AddUnit("撑了", 6) # "撑了"结束于第6列
# AddUnit返回单元编号(字符串),如 "(2,1)"
print(Unit1) # 类似 "(2,1)"
A.5.3 添加单元属性
# 为单元添加键值对属性
gpf.AddUnitKV(Unit3, "POS", "v") # 词性=动词
gpf.AddUnitKV(Unit4, "POS", "v") # 词性=动词
gpf.AddUnitKV(Unit1, "Role", "Agent") # 语义角色=施事
A.5.4 添加关系
# 添加单元之间的关系:AddRelation(Head, Sub, Relation)
gpf.AddRelation(Unit3, Unit1, "sbj") # 吃 → 孩子们(主语)
gpf.AddRelation(Unit3, Unit2, "mod") # 吃 → 都(修饰)
gpf.AddRelation(Unit3, Unit4, "cmp") # 吃 → 撑了(补语)
# 为关系添加属性
gpf.AddRelationKV(Unit3, Unit1, "sbj", "Type", "施事")
A.5.5 查询网格
# 获取完整网格结构(嵌套列表,每列包含该列的所有单元编号)
Grid = gpf.GetGrid()
for Col in Grid:
for U in Col:
print(U, gpf.GetUnitKV(U, "Word"))
# 获取单元的词语文本
word = gpf.GetUnitKV(Unit1, "Word")
print(word) # "孩子们"
# 获取单元的全部属性(返回字典 {Key: [Values]})
KVs = gpf.GetUnitKV(Unit3)
for K, Vs in KVs.items():
print(K, "=", " ".join(Vs))
# 获取单元的指定属性
pos = gpf.GetUnitKV(Unit3, "POS") # 返回列表
word = gpf.GetUnitKV(Unit3, "Word") # 返回词语文本字符串
from_pos = gpf.GetUnitKV(Unit3, "From") # 返回起始位置(整数)
# 获取所有关系(返回列表 [[unitNo1, unitNo2, role], ...])
Relations = gpf.GetRelation()
for r in Relations:
head = gpf.GetUnitKV(r[0], "Word")
sub = gpf.GetUnitKV(r[1], "Word")
print(f"{head} → {sub} ({r[2]})")
# 获取关系的属性
RKV = gpf.GetRelationKV(Unit3, Unit1, "sbj")
for K, Vs in RKV.items():
print(K, "=", " ".join(Vs))
# 获取网格级属性
GridKV = gpf.GetGridKV()
for K, Vs in GridKV.items():
print(K, "=", " ".join(Vs))
特殊key的返回值类型:
- "Word" / "HeadWord":返回str
- "From" / "To":返回int
- 其他:返回list或dict
A.5.6 条件查询单元
# 按属性查询单元(返回单元编号列表)
# 查找所有词性为动词的单元
units = gpf.GetUnit("POS=v")
for u in units:
print(gpf.GetUnitKV(u, "Word"))
# 查找所有带指定标签的单元
units = gpf.GetUnit("Tag=Time")
# 使用通配符
units = gpf.GetUnit("Sem=*") # 有语义属性的所有单元
# 多条件查询(用|分隔表示或)
units = gpf.GetUnit("Type=Word|Type=Phrase")
# 指定 Unit 范围内查询
units = gpf.GetUnit("POS=n", UnitNo="(0,5)", UExpress="Sub")
# 检查单元是否满足条件(返回0/1)
if gpf.IsUnit(Unit3, "POS=v"):
print("是动词")
# 检查关系是否存在
if gpf.IsRelation(Unit3, Unit1, "sbj"):
print("存在主语关系")
A.5.7 从Parse结果构建网格
最常用的模式:先用Parse分析,再用AddStructure加载到网格。
gpf = GPF()
Line = "我们大家今天下午在操场集合"
# 1. 分析文本
Ret = gpf.Parse(Line, Structure="Tree")
# 2. 将分析结果加载到网格
gpf.AddStructure(Ret)
# 3. 可视化网格
gpf.ShowGrid("grid.png")
# 4. 同时显示关系
gpf.ShowGrid("grid_rel.png", IsShowRel=True)
AddStructure能自动识别多种JSON格式:
- GPF结构JSON(Parse返回的带Type字段的结构):直接加载
- 树形JSON(如{"S":[{"NP":"我们"},{"VP":"喜欢"}]}):转换为树结构加载
- 序列JSON(如["词1/pos1", "词2/pos2"]):转换为序列结构加载
- 图JSON(如[["头","尾","关系"],...]):转换为图结构加载
A.5.8 添加网格级属性
# 为整个网格添加属性
gpf.AddGridKV("URoot", Unit3) # 标记根节点
gpf.AddGridKV("Source", "Manual") # 标记来源
A.6 数据表操作(Table)
GPF支持通过数据表存储和检索结构化知识。数据表以Table文件格式存储,索引后可通过API访问。
A.6.1 数据表格式
数据表文件(.tab文件)采用文本格式:
Table 表名
数据项1
Key1=Value1
Key2=[Value2a Value2b]
Coll=[Role1 Role2]
Role1=[词1 词2]
数据项2
...
A.6.2 查询数据表
gpf = GPF("./data") # 数据路径下需有已索引的表文件
# 获取所有数据表名
tables = gpf.GetItem()
for t in tables:
print(t)
# 获取指定表中的所有数据项
items = gpf.GetItem("Event_Tab")
for item in items:
print(item)
# 获取表中满足条件的数据项
items = gpf.GetItem("Event_Tab", "Coll=Agent")
# 获取数据项的所有属性(返回字典)
kvs = gpf.GetItemKV("Event_Tab", "吃")
for k, vs in kvs.items():
val = " ".join(vs)
print(f"{k} : {val}")
# 获取数据项的指定属性(返回列表)
vs = gpf.GetItemKV("Event_Tab", "吃", "Coll")
for v in vs:
print(v)
A.6.3 设置与加载数据表
gpf = GPF("./data")
# 设置当前操作的 Table
gpf.SetTable("City")
# 加载并设置 Table
gpf.CallTable("City")
A.7 有限状态自动机(FSA)
FSA是GPF的规则匹配引擎,可以在网格上运行模式匹配规则。
A.7.1 FSA文件格式
FSA规则文件(.fsa文件)采用文本格式:
FSA 规则名(参数)
上下文模式
{操作代码}
示例(时间识别规则):
FSA Time(Nearby=Yes, MaxLen=Yes)
POS=m POS=q ?的 Entry:[POS=n]
{
UF = self.GetUnit(0)
UT = self.GetUnit(-1)
self.AddRelation(UF, UT, "数量")
}
FSA模式语法:
- KV表达式:POS=v、Word=吃
- ?:可有可无
- +:可重复
- []:搭配表
- ():保序
- ^:离合
- Entry:[KV]:匹配入口
A.7.2 运行FSA
gpf = GPF()
Text = "李明回头看了一看。"
# 1. 先分词,构建网格
Seg = gpf.Parse(Text, Structure="Segment")
gpf.SetText(Text)
gpf.AddStructure(Seg)
# 2. 加载相关数据表(如果FSA需要)
# gpf.CallTable("TableName")
# 3. 运行FSA
gpf.CallFSA("DupWord")
# 4. 查询FSA匹配结果
units = gpf.GetUnit("Tag=DupWord")
for u in units:
print(gpf.GetUnitKV(u, "Word"))
A.7.3 FSA内部方法
在FSA的操作代码块中,可以调用以下方法:
# 获取FSA路径上的节点路径号
PathNo = self.GetNode(-1) # 最后一个节点
PathNo = self.GetNode("$T") # 标签为$T的节点
# 获取FSA节点对应的网格单元
Unit = self.GetUnit(0) # 第一个匹配节点的单元
Unit = self.GetUnit(-1) # 最后一个匹配节点的单元
# 在操作代码中可以调用所有网格操作方法
self.AddRelation(U1, U2, "关系")
self.AddUnitKV(Unit, "Tag", "标签")
A.7.4 完整FSA工作流示例
以下示例展示了"离合词识别"的完整流程:
from LangSC import GPF
gpf = GPF()
Sent = "李明把守的大门被他破了"
# 步骤1:设置文本并分析
gpf.SetText(Sent)
DepStruct = gpf.Parse(gpf.GetText(), Structure="Dep")
gpf.AddStructure(DepStruct)
Seg = gpf.Parse(gpf.GetText(), Structure="Segment")
gpf.AddStructure(Seg)
# 步骤2:加载数据表
gpf.CallTable("Sep_V")
# 步骤3:运行FSA
gpf.CallFSA("SepV1")
# 步骤4:获取结果
units = gpf.GetUnit("Tag=SepWord")
for u in units:
print(gpf.GetUnitKV(u, "Word"))
A.8 JSS结构化检索
JSS(JSON Structured Search)是LangSC的JSON数据搜索引擎,支持将JSON/JSONL数据构建为索引表,通过SQL语法进行高效检索。本课程中,JSS用于实验二任务四(知识库的结构化检索)。
A.8.1 核心特性
- 多种索引类型:KV精确匹配、BM25全文检索、Affix模糊匹配、Number/Float数值索引、Date日期索引、语义向量索引
- SQL查询接口:使用标准SQL SELECT语法进行数据检索
- 中文支持:内置中文分词引擎,支持CJK文本的全文检索
- 跨平台:支持Windows、Linux、macOS
- 高性能:基于C++核心引擎,使用倒排索引和双数组Trie树实现快速检索
A.8.2 快速入门
from LangSC import JSS
# 1. 创建JSS实例(指定数据目录,自动索引并加载)
jss = JSS("./json_data")
# 2. 使用SQL查询
results = jss.Run('SELECT TOP 10 id, title FROM mytable WHERE title LIKE "搜索关键词"')
print(results)
JSS会自动处理索引:检查数据目录中是否有已构建的索引,如果没有则扫描.json/.jsonl文件自动建索引。配置文件命名为cfg_<数据文件名>.txt,缺失时会根据首条记录自动生成。
A.8.3 API参考
构造函数
JSS(dataPath, log_level=0, log_filename='')
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dataPath |
str | (必填) | 数据目录路径,包含JSON/JSONL数据文件和可选的cfg配置文件 |
log_level |
int | 0 | 日志级别,0为关闭,1-5为不同级别 |
log_filename |
str | '' |
日志文件路径,空字符串表示不输出到文件 |
构造时自动执行索引检测和加载:
1. 检查dataPath中是否存在IdxLog_JSS.txt索引日志
2. 若存在:已是索引目录,直接加载
3. 若不存在:扫描目录中的.json/.jsonl文件,自动建索引到dataPathIdx/目录
4. 配置文件命名为cfg_<name>.txt,缺失时根据首条记录自动生成
Run — SQL检索
jss.Run(sql_statement, table="")
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sql_statement |
str | (必填) | SQL查询语句 |
table |
str | "" |
指定查询的表名,为空时使用第一个已加载的表 |
返回值:list,返回查询结果列表,每个元素为一条记录的字典。查询失败或无结果返回空列表[]。
注意:SQL语句中的字段名称和表名称严格区分大小写。
Terminate — 释放资源
jss.Terminate()
释放已加载的索引表,释放内存。JSS对象析构时会自动调用。
A.8.4 配置文件(cfg_*.txt)
配置文件定义了索引表的结构,包括表名、数据格式、字段映射和索引方式。命名规则为cfg_<数据文件名>.txt,例如数据文件poets.jsonl对应cfg_poets.txt。在本课程的语境中,配置文件相当于面向检索的"本体"——它定义了数据的结构和查询方式。如果配置文件缺失,JSS会根据首条记录自动推断并生成。
完整结构
{
"table_name": "表名称",
"record_format": "jsonl",
"content": {
"输出字段名": "源JSON字段路径"
},
"combin": {
"组合字段名": ["字段1", "字段2"]
},
"index": {
"kv": ["字段列表"],
"number": ["字段列表"],
"float": ["字段列表"],
"date": ["字段列表"],
"bm25": ["字段列表"],
"affix": ["字段列表"],
"sem": ["字段列表"]
}
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
table_name |
string | 是 | 表名称,SQL查询中FROM子句使用此名称 |
record_format |
string | 是 | 数据文件格式:"jsonl"或"json" |
content |
object | 是 | 字段映射表,key为输出字段名,value为源JSON数据路径 |
combin |
object | 否 | 组合字段,将多个字段合并为一个虚拟字段用于索引 |
index |
object | 是 | 索引配置,定义各字段使用的索引类型 |
字段映射(content)
支持嵌套JSON路径(用.分隔,最多5层)和数组字段(用[]标记):
{
"content": {
"id": "id",
"name": "metadata.name",
"tags": "data[].tags"
}
}
组合字段(combin)
将多个字段合并为一个虚拟字段,用于跨字段检索:
{
"combin": {
"all_text": ["title", "abstract", "fulltext"]
}
}
查询all_text时,会同时搜索title、abstract和fulltext三个字段。
A.8.5 索引类型详解
| 类型 | 关键字 | 说明 | 适用操作 |
|---|---|---|---|
| KV | kv |
精确匹配 | =, !=, IN, BETWEEN |
| BM25 | bm25 |
全文检索(自动分词,TF-IDF评分) | LIKE '关键词' |
| Affix | affix |
子串/通配符匹配 | LIKE '%关键词%' |
| Number | number |
64位整数索引 | =, !=, >, >=, <, <=, BETWEEN, IN |
| Float | float |
64位浮点索引 | 同Number |
| Date | date |
日期时间索引 | =, BETWEEN |
| Semantic | sem |
语义向量检索 | LIKE '@查询' |
A.8.6 SQL语法参考
基本查询
-- 查询指定字段
SELECT id, title, abstract FROM mytable WHERE title LIKE '关键词'
-- 查询所有字段
SELECT * FROM mytable WHERE id = 'doc001'
-- 限制返回条数
SELECT TOP 10 id, title FROM mytable WHERE title LIKE '搜索词'
-- 使用LIMIT限制
SELECT id, title FROM mytable WHERE title LIKE '搜索词' LIMIT 20
条件运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
= |
等于 | WHERE id = 'abc' |
!= / <> |
不等于 | WHERE status != 'deleted' |
> |
大于 | WHERE weight > 100 |
>= |
大于等于 | WHERE weight >= 100 |
< |
小于 | WHERE weight < 50 |
<= |
小于等于 | WHERE weight <= 50 |
BETWEEN |
范围 | WHERE weight BETWEEN 10 AND 100 |
IN |
集合 | WHERE id IN ('a', 'b', 'c') |
LIKE |
模糊/全文 | WHERE title LIKE '关键词' |
逻辑运算符
-- AND: 同时满足多个条件
SELECT * FROM mytable WHERE category = '技术' AND weight > 50
-- OR: 满足任一条件
SELECT * FROM mytable WHERE title LIKE '人工智能' OR title LIKE '机器学习'
-- NOT: 排除条件
SELECT * FROM mytable WHERE NOT category = '广告'
LIKE运算符的自动索引选择
| 查询格式 | 使用的索引 | 说明 |
|---|---|---|
LIKE '关键词' |
BM25 | 无通配符,使用全文检索 |
LIKE '%关键词%' |
Affix | 含%通配符,使用模糊匹配 |
LIKE '@语义查询' |
Semantic | @前缀,使用语义向量检索 |
特殊函数
-- 统计记录总数
SELECT COUNT(*) FROM mytable
-- 随机返回N条记录
SELECT RAND(10) FROM mytable
-- 从查询结果中随机取N条
SELECT RAND(5) FROM mytable WHERE category = '技术'
排序与JOIN
-- 排序
SELECT * FROM mytable WHERE title LIKE '关键词' ORDER BY weight DESC
-- JOIN查询
SELECT * FROM table1 JOIN table2 ON table1.id = table2.ref_id WHERE table1.title LIKE '关键词'
A.8.7 JSS完整示例
文档搜索系统
数据文件(data/docs.jsonl)
{"id": "doc001", "title": "Python编程入门指南", "abstract": "本文介绍Python的基础语法和常用库", "category": "技术", "weight": 85}
{"id": "doc002", "title": "机器学习算法详解", "abstract": "详细讲解常用的机器学习算法原理", "category": "技术", "weight": 92}
{"id": "doc003", "title": "数据库优化实战", "abstract": "MySQL和PostgreSQL的性能优化技巧", "category": "技术", "weight": 78}
{"id": "doc004", "title": "人工智能发展历史", "abstract": "从图灵测试到深度学习的AI发展脉络", "category": "科学", "weight": 88}
{"id": "doc005", "title": "Web前端开发框架对比", "abstract": "React、Vue、Angular三大框架的优劣分析", "category": "技术", "weight": 80}
配置文件(data/cfg_docs.txt)
{
"table_name": "docs",
"record_format": "jsonl",
"content": {
"id": "id",
"title": "title",
"abstract": "abstract",
"category": "category",
"weight": "weight"
},
"index": {
"kv": ["id", "category"],
"number": ["weight"],
"bm25": ["title", "abstract"],
"affix": ["title"]
}
}
查询代码
from LangSC import JSS
# 自动索引并加载(数据目录下有docs.jsonl和cfg_docs.txt)
jss = JSS("./data", log_level=1, log_filename='jss.log')
# 全文搜索
results = jss.Run('SELECT id, title, weight FROM docs WHERE title LIKE "机器学习"')
for r in results:
print(f" {r['id']}: {r['title']} (权重: {r['weight']})")
# 精确匹配
results = jss.Run('SELECT id, title FROM docs WHERE category = "科学"')
# 数值范围
results = jss.Run('SELECT id, title, weight FROM docs WHERE weight >= 85')
# 组合条件
results = jss.Run('SELECT id, title FROM docs WHERE category = "技术" AND weight > 80')
# 模糊匹配
results = jss.Run('SELECT id, title FROM docs WHERE title LIKE "%Python%"')
# 统计总数
results = jss.Run('SELECT COUNT(*) FROM docs')
# 随机抽取
results = jss.Run('SELECT RAND(2) FROM docs')
嵌套JSON数据处理
数据文件(data/products.jsonl)
{"product": {"id": "P001", "info": {"name": "笔记本电脑", "price": 5999.0, "brand": "联想"}}, "meta": {"tags": ["电子产品", "办公"], "created": "2025-03-15"}}
{"product": {"id": "P002", "info": {"name": "无线鼠标", "price": 129.9, "brand": "罗技"}}, "meta": {"tags": ["外设", "办公"], "created": "2025-04-01"}}
配置文件——通过路径映射提取嵌套字段:
{
"table_name": "products",
"record_format": "jsonl",
"content": {
"id": "product.id",
"name": "product.info.name",
"price": "product.info.price",
"brand": "product.info.brand",
"created": "meta.created"
},
"index": {
"kv": ["id", "brand"],
"float": ["price"],
"date": ["created"],
"bm25": ["name"],
"affix": ["name"]
}
}
查询示例
from LangSC import JSS
# 数据目录下有products.jsonl和cfg_products.txt
jss = JSS("./data")
# 按品牌查找
results = jss.Run('SELECT id, name, price FROM products WHERE brand = "罗技"')
# 按价格范围查找
results = jss.Run('SELECT id, name, price FROM products WHERE price BETWEEN 100.0 AND 1000.0')
# 按日期范围查找
results = jss.Run('SELECT id, name FROM products WHERE created BETWEEN "2025-03-01" AND "2025-12-31"')
多表JOIN查询
当数据目录下有多个数据文件时,JSS会自动为每个文件创建索引表,支持跨表查询:
from LangSC import JSS
# 数据目录下有articles.jsonl和authors.jsonl
jss = JSS("./data")
# 跨表JOIN查询
results = jss.Run('''
SELECT articles.title, authors.name
FROM articles
JOIN authors ON articles.author_id = authors.id
WHERE articles.title LIKE "人工智能"
''')
A.8.8 JSS注意事项
- 大小写敏感:SQL语句中的表名和字段名严格区分大小写,必须与cfg配置文件中定义的一致
- 编码要求:数据文件和配置文件应使用UTF-8编码
- 内存管理:
Run内部使用10MB缓冲区,单次查询结果不应超过此限制 - 自动索引:构造函数自动完成索引构建,索引后的表为只读;通过时间戳比较避免重复索引
- 同一字段多索引:同一个字段可以同时建立多种索引(如同时建kv和bm25),查询时会根据运算符自动选择
- 分词词典:全文检索依赖分词词典(base.lex),包内已内置
- cfg自动生成:若cfg配置文件缺失,JSS会根据首条记录自动推断字段类型并生成配置
A.9 完整示例
A.9.1 分词并可视化
from LangSC import GPF
import json
gpf = GPF()
text = "今天我们上语言结构计算课程"
# 分析为句法树
Ret = gpf.Parse(text, Structure="Tree")
print("分析结果:", Ret)
# 直接可视化分析结果
gpf.ShowStructure(Ret, "tree_result.png")
A.9.2 手动构建结构并可视化
from LangSC import GPF
gpf = GPF()
# 方式1:通过JSON直接可视化
Json = '{"S":[{"NP":[{"r":"我们"},{"n":"大家"}]},{"VP":{"v":"喜欢"}}]}'
gpf.ShowStructure(Json, "manual_tree.png")
# 方式2:通过网格操作构建
gpf.SetText("我们大家喜欢")
u1 = gpf.AddUnit("我们")
u2 = gpf.AddUnit("大家")
u3 = gpf.AddUnit("喜欢")
gpf.AddUnitKV(u1, "POS", "r")
gpf.AddUnitKV(u2, "POS", "n")
gpf.AddUnitKV(u3, "POS", "v")
gpf.AddRelation(u3, u1, "SBV")
gpf.AddRelation(u3, u2, "SBV")
gpf.ShowGrid("manual_grid.png")
gpf.ShowGrid("manual_rel.png", IsShowRel=True)
A.9.3 语料库搭配检索
from LangSC import BCC
bcc = BCC("Corpus")
# 检索"形容词+的+名词"搭配
Ret = bcc.Run("a的n", Command="Freq", Number=500)
print("高频搭配:")
print(Ret)
# 定义词表进行限定检索
bcc.AddBCCKV("颜色词", "红 黄 蓝 绿 白 黑")
Ret = bcc.Run("a的花{$1=[颜色词]}", Command="Freq")
print("颜色+花:")
print(Ret)
A.9.4 Parse + Grid + Show 完整流程
from LangSC import GPF
gpf = GPF()
Line = "我们大家今天下午在邵逸夫操场集合"
# 第1步:分析
Ret = gpf.Parse(Line, Structure="Tree")
# 第2步:加载到网格
gpf.AddStructure(Ret)
# 第3步:查看网格结构
Grid = gpf.GetGrid()
for Col in Grid:
for U in Col:
word = gpf.GetUnitKV(U, "Word")
kvs = gpf.GetUnitKV(U)
print(f"单元 {U}: {word}")
for K, Vs in kvs.items():
print(f" {K} = {' '.join(Vs)}")
# 第4步:查看关系
Relations = gpf.GetRelation()
for r in Relations:
head = gpf.GetUnitKV(r[0], "Word")
sub = gpf.GetUnitKV(r[1], "Word")
print(f"关系:{head} → {sub} ({r[2]})")
# 第5步:可视化
gpf.ShowGrid("full_grid.png")
gpf.ShowGrid("full_rel.png", IsShowRel=True)
A.9.5 BCC检索语言综合示例
from LangSC import BCC
bcc = BCC("Corpus")
# 1. 最简查询——纯词形
Ret = bcc.Run("喜欢", Command="Freq", Number=100)
# 2. 词性+词形组合
Ret = bcc.Run("n喜欢v", Command="Context", Number=50)
# 3. 句尾助词统计(修饰符~表示句尾)
Ret = bcc.Run("u~", Command="Freq", Number=200)
# 4. 同句任意距离检索(连接符*)
Ret = bcc.Run("n*v", Command="Context", Number=100)
# 5. 带条件的检索
Ret = bcc.Run("nv{$1=$2}", Command="Freq")
# 6. 带范围限制的检索
Ret = bcc.Run("n喜欢v{}[(0,713);(714,928)]", Command="Context", Number=50)
# 7. NOT排除查询
Ret = bcc.Run("n喜欢v{}Context() NOT 的{}")
# 8. AND交集查询
Ret = bcc.Run("a美丽{} AND 好{$1=$2}")
# 9. 特征值检索
Ret = bcc.Run("q[*的]", Command="Freq", Number=100)
# 10. 管理命令——设置词表后检索
bcc.AddBCCKV("情感词", "喜欢 爱 讨厌 恨")
Ret = bcc.Run("v的n{$1=[情感词]}", Command="Freq")
bcc.ClearBCCKV("情感词")
A.9.6 GPF + BCC + JSS 综合应用
from LangSC import GPF, BCC, JSS
import json
# ---- 初始化 ----
gpf = GPF()
bcc = BCC("Corpus")
jss = JSS("./json")
# ---- 第一步:用 GPF 分析文本 ----
text = "深度学习在自然语言处理中的应用"
pos_result = gpf.Parse(text, Structure="POS")
print("词性标注:", pos_result)
# ---- 第二步:用 BCC 检索相关语料 ----
# freq = bcc.Run("深度学习", Command="Freq", Number=50)
# print("相关词频:", freq)
# ---- 第三步:用 JSS 检索相关文档 ----
# docs = jss.Run('SELECT title, abstract FROM papers WHERE title LIKE "深度学习"')
# for doc in docs:
# print(f" {doc['title']}")
# ---- 第四步:将分析结果可视化 ----
gpf.SetText(text)
gpf.AddStructure(pos_result)
gpf.ShowGrid("./result.png")
A.10 常见问题
Q1: from LangSC import GPF报错?
确保LangSC已正确安装,且动态库文件在LangSC包目录下:gpflib.dll(Windows)、libgpflib.so(Linux)或libgpflib.dylib(macOS)。
Q2: Parse返回的结果怎么处理?
Parse返回JSON字符串,需要用json.loads()转换为Python对象:
import json
Ret = gpf.Parse("大家好", Structure="Segment")
words = json.loads(Ret) # 转为Python列表
Q3: BCC检索没有结果?
- 确认BCC实例已正确初始化:
bcc = BCC("Corpus") - 确认语料库已正确索引
- 尝试更简单的查询测试:
bcc.Run("的")
Q4: 可视化图片没有生成?
- 检查系统是否安装了Graphviz(
dot命令) - 检查输出路径是否有写权限
- 词云可视化需要安装
wordcloud库和中文字体
Q5: AddUnit的位置参数怎么理解?
位置参数colNo是单元结束位置的列号(从0开始)。如果省略,GPF会自动在文本中查找词语位置。建议初学者使用自动定位:
gpf.SetText("孩子们都吃撑了")
Unit = gpf.AddUnit("孩子们") # 自动定位,最简单
Q6: 网格中的单元编号是什么格式?
单元编号是字符串,格式为"(列号,行号)",如"(2,1)"。列号对应文本位置,行号对应标注层次。在操作时直接使用AddUnit返回的字符串即可。
Q7: BCC检索式中的各部分怎么区分?
BCC检索式的完整格式是查询体{条件}[范围]操作(参数)。花括号{}包裹条件,方括号[]包裹范围限制,操作名后跟圆括号。例如:
n喜欢v{$1=$2}[(0,713)]Context(20,0,10)
│ │ │ │
查询体 条件 范围 操作
Q8: JSS的SQL查询没有结果?
- 确认表名和字段名的大小写与cfg配置文件一致
- 确认数据文件为UTF-8编码
- 尝试开启日志排查:
jss = JSS("./data", log_level=1, log_filename='jss.log')
A.11 API速查表
GPF — 分析
| 方法 | 说明 |
|---|---|
Parse(text, Structure="Segment") |
分词 |
Parse(text, Structure="POS") |
词性标注(默认) |
Parse(text, Structure="Tree") |
句法树 |
Parse(text, Structure="Dep") |
依存分析 |
Parse(text, Structure="Chunk") |
组块分析 |
GPF — 可视化
| 方法 | 说明 |
|---|---|
ShowStructure(Json, Img) |
显示JSON结构(自动识别类型) |
ShowGrid(Img) |
显示网格单元 |
ShowGrid(Img, IsShowRel=True) |
显示网格单元及关系 |
GPF — 网格操作
| 方法 | 说明 |
|---|---|
SetText(text) |
设置基础文本 |
GetText() |
获取基础文本 |
GetText(begin, end) |
获取文本子串 |
AddUnit(text, colNo=-1) |
添加单元,返回单元编号 |
AddUnitKV(Unit, Key, Val) |
添加单元属性 |
GetUnit(kv) |
按条件查找单元 |
GetUnit(kv, UnitNo, UExpress) |
在指定范围内查找单元 |
GetUnitKV(Unit) |
获取单元全部属性 |
GetUnitKV(Unit, Key) |
获取单元指定属性 |
IsUnit(Unit, kv) |
检查单元是否满足条件 |
AddRelation(Head, Sub, Rel) |
添加关系 |
AddRelationKV(U1, U2, R, Key, Val) |
添加关系属性 |
GetRelation(kv) |
获取关系(可按条件过滤) |
GetRelationKV(U1, U2, R, Key) |
获取关系属性 |
IsRelation(U1, U2, R) |
检查关系是否存在 |
GetGrid() |
获取网格结构 |
AddStructure(Json) |
将JSON结构加载到网格 |
AddGridKV(Key, Val) |
添加网格级属性 |
GetGridKV(Key) |
获取网格级属性 |
GPF — 数据表
| 方法 | 说明 |
|---|---|
SetTable(TableName) |
设置当前操作的Table |
CallTable(TableName) |
加载并设置Table |
GetItem(TableName, kv) |
获取表项(可按条件过滤) |
GetItemKV(TableName, Item, Key) |
获取表项属性 |
GPF — FSA
| 方法 | 说明 |
|---|---|
CallFSA(fsaName) |
运行FSA规则 |
GetNode(tag) |
获取FSA节点路径号 |
BCC — 语料库检索
| 方法 | 说明 |
|---|---|
BCC(dataPath) |
构造函数(自动索引+加载) |
Run(Query) |
频率检索(默认) |
Run(Query, Command="Context") |
上下文检索 |
Run(Query, Command="Count") |
计数检索 |
Run(Query, Command="Freq", Number=N) |
限定数量的频率检索 |
Run(Query, Target="$1") |
指定统计目标 |
Run(Query, Print="Lua") |
输出Lua检索脚本 |
CallBCC(query) |
执行原始BCC查询 |
AddBCCKV(Key, Val) |
添加检索词表 |
GetBCCKV(Key) |
查看词表 |
ClearBCCKV(Key) |
清除指定词表 |
ClearBCCKV() |
清除所有词表 |
IndexBCC(filelistname) |
构建语料索引 |
JSS — JSON结构化搜索
| 方法 | 说明 |
|---|---|
JSS(dataPath, log_level, log_filename) |
构造函数(自动索引+加载) |
Run(sql) |
SQL检索 |
Terminate() |
释放资源 |
探索与思考
- 观察工具的边界:用LangSC分析5个你自己写的句子(尽量包含长句、口语化表达、网络用语等),观察分词和词性标注的结果。哪些地方分析得准确?哪些地方出现了错误?你能推测错误的原因吗?
import json
from LangSC import GPF
gpf = GPF()
sentences = ["你的句子1", "你的句子2", "你的句子3", "你的句子4", "你的句子5"]
for sent in sentences:
Ret = gpf.Parse(sent, Structure="POS")
tags = json.loads(Ret)
print(f"原句:{sent}")
print(f"标注:{tags}\n")
-
工具与人的分工:LangSC能自动完成分词、标注、检索等任务,但它不能"理解"语言。在你使用LangSC的过程中,哪些环节必须由人来完成?人和工具各自擅长什么?
-
设计你的分析流程:如果要研究"把"字句的结构特点,你会如何组合使用GPF的Parse、BCC检索和网格操作来完成?写出你的分析思路(不需要写完整代码)。
-
BCC检索语言实践:尝试用BCC检索语言完成以下任务,观察不同检索式的区别:
- 检索所有"形容词+的+名词"模式:
a的n - 检索句尾助词的使用分布:
u~ - 检索同一句中名词与动词的搭配(不要求紧邻):
n*v - 用NOT排除含"的"的结果后检索动宾结构:
vn{}Context() NOT 的{}
附录A 完
附录B 延伸阅读
本附录推荐与本书内容相关的进一步阅读材料,供有兴趣深入学习的读者参考。
B.1 语言学基础
B.1.1 语言学概论
《语言学纲要》 - 作者:叶蜚声、徐通锵 - 出版社:北京大学出版社 - 推荐理由:国内语言学入门经典教材,系统介绍语言学基本概念
《普通语言学教程》(Cours de linguistique générale) - 作者:索绪尔(Ferdinand de Saussure) - 推荐理由:现代语言学奠基之作,组合/聚合概念的理论来源
《语言论》(Language) - 作者:布龙菲尔德(Leonard Bloomfield) - 推荐理由:美国结构主义语言学经典,直接成分分析方法的来源
《An Introduction to Language》 - 作者:Victoria Fromkin等 - 推荐理由:英文语言学入门经典,内容全面,例证丰富
B.1.2 汉语语法
《现代汉语语法研究教程》 - 作者:陆俭明 - 出版社:北京大学出版社 - 推荐理由:汉语语法研究方法论,结构分析的经典范例
《汉语语法分析问题》 - 作者:吕叔湘 - 推荐理由:汉语语法研究的经典论述,问题意识强
《语法讲义》 - 作者:朱德熙 - 出版社:商务印书馆 - 推荐理由:现代汉语语法体系的系统阐述
B.1.3 语义与语用
《语义学》 - 作者:束定芳 - 出版社:上海外语教育出版社 - 推荐理由:语义学入门教材,概念清晰
《语用学教程》 - 作者:索振羽 - 出版社:北京大学出版社 - 推荐理由:语用学基础知识系统介绍
B.2 计算语言学与NLP
B.2.1 自然语言处理
《统计自然语言处理》 - 作者:宗成庆 - 出版社:清华大学出版社 - 推荐理由:中文NLP经典教材,理论与实践结合
《Speech and Language Processing》 - 作者:Daniel Jurafsky, James H. Martin - 推荐理由:NLP领域权威教材,内容全面深入 - 在线版:https://web.stanford.edu/~jurafsky/slp3/
《自然语言处理入门》 - 作者:何晗 - 出版社:人民邮电出版社 - 推荐理由:面向实践的NLP入门书,有丰富代码示例
B.2.2 语料库语言学
《语料库语言学》 - 作者:梁茂成、李文中、许家金 - 出版社:北京大学出版社 - 推荐理由:语料库语言学方法论的系统介绍
《Corpus Linguistics: An Introduction》 - 作者:Tony McEnery, Andrew Hardie - 推荐理由:语料库语言学英文入门教材
B.2.3 知识图谱
《知识图谱:概念与技术》 - 作者:肖仰华等 - 出版社:电子工业出版社 - 推荐理由:知识图谱技术的系统介绍
《知识图谱:方法、实践与应用》 - 作者:王昊奋、漆桂林、陈华钧 - 出版社:电子工业出版社 - 推荐理由:知识图谱实践指南
B.3 程序语言与编译原理
B.3.1 程序语言理论
《程序设计语言——实践之路》 - 作者:Michael L. Scott - 推荐理由:程序语言设计原理的经典教材
《Types and Programming Languages》 - 作者:Benjamin C. Pierce - 推荐理由:类型系统与程序语言理论的权威著作
B.3.2 编译原理
《编译原理》(龙书) - 作者:Alfred V. Aho等 - 推荐理由:编译原理经典教材,词法分析、语法分析的权威参考
《Crafting Interpreters》 - 作者:Robert Nystrom - 在线版:https://craftinginterpreters.com/ - 推荐理由:手把手教你实现解释器,理论联系实际
B.3.3 Python编程
《Python编程:从入门到实践》 - 作者:Eric Matthes - 推荐理由:Python入门经典,适合初学者
《流畅的Python》 - 作者:Luciano Ramalho - 推荐理由:Python进阶必读,深入理解语言特性
B.4 大模型与人工智能
B.4.1 大模型基础
《大规模语言模型:从理论到实践》 - 作者:张奇、桂韬、郑锐、黄萱菁 - 出版社:电子工业出版社 - 推荐理由:大模型技术的系统介绍,中文原创
《Attention Is All You Need》 - 作者:Vaswani等(2017) - 推荐理由:Transformer架构的原始论文,大模型的技术基础
《Language Models are Few-Shot Learners》 - 作者:Brown等(2020) - 推荐理由:GPT-3论文,大模型能力的里程碑展示
B.4.2 提示语工程
《Prompt Engineering Guide》 - 网址:https://www.promptingguide.ai/ - 推荐理由:提示语工程实践指南,持续更新
《Building LLM Powered Applications》 - 作者:Valentina Alto - 出版社:Packt Publishing, 2023 - 推荐理由:大模型应用开发实践,覆盖RAG、Agent等核心模式
B.4.3 人工智能哲学
《人工智能的未来》 - 作者:雷·库兹韦尔(Ray Kurzweil) - 推荐理由:对AI未来的深入思考
《Life 3.0》 - 作者:Max Tegmark - 推荐理由:人工智能时代的人类命运思考
B.5 跨学科读物
B.5.1 认知科学
《语言本能》 - 作者:史蒂芬·平克(Steven Pinker) - 推荐理由:语言与认知关系的科普经典
《思想本质》 - 作者:史蒂芬·平克(Steven Pinker) - 推荐理由:语言、思维与人性的深入探讨
《心智探奇》 - 作者:史蒂芬·平克(Steven Pinker) - 推荐理由:认知科学的科普佳作
B.5.2 符号与意义
《符号学基础》 - 作者:约翰·迪利(John Deely) - 推荐理由:符号学入门
《结构主义》 - 作者:皮亚杰(Jean Piaget) - 推荐理由:结构主义思想的经典阐述
B.5.3 信息与计算
《信息简史》 - 作者:詹姆斯·格雷克(James Gleick) - 推荐理由:信息概念的历史与演变
《哥德尔、艾舍尔、巴赫:集异璧之大成》 - 作者:侯世达(Douglas Hofstadter) - 推荐理由:探讨心智、数学、音乐、艺术中的递归与自指
B.6 在线资源
B.6.1 课程资源
| 课程 | 平台 | 说明 |
|---|---|---|
| CS224N: NLP with Deep Learning | Stanford/YouTube | 斯坦福NLP经典课程 |
| Natural Language Processing | Coursera | NLP入门课程 |
| 自然语言处理 | 中国大学MOOC | 中文NLP课程 |
B.6.2 工具文档
| 工具 | 网址 | 说明 |
|---|---|---|
| spaCy | https://spacy.io/ | 工业级NLP库 |
| NLTK | https://www.nltk.org/ | 经典NLP教学库 |
| Hugging Face | https://huggingface.co/ | 大模型资源平台 |
| LangChain | https://langchain.com/ | 大模型应用框架 |
B.6.3 语料库资源
| 资源 | 网址 | 说明 |
|---|---|---|
| BCC语料库 | http://bcc.blcu.edu.cn/ | 在线中文语料库 |
| COCA | https://www.english-corpora.org/coca/ | 在线英文语料库 |
| Wikipedia Dumps | https://dumps.wikimedia.org/ | 维基百科数据 |
| Common Crawl | https://commoncrawl.org/ | 网页爬取数据 |
B.6.4 学术资源
| 资源 | 网址 | 说明 |
|---|---|---|
| ACL Anthology | https://aclanthology.org/ | 计算语言学论文集 |
| arXiv cs.CL | https://arxiv.org/list/cs.CL/recent | NLP预印本 |
| Google Scholar | https://scholar.google.com/ | 学术搜索 |
| Semantic Scholar | https://www.semanticscholar.org/ | AI学术搜索 |
B.7 阅读建议
B.7.1 按背景选择
语言学背景读者: 1. 先读D.3节,了解程序语言和编译原理基础 2. 再读D.2节,了解计算语言学方法 3. 最后读D.4节,了解大模型技术
计算机背景读者: 1. 先读D.1节,建立语言学基础知识 2. 再读D.2节,深入理解NLP技术 3. 最后读D.5节,拓展跨学科视野
一般读者: 1. 从D.5节的科普读物开始 2. 根据兴趣选择D.1-D.4节的具体方向 3. 利用D.6节的在线资源进行实践
B.7.2 按主题深入
结构分析:D.1.2 + D.3.2 语料库方法:D.2.2 + D.6.3 知识表示:D.2.3 + D.3.1 大模型应用:D.4 + D.6.2
B.7.3 实践建议
- 边读边做:阅读理论时配合实践练习
- 建立关联:不同领域的知识相互印证
- 保持更新:大模型领域发展迅速,关注最新进展
- 批判阅读:不同观点对比,形成自己的理解
探索与思考
-
写一份微型书评:从本附录推荐的资源中选一本(或一篇论文),浏览其核心内容,用100字以内写一份结构化推荐——包括"主题"、"核心观点"、"适合谁读"三个要素。
-
连接不同的声音:本附录涵盖了语言学、计算机科学、认知科学等多个视角。选择两个来自不同领域的推荐资源,思考它们之间可能存在怎样的对话关系——它们会在哪些问题上达成共识,又会在哪些问题上分歧?
-
追踪前沿:大模型领域发展极快,本附录的推荐可能很快就不是最新的。尝试搜索一篇2024年以后发表的、与本课程主题相关的论文或文章,简述它的贡献,并说明它与本书哪个章节的内容最相关。
附录B 完
附录C 术语对照表
本附录提供本书涉及的语言学术语与计算机术语的对照,帮助不同背景的读者建立概念关联。
C.1 核心概念对照
C.1.1 结构基本术语
表C-1 结构基本术语对照
| 语言学术语 | 计算机术语 | 英文 | 说明 |
|---|---|---|---|
| 结构 | 数据结构 | Structure | 元素及其关系的组织方式 |
| 单元/成分 | 元素/节点 | Unit/Element | 结构的基本组成部分 |
| 关系 | 边/链接 | Relation | 单元之间的联系 |
| 属性/特征 | 属性/字段 | Attribute | 单元或关系的特征 |
| 结构化思维 | 计算思维 | Structural Thinking | 用结构的视角分析和组织信息 |
| 结构化表达 | 格式化输出 | Structured Expression | 用清晰的结构组织语言输出 |
C.1.2 组合与聚合
表C-2 组合与聚合术语对照
| 语言学术语 | 计算机术语 | 英文 | 说明 |
|---|---|---|---|
| 组合关系 | 顺序/链表 | Syntagmatic | 线性排列关系 |
| 聚合关系 | 类/类型 | Paradigmatic | 可替换的类别关系 |
| 层次结构 | 树结构 | Hierarchy | 分级嵌套结构 |
| 递归 | 递归 | Recursion | 结构的自我嵌套 |
C.2 层次单位对照
C.2.1 从小到大的单位
表C-3 语言层次单位对照
| 层次 | 自然语言 | 程序语言 | 说明 |
|---|---|---|---|
| 最小单位 | 语素(Morpheme) | 字符(Character) | 不可再分的基本单位 |
| 词汇单位 | 词(Word) | 标记/词法单元(Token) | 基本的意义/功能单位 |
| 短语单位 | 短语(Phrase) | 表达式(Expression) | 词的组合 |
| 句子单位 | 句子(Sentence) | 语句(Statement) | 完整的表达单位 |
| 篇章单位 | 篇章(Discourse) | 程序/模块(Program) | 多句的组合 |
C.2.2 结构分析层次
表C-4 结构分析层次对照
| 分析层次 | 自然语言处理 | 程序语言处理 | 说明 |
|---|---|---|---|
| 词法层 | 分词(Segmentation) | 词法分析(Lexical Analysis) | 识别基本单位 |
| 句法层 | 句法分析(Parsing) | 语法分析(Syntax Analysis) | 分析组合结构 |
| 语义层 | 语义分析(Semantic) | 语义分析(Semantic) | 理解意义/效果 |
C.3 语法术语对照
C.3.1 词性/词类
表C-5 词性词类术语对照
| 语言学术语 | 缩写 | 程序语言类比 | 说明 |
|---|---|---|---|
| 名词 | n | 变量名/对象 | 表示事物 |
| 动词 | v | 函数/方法 | 表示动作 |
| 形容词 | a | 属性/状态 | 表示性质 |
| 副词 | d | 修饰符 | 修饰动词/形容词 |
| 代词 | r | 引用/指针 | 指代其他成分 |
| 介词 | p | 运算符 | 表示关系 |
| 连词 | c | 逻辑运算符 | 连接成分 |
| 助词 | u | 语法标记 | 辅助语法功能 |
C.3.2 句法成分
| 语言学术语 | 英文 | 程序语言类比 | 说明 |
|---|---|---|---|
| 主语 | Subject | 调用者/主体 | 动作发出者 |
| 谓语 | Predicate | 方法/函数 | 陈述内容 |
| 宾语 | Object | 参数/对象 | 动作承受者 |
| 定语 | Attributive | 属性修饰 | 修饰名词 |
| 状语 | Adverbial | 参数修饰 | 修饰动词 |
| 补语 | Complement | 返回值/结果状态 | 补充说明 |
C.3.3 句法关系
| 关系类型 | 缩写 | 说明 | 示例 |
|---|---|---|---|
| 主谓关系 | SBV | 主语-谓语 | 学生/读 |
| 动宾关系(也称述宾关系) | VOB | 动词-宾语 | 读/书 |
| 定中关系 | ATT | 定语-中心语 | 好/学生 |
| 状中关系 | ADV | 状语-中心语 | 认真/读 |
| 述补关系 | CMP | 谓语-补语 | 读/完 |
| 介宾关系 | POB | 介词-宾语 | 在/教室 |
| 并列关系 | COO | 并列成分 | 学生/和/老师 |
| 核心词 | HED | 句子核心 | 读(根节点) |
C.4 语义术语对照
C.4.1 语义角色
| 语义角色 | 英文 | 说明 | 示例 |
|---|---|---|---|
| 施事 | Agent | 动作发出者 | 他打了球 |
| 受事 | Patient | 动作承受者 | 他打了球 |
| 与事 | Recipient | 间接对象 | 他给我书 |
| 工具 | Instrument | 使用的工具 | 用刀切 |
| 处所 | Location | 发生地点 | 在教室学习 |
| 时间 | Time | 发生时间 | 昨天来了 |
| 方式 | Manner | 动作方式 | 慢慢走 |
| 原因 | Cause | 事件原因 | 因为下雨没去 |
| 目的 | Purpose | 行为目的 | 为了健康锻炼 |
C.4.2 语义关系
| 关系类型 | 英文 | 说明 | 示例 |
|---|---|---|---|
| 同义关系 | Synonymy | 意义相同 | 高兴-快乐 |
| 反义关系 | Antonymy | 意义相反 | 高-矮 |
| 上下位关系 | Hyponymy | 类属关系 | 动物-猫 |
| 整体部分关系 | Meronymy | 部分关系 | 身体-手 |
| 同现关系 | Collocation | 搭配关系 | 喝-水 |
C.5 表示术语对照
C.5.1 数据结构
| 语言学应用 | 数据结构 | 说明 |
|---|---|---|
| 词表 | 集合(Set) | 无序不重复 |
| 词序列 | 列表(List) | 有序可重复 |
| 句法树 | 树(Tree) | 层次嵌套 |
| 知识图谱 | 图(Graph) | 实体-关系网络 |
| 概念分类体系 | 本体(Ontology) | 概念-层次-继承 |
| 词典 | 哈希表(HashMap) | 键值对 |
C.5.2 文件格式
| 用途 | 格式 | 特点 |
|---|---|---|
| 数据交换 | JSON | 结构化、可嵌套 |
| 文档标记 | XML | 可扩展、自描述 |
| 配置文件 | YAML | 人类可读 |
| 表格数据 | CSV/TSV | 简单、通用 |
| 依存标注 | CoNLL(Conference on Natural Language Learning) | NLP标准格式 |
C.6 大模型相关术语
C.6.1 基本概念
| 术语 | 英文 | 说明 |
|---|---|---|
| 大语言模型 | Large Language Model (LLM) | 大规模预训练语言模型 |
| 词元 | Token | 模型处理的基本单位 |
| 上下文窗口 | Context Window | 模型能处理的最大长度 |
| 提示语 | Prompt | 输入给模型的指令 |
| 生成 | Generation | 模型输出文本 |
| 幻觉 | Hallucination | 模型生成的虚假信息 |
| 词嵌入/词向量 | Word Embedding | 将词映射为连续向量 |
| 注意力机制 | Attention | 模型聚焦输入中关键部分的机制 |
| Transformer | Transformer | 当前大模型的核心架构 |
C.6.2 表示方式对比
| 维度 | 符号表示 | 向量表示 |
|---|---|---|
| 形式 | 离散符号 | 连续向量 |
| 可读性 | 人可读 | 人不可读 |
| 知识形态 | 显性 | 隐性 |
| 处理方式 | 规则推理 | 统计计算 |
| 代表系统 | 专家系统、知识库 | 神经网络、大模型 |
C.7 缩写对照表
C.7.1 语言学缩写
| 缩写 | 全称 | 中文 |
|---|---|---|
| NLP | Natural Language Processing | 自然语言处理 |
| POS | Part of Speech | 词性 |
| NER | Named Entity Recognition | 命名实体识别 |
| SRL | Semantic Role Labeling | 语义角色标注 |
| WSD | Word Sense Disambiguation | 词义消歧 |
| TF-IDF | Term Frequency-Inverse Document Frequency | 词频-逆文档频率 |
| PMI | Pointwise Mutual Information | 点互信息 |
| UD | Universal Dependencies | 通用依存标注体系 |
C.7.2 计算机缩写
| 缩写 | 全称 | 中文 |
|---|---|---|
| AST | Abstract Syntax Tree | 抽象语法树 |
| API | Application Programming Interface | 应用程序接口 |
| JSON | JavaScript Object Notation | JSON数据格式 |
| XML | eXtensible Markup Language | 可扩展标记语言 |
| OOP | Object-Oriented Programming | 面向对象编程 |
C.7.3 语料库缩写
| 缩写 | 全称 | 说明 |
|---|---|---|
| BCC | BLCU Corpus Center | 北语语料库 |
| CCL | Center for Chinese Linguistics (PKU) | 北大中文语料库 |
| COCA | Corpus of Contemporary American English | 当代美国英语语料库 |
| PTB | Penn Treebank | 宾大树库 |
C.8 概念映射图
C.8.1 自然语言 → 程序语言
自然语言概念 程序语言概念
─────────────────────────────────
词 → 标识符/Token
词性 → 数据类型
句子 → 语句
主语 → 调用者/主体对象
谓语 → 方法/函数
宾语 → 参数
修饰语 → 修饰符/属性
从句 → 嵌套结构/回调
篇章 → 程序/模块
语法规则 → 语法规范
语义 → 程序行为/效果
语用/意图 → 设计目的
C.8.2 语料库 → 知识库
语料库概念 知识库概念
─────────────────────────────────
语料 → 数据
句子实例 → 事实陈述
词频统计 → 知识置信度
搭配模式 → 关系类型
分布特征 → 概念特征
上下文 → 知识语境
标注 → 结构化表示
探索与思考
-
寻找新的对应:本附录列出了语言学与计算机之间的术语对应,但这张表并不完整。你能找到一对新的跨学科对应吗?说说它们在各自领域中的含义,以及为什么你认为它们可以对应。
-
对应的局限:术语对照表暗示了两个领域之间的相似性,但相似不等于相同。选择表中一组对应(如"代词→引用/指针"),分析这个类比在哪些方面成立、在哪些方面会产生误导。
-
术语背后的思维方式:同一个概念在不同学科中有不同的名字(如语言学的"递归"与计算机的"递归")。它们指的真的是同一个东西吗?选择一个这样的概念,比较两个领域对它的理解有何异同。
附录C 完