汉字嵌部首,图卷蕴深机。嵌入相结合,结构见玄机。泛化能力强,共享共根基。
针对汉字在新环境下的调整,本文提出了一种结合传统字符嵌入与部首结构的图表示法,以捕捉汉字的语义和组成结构,专供大模型理解汉字。该方法通过将字符分解为部首并构建图表示,利用图卷积网络生成部首嵌入,并结合字符嵌入形成最终表示。提高了模型对共享部首信息的理解,并展示了较高的灵活性。文章提供了代码实现,包括字符嵌入和部首分解示例,同时提出了对图构建、复杂图神经网络和多模态融合的优化方向。
新词解释: 字元 - 汉字的一个不可拆分笔画组合,暂时用连接性进行分解
汉字字元分解法
结合了传统的字符级嵌入和基于部首结构的图表示,旨在同时捕捉字符的语义意义和组成结构。
工作原理
- 每个字符由一个传统的嵌入向量表示。
- 将字符分解为其构成部首。
- 构建一个图,其中节点表示部首,边表示部首之间的连接。
- 对这个部首图应用图卷积,创建基于图的嵌入。
- 将字符嵌入和图嵌入相结合(通过加法)形成最终表示。
优点
- 组合理解:通过整合部首信息,模型可能能够捕捉中文字符的组合性质。
- 共享部首信息:共享部首的字符在嵌入中会有一些共同的成分,可能提高模型的泛化能力。
- 灵活性:这种方法可以处理具有不同数量部首的字符。
代码实现
# -*- coding: utf-8 -*- """ Created on Wed Sep 11 14:52:56 2024 @author: yuhj """ import torch import torch.nn as nn import networkx as nx class ChineseCharacterGraphEmbedding(nn.Module): def __init__(self, vocab_size, embedding_dim, num_radicals): super().__init__() self.character_embedding = nn.Embedding(vocab_size, embedding_dim) self.radical_embedding = nn.Embedding(num_radicals, embedding_dim) self.graph_conv = GraphConv(embedding_dim, embedding_dim) def forward(self, characters, radical_graphs): char_embeds = self.character_embedding(characters) graph_embeds = [] for graph in radical_graphs: radical_ids = torch.tensor([node for node in graph.nodes()]).to(characters.device) radical_embeds = self.radical_embedding(radical_ids) edge_index = torch.tensor(list(graph.edges)).t().contiguous().to(characters.device) graph_embed = self.graph_conv(radical_embeds, edge_index) graph_embeds.append(graph_embed.mean(dim=0)) # 取平均作为整个图的嵌入 graph_embeds = torch.stack(graph_embeds) return char_embeds + graph_embeds class GraphConv(nn.Module): def __init__(self, in_dim, out_dim): super().__init__() self.linear = nn.Linear(in_dim, out_dim) def forward(self, node_features, edge_index): # 简化的图卷积操作 row, col = edge_index out = self.linear(node_features) out = torch.index_select(out, 0, row) + torch.index_select(out, 0, col) return out def build_radical_graph(character): # 这是一个简化的实现,实际应用中需要更复杂的逻辑 radical_dict = { '森': ['木', '木', '木'], '湖': ['氵', '胡'], '明': ['日', '月'] } if character not in radical_dict: return nx.Graph() # 返回空图 radicals = radical_dict[character] G = nx.Graph() for i, radical in enumerate(radicals): G.add_node(i, radical=radical) # 简单地将所有部首连接起来 for i in range(len(radicals)): for j in range(i+1, len(radicals)): G.add_edge(i, j) return G # 使用示例 vocab_size = 5000 embedding_dim = 128 num_radicals = 214 # 常用部首数量 model = ChineseCharacterGraphEmbedding(vocab_size, embedding_dim, num_radicals) # 假设我们有一个字符"森" character_id = torch.tensor([42]) # 假设"森"的ID是42 radical_graph = build_radical_graph("森") embedding = model(character_id, [radical_graph]) print(f"Embedding shape: {embedding.shape}")
可优化点
因为这是一个demo实现,所以具体使用时刻进行如下改进
- 改进图构建:开发更精确的算法来构建字符的部首图,考虑部首的空间关系和结构重要性。
- 扩展部首系统:建立一个全面的部首分解系统,包括罕见字符和异体字。
- 复杂的图神经网络:探索更先进的GNN架构,如GraphSAGE或GAT,以更好地捕捉部首间的复杂关系。
- 多模态融合:将这种基于图的方法与其他中文表示方法(如笔画序列或视觉特征)结合。
- 下游任务评估:在各种NLP任务(如命名实体识别、情感分析等)中评估这种嵌入方法的效果。
汉字字元提取
考虑到使用部首分解需要将汉字进行分解,提取出偏旁和部首等,后续还涉及到音调等,如果完全人工操作,会需要较大的工作量(由人工来做相对质量会高很多),但我没时间。于是设计了一个原型来实现汉字分解,将汉字拆分成构成它的独立部件或笔画。
工作原理
- 使用系统字体将汉字渲染为图像
- 对图像进行二值化处理
- 应用连通组件分析识别独立部件
- 使用自定义算法对部件进行排序
- 可视化展示分解结果
代码实现
# -*- coding: utf-8 -*- """ Created on Wed Sep 11 15:51:58 2024 @author: yuhj """ import numpy as np import cv2 import matplotlib.pyplot as plt from PIL import Image, ImageDraw, ImageFont def draw_chinese_char(character, size=32): image = Image.new("RGB", (size, size), color="white") draw = ImageDraw.Draw(image) font = ImageFont.truetype("C:/Windows/Fonts/Deng.ttf", size) draw.text((0, 0), character, font=font, fill="black") return np.array(image) def get_component_features(component): y, x = np.where(component > 0) x_min, y_min = np.min(x), np.min(y) x_max, y_max = np.max(x), np.max(y) area = np.sum(component > 0) return x_min, y_min, x_max, y_max, area def custom_sort(components): features = [get_component_features(comp) for comp in components] def sort_key(item): comp, (x_min, y_min, x_max, y_max, area) = item return (x_min // 3, y_min, -area) # Group by x (in bins of 3 pixels), then by y, then by descending area return [comp for comp, _ in sorted(zip(components, features), key=sort_key)] def decompose_character(image): gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 寻找连通区域 num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary, connectivity=8) # 创建组件列表(跳过背景) components = [] for i in range(1, num_labels): component = np.zeros_like(binary) component[labels == i] = 255 components.append(component) # 使用自定义排序 sorted_components = custom_sort(components) return sorted_components, binary def visualize_decomposition(original, components, binary): n = len(components) + 2 # 原图、二值图、分割结果 fig, axes = plt.subplots(1, n, figsize=(n*2, 2)) axes[0].imshow(original) axes[0].set_title("Original") axes[0].axis('off') axes[1].imshow(binary, cmap='gray') axes[1].set_title("Binary") axes[1].axis('off') for i, comp in enumerate(components): axes[i+2].imshow(comp, cmap='gray') axes[i+2].set_title(f"Component {i+1}") axes[i+2].axis('off') plt.suptitle(f"Number of components: {len(components)}") plt.tight_layout() plt.show() # 使用示例 characters = ["河", "吃", "森"] for char in characters: char_image = draw_chinese_char(char) components, binary = decompose_character(char_image) visualize_decomposition(char_image, components, binary)
优化方向
- 提高鲁棒性:增强对不同字体和书写风格的适应能力
- 优化性能:改进算法以处理更复杂的汉字结构
- 扩展功能:添加部件识别和命名功能
- 字符匹配:将分解出的字符图形与汉字进行匹配,找出最匹配的最小氮原字,例如胖->月半,二者都不可再分
看完记得望望远方,后续全凭个人兴趣,会分开去深入探索,或者暂停