3. 语言指令编码与嵌入

任务目标

实现一个轻量级语言指令编码器,将自然语言指令(如“拿起红色方块”)转换为固定长度的嵌入向量,并通过余弦相似度验证语义相近指令的向量距离更近。

核心实现

import re
import numpy as np
from collections import Counter

class SimpleInstructionEncoder:
    """基于词袋+TF-IDF加权+哈希降维的轻量指令编码器(无外部依赖)"""
    
    def __init__(self, dim=64):
        self.dim = dim
        self.vocab = {}
        self.idf = {}
        self._fitted = False
        
    def _tokenize(self, text):
        # 中文按字切分,英文按词切分,统一小写
        text = text.lower().strip()
        tokens = re.findall(r'[\u4e00-\u9fff]|[a-z]+', text)
        return tokens
    
    def _hash_(self, , salt=0):
        # 简单字符串哈希到[0, dim)
        h = 0
        for ch in  + str(salt):
            h = (h * 31 + ord(ch)) & 0xffffffff
        return h % self.dim
    
    def fit(self, sentences):
        """构建词表与IDF权重"""
        doc_freq = Counter()
        tokenized_docs = []
        for sent in sentences:
            tokens = set(self._tokenize(sent))
            tokenized_docs.append(tokens)
            for tok in tokens:
                doc_freq[tok] += 1
        
        n_docs = len(sentences)
        self.vocab = {tok: idx for idx, tok in enumerate(doc_freq.keys())}
        self.idf = {tok: np.log((1 + n_docs) / (1 + freq)) + 1.0 
                    for tok, freq in doc_freq.items()}
        self._fitted = True
        return self
    
    def encode(self, sentence):
        """将单条指令编码为固定维度向量"""
        if not self._fitted:
            raise RuntimeError("必须先调用fit()")
        tokens = self._tokenize(sentence)
        vec = np.zeros(self.dim)
        for tok in tokens:
            if tok in self.idf:
                w = self.idf[tok]
                # 正负哈希各一次,减少碰撞偏差
                vec[self._hash_(tok, 0)] += w
                vec[self._hash_(tok, 1)] -= w
        # L2归一化
        norm = np.linalg.norm(vec)
        return vec / norm if norm > 0 else vec
    
    def encode_batch(self, sentences):
        return np.array([self.encode(s) for s in sentences])
    
    def similarity(self, s1, s2):
        """计算两条指令的余弦相似度"""
        v1, v2 = self.encode(s1), self.encode(s2)
        return float(np.dot(v1, v2))

def demo():
    # 训练语料(模拟机器人指令场景)
    corpus = [
        "拿起红色方块",
        "放下蓝色圆柱",
        "抓取红色方块",
        "放置蓝色圆柱到桌面",
        "移动到门口",
        "走到窗边",
        "打开抽屉",
        "关闭抽屉",
        "拿起蓝色方块",
        "放下红色圆柱"
    ]
    
    encoder = SimpleInstructionEncoder(dim=64)
    encoder.fit(corpus)
    
    # 测试语义相似性
    test_pairs = [
        ("拿起红色方块", "抓取红色方块"),   # 近义
        ("拿起红色方块", "拿起蓝色方块"),   # 部分相似
        ("拿起红色方块", "打开抽屉"),       # 不相关
        ("移动到门口", "走到窗边"),         # 近义
    ]
    
    print("=== 指令语义相似度测试 ===")
    for s1, s2 in test_pairs:
        sim = encoder.similarity(s1, s2)
        print(f"{s1} <-> {s2}: {sim:.4f}")
    
    # 验证嵌入维度固定
    vec = encoder.encode("拿起红色方块")
    print(f"\n嵌入维度: {vec.shape[0]} (固定长度)")
    print(f"向量范数: {np.linalg.norm(vec):.4f} (L2归一化)")

if __name__ == "__main__":
    demo()

运行与验证

  1. 环境要求:Python 3.6+,仅需NumPy(pip install numpy)
  2. 运行方式:将代码保存为 instruction_encoder.py,执行 python instruction_encoder.py
  3. 通过条件:观察输出,应满足:
    • “拿起红色方块”与“抓取红色方块”的相似度明显高于与“打开抽屉”的相似度(通常差距 > 0.3)
    • 所有输出向量的维度均为64,且L2范数为1.0(归一化)
    • “移动到门口”与“走到窗边”的相似度高于与“拿起红色方块”的相似度

关键说明

  • 编码器采用词袋+TF-IDF加权+哈希降维,无需外部预训练模型即可运行,适合教学演示
  • 中文按单字切分、英文按单词切分,通过正负哈希降低碰撞影响
  • 实际工程中可替换为BERT等预训练模型,但本案例聚焦于“语言→固定长度向量”的核心映射逻辑
  • 相似度计算采用余弦相似度,值域[-1,1],越接近1表示语义越相近
语言指令编码流程 自然语言指令 “拿起红色方块” 分词 拿/起/红/色/方/块 TF-IDF加权 稀有词权重更高 哈希降维 固定64维向量 输出:固定长度嵌入向量(维度=64) L2归一化后,可直接用于余弦相似度计算