2022年认证杯SPSSPRO杯数学建模
B题 唐宋诗的定量分析与比较研究
原题再现:
唐诗和宋诗在文学风格上有较为明显的区别,这一点在古代文学研究中早有定论。所以唐诗和宋诗有时甚至会直接指代两类不同的诗作风格。历史学家缪钺在《论宋诗》一文中说:“唐诗以韵胜,故浑雅,而贵蕴藉空灵;宋诗以意胜,故精能,而贵深折透辟。唐诗之美在情辞,故丰腴;宋诗之美在气骨,故瘦劲。唐诗如芍药海棠,秾华繁采;宋诗如寒梅秋菊,幽韵冷香。……譬诸游山水,唐诗则如高峰远望,意气浩然;宋诗则如曲漳寻幽,情境冷峭。唐诗之弊为肤廓平滑,宋诗之弊为生涩枯淡。虽唐诗之中,亦有下开宋诗派者,宋诗之中,亦有酷肖唐人者;然论其大较,固如此矣。”在钱钟书的《谈艺录》中也谈到“诗分唐宋”的问题:“唐诗、宋诗,亦非仅朝代之别,乃体格性分之殊。天下有两种人,斯分两种诗。唐诗多以丰神情韵擅长,宋诗多以筋骨思理见胜。严仪卿首倡断代言诗,《沧浪诗话》即谓‘本朝人尚理,唐人尚意兴’云云。曰唐曰宋,特举大概而言,为称谓之便,非曰唐诗必出唐人,宋诗必出宋人也。故唐之少陵、昌黎、香山、东野,实唐人之开宋调者;宋之柯山、白石、九僧、四灵,则宋人之有唐音者。”他们的观点都认为唐诗和宋诗并非只有年代上的区分,而且在文学风格上也代表着完全不同的类型,甚至有个别唐朝诗人的诗作是宋诗的风格,而个别宋朝诗人的诗作更接近唐诗。
我们试图使用定量分析的手段来研究唐宋诗之差异。附件中包含了《全唐诗》收录的 5 万余首诗,《全宋诗》收录的约 26 万首诗。为了研究唐诗与宋诗在风格上的差异,请你建立合理的数学模型,研究如下问题:
第一阶段问题:
1. 请研究诗中出现的常见字(词),研究是否能够通过比较字(词)频上的差异来区分不同诗人的风格。请注意,由于诗的特殊格式限制,所以诗
作中的字词用法与散文或日常语言中可能会有不同。
2. 如果有一对字(词)在同一首诗中(或同一句中)同时出现,我们可以认为它们之间具有某种关联,以下将其称为字词关联。请统计不同时代的诗中的字词关联,并研究这项指标是否能够体现诗作时代的变化。
3. 请设计一个或多个有效的指标,来衡量唐诗和宋诗的风格差异。如果能给研究带来便利的话,我们也可以考虑选择唐代和宋代的某些风格强烈且时间距离较远的时期(例如初唐时期和南宋时期)的诗作来进行比较研究。
4. 请分别选出 50 首风格最具代表性的(并非文学成就最高的)唐诗和宋诗,并说明选择标准的合理性。
整体求解过程概述(摘要)
针对问题一,我们选择分词工具 jieba 对诗进行分词,并使用 Python 将诗词中的繁体字转化为简体字。我们在分词时采取了逐字切分为主、逐词切分为辅的处理方式,同时去掉了一些常见的虚词、代词、介词、语气词等无明显含义的字词。最后,唐诗和宋诗的常见字(词)分别为 210 个和 230 个。
在得到常见字后,运用 LDA 主题模型对所有的常见字(词)分为 8 类主题,然后统计每个诗人的诗在这些主题下的分布情况,使用 K-Means 聚类模型将唐朝诗人和宋朝诗人分为 5 种和 2 种风格,最后使用轮廓系数和 CH 分数对模型进行检验。
针对问题二,在检索字词关联时遵循两个原则:①两个常见字(词)只要在同一首诗中出现一次,则相应的字词关联频数就加 1;②若没有出现则频数记为 0。我们分别得到唐诗关联字词 107 个、宋诗关联字词 112 个。在得到字词关联后,运用 LDA 主题模型将唐诗和宋诗的字词关联分为 7 类和 5 类,然后统计各朝代的诗在这些主题下频数的分布情况。为了更直观准确地对比唐宋代的特色差异,我们将频数转化成了百分比并减去平均百分比,然后通过比较大小得到唐宋诗作的变化,具体结果请详见图 5-4。
针对问题三,我们基于 TF-IDF 算法生成新指标——关键字(词)。该指标同时兼顾了衡量诗的共性与个性。在得到关键字(词)后,运用 LDA 主题模型将唐诗和宋诗的关键字(词)分为 5 类和 4 类,然后统计各朝代的诗在这些主题下频数的分布情况。我们按照问题二的做法将频数进行转化、计算,并通过比较大小得到唐宋诗作的变化,变化情况请详见图 6-6。最后,我们将问题二、问题三的结果两两比较,认为关键字(词)可以衡量唐诗和宋诗的风格差异。
针对问题四,为了把唐代和宋代中最具风格的 50 首诗挑选出来,我们利用熵权法综合评价模型,把第三问得到的每首诗在不同主题下的关键字(词)频数的指标,作为风格的评价指标,得到每首诗风格显著程度的分数,按照从大到小排序后,选取前 50 首诗,唐诗的前三首依次是李白的《梦游天姥吟留别》、杜甫的《自京赴奉先县咏怀五百字》、杜甫的《茅屋为秋风所破歌》,具体见表 7-1,宋诗的前三首依次是苏轼的《题西林壁》、刘克庄的《归至武阳渡作》、邵雍的《寄谢三城太守韩子华舍人》,详细排名见表7-2。
模型假设:
1.假设所有字词在诗中等概率出现。
2.假设所有数据是真实准确的,不存在错别字。
3.假设虚词、代词、介词、语气词等无明显含义的字词无法构成具有重要含义的字词,可以直接剔除。
模型的建立与求解
问题分析
问题一分为两个步骤:一是检索常见字(词),二是根据字的字频差异来区分诗人的风格。针对第一个步骤,首先需要将附件中的诗句进行分词,由于唐诗和宋诗数目巨大、检索繁重,因此我们选择当前十分流行的分词工具 jieba 对诗进行分词;其次,我们发现部分诗词中存在繁体字,这会对文字识别产生干扰,进而影响分词效果,所以需要将其转化为简体字;最后,对于古汉语(文言文),尤其是诗的分词处理并不是看上去那么简单,因为单字词约占古汉语词汇统计信息的 80%以上,再加上古汉语微言大义,字字千钧,所以针对现代汉语的分词技术往往不适用于它。鉴于此种情况,我们在分词时采取了逐字切分为主、逐词切分为辅的处理方式。同时去掉了一些常见的虚词、代词、介词、语气词等无明显含义的字词。
在得到常见字后,第二个步骤的具体操作如下:第一,运用 LDA 主题模型将所有的常见字(词)分为 n 类;第二,使用 Python 统计每首诗中 n 类常见字(词)出现的频数,然后将同一诗人的诗进行加总,从而得到每个诗人的 n 类常见字(词)出现频数;第三,每个诗人的 n 类常见字(词)出现频数宛如诗人的 n 项“属性值”,因此我们可以据此应用 K-Means 聚类模型划分诗人的风格
本题具体分析流程如下:
常见字(词)的检索
本文在参考 README 文件以及熟悉掌握 json 文件中数据的格式及含义后,分别对唐诗、宋诗进行文本分析,运用 Python 中的 jieba 库对 57000 余首唐诗和 254000 余首宋诗进行分词,并统计每个字词出现的次数。为了使常见词的意义更加鲜明,我们对得到的字词进行了筛选,剔除了标点符号、介词、语气词以及代词等没有诗词意象的字词,部分被剔除的字词展示如下:
原始样本在经过分词处理和剔除无用字词后,得到了唐诗和宋诗的全部字词以及频数。在参考网上的有关文章[3]和相关文献[4]后,本文又根据唐诗和宋诗的样本量大小、字词总量以及字词频数的分布情况,决定唐诗选取频数大于等于 500 的字词作为常见字(词),共 210 个,其中出现最多的分别是月;宋诗选取频数大于等于 2000 的字词作为常见字(词),共 238 个,其中出现最多的分别是人。唐诗、宋诗的常见字(词)结果如下图所示:
基于 LDA 主题模型对常见字(词)分类
LDA(Latent Dirichlet Allocation)是一种文档主题生成模型[5][6],也称为三层贝叶斯概率模型,是一种非监督机器学习技术,可以用来识别大规模文档集或语料库中潜藏的主题信息,包含词、主题和文档三层结构。所谓生成模型,就是说,我们认为一篇文章的每个词都是通过“文章以一定概率选择了某个主题,并从这个主题中以一定概率选择某个字词”这样一个过程得到。文档到主题服从多项式分布,主题到词服从多项式分布。
在 LDA 模型中,工作原理[7]如下所示:
Step1:对于每个文档,随机将每个字词初始化为?个主题中的一个(事先选择?个主
题)。
Step2:对于每个文档?,浏览每个字词?并计算:
(1)?(? | ?):文档?中,指定给主题?的字词的比例。
(2)?(? | ?):所有包含字词?的文档中,指定给主题?的比例。
Step3:考虑所有其他字词及其主题分配,以概率?(?|?) = ?(? | ?)´ ?(? | ?)将字词?与主题?重新分配。
图示如下:
LDA 模型的求解
我们运用 Python,并结合前面得到的常见词,再次对唐诗、宋诗进行遍历、检索及统计,从而得到每首诗中常见词出现的次数,将其代入到 LDA 主题模型中求解。目前,评价 LDA 主题模型好坏的指标主要有困惑度和主题一致性,困惑度越低或者主题一致性越高则说明模型越好,反之则越坏。困惑度表示一篇文章属于某个主题的确定度,当分类标准越细致、主题的个数越多时,诗属于哪个主题的确定度就越高,模型的困惑度也会越低。主题一致性衡量的是契合所属主题的程度,当一首诗的特征越符合所属主题,该指标的值越大,反之则越小。另外,值得注意的是,当主题数量过多时,生成的 LDA 模型往往会存在过拟合问题,故不能仅依靠困惑度来判断一个模型的好坏。因此,本文首先从困惑度角度确定一个主题数目的区间,再使用一致性指标从区间内选取最合适的主题,即最优主题数。主题一致性随主题数目变化的曲线如下图所示:
通过观察困惑度的变化,我们确定本文主题个数的选取区间锁定在(3,20)内,之后将
区间内的主题个数依次代入到 LDA 模型中,然后通过比较主题一致性的大小,选取常见字(词)的最优主题数。最终,唐诗和宋诗的最优主题数分别确定为 8 个和 6 个,对应
的主题一致性分别为 0.62 和 0.71。
通过 Python 中的 pyLDAvis 库分别制作唐诗、宋诗的主题气泡图,气泡图右侧的红色柱形图会随着选择不同的圆而发生变化。本文以唐诗为例进行展示,如图 4-7。
气泡图中的每个圆代表不同的主题,圆的大小代表每个主题所占比例的高低,不同圆之间的距离代表主题的相似性程度,若有的圆之间存在交集,说明有字词同时在这几个主题中。由图可知,最大的是第Ⅰ类主题,占比为 16.7%;最特殊的是第Ⅷ类主题,因为代表它的圆距离其他圆最远。
气泡图中的柱形图代表主题含有的常见字(词)的频数,其中蓝色柱形图统计所有主题中占比前 30 的常见字(词)的频数,红色柱形图统计的是单个主题中常见字(词)的频数(本图红色柱形图展示的是第Ⅰ类主题)。可以发现,所有主题中常见字(词)占比最高的是人,其次是山、云,而第Ⅰ类主题中常见字(词)中占比最高的是秋,其次是月、夜。
最后,通过选取气泡图中不同的圆,我们可以得到每个主题中占比最高的常见字(词),然后将其所代表的的意象寓意作为该主题的风格。
基于 K-Means 聚类划分唐宋诗人风格
分别对唐诗和宋诗进行如下操作:
①检索所有诗,统计每首诗中常见字(词)出现的频数;
②针对每首诗的常见字(词),把属于同一主题下的常见字(词)汇总,并将其频数求和,得到每首诗主题的频数(如唐诗有 8 类主题,每首诗要统计 8 类主题各自的频数)。
③把属于同一诗人的诗汇总,并将诗的各主题的频数求和,从而得到每个诗人主题的频数。
④根据诗人主题的频数,使用 K-Means 模型对诗人聚类。
⑤找出每一类中频数最多的主题,将其作为此类的代表主题,其代表的风格即为属于此类诗人的风格。最终结果如表 4-2 所示:
在检索字词关联方面,由于非常见字频数较低,且预计两两组合后的频数会更低,因此我们决定只考察常见字中产生的字词关联;在统计字词关联方面,我们遵循以下两个原则:
①两个常见字(词)只要在同一首诗中出现一次,则相应的字词关联的频数就加 1;
②若没有出现则频数记为 0。
在得到字词关联后,运用 LDA 主题模型将字词关联分为几个主题,然后统计每首诗各主题的字词关联频数,再累加求和到唐宋朝代层面,从而衡量唐诗和宋诗的风格差异。
问题二的总体思路如图 5-1 所示:
关联字词的检索
针对第一问得到的常见字(词),分别对唐诗、宋诗进行以下判断:
①两个常见字(词)只要在同一首诗中出现一次,则相应的字词关联的频数就加 1;
②若没有出现则频数记为 0。
上述做法共得到唐诗关联字词 107 个,宋诗关联字词 112 个。唐诗中出现次数排名前三的关联字词是月还、欲远、人归,宋诗中出现次数排名前三的关联字词是人月、人归、月年。
基于 LDA 主题模型分析唐宋诗作的变化
与第一问的做法相似,我们运用 Python,并结合前面得到的字词关联,再次对唐诗、宋诗进行遍历、检索及统计,从而得到每首诗中字词关联出现的次数,将其代入到 LDA主题模型中求解,然后根据困惑度大小确定一个主题数目的区间,最后使用一致性指标从区间内选取最合适的主题,即最优主题数。主题一致性随主题数目变化的曲线如下图所示:
通过观察困惑度的变化,我们确定本文主题个数的选取区间锁定在(3,20)内,之后将区间内的主题个数依次代入到 LDA 模型中,然后通过比较主题一致性的大小,选取字词关联的最优主题数。最终,唐诗和宋诗的最优主题数分别确定为 7 个和 5 个,对应的主题一致性分别为 0.77 和 0.59。
与问题一的做法相似,我们根据关联字词统计每首诗主题的频数,然后将诗各主题的频数加总到朝代层面,得到了唐代、宋代主题的频数。
为了更直观准确地对比唐宋代的差异,我们进行了两步处理:第一,考虑到唐诗和宋诗的样本量不同,我们将频数转化成了百分数;第二,考虑到唐诗和宋诗主题数不同,我们又对转化得到的百分数减去平均百分数,以宋诗为例,就是减去 20%。统计结果如下图所示:
由图可知,从唐代过渡到宋代,山水诗、怀古诗的比例出现下降,其中以山水诗下降幅度最大;而咏物诗、送别诗以及行旅诗的比例出现上升,其中以咏物诗上升幅度最大。通过查阅网上资料得知,唐诗大多用字清澹高华与含蓄,诗味较浓,寄情山水和金戈铁马的特征明显,大都反映大唐诗人志趣高远、投效报国的情怀;而宋诗则受政治、经济和战争等因素的影响,表现的多是儿女情长、生活点滴,其诗倾向于议论化、散文化,擅长引据经典。因此,我们的结果与真实的唐、宋诗情况基本相符,故该指标能够体现唐宋诗作时代的变化。
我们发现,无论是第一问通过常见字(词)的频数来衡量不同诗人的风格,还是第二问通过字词关联的频数来说明诗作时代的变化,都是以常见字(词)为核心来展开分析的,可见常见字(词)对评估诗的特征具有非常重要的意义。但与此同时,常见词更多地是体现诗的共性,在反映不同诗的个性方面略有不足。结合以上分析,新指标需要衍生于常见字(词),且要能够弥补常见字(词)的不足。我们通过 Python 中的 jieba 库,基于 TF-IDF 算法生成新指标——关键字(词)。该指标一方面是由常见字(词)计算得到,另一方面,TF-IDF 算法在计算时考虑到了诗之间的差异性,因此该指标兼顾了衡量诗的共性与个性。
在得到关键字(词)后,运用 LDA 主题模型将关键字(词)分为几个主题,然后统计每首诗各主题的关键字(词)频数,再累加求和到唐宋朝代层面,从而衡量唐诗和宋诗的风格差异。最后,将结果与问题二中得出的唐宋风格差异情况进行比较,从而判断指标的有效性。
问题三的总体思路如下所示:
指标设计依据与 TF-IDF 算法
我们发现,第一问和第二问实际上是围绕常见字(词)来展开的,但常见词更多地是体现诗词的共性,一些诗词中具有的特殊意象可能不会被挖掘出来,而关键字(词)则是为了探寻诗各自的特点,从而能够更好地发现诗自身的个性;另外,关键词是由TF-IDF 算法通过分析常见字(词)得到的,其可以看作是常见字(词)的一种“继承”与“凝练”。因此,关键字(词)兼顾了诗的共性与个性,很好地弥补了常见字的不足。
指标的提取
我们设置提取 100 个关键字(词),然后剔除掉问题一数据预处理时列举的没有实际含义和意象的字词,最终得到 91 个唐诗和 88 个宋诗的关键词(词)及其权重,唐诗中出现最多的关键字(词)是何处,宋代中出现最多的关键字(词)是春风。具体结果如下图所示:
基于 LDA 主题模型和关键字(词)分析唐宋诗作的变化
我们运用 Python,并结合关键字(词),再次对唐诗、宋诗进行遍历、检索及统计,从而得到每首诗中关键字(词)出现的次数,将其代入到 LDA 主题模型并求解。主题一致性随主题数目变化的曲线如下图所示:
通过困惑度的变化以及通过观察主题一致性变化曲线,本文主题个数的选取范围锁定在(3,20)个内,之后,将选取范围内的主题个数依次代入到 LDA 模型中,然后通过比较主题一致性值大小,选取关键字(词)最优主题数,唐诗和宋诗最优主题数分别确定为5 个和 4 个,对应的主题一致性分别为 0.74 和 0.738。
我们根据关键字(词)统计每首诗主题的频数,然后将诗各主题的频数加总到朝代层面,得到了唐代、宋代主题的频数。为了更直观准确地对比唐宋代的差异,我们进行了两步处理:第一,考虑到唐诗和宋诗的样本量不同,我们将频数转化成了百分数;第二,考虑到唐诗和宋诗主题数不同,我们又对转化得到的百分数减去平均百分数,以宋诗为例,就是减去 25%。统计结果如下图所示:
根据第三问中的结论,可知关键字(词)是衡量唐诗和宋诗风格差异的有效指标,因此可以使用关键字(词)来筛选不同风格的诗。另外,根据第三问的统计结果,分别对唐诗、宋诗按照主题分类,能够得到每首诗含有每个主题的关键字(词)的个数,个数越多越能体现此类主题的风格。然后,使用熵权法对每一首诗打分得到衡量风格显著程度的分数情况。分数越高说明该诗的风格越显著突出,代表性越强,将所有诗按“分数”大小进行排序,排名前 50 的诗即为此题解。
问题四的总体思路如图 7-1 所示:
将第三问 LDA 主题模型得到的每个主题含有的关键字(词)的个数作为打分的指标,使用熵权法得到每首诗的最终得分,最后选取唐诗、宋诗综合分数在前 50 的诗。结果如下表所示:
全部论文及程序请见下方“ 只会建模 QQ名片” 点击QQ名片即可
论文和程序不是免费的 鸽子勿扰 脑残勿扰
论文缩略图:
程序代码:
import jsonfrom glob import globfrom langconv import *import jieba as jbimport jieba.analyseimport pandas as pdfrom tqdm import trange# 检索常见词和关键词def tradition2simple(line): # 将繁体转换成简体 line = Converter('zh-hans').convert(line) line.encode('utf-8') return line# Set dynasty. Please note that poet and dynasty should match!dynasty = "tang" # choice from ["tang", "song"]# identify poerty filesfiles = glob(r'./B 题附件\json/poet.{}.*.json'.format(dynasty))counts = {} # 统计字词频数all_poem = ''# 遍历文件for num in trange(0,len(files)): # file='./B 题附件\\json\\poet.tang.0.json' fi = open(files[num], 'r', encoding='utf-8') fi_json = json.load(fi) for i in fi_json: poem = tradition2simple(''.join(i['paragraphs'])) all_poem += poem words = jb.lcut(poem) # 对分割样本剔除逗号和句号 while words.count(',') > 0: for j in words: if j == ',': words.remove(',') pass pass pass while words.count('。') > 0: for j in words: if j == '。': words.remove('。') pass pass pass for word in words: counts[word] = counts.get(word, 0) + 1 pass pass passkeywords = jb.analyse.extract_tags(all_poem, topK=100, withWeight=True, allowPOS=())key_words = pd.DataFrame(keywords, columns=['关键词', '权重'])frequent_words = pd.DataFrame(list(counts.items()), columns=['字词', '频数'])frequent_words.sort_values(by='频数', ascending=False, inplace=True)# 导出文件key_words.to_excel(rf'./{dynasty}-关键词.xlsx', index=False)frequent_words.to_csv(rf'./{dynasty}- 常见词 .csv', index=False,encoding='utf_8_sig')
import jsonfrom glob import globfrom langconv import *import pandas as pdfrom tqdm import trange# 构建 LDA 变量def tradition2simple(line): # 将繁体转换成简体 line = Converter('zh-hans').convert(line) line.encode('utf-8') return line# Set dynasty. Please note that poet and dynasty should match!dynasty = "tang" # choice from ["tang", "song"]# identify poerty fileschangjianci = pd.read_excel(rf'./{dynasty}-常见词.xlsx')words = list(changjianci['字词'])files = glob(r'./B 题附件\json/poet.{}.*.json'.format(dynasty)) # 文件路径documents = pd.DataFrame()# 遍历文件for num in trange(0, len(files)): fi = open(files[num], 'r', encoding='utf-8') # json 文件 # file='./B 题附件\\json\\poet.tang.0.json' # fi = open(file, 'r', encoding='utf-8') fi_json = json.load(fi) # 一首诗 for i in fi_json: l = [] l.append(tradition2simple(''.join(i['author']))) l.append(tradition2simple(''.join(i['title']))) poem = tradition2simple(''.join(i['paragraphs'])) for j in words: counts = poem.count(j) if counts != 0: for item in range(0, counts): l.append(j) pass else: pass pass l = pd.DataFrame(l).T documents = pd.concat([documents, l], axis=0, ignore_index=True) pass# 导出文件documents.to_csv(rf'./{dynasty}_importLDA 模型_常见词.csv', index=False, encoding='utf_8_sig', mode='w')
import jsonfrom glob import globfrom langconv import *import pandas as pdfrom tqdm import trange# 构建 LDA 变量def tradition2simple(line): # 将繁体转换成简体 line = Converter('zh-hans').convert(line) line.encode('utf-8') return line# Set dynasty. Please note that poet and dynasty should match!dynasty = "tang" # choice from ["tang", "song"]# identify poerty fileschangjianci = pd.read_excel(rf'./{dynasty}-关键词.xlsx')words = list(changjianci['字词'])files = glob(r'./B 题附件\json/poet.{}.*.json'.format(dynasty)) # 文件路径documents = pd.DataFrame()# 遍历文件for num in trange(0, len(files)): fi = open(files[num], 'r', encoding='utf-8') # json 文件 # file='./B 题附件\\json\\poet.tang.0.json' # fi = open(file, 'r', encoding='utf-8') fi_json = json.load(fi) # 一首诗 for i in fi_json: l = [] l.append(tradition2simple(''.join(i['author']))) l.append(tradition2simple(''.join(i['title']))) poem = tradition2simple(''.join(i['paragraphs'])) for j in words: counts = poem.count(j) if counts != 0: for item in range(0, counts): l.append(j) pass else: pass pass l = pd.DataFrame(l).T documents = pd.concat([documents, l], axis=0, ignore_index=True) pass# 导出文件documents.to_csv(rf'./{dynasty}_importLDA 模型_关键词.csv', index=False, encoding='utf_8_sig', mode='w')