基于jieba分詞和RNN實現的中文分詞
- 目標
- 數據準備
- 主程序
- 預測效果
目標
本文基于給定的中文詞表,將輸入的文本基于jieba分詞分割為若干個詞,詞的末尾對應的標簽為1
,中間部分對應的標簽為0
,同時將分詞后的單詞基于中文詞表做初步序列化,之后經過embedding
和 RNN
循環神經網絡等網絡結構層,最后輸出在兩類別
(詞內部和詞邊界)標簽上的概率分布,從而實現一個簡單中文分詞任務。
數據準備
詞表文件chars.txt
中文語料文件corpus.txt中文語料文件
主程序
#coding:utf8import torch
import torch.nn as nn
import jieba
import numpy as np
import random
import json
from torch.utils.data import DataLoader"""
基于pytorch的網絡編寫一個分詞模型
我們使用jieba分詞的結果作為訓練數據
看看是否可以得到一個效果接近的神經網絡模型
"""class TorchModel(nn.Module):def __init__(self, input_dim, hidden_size, num_rnn_layers, vocab):super(TorchModel, self).__init__()self.embedding = nn.Embedding(len(vocab) + 1, input_dim) #shape=(vocab_size, dim)self.rnn_layer = nn.RNN(input_size=input_dim,hidden_size=hidden_size,batch_first=True,bidirectional=False,num_layers=num_rnn_layers,nonlinearity="relu",dropout=0.1)self.classify = nn.Linear(hidden_size, 2)self.loss_func = nn.CrossEntropyLoss(ignore_index=-100)#當輸入真實標簽,返回loss值;無真實標簽,返回預測值def forward(self, x, y=None):x = self.embedding(x) #output shape:(batch_size, sen_len, input_dim)x, _ = self.rnn_layer(x) #output shape:(batch_size, sen_len, hidden_size)y_pred = self.classify(x) #input shape:(batch_size, sen_len, class_num)if y is not None:#(batch_size * sen_len, class_num), (batch_size * sen_len, 1)return self.loss_func(y_pred.view(-1, 2), y.view(-1))else:return y_predclass Dataset:def __init__(self, corpus_path, vocab, max_length):self.vocab = vocabself.corpus_path = corpus_pathself.max_length = max_lengthself.load()def load(self):self.data = []with open(self.corpus_path, encoding="utf8") as f:for line in f:sequence = sentence_to_sequence(line, self.vocab)label = sequence_to_label(line)sequence, label = self.padding(sequence, label)sequence = torch.LongTensor(sequence)label = torch.LongTensor(label)self.data.append([sequence, label])if len(self.data) > 10000:breakdef padding(self, sequence, label):sequence = sequence[:self.max_length]sequence += [0] * (self.max_length - len(sequence))label = label[:self.max_length]label += [-100] * (self.max_length - len(label))return sequence, labeldef __len__(self):return len(self.data)def __getitem__(self, item):return self.data[item]#文本轉化為數字序列,為embedding做準備
def sentence_to_sequence(sentence, vocab):sequence = [vocab.get(char, vocab['unk']) for char in sentence]return sequence#基于結巴生成分級結果的標注
def sequence_to_label(sentence):words = jieba.lcut(sentence)label = [0] * len(sentence)pointer = 0for word in words:pointer += len(word)label[pointer - 1] = 1return label#加載字表
def build_vocab(vocab_path):vocab = {}with open(vocab_path, "r", encoding="utf8") as f:for index, line in enumerate(f):char = line.strip()vocab[char] = index + 1 #每個字對應一個序號vocab['unk'] = len(vocab) + 1return vocab#建立數據集
def build_dataset(corpus_path, vocab, max_length, batch_size):dataset = Dataset(corpus_path, vocab, max_length) #diy __len__ __getitem__data_loader = DataLoader(dataset, shuffle=True, batch_size=batch_size) #torchreturn data_loaderdef main():epoch_num = 10 #訓練輪數batch_size = 20 #每次訓練樣本個數char_dim = 50 #每個字的維度hidden_size = 100 #隱含層維度num_rnn_layers = 3 #rnn層數max_length = 20 #樣本最大長度learning_rate = 1e-3 #學習率vocab_path = "chars.txt" #字表文件路徑corpus_path = "corpus.txt" #語料文件路徑vocab = build_vocab(vocab_path) #建立字表data_loader = build_dataset(corpus_path, vocab, max_length, batch_size) #建立數據集model = TorchModel(char_dim, hidden_size, num_rnn_layers, vocab) #建立模型optim = torch.optim.Adam(model.parameters(), lr=learning_rate) #建立優化器#訓練開始for epoch in range(epoch_num):model.train()watch_loss = []for x, y in data_loader:optim.zero_grad() #梯度歸零loss = model(x, y) #計算lossloss.backward() #計算梯度optim.step() #更新權重watch_loss.append(loss.item())print("=========\n第%d輪平均loss:%f" % (epoch + 1, np.mean(watch_loss)))#保存模型torch.save(model.state_dict(), "model.pth")#保存詞表writer = open("vocab.json", "w", encoding="utf8")writer.write(json.dumps(vocab, ensure_ascii=False, indent=2))writer.close()return#最終預測
def predict(model_path, vocab_path, input_strings):#配置保持和訓練時一致char_dim = 50 # 每個字的維度hidden_size = 100 # 隱含層維度num_rnn_layers = 3 # rnn層數vocab = build_vocab(vocab_path) #建立字表model = TorchModel(char_dim, hidden_size, num_rnn_layers, vocab) #建立模型model.load_state_dict(torch.load(model_path)) #加載訓練好的模型權重model.eval()for input_string in input_strings:#逐條預測x = sentence_to_sequence(input_string, vocab)# print(x)with torch.no_grad():result = model.forward(torch.LongTensor([x]))[0]result = torch.argmax(result, dim=-1) #預測出的01序列print(result)#在預測為1的地方切分,將切分后文本打印出來for index, p in enumerate(result):if p == 1:print(input_string[index], end=" ")else:print(input_string[index], end="")print()if __name__ == "__main__":print(torch.backends.mps.is_available())main()input_strings = ["同時國內有望出臺新汽車刺激方案","滬膠后市有望延續強勢","經過兩個交易日的強勢調整后","昨日上海天然橡膠期貨價格再度大幅上揚"]predict("model.pth", "chars.txt", input_strings)
主要實現了一個基于jieba分詞
的中文分詞模型,模型采用 RNN(循環神經網絡)
來處理中文文本,通過對句子進行分詞,預測每個字是否為詞的結尾。具體內容如下:
-
模型結構(
TorchModel
):- 使用
nn.Embedding
層將每個字符映射到一個高維空間。 - 通過
nn.RNN
層處理字符序列,提取上下文信息,使用單向RNN
(bidirectional=False
)。 - 最后通過
nn.Linear
層將 RNN 輸出轉化為每個字符的分類結果,分類為0
(非詞結尾)或1
(詞結尾)。 - 損失函數為
CrossEntropyLoss
,計算預測與真實標簽的差異。
- 使用
-
數據處理(
Dataset
):- 使用
jieba
分詞工具將文本切分為詞,并為每個字符標注一個標簽。標簽為1
表示該字符是詞的結尾,0
表示不是詞結尾。 - 將文本轉換為數字序列,并根據最大句子長度進行填充,使得輸入數據的形狀一致。
- 使用
-
訓練過程:
- 數據通過
DataLoader
按批加載,使用Adam
優化器進行訓練。 - 在每一輪訓練中,計算損失并通過反向傳播優化模型權重,訓練 10 輪。
- 數據通過
-
預測功能(
predict
):- 加載訓練好的模型,使用
torch.no_grad()
禁用梯度計算,提高推理速度。 - 對每個輸入字符串進行分詞預測,輸出每個字是否為詞的結尾。若為詞結尾,則切分該詞并打印。
- 加載訓練好的模型,使用
-
核心流程:
sentence_to_sequence
將文本轉換為字符序列,sequence_to_label
生成對應的標簽序列。- 訓練完成后,保存模型和詞表,以便后續加載和預測。
代碼實現了一個簡單的中文分詞模型,通過標注每個字符是否為詞的結尾,結合 RNN
提取上下文信息,從而實現文本分詞功能。
預測效果
輸入語句:
“同時國內有望出臺新汽車刺激方案”,
“滬膠后市有望延續強勢”,
“經過兩個交易日的強勢調整后”,
“昨日上海天然橡膠期貨價格再度大幅上揚”
中文分詞后結果:
tensor([0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1])
同時 國內 有 望 出臺 新 汽車 刺激 方案
tensor([1, 1, 0, 1, 1, 1, 0, 1, 0, 1])
滬 膠 后市 有 望 延續 強勢
tensor([0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1])
經過 兩個 交易 日 的 強勢 調整 后
tensor([0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
昨日 上海 天然 橡膠 期貨 價格 再度 大幅 上揚