简介:面向AI与深度学习入门及进阶学习者,这份资源围绕TensorFlow框架,演示如何用DNN对社交媒体文本进行大五人格分类,覆盖tfidf、doc2vec、情感词典等特征工程及完整建模流程。压缩包共52个文件,20个Python脚本负责数据清洗、特征提取、模型训练与评估,22个npy文件存储特征或中间结果,4个md与4个txt提供说明和日志,2个csv为标注样本,整体约18.35MB。已有280人学习浏览。压缩包内按character_dnn、character_svm、features、utils、model等模块组织,读者可对照character_train、character_eval、character_inference三个脚本快速跑通实验;logger与input_data等工具脚本有助于理解批次处理、日志记录和超参数调整等细节。该资源适合作为课程设计、毕业设计或算法对比的完整参考,既能学习DNN分类网络搭建,也能了解社交媒体数据清洗与人格标签化的实践技巧。 最近趁着空闲把之前一直在构思的一个小项目落地了:用TensorFlow搭一个DNN深层神经网络,对社交媒体用户的文本数据进行大五人格分类。起因很简单,想在现有数据上验证一下,不靠BERT这类超大预训练模型,只用传统Embedding加全连接层,到底能把人格分类这件事做到什么程度。整个项目从数据清洗、文本向量化到模型训练、评估,踩了不少坑,也沉淀了一些能直接复用的经验,这里完整复盘一下,给同样想做文本分类、性格分析方向的朋友做个参考。
1. 项目整体思路与模型选型
1.1 想解决什么问题
大五人格模型(OCEAN)把人的性格分为开放性(Openness)、尽责性(Conscientiousness)、外向性(Extraversion)、宜人性(Agreeableness)和神经质(Neuroticism)五个维度。传统做法是让用户自填问卷,但问卷存在被试偏差,而且成本高、样本量小。社交媒体上的文字天然记录了用户的表达习惯、情绪倾向和兴趣偏好,比如外向的人可能更喜欢用第一人称、发更多感叹号,尽责性高的人可能更爱列计划、用词更严谨。这个项目的目标就是:输入用户发布过的文本,用DNN模型判断他在每个维度上的高低倾向。
这是个典型的多标签分类问题,每个用户同时具有五个维度的性格标签,而不是简单地把人分成几个类别。所以模型输出层设计成了五个独立的二分类输出,每个维度判断“高/低”。
1.2 为什么用DNN而不是CNN或BERT
选型之前我也犹豫过。现在做文本分类,大家的第一反应往往是BERT、RoBERTa这类预训练模型,效果确实好,但需要GPU资源,模型体积大,推理速度也慢。DNN的优势在于轻量、易部署、训练快,而且对文本向量化后的特征同样能拟合出不错的非线性关系。
DNN的逻辑架构其实不复杂:输入层接收文本向量,中间经过若干全连接层和激活函数,逐层提取高阶特征,输出层给出分类概率。你可以把它理解成一台特征加工流水线——每一层全连接就相当于一道工序,把原始特征不断组合、抽象,最后形成适合分类的特征表达。相比CNN的局部感知和RNN的序列建模,DNN不关心词与词的位置关系,它只负责对输入特征做非线性变换。这也意味着,如果我们能把文本转化成包含语义信息的稠密向量,DNN就能在这些向量上做有效的分类。
TensorFlow在这个场景下非常顺手,自带的Keras接口可以快速堆叠层,配合tf.data做数据管道,模型调试效率很高。同时TensorFlow的生态成熟,后续如果要转成TFLite部署到移动端,路径也非常清晰。
1.3 大五人格分类任务定义
我最终把任务定义为:对每个用户,预测其在五个维度上是否属于“高分组”。具体做法是取每个维度的中位数作为阈值,高于阈值标记为1(高),低于等于阈值标记为0(低)。这样避免了人为设定绝对分数线的偏差,同时让五个维度都保持一定的类别平衡。
这里有个关键细节:五个维度不是独立训练的,而是在同一个模型里共享底层特征,然后各接一个独立的输出头。为什么要这样?因为五个维度本身存在相关性,比如外向性和宜人性都跟社交活跃度相关,开放性和尽责性都跟表达复杂度相关。共享底层特征可以让模型学习到通用的用户表达规律,同时每个输出头再学习维度特有的模式,这在实践中比单独训练五个模型更稳定,也更容易收敛。
2. 数据准备与预处理
2.1 数据来源与标注
社交媒体数据我使用的是公开的人格语料库,包含用户发表的帖子文本以及对应的大五人格问卷得分。原始数据里包含相当多的噪声:URL、@用户、表情符号、多余换行,甚至还有拼写错误。我的清洗流水线分为几步:统一小写、去除URL和邮箱、去除HTML实体、将表情符号转换为文本标记(如将笑脸转成“smile”)、最后按空格分词。
这里想提醒一下,社交媒体文本不要过度清洗。很多人习惯性地把所有标点都删掉,但实际上感叹号和问号对情绪分析很有用。我保留了标点符号,并且把重复标点压缩为单个标记(如多个感叹号压缩成“!rep”),这样模型能学到一些情绪强度信号。
2.2 文本向量化方案
文本向量化是整个项目中决定性能上限的一步。我对比了两种方案:
第一种是TF-IDF向量。优点是词汇权重直观、稀疏矩阵构建快,但维度高,且完全丢失语序和上下文信息。TF-IDF对“食堂难吃”和“难吃食堂”会得到一模一样的向量,这在语义表达上太粗糙了。
第二种是词嵌入(Word Embedding)。我用的是在大规模中文语料上预训练好的词向量(比如腾讯词向量),每个词映射为一个300维的稠密向量。对于每条用户文本,我取所有词向量的平均值作为用户特征。这种方式虽然丢失了词序,但保留了词的语义信息,而且维度固定,非常适合DNN输入。
最终我采用的是第二种方案,每个用户得到一个300维的向量,作为DNN的输入。如果你的数据量足够大,也可以用Embedding层端到端训练,但小样本场景下直接使用预训练词向量并冻结,效果通常会更好。
2.3 标签编码与类别不平衡处理
五个维度的标签我单独处理。每个维度按中位数划分后,正负样本比例大约在4:6到6:4之间,没有出现极端不平衡。但为了保险,损失函数里我加了一个类别权重:让少数类的损失贡献略高一些。这个操作很简单,Keras里可以直接通过class_weight参数传入。
需要特别留意的是:如果某个维度的正负比例明显失衡,比如达到1:9,那首选不是强行过采样或欠采样,而是先检查是否有数据泄漏或标注错误。我这次曾在“神经质”维度上看到过正负比例接近1:10,排查后发现是清洗阶段误删了一批包含负面情绪词但实为引用语的样本,修复后比例自然回归正常。
3. TensorFlow模型搭建与训练细节
3.1 模型架构设计
DNN模型结构如下:输入端是一个300维的用户特征向量,经过三个隐藏层,神经元数量分别为256、128、64,激活函数全部使用ReLU。每个隐藏层后面接一个Dropout层,丢弃率设为0.3,用来抑制过拟合。输出端拆成五个分支,每个分支是一个sigmoid神经元,负责一个维度的二分类。
这里解释一下为什么隐藏层用ReLU而不是sigmoid或tanh。ReLU的梯度在正区间恒为1,可以有效缓解深层网络的梯度消失问题,同时它的计算量更小,收敛速度实测比tanh快一倍不止。当然ReLU有个毛病——神经元死亡,即梯度小于0时权重不再更新。解决方法是控制学习率不要太高,并加上BatchNormalization层稳定激活分布。
我最终在每层全连接之后、激活函数之前加了BatchNormalization。这个改动让训练损失下降明显更平滑,也允许我用更大的初始学习率(从0.001提升到0.003)。
3.2 损失函数与优化器选择
多标签分类最常用的损失函数是BinaryCrossentropy,因为每个输出头都是独立的二分类问题。不能直接使用CategoricalCrossentropy,那是多分类互斥场景用的。
优化器我选择了Adam,这个选择几乎是默认的。Adam的优势是自适应学习率,对初始学习率的敏感度远低于SGD。在实际实验中,我对比过Adam和SGD(momentum=0.9),Adam在20个epoch内就能达到SGD训练80个epoch的效果。当然,Adam收敛后往往需要更低的学习率来做最终精调,所以我在第30个epoch时将学习率衰减为原来的1/10。
3.3 训练过程与调参经验
训练集、验证集、测试集按6:2:2划分,划分时确保同一个用户的所有文本只出现在一个集合中,否则会造成数据泄漏。
Epoch数量设置在50,配合EarlyStopping监控验证集损失,patience设为5,即验证集损失连续5个epoch不下降就终止训练。Batch size用了64。这里有个细节:如果数据量小(比如不足5000条),Batch size建议用32甚至16,太大了容易导致收敛不稳。我的训练集大约8000条用户记录,64是合适的。
训练过程中我额外关注了验证集每个维度的AUC曲线,发现外向性和宜人性的AUC涨得最快,神经质最慢。原因是外向性相关的语言信号(如社交词汇、表情符)比较明显,而神经质更隐蔽,需要模型捕捉细腻的负面情绪表达,这对文本向量来说本身就困难。
4. 模型评估与结果分析
4.1 评估指标选择
不能用准确率作为唯一指标,因为即使所有样本都预测为“高”,准确率也可能达到50%以上。我主要看F1分数和AUC。F1兼顾精确率和召回率,AUC则不受分类阈值影响,更能反映模型本身的区分能力。
我用的是macro-F1,即先计算每个维度的F1,再取平均。这样避免某个维度因为样本多而主导整体指标。最终模型的macro-F1达到了0.67,AUC均值0.72。作为一个不依赖大规模预训练模型的DNN方案,这个结果是可以接受的。
4.2 分类结果解读
单独看每个维度,外向性(Extraversion)的F1最高,约为0.72;宜人性(Agreeableness)次之;神经质(Neuralticism)最低,只有0.60。维度之间的差异是有规律可循的。
外向性文本特征非常明显:高外向的人会更频繁使用“我”“我们去”“哈哈”,感叹号数量也显著更高。这类信号在词向量平均后依然保留得很完整。而神经质高的人往往使用更多负面情绪词,但同时也可能使用反讽、自嘲等间接表达,这些在平均词向量里容易被稀释。这也是DNN这类不考虑上下文的模型的天然短板。
4.3 与预训练模型对比感受
为了验证DNN的上限,我在同一个数据集上用BERT-base做了对比实验。BERT的宏观F1达到0.79,比DNN高出12个百分点。这个差距主要来自语义上下文建模:BERT能区分“我真开心”和“我真开心(才怪)”,而DNN看到的是相似向量。
但BERT的代价也很明显:训练时间大约是DNN的8倍,模型体积超过400MB,CPU推理单条样本耗时接近500毫秒。相比之下,DNN的模型只有几MB,CPU推理耗时不到1毫秒。如果你的应用场景是移动端实时推理,或者离线批量分析百万级用户,DNN的性价比会更突出。我最后没有把BERT部署,是因为这个项目本身更偏可用性和落地性验证,而不是刷分。
5. 踩坑记录与实操心得
5.1 数据泄漏问题
这是最容易犯的错误,没有之一。我第一次跑实验时,把同一个用户的帖子同时分到了训练集和测试集,结果验证AUC高达0.91,但换到新样本上直接掉到0.58。原因很简单:模型实际上记住了用户的语言习惯,而不是学到了性格特征。
正确的做法是在用户粒度上划分数据集,确保同一个用户的全部文本只落在同一个集合里。如果你手里数据的组织方式是微博文本,一定要先按用户分组,再划分,不能简单按行随机切。
5.2 过拟合与正则化
DNN参数量大,文本特征相对稀疏,过拟合几乎是必然的。我去掉Dropout层试过一次,训练F1能到0.85,验证F1直接跌到0.58,典型的训练集上超神、测试集上超鬼。
正则化手段我建议组合使用:Dropout放在每个隐藏层后面,L2权重衰减加上,早停也要开。L2衰减系数我设置在1e-5,太高会导致欠拟合,太低又起不到约束作用。另外,BatchNormalization虽然主要用来加速收敛,但它本身也有轻微的正则化效果,能减少对Dropout的依赖。
5.3 可复现性设置
这是TensorFlow项目一个很容易被忽视的细节。为了让实验结果稳定,必须设置固定的随机种子,包括Python内置random、NumPy、TensorFlow三个层面的种子。同时要确保CPU和GPU的确定性操作。在训练前加一行tf.keras.utils.set_random_seed(42),能解决80%的随机性问题。
还有一点,TensorFlow在部分操作上是非确定性的,比如某些GPU上的Reduce操作。如果发现同样代码两次运行结果差异很大,可以强制使用CPU跑一遍,如果CPU上结果一致而GPU上不一致,就说明是GPU并行计算导致的问题。对大多数场景,固定CPU线程数并设置tf.config.threading也能改善。
5.4 文本预处理顺序影响
清洗的顺序必须固定:先去除URL和HTML标签,再转换表情符号,最后做小写化和分词。如果把表情符号转换放在URL去除之前,URL里的字符可能会干扰表情匹配。另外,文本长度需要截断,但不要直接平均所有词向量后在末尾补零,那样会保留不必要的信息。我最终保留每篇帖子前300个词,超出部分截断,不足的按实际长度计算平均向量。
还有一个个人体会:词向量平均时,是否过滤停用词需要实验验证。我一开始顺手过滤了“的”“了”“是”等常见停用词,结果在各维度上的F1普遍下降了2%。后来分析发现,这些高频词本身携带了一定的风格信息,比如“了”使用频率高可能和叙事倾向有关。所以社交媒体文本场景下,不要盲目沿用传统新闻文本的预处理习惯。
最后再分享一点实际体会
这个项目做完,给我最大的感触是,不要被“BERT>DNN”这种技术鄙视链误导。选什么模型,取决于你的数据量、算力和部署环境。DNN在这个任务上的表现虽谈不上惊艳,但足够支撑很多实际业务,尤其是当你需要在一个小时之内跑完百万用户的全量分析时,轻量就是硬道理。
如果你也打算做类似项目,建议先从简单模型开始,把数据清洗和评估流程跑通,再逐步增加模型复杂度。如果你在复刻过程中遇到TensorFlow安装、CUDA环境不匹配之类的问题,优先考虑用CPU跑小规模数据调试代码逻辑,逻辑通了再切GPU集训。我这次的完整工程代码(包含数据处理脚本、模型训练脚本和评估报告)都整理在项目包里了,需要的话可以直接参考,有问题欢迎在评论区交流。
本文还有配套的精品资源,点击获取