多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Python实战:基于NLP与情感分析的小说文本量化研究

Python实战:基于NLP与情感分析的小说文本量化研究 最近在追更一部校园到职场的甜宠小说被里面细腻的情感描写和真实的职场互动深深吸引。作为技术博主我习惯性地思考如果要用技术手段来“复现”或“分析”这样一个充满情感张力的故事线我们能做些什么这让我想到了自然语言处理NLP和情感分析技术。今天我们就抛开文学创作从数据科学的角度探讨如何用Python对类似《雨天私语》这样的文本进行量化分析挖掘其情感脉络、人物关系甚至模拟生成符合风格的片段。无论是想研究文本的开发者还是对情感计算感兴趣的读者都能从这篇实战指南中获得一套完整的可运行方案。1. 背景与核心概念当小说遇见情感计算我们讨论的并非文学批评而是计算文学分析的一个有趣分支。核心目标是利用计算机技术对小说文本进行结构化解析和量化研究。情感分析也称为观点挖掘是NLP的主要任务之一。它旨在识别、提取和量化文本中的主观信息如情绪积极、消极、中性、情感强度甚至具体情感类别喜悦、悲伤、愤怒等。对于连载小说我们可以追踪章节级的情感变化曲线可视化故事的“情绪节奏”。人物关系抽取从非结构化文本中自动识别出实体如人物“萧野”、“沈晏”以及实体之间的关系如“同学”、“上司”、“恋人”。通过构建人物关系图可以直观展示角色间的互动网络。文本风格模仿与生成基于给定的文本数据如一部小说的前100章训练一个语言模型学习其用词习惯、句式结构和叙事风格从而能够生成新的、风格类似的文本片段。这可以用于辅助创作或进行文本风格研究。为什么开发者需要了解这个这不仅是学术研究更有广泛的实用场景社交媒体舆情监控、产品评论分析、自动写作辅助、交互式叙事游戏开发等。通过本项目你将掌握从原始文本处理到情感可视化、关系图谱构建的全流程实战技能。2. 环境准备与版本说明本项目主要使用Python因其在NLP领域有丰富的生态系统。以下环境是完成本教程的推荐配置请确保你的环境已就绪。操作系统Windows 10/11, macOS, 或 Linux (如Ubuntu 20.04)均可。Python版本3.8 或 3.9兼容性最好。避免使用Python 3.10以上版本可能遇到的某些库的未适配问题。IDE或编辑器VS Code, PyCharm, Jupyter Notebook 任选。核心Python库及版本 我们将使用pip进行安装。建议先创建一个新的虚拟环境。# 创建并激活虚拟环境 (以venv为例) python -m venv nlp-novel # Windows 激活 nlp-novel\Scripts\activate # Linux/macOS 激活 source nlp-novel/bin/activate # 安装核心库 pip install jieba0.42.1 # 中文分词 pip install snownlp0.12.3 # 中文情感分析 pip install networkx2.8.8 # 构建和操作复杂图 pip install matplotlib3.5.3 # 绘图 pip install pandas1.5.0 # 数据处理 pip install numpy1.23.5 # 数值计算 # 用于关系抽取和生成的库可选但建议安装以体验完整流程 pip install transformers4.26.1 pip install torch1.13.1 --index-url https://download.pytorch.org/whl/cpu # CPU版本根据你的CUDA情况调整示例项目结构 在开始前建议建立如下目录结构以便管理代码和数据。novel_analysis_project/ │ ├── data/ │ ├── raw/ # 存放原始小说文本文件(.txt) │ └── processed/ # 存放处理后的数据 │ ├── src/ │ ├── 01_data_preprocess.py │ ├── 02_sentiment_analysis.py │ ├── 03_character_network.py │ └── 04_text_generation.py │ ├── results/ │ ├── figures/ # 存放生成的图表 │ └── reports/ # 存放分析报告 │ └── requirements.txt # 依赖库列表3. 核心技术与原理拆解在动手编码前理解背后的工具原理至关重要。3.1 中文分词与jieba中文文本没有像英文那样的天然空格分隔因此分词是首要任务。jieba采用了基于前缀词典和动态规划的方法能够高效准确地进行分词。它支持多种模式精确模式最常用试图最精确地切分文本。全模式扫描出所有可能的词语速度快但会有歧义。搜索引擎模式在精确模式基础上对长词再次切分提高召回率。3.2 情感分析与SnowNLPSnowNLP是一个Python库专门用于处理中文文本的情感分析、分词、关键词提取等。其情感分析模块基于朴素贝叶斯分类器在中文商品评论、社交媒体文本上训练。它输出一个0到1之间的值越接近1表示情感越积极。虽然对于文学性强的文本精度可能不如专用模型但作为快速评估和趋势分析工具非常有效。3.3 人物关系抽取与NetworkX这不是一个开箱即用的“关系抽取器”而是一个图论工具包。我们需要自己定义规则来识别关系。基本思路是命名实体识别先识别出文本中的人名可以用自定义词典jieba分词实现。共现分析如果两个人物在同一个句子或一个固定大小的滑动窗口如一段话内同时出现则认为他们之间存在一次“互动”。构建图使用NetworkX将人物作为节点互动频率作为边的权重构建一个无向加权图。3.4 文本生成与TransformersTransformers库提供了预训练语言模型的统一接口。对于中文文本生成我们可以使用如GPT2-Chinese,ChatGLM或T5等模型的预训练权重。其核心原理是自回归生成模型根据已生成的上文预测下一个最可能的词如此循环直至生成指定长度的文本。微调Fine-tuning是指在一个特定领域的数据集如我们的目标小说上继续训练预训练模型使其适应特定风格和内容。4. 完整实战案例分析你的小说文本假设我们已经拥有小说《雨天私语》的纯文本文件novel.txt并将其放在data/raw/目录下。4.1 数据预处理与章节分割第一步是将连续的文本按章节分割并清洗数据。# src/01_data_preprocess.py import re import os import pandas as pd def split_into_chapters(file_path, chapter_patternr第[零一二三四五六七八九十百千\d]章): 根据章节标题模式分割小说文本。 :param file_path: 小说文本文件路径 :param chapter_pattern: 匹配章节标题的正则表达式 :return: 章节字典 {章节标题: 章节内容} with open(file_path, r, encodingutf-8) as f: full_text f.read() # 使用正则表达式找到所有章节标题的位置 # 包括标题本身 pattern re.compile(chapter_pattern) matches list(pattern.finditer(full_text)) chapters {} for i, match in enumerate(matches): chapter_title match.group() start_idx match.start() # 结束位置是下一个章节的开始或者是文本末尾 end_idx matches[i 1].start() if i 1 len(matches) else len(full_text) chapter_content full_text[start_idx:end_idx].strip() chapters[chapter_title] chapter_content print(f共分割出 {len(chapters)} 个章节。) return chapters def clean_text(text): 简单的文本清洗移除多余的空格和换行。 # 将多个换行符替换为一个 text re.sub(r\n, \n, text) # 将多个空格替换为一个 text re.sub(r , , text) return text.strip() if __name__ __main__: raw_file ../data/raw/novel.txt chapters split_into_chapters(raw_file) # 清洗每个章节的内容 cleaned_chapters {title: clean_text(content) for title, content in chapters.items()} # 保存为DataFrame方便后续处理 df_chapters pd.DataFrame({ chapter_title: list(cleaned_chapters.keys()), content: list(cleaned_chapters.values()) }) # 添加章节序号 df_chapters[chapter_num] range(1, len(df_chapters) 1) processed_dir ../data/processed/ os.makedirs(processed_dir, exist_okTrue) df_chapters.to_csv(os.path.join(processed_dir, chapters.csv), indexFalse, encodingutf-8-sig) print(章节数据已保存至 chapters.csv)4.2 情感分析绘制故事情绪曲线接下来我们对每个章节进行情感分析并可视化结果。# src/02_sentiment_analysis.py import pandas as pd from snownlp import SnowNLP import matplotlib.pyplot as plt import os def analyze_sentiment(text): 使用SnowNLP分析单段文本的情感倾向得分。 try: s SnowNLP(text) # 返回0-1之间的情感值0.5偏积极0.5偏消极 return s.sentiments except Exception as e: print(f情感分析出错: {e}) return 0.5 # 返回中性值 def plot_sentiment_trend(df, save_pathNone): 绘制章节情感趋势图。 plt.figure(figsize(14, 6)) plt.plot(df[chapter_num], df[sentiment_score], markero, linestyle-, linewidth2, markersize6) plt.axhline(y0.5, colorr, linestyle--, alpha0.5, label中性线 (0.5)) plt.xlabel(章节序号, fontsize12) plt.ylabel(情感得分 (越接近1越积极), fontsize12) plt.title(《雨天私语》章节情感趋势分析, fontsize16, pad20) plt.grid(True, alpha0.3) plt.legend() plt.tight_layout() if save_path: os.makedirs(os.path.dirname(save_path), exist_okTrue) plt.savefig(save_path, dpi300) print(f情感趋势图已保存至: {save_path}) plt.show() if __name__ __main__: # 读取预处理好的章节数据 df pd.read_csv(../data/processed/chapters.csv) # 对每个章节内容进行情感分析 (可能较慢可以抽样或分段) print(正在进行情感分析请稍候...) # 为了演示这里只分析前50章或全部章节的前5000字避免过长等待 sample_texts df[content].apply(lambda x: x[:5000]) # 取每章前5000字分析 df[sentiment_score] sample_texts.apply(analyze_sentiment) # 保存结果 result_dir ../results/ os.makedirs(result_dir, exist_okTrue) df[[chapter_num, chapter_title, sentiment_score]].to_csv( os.path.join(result_dir, sentiment_results.csv), indexFalse, encodingutf-8-sig ) # 绘制趋势图 plot_sentiment_trend(df, save_path../results/figures/sentiment_trend.png)运行此脚本你将得到一个CSV文件记录每章情感得分以及一张折线图直观展示故事的情感起伏。例如重逢章节第58章得分可能显著升高。4.3 构建人物关系网络这一步我们识别主要人物并通过共现分析构建他们的关系图。# src/03_character_network.py import jieba import jieba.posseg as pseg import pandas as pd import networkx as nx import matplotlib.pyplot as plt from collections import defaultdict import os # 添加自定义词典确保主要人物姓名能被正确识别 jieba.load_userdict(../data/raw/character_dict.txt) # 需要提前创建这个文件每行一个人名如“萧野\n沈晏\n”) def extract_characters(text, top_k10): 从文本中提取可能的人名基于词性和自定义词典。 words pseg.cut(text) characters [] for word, flag in words: # 识别为人名或来自自定义词典的词 if flag nr or word in jieba.get_FREQ(word): # 简化逻辑实际中需要更精细的规则 characters.append(word) return characters def build_cooccurrence_network(chapters_df, character_list, window_size100): 构建人物共现网络。 :param chapters_df: 包含章节内容的DataFrame :param character_list: 关注的人物列表 :param window_size: 共现窗口大小字符数 :return: networkx Graph对象 G nx.Graph() # 初始化节点 for char in character_list: G.add_node(char) # 统计共现 cooccur defaultdict(int) for content in chapters_df[content]: # 简化处理在整个章节文本中滑动窗口 for i in range(0, len(content), window_size // 2): # 使用步长为窗口一半有重叠 window content[i:i window_size] chars_in_window set() for char in character_list: if char in window: chars_in_window.add(char) # 窗口内每对人物共现次数1 chars_list list(chars_in_window) for i in range(len(chars_list)): for j in range(i 1, len(chars_list)): pair tuple(sorted([chars_list[i], chars_list[j]])) cooccur[pair] 1 # 将共现次数作为边权重添加到图中 for (char1, char2), weight in cooccur.items(): if weight 0: # 只添加有共现的边 G.add_edge(char1, char2, weightweight) return G def plot_network(G, save_pathNone): 绘制人物关系网络图。 plt.figure(figsize(12, 10)) # 使用spring布局 pos nx.spring_layout(G, k1, iterations50) # 节点大小根据度中心性调整 node_size [G.degree(n) * 300 for n in G.nodes()] # 边宽度根据权重调整 edge_width [G[u][v][weight] * 0.5 for u, v in G.edges()] nx.draw_networkx_nodes(G, pos, node_sizenode_size, node_colorlightblue, alpha0.9) nx.draw_networkx_edges(G, pos, widthedge_width, alpha0.5, edge_colorgray) nx.draw_networkx_labels(G, pos, font_size12, font_familySimHei) plt.title(《雨天私语》主要人物关系共现网络, fontsize16, pad20) plt.axis(off) plt.tight_layout() if save_path: os.makedirs(os.path.dirname(save_path), exist_okTrue) plt.savefig(save_path, dpi300, bbox_inchestight) print(f关系网络图已保存至: {save_path}) plt.show() if __name__ __main__: df pd.read_csv(../data/processed/chapters.csv) # 假设我们关注的主要人物 main_characters [萧野, 沈晏, 林薇, 陆子航] # 根据小说内容调整 # 构建网络 print(正在构建人物关系网络...) G build_cooccurrence_network(df, main_characters, window_size150) print(f网络包含 {G.number_of_nodes()} 个节点 {G.number_of_edges()} 条边。) # 打印边权重共现次数 for u, v, data in G.edges(dataTrue): print(f{u} -- {v}: 共现 {data[weight]} 次) # 绘制网络图 plot_network(G, save_path../results/figures/character_network.png)你需要提前在data/raw/character_dict.txt中列出小说中的主要人物名每行一个。运行后将生成一张人物关系图节点大小代表角色出现频次连线粗细代表互动强度。4.4 进阶文本风格模仿生成这是一个进阶任务使用预训练模型进行微调。由于计算资源要求这里给出一个概念性代码框架。# src/04_text_generation.py (概念框架) 注意这是一个高级示例需要足够的计算资源GPU为佳和大量的训练数据数十万字。 这里仅展示使用Hugging Face Transformers库的基本流程。 from transformers import GPT2LMHeadModel, GPT2Tokenizer, TextDataset, DataCollatorForLanguageModeling from transformers import Trainer, TrainingArguments def fine_tune_gpt2(train_file, model_nameuer/gpt2-chinese-cluecorpussmall, output_dir./results/gpt2-novel): # 1. 加载分词器和模型 tokenizer GPT2Tokenizer.from_pretrained(model_name) model GPT2LMHeadModel.from_pretrained(model_name) # 2. 准备数据集 train_dataset TextDataset( tokenizertokenizer, file_pathtrain_file, block_size128 # 输入序列长度 ) data_collator DataCollatorForLanguageModeling( tokenizertokenizer, mlmFalse # GPT-2是因果语言模型不是MLM ) # 3. 设置训练参数 training_args TrainingArguments( output_diroutput_dir, overwrite_output_dirTrue, num_train_epochs3, # 根据数据量调整 per_device_train_batch_size4, # 根据GPU内存调整 save_steps10_000, save_total_limit2, prediction_loss_onlyTrue, ) # 4. 创建Trainer并训练 trainer Trainer( modelmodel, argstraining_args, data_collatordata_collator, train_datasettrain_dataset, ) trainer.train() trainer.save_model() tokenizer.save_pretrained(output_dir) print(f模型已保存至 {output_dir}) def generate_text(prompt, model_path, length100): 使用微调后的模型生成文本。 tokenizer GPT2Tokenizer.from_pretrained(model_path) model GPT2LMHeadModel.from_pretrained(model_path) inputs tokenizer.encode(prompt, return_tensorspt) outputs model.generate(inputs, max_lengthlength, do_sampleTrue, top_k50) generated_text tokenizer.decode(outputs[0], skip_special_tokensTrue) return generated_text if __name__ __main__: # 假设我们已经将小说文本处理成一个大的训练文件 train.txt # fine_tune_gpt2(train_file../data/processed/train.txt) # 生成示例 # model_path ./results/gpt2-novel # prompt 又是一个雨天萧野站在公司楼下 # result generate_text(prompt, model_path, length150) # print(生成文本, result) print(此部分为进阶内容需要大量数据和计算资源。请确保环境配置正确后再运行。)重要提示文本生成模型训练需要大量文本数据至少几十万到上百万字和较强的计算能力推荐使用GPU。对于个人开发者可以考虑使用云GPU服务或从生成短文本开始尝试。5. 常见问题与排查思路在运行上述代码时你可能会遇到以下问题问题现象可能原因解决思路jieba分词无法识别人名人名不在默认词典中创建character_dict.txt自定义词典文件并使用jieba.load_userdict()加载。确保文件为UTF-8编码每行一个词。SnowNLP情感分析速度慢文本过长对章节文本进行截断分析如只取前N个字符或考虑使用更高效的模型如bert-base-chinese 情感分类头。SnowNLP情感得分全部接近0.5文本风格与训练数据差异大SnowNLP主要针对现代评论。对于文学文本得分可能不敏感。可考虑使用在文学语料上微调过的情感分析模型或将其结果作为相对趋势参考。人物关系图节点重叠严重网络布局算法参数不适配调整nx.spring_layout中的k(节点间最优距离) 和iterations(迭代次数) 参数。也可以尝试其他布局如nx.circular_layout。transformers训练时内存/显存不足模型太大或批次太大1. 使用更小的预训练模型如distilgpt2。2. 减小per_device_train_batch_size。3. 使用梯度累积 (gradient_accumulation_steps)。4. 启用混合精度训练 (fp16True)。生成的文本不通顺或重复训练数据不足或训练不充分1. 增加训练数据量。2. 增加训练轮数 (num_train_epochs)。3. 调整生成参数如降低temperature使用top-p(nucleus)采样。6. 最佳实践与工程建议将文本分析项目工程化可以提升代码的可维护性和结果的可复现性。数据管道化将数据预处理、分析、可视化的步骤封装成独立的函数或类并通过配置文件管理参数如章节正则模式、人物列表、情感分析模型选择。使用pipeline的概念串联流程。结果可复现固定随机种子特别是在使用networkx的布局算法或模型训练时。保存每次运行的配置和中间结果。性能优化情感分析对于大批量文本可以考虑批量处理或使用多进程/线程注意SnowNLP非线程安全。关系抽取共现分析算法复杂度较高。对于超长文本可以按章节并行处理或使用更高效的字符串搜索库如ahocorasick进行多模式匹配。模型训练使用Datasets库高效加载数据利用Transformers的TrainerAPI 自动处理设备放置和混合精度。模型选择与评估不要盲目追求大模型。对于情感分析可以尝试sklearnTF-IDF 分类器如SVM的基线模型与SnowNLP或BERT对比。对于文本生成在投入大量资源微调大模型前可以先尝试基于n-gram或LSTM的简单模型验证任务可行性。可视化增强人物关系图中可以使用不同颜色区分人物阵营如主角、配角。情感趋势图上可以叠加重要情节点如“重逢”、“冲突”的标记进行关联分析。考虑使用交互式图表库如Plotly或Pyecharts生成网页报告便于探索。安全与伦理确保用于分析的文本数据来源合法尊重版权。文本生成模型可能产生不可控或带有偏见的内容在部署应用前需要设计内容过滤和人工审核机制。本项目为技术演示旨在辅助分析和理解文本模式而非替代人类创作。7. 总结与扩展方向通过本实战项目我们完成了一个从原始小说文本到情感分析、人物关系可视化的完整数据分析流程。你掌握了使用jieba,SnowNLP,networkx等工具处理中文文本的基本方法并了解了使用Transformers进行文本生成的进阶方向。核心收获数据预处理是基础正则表达式是分割和清洗文本的利器。情感分析提供了量化故事情绪波动的视角。共现网络是揭示人物关系的简单有效方法。深度学习模型为风格模仿和内容生成提供了可能。下一步可以探索更精细的情感分析使用在小说/文学语料上微调过的BERT模型进行句子级甚至方面级的情感分析。复杂关系抽取利用依存句法分析或预训练的关系抽取模型如CasRel识别更具体的关系类型如“暗恋”、“合作”、“竞争”。情节结构分析尝试识别故事的“起承转合”或利用主题模型如LDA分析章节主题演变。构建交互式应用使用Streamlit或Gradio快速搭建一个Web应用上传小说文本一键生成分析报告和图表。技术是解读世界的新语言。用代码分析故事不仅能获得有趣的发现更能深化对自然语言处理技术的理解。希望这篇教程能为你打开一扇门鼓励你将这些方法应用到更广阔的文本领域中去探索。
返回列表