古詩續(xù)寫的NLP項目全解析)
簡介面向NLP入門與Python實踐者的詩歌接龍項目完整代碼包聚焦中文分詞、詞性標注、拼音轉換、押韻規(guī)則匹配及網絡爬蟲等核心環(huán)節(jié)適合希望通過完整實戰(zhàn)案例理解自然語言處理流程的學生與開發(fā)者。壓縮包共17個文件主要包含Python源碼、jieba/pypinyin等庫的依賴說明、預訓練數(shù)據(jù)文件、可執(zhí)行程序、txt說明文檔及docx詳細設計文檔其中docx對項目背景、模塊設計與調用流程做了系統(tǒng)說明整體大小僅5.86MB輕量便于快速部署。已有496人學習瀏覽。項目中除基礎爬蟲與文本處理外還提供了基于規(guī)則與統(tǒng)計方法的接龍算法實現(xiàn)并附有項目目標、實現(xiàn)過程與使用方法能幫助讀者從分詞、音韻匹配到算法調優(yōu)獲得完整實戰(zhàn)閉環(huán)適合作為課程設計或NLP入門練習的參考。1. 詩歌接龍是什么一個讓Python寫詩的下一個字的NLP項目“詩歌接龍”聽起來像語文課上的游戲但落到程序里它其實是NLP自然語言處理里最典型的序列生成任務用戶給出一句詩或半句詩程序需要預測下一個字甚至把后面的詩續(xù)完。用Python實現(xiàn)這樣一個項目意味著你要走完中文語料清洗、序列建模、概率采樣、結果評估的完整鏈路。它的難度比文本分類高一檔但比從頭做對話機器人低很多特別適合用來檢驗你對Python字符串處理、數(shù)據(jù)結構和簡單模型的掌握程度。你做出來的東西也不是那種跑完loss就扔掉的玩具——它能把“床前明月光”續(xù)成“地上鞋兩雙”也能在調好參數(shù)后生成像模像樣的五言句。這個項目適合誰剛學完Python基礎、想找?guī)谋緢鼍暗捻椖烤毷值娜嘶蛘咭呀浽谟胣-gram做關鍵詞提取、想跨進生成式NLP的人。接下來我按自己實際做過的思路把這條路上的選型、代碼、參數(shù)和坑一次講清。2. 先想清楚接龍的數(shù)學邏輯從n-gram到序列模型的選型思路2.1 詩歌接龍不是在寫詩是在預測下一個字很多人第一次做詩歌接龍腦子里想的是“讓AI學會作詩”于是直接上GPT。但真正的第一步是把問題簡化成條件概率給定前文“床前明月”下一個字是“光”的概率是多少如果模型能算出候選字在給定上下文下的概率分布那么接龍就變成一個采樣問題——按概率分布挑一個字然后把它拼到輸入末尾繼續(xù)預測下一個字。這個思維決定了技術路線。詩歌接龍的輸入輸出都是字符序列本質是語言模型Language Model。語言模型做的事就是最大化序列概率 P(text) P(w1) * P(w2|w1) * ... * P(wN|w1..wN-1)。而接龍只要計算 P(下一個字 | 當前已生成序列)所以訓練的樣本就是“給定前面N個字預測第N1個字”。這個視角統(tǒng)一了所有后續(xù)做法n-gram里的轉移概率表、LSTM里的softmax輸出、Transformer里的自回歸解碼全部是在求這個條件分布。這里要強調一個容易被新手搞混的點接龍其實不需要先分詞。中文詩歌很多時候一個字就是一個語義單元按“字”做語言模型比按“詞”做更穩(wěn)。詞表就是全部出現(xiàn)過的字幾百個字就夠用訓練快、可控性強。按詞的話古詩里大量典故、人名、地名會切成碎片反而學不好。我一般默認用字符級建模除非要做主題分類才考慮詞級特征。2.2 數(shù)據(jù)準備把古詩庫變成模型能吃的序列樣本數(shù)據(jù)是詩歌接龍的命根子。常見做法是用公開的古詩數(shù)據(jù)集里面包含唐詩、宋詞、詩經等文本。拿到的原始數(shù)據(jù)通常是這種格式[{author: 李白, title: 靜夜思, paragraphs: [床前明月光, 疑是地上霜]}]也有的是純文本一行一首詩或描寫段落后跟詩句。第一步是把所有詩句抽出來去掉作者、標題、注釋。注意古詩里會有繁體字、異體字比如“裏”和“里”“閒”和“閑”。我一般會統(tǒng)一轉成簡體做法是用opencc庫pip install opencc-python-reimplemented然后import opencc cc opencc.OpenCC(t2s) # 繁體轉簡體 text cc.convert(raw_text)轉完之后還要做清洗去掉空格、換行、標點保留句號逗號也行但接龍目標是詩句通常去掉標點讓模型自己學去掉長度小于4或大于12的行因為五言絕句一般是5或7個字一句太短的行多半殘缺太長的可能是散文混入。清洗后的語料長這樣床前明月光 疑是地上霜 舉頭望明月 低頭思故鄉(xiāng)有了這個純文本列表就可以構造訓練樣本了。如果是n-gram需要統(tǒng)計“前N-1個字 - 下一個字”的計數(shù)如果是LSTM需要把每句詩轉成整數(shù)序列再用滑動窗口切出輸入和標簽。這一步是整個項目的地基沒做好后面模型再強也白搭。2.3 選型對照n-gram、LSTM與Transformer該用哪個搞清楚任務本質后選型就清晰了。常見做法是分三檔n-gram字符級簡單到用Python字典就能實現(xiàn)。它統(tǒng)計“前面n-1個字”這個狀態(tài)出現(xiàn)時后面跟的每個字的次數(shù)然后算概率。優(yōu)點是訓練只要一次遍歷語料完全沒有GPU需求缺點是只能記住連續(xù)n個字內的關系n設大了矩陣稀疏爆炸n設小了生成出來的句子前后邏輯差。對詩歌接龍這種短文本n3或4已經能生成語法通順但內容隨機的小詩。LSTM/GRU能捕捉遠距離依賴比如律詩里上下句的語義呼應。訓練需要神經網絡框架我一般用PyTorch因為動態(tài)圖方便寫采樣邏輯。LSTM接收前文序列的embedding最后一個時間步的輸出通過全連接層映射到字典大小的向量softmax后就是下一個字的概率。它比n-gram學到的泛化能力強你輸入一句不在語料里的“半句詩”LSTM也能接出個差不多的下句。缺點是訓練時間受序列長度和數(shù)據(jù)量影響字符級詞表小的情況下CPU也能跑但效率低還是建議裝個CUDA版PyTorch。Transformer/預訓練模型如果你手里有百G語料或者想直接用中文GPT-2那效果肯定最好但這不是普通“python實現(xiàn)NLP項目”該走的路。預訓練模型像是拿電鋸切豆腐模型下載幾個G、顯存要十幾G而詩歌接龍本身數(shù)據(jù)量不大幾百KB古詩就夠。除非你后來要做風格遷移、多輪對詩否則用Transformer從頭訓是在給自己找罪受。我自己的建議路線是先花半小時用n-gram跑通整個“輸入-生成-輸出”流程感受一下概率采樣的效果再花兩小時把同樣的數(shù)據(jù)喂給LSTM對比質量差異。兩條路寫一遍你對序列生成的理解會深很多。下面的章節(jié)我就按這個順序展開。3. 用純Python實現(xiàn)第一個可用的詩歌接龍字符級n-gram與beam search3.1 編碼與計數(shù)構建轉移概率表n-gram的實現(xiàn)核心是計數(shù)器。我以“三元語法”為例也就是n3用前兩個字預測下一個字。你需要一個字典key是長度為2的字符串或元組value是另一個字典記錄每個下一個字的出現(xiàn)次數(shù)。import os import random from collections import defaultdict, Counter def load_lines(path): lines [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if len(line) 5 and len(line) 7: # 只保留五言或七言 lines.append(line) return lines def build_trigram(lines): # key: 前兩個字組成的字符串, value: Counter(下一個字 - 次數(shù)) model defaultdict(Counter) for line in lines: # 在開頭加兩個特殊字符讓首字母也有前文 padded ^^ line $ for i in range(len(padded) - 2): context padded[i:i2] next_char padded[i2] model[context][next_char] 1 return model這段代碼做了兩件關鍵的事。第一給每一行首尾加了特殊字符^表示句首$表示句尾。這樣生成的時候模型知道如何開始和結束。第二用defaultdict(Counter)構建兩層字典外層的key是“前兩個字”內層的key是“下一個字”。統(tǒng)計完成后你隨時可以查model[明月][光]得到“明月”后面跟“光”的次數(shù)。注意我過濾了長度在5到7之間的行。嚴格來說五言是5字七言是7字但古詩里也有四言和雜言過濾掉能減少噪聲。如果你想讓模型也能生成七言和五言混合就不要死卡長度可以在構造樣本時把句子按逗號再分割逐句加入。下一步是把這個計數(shù)模型轉成概率模型。因為要用到隨機采樣我直接保留計數(shù)生成時再除以總數(shù)或者用random.choices按權重采樣。后者更省事。3.2 beam search采樣讓接龍不只會重復有了模型最樸素的生成是貪心每次選概率最大的字。但這樣會陷入循環(huán)比如“床前明月光”之后永遠接“疑是地上霜”因為訓練語料里這個組合頻率高。更糟糕的是如果模型只學到高頻搭配你就會得到“床前明月光床前明月光”的復讀機。解決手法是用beam search束搜索每一步維護K個候選序列每個候選序列按累計log概率排序下一輪對每個候選繼續(xù)展開所有可能的字再取前K個。這樣生成的質量遠好于貪心又比純隨機穩(wěn)定。def beam_search(model, prefix, beam_width5, max_len10): # prefix: 用戶輸入的字符串至少2個字符因為n3 start prefix[-2:] # 取后兩個字作為初始context candidates [(start, prefix, 0.0)] # (context, full_text, log_prob) for _ in range(max_len - len(prefix)): new_candidates [] for ctx, full, lp in candidates: counts model.get(ctx) if not counts: continue # 沒有見過這個context放棄該分支 total sum(counts.values()) for ch, cnt in counts.items(): prob cnt / total new_lp lp math.log(prob) new_full full ch new_ctx ctx[1] ch new_candidates.append((new_ctx, new_full, new_lp)) # 只保留分數(shù)最高的beam_size個 new_candidates.sort(keylambda x: x[2], reverseTrue) candidates new_candidates[:beam_width] # 若所有候選都斷了提前結束 if not candidates: break # 返回分數(shù)最高的完整文本 candidates.sort(keylambda x: x[2], reverseTrue) return candidates[0][1]這個函數(shù)每步對當前所有候選展開所有可能的下一個字計算累計對數(shù)概率然后截斷到beam_width。對數(shù)概率避免了很多個小概率相乘導致的浮點下溢這是做beam search必須養(yǎng)成的習慣。參數(shù)beam_width越大生成的多樣性越好但耗時線性增加。對詩歌接龍5到10就夠。3.3 三個關鍵參數(shù)n、溫度、beam size怎么設n-gram里的n決定記憶力長度。n2時很容易生成無厘頭的順口溜n4時生成的句子更像原詩片段但數(shù)據(jù)稀疏非常明顯很多context沒見過導致生成中斷。我實測下來n3是性價比最高的。如果你語料量大到幾萬首可以試n4但要把未登錄詞處理做好——比如context沒見過時回退到較低階的n-gram這叫backoff。beam width不能太大太大容易讓所有候選都來自同一高頻分支反而失去多樣性。這里有個反直覺點beam search的目標函數(shù)是“序列整體概率最高”這意味著它會優(yōu)先選語料里出現(xiàn)過的整句而不是有創(chuàng)意的句子。所以如果你想看到模型“創(chuàng)作”新詩而不是背誦原詩需要在采樣時引入溫度參數(shù)。溫度temperature是軟max里的縮放系數(shù)。在生成時我們把概率換成p_i exp(log(p_i)/T)再歸一化。T1時分布變平緩低概率字也有機會被選中T1時分布變陡峭趨向貪心。我用n-gram時一般不在beam search里加溫度因為beam search本身就是取top溫度會影響排序。正確做法是想要質量高、像古詩用beam searchT1想要花樣多、能“作詩”改用random sample并按溫度重新加權概率。兩種路徑我都保留在代碼里隨場景切換。另外一個容易忽略的參數(shù)是最大生成長度。詩歌接龍通常續(xù)3到4個字就能構成完整一句因為用戶輸入半句或者想續(xù)下一句。我的經驗是如果用戶輸入的是“床前明月”最大長度設為10讓它生成通順的七言如果用戶輸入的是完整“床前明月光”再接龍就是出下一句最大長度設為12也夠。長度太長會讓模型變散文失去詩的節(jié)奏感。到這里你已經有一個不開GPU也能跑的詩歌接龍腳本。它會讀古詩文件記住所有三字結合接著用戶輸入的半句詩返回一個續(xù)句。如果你想繼續(xù)提高生成質量下一步就該上深度學習了。4. 把接龍升到深度學習用LSTM做更自然的續(xù)句4.1 構造訓練數(shù)據(jù)滑動窗口生成特征與標簽深度學習的訓練數(shù)據(jù)不再是計數(shù)表而是整數(shù)序列。先把所有詩句拼接成一個長序列或者按句切割。我傾向于按句切割但為了學習上下文關系也可以把整首詩作為一個序列中間用分隔符連接。這里提供一種簡潔的按句切法def build_dataset(lines, seq_len5): # 建立字-id映射 chars sorted(set(.join(lines))) char2id {c: i for i, c in enumerate(chars)} id2char {i: c for c, i in char2id.items()} X, y [], [] for line in lines: ids [char2id[c] for c in line] # 用前seq_len個字預測后面所有字 for i in range(len(line) - seq_len): X.append(ids[i:iseq_len]) y.append(ids[iseq_len]) return X, y, char2id, id2char這段代碼的核心是滑動窗口。比如句子“床前明月光”轉成ids后seq_len3時[床,前,明]預測月[前,明,月]預測光。這個做法讓一條五言詩句產生兩個訓練樣本。注意我沒有把整首詩不分句來滑窗因為古詩的上下句之間有語義呼應但接龍通常只延續(xù)半句跨句訓練反而會讓模型去記憶下一句開頭降低泛化性。如果你要玩藏頭詩、要續(xù)一整首那再改成跨句滑窗。seq_len是模型看到的“上文長度”。n-gram里的n是固定的3LSTM里的seq_len可以靈活調。對七言詩seq_len設為7能覆蓋完整的一句但我發(fā)現(xiàn)seq_len5到6已經足夠因為接龍時用戶給的前綴通常不會太長而且LSTM內部有隱狀態(tài)理論上能記住更長信息。設太長會加大訓練負擔還會讓模型過于依賴前文的精確順序。4.2 搭建一個可訓練的中文詩歌LSTM模型這里用PyTorch搭一個兩層LSTM。詞表大小就是剛才的char2id長度一般幾百到一千。embedding維度我選128隱藏單元選256。下面是完整模型定義import torch import torch.nn as nn class PoemLSTM(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim256, num_layers2, dropout0.3): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim) self.lstm nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_dim, vocab_size) def forward(self, x): # x: (batch, seq_len) emb self.embedding(x) # (batch, seq_len, embed_dim) out, _ self.lstm(emb) # (batch, seq_len, hidden_dim) last out[:, -1, :] # 取最后一個時間步 logits self.fc(last) # (batch, vocab_size) return logits這里有幾個關鍵點batch_firstTrue讓輸入形狀變成(batch, seq_len, feature)這是PyTorch LSTM的習慣性寫法out[:, -1, :]只取序列最后一個時間步的輸出因為預測目標就是最后一個字之后的字。如果你用雙向LSTM這里要拼接前后兩個方向但古詩接龍只能看上文所以用單向LSTM更合理。訓練時用交叉熵損失但注意別把標簽做成one-hot直接用nn.CrossEntropyLoss()配合整數(shù)標簽就行。優(yōu)化器我常用Adam學習率設0.001。下面是訓練循環(huán)的骨架def train_model(model, X, y, epochs30, batch_size128, lr0.001): optimizer torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.CrossEntropyLoss() dataset torch.utils.data.TensorDataset( torch.tensor(X, dtypetorch.long), torch.tensor(y, dtypetorch.long) ) loader torch.utils.data.DataLoader(dataset, batch_sizebatch_size, shuffleTrue) for epoch in range(epochs): total_loss 0 for xb, yb in loader: model.zero_grad() logits model(xb) loss loss_fn(logits, yb) loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 5 0: print(fepoch {epoch1}, loss {total_loss/len(loader):.4f})訓練結束后保存model.state_dict()方便下次加載。注意embedding層會把每個字映射成一個向量LSTM學的是“在這種向量排列下下一個字通常是什么”。如果你的語料只有幾千首過擬合會很明顯所以訓練到loss降到一定值后就得停止不要追求完美。4.3 訓練與采樣loss降到多少才夠用訓練loss不是越低越好。我拿全唐詩五萬多首做實驗字符級詞表大約3000字在兩層LSTM 256隱藏單元下loss約在1.8左右就能穩(wěn)定生成通順句子降到1.5以下時模型開始傾向于復讀原句生成創(chuàng)新性下降。這個規(guī)律挺反直覺的——很多NLP項目追求loss越低越好但生成任務里loss太低等于記住了訓練集。采樣時深度學習模型不像n-gram有硬性的context匹配失敗問題因為它對任意輸入都能給出softmax分布。但正因如此沒有temperature調節(jié)的話它更容易生成高概率的常見字。我常用temperature0.8生成既能保住通順度又有一定創(chuàng)新。下面是采樣代碼def sample(model, prefix, char2id, id2char, seq_len, max_len20, temperature0.8): model.eval() # 把prefix補到seq_len長度 chars list(prefix) for _ in range(max_len - len(prefix)): # 取最后seq_len個字不夠則左側補起始符這里用第一個字的id補 context chars[-(seq_len-1):] if len(chars) seq_len-1 else chars if len(context) seq_len: pad [char2id[chars[0]]] * (seq_len - len(context)) context_ids pad [char2id[c] for c in context] else: context_ids [char2id[c] for c in context[-seq_len:]] # 確保長度正確 context_ids context_ids[-seq_len:] x torch.tensor([context_ids], dtypetorch.long) with torch.no_grad(): logits model(x)[0] # 溫度采樣 logits logits / temperature probs torch.softmax(logits, dim-1) next_id torch.multinomial(probs, 1).item() chars.append(id2char[next_id]) return .join(chars)采樣時每次只取最后seq_len個字作為輸入然后逐個生成。注意我用了torch.multinomial而不是argmax這是生成任務的關鍵——純argmax會生成大概率但無聊的句子multinomial按概率隨機抽配合temperature可以得到多樣且合理的輸出。如果你想讓輸出更像格律詩可以在采樣后加一個大體檢測檢查最后一個字的韻腳或者檢查每句字數(shù)。但這些都算是后處理放在進階技巧里展開。5. 詩歌接龍避坑指南數(shù)據(jù)、生成與環(huán)境的四個典型翻車點5.1 古詩庫的編碼坑繁體、異體和不完整的標點現(xiàn)象模型生成的句子全是簡體字但訓練語料里的“里”和“裏”被當成兩個不同字導致“故里”這個常見詞被拆散。更嚴重的是有的古詩庫直接從古文獻掃描OCR出來混有“丨”“丶”等生僻符號會拉高詞表大小浪費訓練空間。原因數(shù)據(jù)來源不是為機器學習準備的原始文本包含繁體、異體和噪聲符號。我一開始偷懶直接用原始語料跑結果詞表里冒出3000多個罕見的收字模型把所有精力用在區(qū)分這些低頻字上生成質量大打折扣。解決訓練前統(tǒng)一做三步先用opencc轉簡體再手動清掉非漢字字符最后過濾掉長度異常的行。這一步沒法全自動需要定期抽查樣本。血的教訓是別相信任何“已經清理好”的古詩下載包自己跑一遍清洗才放心。5.2 訓練時的loss不降學習率與序列長度現(xiàn)象LSTM訓練50個epochloss一直在3.0附近徘徊生成的全是“之乎者也”一類的高頻虛詞。原因最常見的是學習率太大導致loss震蕩或者學習率太小導致模型卡在較差局部解。另一個隱藏原因是序列長度太短比如seq_len2模型根本學不到語義。還有一個容易忽略的沒有加embedding的初始化或者輸入id從0開始但字典里有0填充導致padding干擾。解決先把學習率調到0.0005batch_size調小一點觀察loss是否有下降趨勢。如果序列長度小于5先改成5。如果還是不動檢查輸入數(shù)據(jù)是否真的被正確塑形用print(X[0], y[0])看一眼前后對應關系。我遇到過把X和y錯位一行的情況模型等于在猜隨機數(shù)loss當然不降。5.3 生成內容全是“的”“了”溫度太低與詞頻熵現(xiàn)象無論用戶輸入什么生成的下一句都是“春風得意馬蹄疾”這種高頻詩句或者反復輸出“的”“了”這種虛詞。原因古詩語料里“之”“人”“風”等字出現(xiàn)頻率極高模型學到的先驗分布被這些高頻詞主導。如果temperature設得太低比如0.2softmax輸出幾乎等同于argmax模型就只會選這些高頻詞。beam search的寬度如果設得太大也會導致候選全是高頻組合看起來反而單一。解決把temperature調到0.81.2多試幾次。另外可以在采樣時對高頻虛詞做降權比如把“的”“了”“之”的logits減去一個大常數(shù)。這個方法雖然粗暴但很有效。還有一個技巧如果使用beam search從5個候選中挑一個“不是最順口”的反而更有詩意。5.4 環(huán)境依賴numpy版本和Python 3.8的兼容問題現(xiàn)象代碼在一臺機器上跑得好好的換到另一臺裝了Python 3.8的機器上import torch直接報錯或者numpy操作報AttributeError: module numpy has no attribute float。原因PyTorch版本和numpy版本不匹配老版本numpy里np.float這種別名在numpy 1.24之后被移除了。很多詩歌接龍項目demo寫于兩三年前里面用了np.float、np.int在新環(huán)境里直接崩。解決給項目寫一個requirements.txt鎖定版本。我常用的組合是python3.8.18、numpy1.23.5、torch2.0.1。不要一上來就裝numpy 2.x除非你確認代碼里沒用舊別名。還有一個更穩(wěn)做法用from numpy import float這種地方全改成float或者干脆pip install numpy1.23.5一步到位。環(huán)境問題雖然不大但能卡你半天這真不是玄學。6. 進階技巧從單一接龍到藏頭詩與風格控制當你的接龍模型能穩(wěn)定續(xù)出通順句子時下一步可以讓輸出更“好玩”或者更“可控”。這里我給你三個我實際用過、效果明顯的小改造。第一個是藏頭詩生成。用戶指定四個字比如“山河無恙”要求每句開頭分別是這四個字。實現(xiàn)思路叫約束beam search在beam search擴展候選時對于每一句的第一個字符只允許候選等于指定字。具體來說你可以在生成前設置一個forced_chars列表第i句生成時將beam search的每一步查詢改為當生成的字符位置是句首時直接過濾掉不是目標字的候選。這樣做需要注意被強制選擇的字如果在模型里概率很低整句的log概率會比其他候選低于是beam search可能把整個分支剪掉。解決方法是放寬剪枝對被強制的字乘上一個放行權重或者干脆直接不讓概率參與排序只讓后面的詞決定排序。這種方法雖然簡單但能讓輸出每句都以指定字開頭實現(xiàn)成本不到20行代碼。第二個是風格控制。你想讓模型生成“李白一點”還是“杜甫一點”可以在訓練數(shù)據(jù)上下功夫把作者信息作為條件在輸入序列最前面加一個“風格標記”。比如對李白的詩輸入為“[李白]床前明月光”對杜甫的詩輸入為“[杜甫]國破山河在”。訓練時模型會把風格標記當作前文的一部分生成時你也把“[李白]”拼在輸入前綴前面。這樣同一個模型能感知不同詩人的用詞習慣算是很輕量的conditional language model。如果你不想重新訓練也可以在采樣后做規(guī)則過濾比如檢測是否含“劍”“酒”“月”——但規(guī)則永遠不如數(shù)據(jù)條件自然。第三個是韻律校驗。古詩接龍最讓人頭疼的是最后一句的韻腳。你可以準備一個韻表在生成最后一個字時只采樣壓韻母的字。做法很簡單把logits中不符合當前韻部的位置全部置為負無窮然后重新softmax。注意必須先確定本首詩壓什么韻通常是第一句的最后一個字決定。如果用戶輸入的是“床前明月光”最后生成的字必須壓“ang”韻于是“霜”“鄉(xiāng)”“望”“裳”成為候選。這個校驗可以放在溫度采樣之后也可以直接把采樣空間限制在韻部內。我的習慣是先限制韻部再采樣這樣既快又自然。我自己做過一版用藏頭詩LSTM的生成器翻車的地方在于強制字常常是“之”“以”這類虛詞導致整句變成“山之高兮水之長兮”雖然壓韻但不口語。后來我把強制字出現(xiàn)的概率閾值放寬甚至在候選為空時允許放棄該句改用次優(yōu)句才算穩(wěn)定下來。這是血淚經驗約束越多生成質量越容易崩需要給模型留一條“破例”的后路。這個項目做到最后你會發(fā)現(xiàn)詩歌接龍的核心價值不在于“寫詩有多像”而在于你對語言模型生成機制的完整理解——從計數(shù)到神經網絡從貪心到beam search從過擬合到溫度調節(jié)。這些經驗會直接遷移到其他生成任務比如切詞、評論生成、甚至簡單對話。希望我的這些踩坑記錄和參數(shù)習慣能幫到你少走點彎路。本文還有配套的精品資源點擊獲取