<center><strong><font size="8">作家风格识别
### 一 题目背景
#### 1.1 实验背景
作家风格是作家在作品中表现出来的独特的审美风貌。通过分析作品的写作风格来识别作者这一研究有很多应用,比如可以帮助人们鉴定某些存在争议的文学作品的作者、判断文章是否剽窃他人作品等。作者识别其实就是一个文本分类的过程,文本分类就是在给定的分类体系下,根据文本的内容自动地确定文本所关联的类别。 写作风格学就是通过统计的方法来分析作者的写作风格,作者的写作风格是其在语言文字表达活动中的个人言语特征,是人格在语言活动中的某种体现。
#### 1.2 实验要求
- 建立深度神经网络模型,对一段文本信息进行检测识别出该文本对应的作者。
- 绘制深度神经网络模型图、绘制并分析学习曲线。
- 用准确率等指标对模型进行评估。
#### 1.3 实验环境
重点使用到的python包:
```python
import numpy as np
import torch
import jieba as jb
from transformers import BertTokenizer
```
### 二 实验内容
#### 2.1 数据集
该数据集包含了 8438 个经典中国文学作品片段,对应文件分别以作家姓名的首字母大写命名。
数据集中的作品片段分别取自 5 位作家的经典作品,分别是:
| 序号 | 中文名 | 英文名 | 文本片段个数 |
| :--: | :----: | :----: | :----------: |
| 1 | 鲁迅 | LX | 1500 条 |
| 2 | 莫言 | MY | 2219 条 |
| 3 | 钱钟书 | QZS | 1419 条 |
| 4 | 王小波 | WXB | 1300 条 |
| 5 | 张爱玲 | ZAL | 2000 条 |
#### 2.2 数据预处理
使用 TF-IDF 算法统计各个作品的关键词频率
TF-IDF(term frequency–inverse document frequency,词频-逆向文件频率)是一种用于信息检索与文本挖掘的常用加权技术。
* TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
* TF-IDF的主要思想是:如果某个单词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
这里我们使用 jieba 中的默认语料库来进行关键词抽取,取出每位作家频率前500的词
```python
# 词频特征统计,取出各个作家前 500 的词
high_words = set()
for label, text in enumerate(fragment): # 提取每个作家频率前500的词汇,不返回关键词权重值
for word in jb.analyse.extract_tags(text, topK=500, withWeight=False):
if word in high_words:
high_words.remove(word)
else:
high_words.add(word) # 将高频词汇存入
```
1. 总共 5 个作者,每个作者的前 500 个最高词频的词作为特征,共 2500 维(或者小于 2500 维)
> 选用每个作者的前500个高频词汇是因为当选择高频词汇太少的时候,不能充分反映作者的特点,导致各个作者的区分度不高,最终模型准确率较低;但是当高频词汇选择太多的话作家之间的界限会模糊,导致分类失效。通过多次调整,最终确定选择500个高频词。
2. 单独计算每个作者每一句话的这 500 维特征
3. 去除作家中重复出现的高频词汇
> jieba分词后,“我们”、“他们”等等的这类汉语常用词汇会大量的出现,这些词汇使用频率高但是机会所有作家都会大量用到这些词汇,这些词汇并不能够区分开各个作家,反而可能影响分类结果,所以选择将这些重复词汇去掉,只选用每个作家的独有词汇用于训练网络,以获取最大的特征。
4. 用这个特征训练神经网络
#### 2.3 搭建模型
详见***算法描述***部分
### 三 算法描述
#### 3.1 pytorch网络搭建模型
##### 3.1.1 建立number_to_word映射
将五位作者分别对应5个数字,便于后期各种操作。
```python
number_to_word = list(high_words)
word_number = len(number_to_word) # 所有高频词汇的个数
word_to_number = {word: i for i, word in enumerate(number_to_word)} # 建立高频词汇字典,一一对应
```
##### 3.1.2 划分数据集
在数据预处理的基础上,对读入的数据集进行划分,取出数据中的75%作为训练集,25%作为验证集;之后创建一个***DataLoader*** 对象。
```python
# 划分数据集
valid_weight = 0.25 # 25%验证集
train_size = int((1 - valid_weight) * len(dataset)) # 训练集大小
valid_size = len(dataset) - train_size # 验证集大小
train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, valid_size])
# 创建一个 DataLoader 对象
train_loader = data.DataLoader(train_dataset, batch_size=16, shuffle=True) # batch_size=16
valid_loader = data.DataLoader(test_dataset, batch_size=1000, shuffle=True) # batch_size=1000
```
##### 3.1.3 确定训练模型、优化器、损失函数、学习率(最终确定版本及具体确定过程见4.1)
首先,我们使用Sequential 序贯模型,通过将层的列表传递给Sequential的构造函数,来创建一个Sequential模型。之后,在Sequential 序贯模型中使用`nn.Linear()`设置隐藏层;同时使用`nn.ReLU()`线性整流函数使输入小于0的值输出幅值为0,输入大于零的值幅值不变(激活函数)。
其次,使用`nn.CrossEntropyLoss()`计算交叉熵损失。
最后,构造优化器,使用使用`torch.optim`这一个实现了多种优化算法的包,之后调用`adam`算法,Adam(Adaptive Moment Estimation)本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。它的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
###### 神经网络结构如图所示:
![](https://www.writebug.com/myres/static/uploads/2022/5/3/ef9cc2e0a323934630d4131d0803a183.writebug)
![](https://www.writebug.com/myres/static/uploads/2022/5/3/0e8396c8d5fcd3a5376092a86cdfd2e8.writebug)
<strong>图1 神经网络模型图</strong>
神经网络包含三个隐藏层,输出层个数为六,包含五个作者和一个无法识别选项。三个隐含层神经元个数通过多次实验测试最终选定为1024、1024和1024,在此输入输出条件下正确率比其它条件略高。
初始学习率设定为1e-4,这是通过多次调整测试得到的当前情况下的一个最优值。
###### 部分代码如下:
```python
# 设定模型参数,使用ReLU作为激活函数,简单顺序连接模型
model = nn.Sequential(
nn.Linear(word_number, 512), # 三个隐含层神经网络,尝试(512,1024,1024)
nn.ReLU(), # 激活函数尝试ReLU,
nn.Linear(512, 1024),
nn.ReLU(),
nn.Linear(1024, 1024),
nn.ReLU(),
nn.Linear(1024, 6), # 最后一个隐含层不需要激活函数
).to(device)
epochs = 20 # 设定训练轮次
loss_fn = nn.CrossEntropyLoss() # 定义损失函数(尝试nn.CrossEntropyLoss()和nn.NLLLoss(),二者多用于多分类任务)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) # 定义优化器(adam),初始学习率为1e-4
best_acc = 0 # 优化器尝试RMSProp()、Adam()、Adamax()
history_acc = []
history_loss = []
best_model = model.cpu().state_dict().copy() # 最优模型
```
##### 3.1.4 训练
共训练20轮,训练结束绘制`valid_loss`曲线和`valid_acc`曲线,并输出最佳准确率
![](https://www.writebug.com/myres/static/uploads/2022/5/3/8d2a9b2e7266332bae516f506dc979af.writebug)
<strong>图2 valid_loss/valid_acc曲线</strong>
![](https://www.writebug.com/myres/stati