基于机器学习与NLP的《红楼梦》作者风格量化分析实践 1. 项目概述当数学建模遇见《红楼梦》最近在整理过往的参赛项目时翻到了一个挺有意思的旧作——用数学模型来分析《红楼梦》的作者问题。这听起来像是文理交叉的“跨界”研究实际上也确实如此。这个项目的核心就是尝试用量化的、可计算的方式去触碰一个经典的文学考据难题《红楼梦》前八十回与后四十回在写作风格和语言特征上是否存在显著差异这种差异是否足以支撑“曹雪芹著前八十回高鹗续后四十回”的主流观点或者说我们能否从文本数据本身找到一些客观的证据对于数学建模的参赛者或者爱好者来说这个题目提供了一个绝佳的实践场景。它不像传统的物理、工程问题那样有明确的公式和边界你需要自己从浩如烟海的文本中定义“特征”构建“模型”并解释“结果”。整个过程充满了不确定性但也正是这种探索性让项目充满了魅力。它考验的不仅仅是你对聚类分析、主成分分析PCA、支持向量机SVM等算法的掌握更考验你如何将感性的文学语言转化为理性的数学向量以及如何让冷冰冰的统计结果回归到有温度的文学阐释。如果你对数据挖掘、自然语言处理NLP的初级应用感兴趣或者正在寻找一个有别于常规赛题的数学建模练手项目那么跟随这篇内容复盘一下从数据爬取、特征工程到模型构建与评估的全过程应该会有所收获。我们会避开深奥的文学理论争论专注于“如何做”和“为什么这么做”分享在实际操作中踩过的坑和总结出的有效经验。2. 核心思路与模型选型定义文本的“数学指纹”面对《红楼梦》一百二十回的文本我们的首要任务是为每一回文字建立一个数学上的“特征向量”或者说“数字指纹”。这个指纹要能捕捉到作者在无意识中流露出的写作习惯比如用词偏好、句法结构、虚词频率等。思路的核心在于如果前后作者是同一人那么所有章回的特征向量在特征空间中的分布应该是均匀或连续的反之如果存在两个作者则向量可能会自然地聚成两个簇并且分界线大致在第八十回附近。2.1 特征工程从文字到数字特征提取是整个项目的基石特征选得好不好直接决定了模型的上限。我们当时尝试了多种特征主要分为以下几类词频特征这是最基础也是最重要的特征。但并非所有词都有用。我们首先进行了停用词过滤去掉“的”、“了”、“在”等极高频但无区分度的词然后分别统计了高频实词选取前N个如N500在全书中出现频率最高的名词、动词、形容词。这些词往往能反映作者的常用词汇库。特色虚词/功能词如“之”、“乎”、“者”、“也”、“矣”等文言虚词以及“便”、“就”、“却”、“倒”等副词在现代汉语中的使用频率。语言学研究表明功能词的使用习惯非常稳定几乎不受内容影响是作者风格的重要标志。特定词类比例如每回中副词占总词数的比例、连词的比例等。句法复杂度特征平均句长以标点句号、问号、感叹号分割句子计算每回的平均句子长度以字数为单位。这可以粗略衡量作者造句的繁简习惯。长句比例定义句子长度超过某个阈值如50字的句子为长句计算其占比。段落与结构特征段落平均长度以换行为段落分隔计算平均段落长度。对话占比通过识别引导对话的标点如“道”、“说”后接冒号引号来近似估算对话内容的比例。这可以反映叙事风格的差异。注意特征不是越多越好。一开始我们陷入了“特征越多越全面”的误区提取了上百个特征结果导致维度灾难且引入了大量噪声。后来通过特征相关性分析和后续的PCA降维发现很多特征是高度相关的真正有效的独立特征可能就二三十个。2.2 模型选型如何发现“分界线”有了特征向量我们需要模型来发现其中的模式。我们采用了由浅入深的多模型验证策略以增强结论的说服力。无监督学习聚类分析目的在不告知模型任何章回信息如第几回的情况下让模型自动将所有120回文本分成若干类。方法主要使用了K-means聚类和层次聚类Hierarchical Clustering。预期如果聚类结果恰好将前80回和后40回大致分开这就是一个非常有力的客观证据。我们特别关注当设定聚类数K2时的结果。有监督学习分类模型目的假设前80回为作者A后40回为作者B用这部分数据训练一个分类器然后看这个分类器能否准确区分它们。更重要的是观察模型在“学习”过程中更依赖哪些特征。方法使用了支持向量机SVM和随机森林Random Forest。SVM擅长处理高维数据可以找到将两类样本最大程度分开的超平面。我们可以通过观察支持向量和权重了解哪些特征在区分中起了关键作用。随机森林除了能提供分类结果还能输出特征的重要性排序Feature Importance这直接告诉我们从模型的角度看哪些语言特征对辨别“前后作者”贡献最大。降维与可视化主成分分析PCA目的我们的特征向量可能是几十维的人眼无法直接观察。PCA可以将高维数据压缩到2维或3维并保留最主要的变异信息。应用将120回文本的降维结果画在二维散点图上用不同颜色标记前80回和后40回。如果能在图中看到明显的分离趋势那将是最直观的证据。这个多模型框架的设计思路是无监督聚类提供“是否存在自然分组”的客观发现有监督分类验证“假设的分组是否可被模型有效学习”降维可视化则提供人类可直观理解的证据。三者结论若能相互印证则论证力度大大增强。3. 实操流程与关键技术实现下面我将以Python为主要工具拆解整个分析流程的关键步骤。这里会包含一些核心代码片段和当时的具体参数选择。3.1 数据准备与预处理首先你需要一份干净、分回目的《红楼梦》电子文本。可以从权威的电子古籍网站获取。import re import jieba from collections import Counter # 1. 读取文本按回分割 def split_into_chapters(text): # 使用正则表达式匹配“第xx回”作为分割点 pattern r第[一二三四五六七八九十百]回 chapters re.split(pattern, text)[1:] # 第一个元素是分割前的空文本或前言去掉 # 简单清理每回文本的头部尾部空白和常见非正文内容如回目名本身 cleaned_chapters [] for chap in chapters: # 去除首尾空白并可能去除前几行可能包含回目名重复 lines chap.strip().split(\n) content \n.join(lines[2:]) if len(lines) 2 else chap.strip() # 假设前两行是回目 cleaned_chapters.append(content) return cleaned_chapters with open(hongloumeng_full.txt, r, encodingutf-8) as f: full_text f.read() all_chapters split_into_chapters(full_text) # 得到一个包含120个字符串的列表 print(f总章回数{len(all_chapters)})3.2 特征提取的具体实现我们以“词频特征”和“平均句长”为例展示特征提取函数。import jieba import numpy as np # 自定义停用词列表需根据实际情况扩充 stopwords set([的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 要, 去, 你, 会, 着, 没有, 看, 好, 自己, 这, 那, 他, 她, 它]) def extract_features_for_chapter(chapter_text, top_common_words): 为单一章回文本提取特征。 top_common_words: 预先从全书中统计出的前N个高频实词列表作为固定词表。 features {} # 1. 分词并过滤停用词 words [w for w in jieba.lcut(chapter_text) if w not in stopwords and w.strip()] # 2. 高频实词词频特征 word_freq Counter(words) for word in top_common_words: features[fword_{word}] word_freq.get(word, 0) / len(words) if len(words) 0 else 0 # 归一化为频率 # 3. 特色虚词频率示例 function_words [之, 乎, 者, 也, 矣, 便, 就, 却, 倒, 竟] for fw in function_words: # 注意这里直接在整个文本中计数未分词因为虚词通常是单字 features[ffunc_{fw}] chapter_text.count(fw) / len(chapter_text) if len(chapter_text) 0 else 0 # 4. 平均句长 # 简单以中文句号、问号、感叹号分割句子 sentences re.split(r[。], chapter_text) sentences [s.strip() for s in sentences if len(s.strip()) 0] if sentences: avg_sentence_len np.mean([len(s) for s in sentences]) else: avg_sentence_len 0 features[avg_sentence_length] avg_sentence_len # 5. 副词占比需要词性标注这里简化处理用一个副词列表匹配 # 实际操作中建议使用jieba.posseg或更专业的NLP工具 # 此处仅示意 adverb_list [很, 非常, 都, 就, 才, 不, 没有, 也, 还, 再] adverb_count sum([1 for w in words if w in adverb_list]) features[adverb_ratio] adverb_count / len(words) if len(words) 0 else 0 return features # 首先需要获取全书的“高频实词词表” all_words [] for chap in all_chapters: all_words.extend([w for w in jieba.lcut(chap) if w not in stopwords and w.strip()]) # 统计全词频取前300个作为特征词表 global_word_freq Counter(all_words) top_common_words [word for word, _ in global_word_freq.most_common(300)] # 为每一回提取特征 feature_list [] for i, chap_text in enumerate(all_chapters): feats extract_features_for_chapter(chap_text, top_common_words) feats[chapter_index] i 1 # 记录章回序号 feature_list.append(feats) # 将特征列表转换为DataFrame假设使用pandas import pandas as pd df_features pd.DataFrame(feature_list)3.3 模型构建与训练特征数据准备完成后我们分别进行聚类和分类分析。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans, AgglomerativeClustering from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.decomposition import PCA import matplotlib.pyplot as plt # 准备数据 X df_features.drop(columns[chapter_index]).values # 特征矩阵 y np.array([0 if i 80 else 1 for i in range(120)]) # 标签前80回为0后40回为1用于有监督学习 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 标准化对基于距离的模型非常重要 # 1. K-means聚类 (K2) kmeans KMeans(n_clusters2, random_state42, n_init10) cluster_labels kmeans.fit_predict(X_scaled) # 查看聚类结果与前80/后40的吻合度 df_features[kmeans_label] cluster_labels # 计算前80回中被归为同一类的比例假设标签0是主要前80回簇 majority_label_in_first_80 df_features[df_features.chapter_index 80][kmeans_label].mode()[0] accuracy_kmeans (df_features[df_features.chapter_index 80][kmeans_label] majority_label_in_first_80).mean() print(fK-means聚类下前80回内部一致性: {accuracy_kmeans:.2%}) # 2. 层次聚类可视化树状图 from scipy.cluster.hierarchy import dendrogram, linkage Z linkage(X_scaled, methodward) # 使用Ward方法 plt.figure(figsize(12, 6)) dendrogram(Z, labelsdf_features[chapter_index].values, leaf_rotation90) plt.title(Hierarchical Clustering Dendrogram (Redwood)) plt.xlabel(Chapter Number) plt.ylabel(Distance) # 在y某个距离例如15处画一条水平线可以观察如果分成两类分割点在哪 plt.axhline(y15, ck, linestyle--) plt.show() # 3. 支持向量机 (SVM) svm_clf SVC(kernellinear, random_state42) # 使用线性核便于查看特征权重 svm_clf.fit(X_scaled[:80], y[:80]) # 仅用前80回训练模拟已知作者A风格 # 预测所有120回 y_pred_svm svm_clf.predict(X_scaled) # 评估在整个120回上的“分类”效果注意这不是真正的泛化测试因为后40回可能风格不同 from sklearn.metrics import classification_report print(SVM分类报告以前80回为训练集预测全部120回:) print(classification_report(y, y_pred_svm, target_names[Chaps 1-80, Chaps 81-120])) # 4. 随机森林 rf_clf RandomForestClassifier(n_estimators100, random_state42) rf_clf.fit(X_scaled[:80], y[:80]) # 同样仅用前80回训练 y_pred_rf rf_clf.predict(X_scaled) print(随机森林分类报告:) print(classification_report(y, y_pred_rf, target_names[Chaps 1-80, Chaps 81-120])) # 获取特征重要性 feature_names df_features.drop(columns[chapter_index, kmeans_label]).columns importances rf_clf.feature_importances_ indices np.argsort(importances)[::-1][:10] # 取前10个最重要的特征 print(\n随机森林特征重要性 Top 10:) for i in indices: print(f{feature_names[i]:30} {importances[i]:.4f}) # 5. PCA降维可视化 pca PCA(n_components2) X_pca pca.fit_transform(X_scaled) plt.figure(figsize(10, 8)) colors [blue if i 80 else red for i in range(120)] plt.scatter(X_pca[:, 0], X_pca[:, 1], ccolors, alpha0.6, edgecolorsw, s50) for i, (x, y) in enumerate(X_pca): plt.annotate(str(i1), (x, y), fontsize8, alpha0.5) plt.xlabel(Principal Component 1 (解释方差: {:.2f}%).format(pca.explained_variance_ratio_[0]*100)) plt.ylabel(Principal Component 2 (解释方差: {:.2f}%).format(pca.explained_variance_ratio_[1]*100)) plt.title(PCA of Chapter Features (Blue: 1-80, Red: 81-120)) plt.grid(True, alpha0.3) plt.show()4. 结果解读与文学意义阐释跑完模型得到一堆数字和图表后最关键也最困难的一步来了如何解读这些结果并将其与文学问题联系起来这需要谨慎和克制。4.1 典型结果分析根据我们当时的多次实验通常会观察到以下一些现象具体数值因文本版本、特征选择、预处理方式而异聚类结果K-meansK2通常能将大部分前80回和后40回分开但交界处70-85回常有几回“分错”。层次聚类的树状图在较高距离切割时会形成一个包含绝大部分前80回的大分支和另一个包含绝大部分后40回的大分支但同样存在交叉。这说明从整体统计风格上看前后部分存在可区分的“簇”但边界是模糊的并非一刀切。这或许反映了续作者高鹗在极力模仿曹雪芹风格但某些深层次习惯仍有差异也可能暗示原作后部分或传抄过程中存在复杂情况。分类模型表现SVM和随机森林在用前80回训练后对后40回的“分类”准确率通常能达到80%-90%。这强烈表明模型学习到了一种能够区分前后文本的模式。更重要的是随机森林给出的特征重要性排名常常显示某些虚词如“便”、“了”的频率、平均句长等特征贡献度最高。这为语言学家的定性研究提供了数据支持。PCA可视化这是最直观的部分。在二维散点图上我们往往能看到蓝色点前80回和红色点后40回各自相对聚集中间有部分重叠区域。前80回的点通常分布更紧凑而后40回的点可能分布稍散。这直观印证了“两个群体”的存在以及它们之间的过渡与混杂。4.2 从数据回归文学我们能下什么结论数学模型给出的是一种概率性的、基于统计规律的证据而不是“铁证”。在论文中我们需要进行严谨的表述避免绝对化不能说“模型证明了前后作者不同”而应该说“模型分析结果显示前八十回与后四十回在多项语言特征上存在统计显著性差异这种差异与‘不同作者创作’的假设相符”。指出局限性文本一致性分析基于特定电子版本不同版本程甲本、程乙本等的异文会影响结果。内容影响小说前后部分内容主题、人物对话密集度不同如前部分更多描写后部分更多叙事收尾可能影响某些特征如对话占比。特征代表性我们选取的特征是否真的抓住了“作者风格”的本质是否忽略了更复杂的句法、叙事结构特征模型假设聚类和分类模型本身有其假设比如K-means假设簇是凸形的。提出多元解释数据差异可能源于1不同作者2同一作者在不同时期风格演变3后续整理、抄录者的大规模修改4前后内容本身的内在需求不同。我们的工作是为第一种可能性增加了定量化的支持但并不能排除其他可能性。实操心得在论文的“结论”部分一定要留出足够篇幅讨论模型的局限性和结果的多种解释可能。评委或读者非常看重这种批判性思维。一个成熟的建模者不仅要会展示模型的“成功”更要能清晰地界定其能力的边界。5. 项目深化与常见问题排查一个基础的模型跑通后这个项目还有很多可以深化的方向这也是让论文脱颖而出的关键。5.1 如何提升模型说服力与深度引入更先进的NLP特征N-gram语言模型计算每个章回的语言模型概率比较前后部分语言模型的困惑度Perplexity差异。词向量与主题模型使用Word2Vec或BERT获取词向量再计算章回的平均向量或使用Doc2Vec应用LDA主题模型分析前后部分主题分布的差异。句法树特征使用依存句法分析提取平均依存距离、特定依存关系比例等更复杂的句法特征。滑动窗口分析与突变点检测不要只做“前80 vs 后40”的粗暴二分。将120回视为一个时间序列用一个固定大小的窗口如10回滑动计算窗口内文本的特征值如“之”字频率绘制其变化曲线。观察曲线在80回附近是否存在明显的“跃迁”或“断点”。可以使用专门的变点检测算法如PELT。风格一致性检验将前80回随机分成两组用同样的模型检验它们之间的差异再将前80回作为一组与后40回比较差异。如果组内差异远小于组间差异则结论更可靠。5.2 常见问题与解决方案速查表在实际操作中你几乎一定会遇到下面这些问题问题现象可能原因排查与解决方案聚类/分类效果很差前后完全混在一起1. 特征选择不当没有捕捉到风格差异。2. 特征未标准化量纲差异大的特征主导了距离计算。3. 文本预处理不一致如分回错误、编码问题。1.检查特征重点引入功能词频率、句长等稳定特征。使用随机森林的特征重要性功能剔除不重要特征。2.务必标准化使用StandardScaler对特征矩阵进行Z-score标准化。3.复核数据人工抽查几回文本确保分割正确无大量非正文字符。PCA图上前后部分分离不明显1. 前两个主成分包含的方差比例太低不足以反映主要差异。2. 特征间的噪声太大。1. 查看pca.explained_variance_ratio_如果前两个成分和小于60%考虑只查看第一主成分与章回序号的折线图或使用t-SNE等非线性降维方法需谨慎解释。2. 进行特征选择去除相关性高或方差极小的特征。模型在80回附近“误判”严重1. 这是正常现象可能反映了风格的渐变或内容的过渡。2. 某些章回如公认较特殊的第67回本身风格有异。1.深入分析这些章回单独查看这些“误判”章回的特征值看它们是否在某些特征上更接近另一部分。这可能是最有趣的发现2.进行鲁棒性分析尝试移除这些边界章回后再训练模型观察核心部分如1-70回 vs 81-120回的分离是否更清晰。随机森林特征重要性显示的内容词如人物名排名很高这可能是由内容主题差异导致的而非作者风格差异。例如后40回“贾母”、“宝玉”出现频率变化可能只因剧情发展。过滤内容相关特征在特征工程阶段应尽量避免使用与具体情节、人物强相关的高频实词。专注于功能词、句法特征等与内容无关的“风格标记”。可以尝试仅用虚词特征重新建模观察效果。不同算法结论不一致不同模型对数据结构和噪声的敏感度不同。采用集成或多数表决不要依赖单一模型。综合K-means、层次聚类、SVM、随机森林等多种方法的结果。如果大多数方法都指向相似的结论那么结论的可靠性就更高。在论文中展示这种多模型一致性。5.3 论文写作要点与避坑指南最后如果你想将这项研究写成一篇数学建模论文或报告以下几点至关重要问题重述要清晰开篇明确你要用数学模型解决《红楼梦》作者研究中的哪个具体问题风格一致性检验并指出定量分析相对于传统定性分析的价值与局限。假设条件要列明明确列出你的核心假设例如“假设作者的个人写作风格可以通过其使用功能词、句法结构的统计特征稳定地表征”“假设我们采用的电子文本版本能代表原作的面貌”等。流程图是必备的用一张清晰的流程图展示你的整体技术路线数据获取→预处理→特征工程→模型构建聚类、分类、降维→结果分析→结论。可视化图表要精致PCA散点图、层次聚类树状图、特征重要性条形图、滑动窗口变化曲线图等都要精心绘制确保清晰美观并有详细的图注说明。敏感性分析讨论你的结论对关键参数如选取的高频词数量N、聚类数目K、分类器参数的敏感度。如果改变这些参数主要结论是否依然成立这能体现模型的稳健性。结论部分要辩证重申你的主要发现但必须紧接着讨论本研究的局限性并对未来工作提出展望如使用更深的语言模型、结合更多版本进行对比分析等。这个项目最大的魅力在于它是一场在文理边界上的有趣探险。模型给出的数字和图表就像一台望远镜让我们得以从一个新的角度眺望那座名为《红楼梦》的文学高峰。它不能替代深入的文本细读和严谨的文史考据但它提供了一种补充的、客观的视角。当你看到PCA图上那些点簇分离时你仿佛能感受到两个不同灵魂在文字深处留下的、细微却可追溯的指纹。这种通过代码和算法与经典对话的过程本身就是一种极具成就感的体验。