Pinyin2Hanzi_HMM:基于预训练隐马尔可夫模型的汉语拼音序列转汉字语句序列程序

本文提供了一个基于预训练隐马尔可夫模型(HMM)的汉语拼音序列转汉字语句序列程序。

项目地址:Pinyin2Hanzi_HMM: 基于预训练隐马尔可夫模型的汉语拼音序列转汉字语句序列程序 Chinese Pinyin to Chinese Character (Hanzi) Conversion Program Based on Hidden Markov Model (HMM).icon-default.png?t=O83Ahttps://github.com/duyu09/Pinyin2Hanzi_HMM

项目原理(此处不过多赘述)

本项目基于 隐马尔可夫模型 (HMM) 实现汉语拼音序列到汉字序列的转换。HMM是一种概率模型,用以解决序列到序列问题。假设观察序列(拼音)由隐藏状态序列(汉字)生成,并通过状态转移和发射概率描述序列关系。模型训练时,程序首先加载训练数据,提取拼音和汉字构建词汇表,并统计初始状态、状态转移和发射概率矩阵。训练过程中,HMM使用最大似然估计优化这些概率,以捕捉拼音与汉字的映射关系。解码阶段,利用维特比算法(Viterbi Algorithm)寻找最可能的汉字序列作为输出结果。本项目适合处理语言序列建模和序列标注等的任务。

数据集准备

第一列 (汉字序列)第二列 (拼音序列)
我们试试看!wo3 men shi4 shi4 kan4 !
我该去睡觉了。wo3 gai1 qu4 shui4 jiao4 le 。
你在干什么啊?ni3 zai4 gan4 shen2 me a ?
这是什么啊?zhe4 shi4 shen2 me a ?
我会尽量不打扰你复习。wo3 hui4 jin3 liang4 bu4 da3 rao3 ni3 fu4 xi2 。

运行环境

基于`Python 3.x`,版本不限,需要`numpy`、`pandas`、`hmmlearn`库,参考以下命令安装:

python3 -m pip install numpy pandas hmmlearn
  • 硬件环境:假设训练语料数据包含 2.65 万个不同的汉字,则推理至少需要 6GB 左右的内存,训练至少需要 8GB 左右的内存。

训练和推理方法

修改`py2hz.py`的主函数代码以运行。我们已开源了基于多领域文本的预训练的模型权重`hmm_model.pkl.bz2``hmm_model_large.pkl.bz2`,可以直接使用。`hmm_model.pkl.bz2`规模稍小,可满足日常汉语的转换需求,其解压缩后约为 800MB 左右;`hmm_model_large.pkl.bz2`覆盖了几乎所有汉字的读音,并在规模更大的语料库上进行训练,其解压缩后约为 4.5GB 左右。

若需自行训练则取消train函数的注释,并修改函数参数。训练完成后模型将会被压缩保存,原因是模型中存在非常稀疏的大矩阵,适合压缩存储。

程序代码

按照前文所述的方法即可运行。

# _*_ coding:utf-8 _*_
"""
@Version  : 1.2.0
@Time     : 2024年12月29日
@Author   : DuYu (@duyu09, 202103180009@stu.qlu.edu.cn)
@File     : py2hz.py
@Describe : 基于隐马尔可夫模型(HMM)的拼音转汉字程序。
@Copyright: Copyright (c) 2024 DuYu (No.202103180009), Faculty of Computer Science & Technology, Qilu University of Technology (Shandong Academy of Sciences).
@Note     : 训练集csv文件,要求第一列为由汉语拼音构成的句子,第二列为由汉字构成的句子。
"""

import re
import bz2
import pickle
import numpy as np
import pandas as pd
from hmmlearn import hmm


# 1. 数据预处理:加载CSV数据集
def load_dataset(file_path):
    data = pd.read_csv(file_path)
    sentences = data.iloc[:, 0].tolist()  # 第一列:汉字句子
    pinyins = data.iloc[:, 1].tolist()  # 第二列:拼音句子
    return sentences, pinyins

# 分词函数,确保英文单词保持完整
def segment_sentence(sentence):
    tokens = re.findall(r'[a-zA-Z]+|[一-鿿]', sentence)  # 使用正则表达式分割句子,确保英文单词保持完整
    return tokens

# 2. 构建字典和状态观测集合
def build_vocab(sentences, pinyins):
    hanzi_set = set()
    pinyin_set = set()

    for sentence, pinyin in zip(sentences, pinyins):
        hanzi_set.update(segment_sentence(sentence))
        pinyin_set.update(pinyin.split())

    hanzi_list = list(hanzi_set)
    pinyin_list = list(pinyin_set)

    hanzi2id = {h: i for i, h in enumerate(hanzi_list)}
    id2hanzi = {i: h for i, h in enumerate(hanzi_list)}
    pinyin2id = {p: i for i, p in enumerate(pinyin_list)}
    id2pinyin = {i: p for i, p in enumerate(pinyin_list)}

    return hanzi2id, id2hanzi, pinyin2id, id2pinyin

# 3. 模型训练
def train_hmm(sentences, pinyins, hanzi2id, pinyin2id):
    n_states = len(hanzi2id)
    n_observations = len(pinyin2id)

    model = hmm.MultinomialHMM(n_components=n_states, n_iter=100, tol=1e-4)

    # 统计初始状态概率、转移概率和发射概率
    start_prob = np.zeros(n_states)
    trans_prob = np.zeros((n_states, n_states))
    emit_prob = np.zeros((n_states, n_observations))

    for sentence, pinyin in zip(sentences, pinyins):
        # print(sentence, pinyin)
        hanzi_seq = [hanzi2id[h] for h in segment_sentence(sentence)]
        pinyin_seq = [pinyin2id[p] for p in pinyin.split()]

        # 初始状态概率
        if len(hanzi_seq) == 0:
            continue
        start_prob[hanzi_seq[0]] += 1

        # 转移概率
        for i in range(len(hanzi_seq) - 1):
            trans_prob[hanzi_seq[i], hanzi_seq[i + 1]] += 1

        # 发射概率
        for h, p in zip(hanzi_seq, pinyin_seq):
            emit_prob[h, p] += 1

    # 确保矩阵行和为1,并处理全零行
    if start_prob.sum() == 0:
        start_prob += 1
    start_prob /= start_prob.sum()

    row_sums = trans_prob.sum(axis=1, keepdims=True)
    zero_rows = (row_sums == 0).flatten()  # 修复索引错误
    trans_prob[zero_rows, :] = 1.0 / n_states  # 用均匀分布填充全零行
    trans_prob /= trans_prob.sum(axis=1, keepdims=True)

    emit_sums = emit_prob.sum(axis=1, keepdims=True)
    zero_emit_rows = (emit_sums == 0).flatten()
    emit_prob[zero_emit_rows, :] = 1.0 / n_observations  # 均匀填充
    emit_prob /= emit_prob.sum(axis=1, keepdims=True)
    
    model.startprob_ = start_prob
    model.transmat_ = trans_prob
    model.emissionprob_ = emit_prob
    return model

# 4. 保存和加载模型
def save_model(model, filepath, mode='compress'):  # mode='normal'意味着不使用压缩
    if mode == 'normal':
        with open(filepath, 'wb') as f:
            pickle.dump(model, f)
    else:
        with bz2.BZ2File(filepath, 'wb') as f:
            pickle.dump(model, f)

def load_model(filepath, mode='compress'):   # mode='normal'意味着不使用压缩
    if mode == 'normal':
        with open(filepath, 'rb') as f:
            return pickle.load(f)
    else:
        with bz2.BZ2File(filepath, 'rb') as f:
            return pickle.load(f)

    
def train(dataset_path='train.csv', model_path='hmm_model.pkl.bz2'):
    sentences, pinyins = load_dataset(dataset_path)  # 加载数据集
    hanzi2id, id2hanzi, pinyin2id, id2pinyin = build_vocab(sentences, pinyins)  # 构建字典
    model = train_hmm(sentences, pinyins, hanzi2id, pinyin2id)  # 训练模型
    model.pinyin2id = pinyin2id
    model.id2hanzi = id2hanzi
    model.hanzi2id = hanzi2id
    model.id2pinyin = id2pinyin
    save_model(model, model_path)  # 保存模型
    
    
def pred(model_path='hmm_model.pkl.bz2', pinyin_str='ce4 shi4', n_trials=3):
    model = load_model(model_path)
    pinyin_list = pinyin_str.split()
    pinyin2id, id2hanzi = model.pinyin2id, model.id2hanzi
    obs_seq = np.zeros((len(pinyin_list), len(pinyin2id)))  # 转换观测序列为 one-hot 格式
    for t, p in enumerate(pinyin_list):
        if p in pinyin2id:
            obs_seq[t, pinyin2id[p]] = 1
        else:
            obs_seq[t, 0] = 1  # 未知拼音默认处理

    # 解码预测
    model.n_trials = n_trials
    log_prob, state_seq = model.decode(obs_seq, algorithm=model.algorithm)
    result = ''.join([id2hanzi[s] for s in state_seq])
    print('预测结果:', result)

if __name__ == '__main__':
    # train(dataset_path='train.csv', model_path='hmm_model_large.pkl.bz2')
    pred(model_path='hmm_model_large.pkl.bz2', pinyin_str='hong2 yan2 bo2 ming4')  # 预测结果:红颜薄命

预训练模型效果

下表展示了预训练模型`hmm_model_large.pkl.bz2`的使用效果。

输入输出
hong2 yan2 bo2 ming4红颜薄命
guo2 jia1 chao1 suan4 ji3 nan2 zhong1 xin1国家超算济南中心
liu3 an4 hua1 ming2 you4 yi1 cun1柳暗花明又一村
gu3 zhi4 shu1 song1 zheng4骨质疏松症
xi1 an1 dian4 zi3 ke1 ji4 da4 xue2西安电子科技大学
ye4 mian4 zhi4 huan4 suan4 fa3页面置换算法

作者声明及开源地址

Author: Du Yu (@duyu09, 202103180009@stu.qlu.edu.cn), 
Faculty of Computer Science and Technology, Qilu University of Technology (Shandong Academy of Sciences).

Repository:https://github.com/duyu09/Pinyin2Hanzi_HMM

感谢您的阅览!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Duyu09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值