简介:一份基于DNN深层神经网络的社交媒体用户性格分析完整项目,面向AI与深度学习方向的研究者、学生及算法工程师,可用于理解文本情感与五大人格预测任务。包体共54个文件,含20个Python脚本,覆盖数据预处理、特征构建、模型训练、评估与推理模块;22个npy文件存储中间特征或嵌入向量,2个csv提供原始标注数据,另有5个md说明和4个txt文档辅助上手。压缩包整体18.34MB,内容围绕TF-IDF、Doc2Vec、情感词典等多种特征方法,以及DNN、SVM等模型的对比实现展开。项目结构清晰,从textmind数据抓取到性格维度得分输出均有代码支撑,便于二次开发和结果复现。目前已有136人学习,适合作为课程设计、毕业设计或入门自然语言处理与深度学习的参考资料。
1. 一场关于“你是什么样的人”的预测,DNN 正在给出答案
性格分析不是算命,它在产品推荐、舆情洞察、心理咨询辅助等场景里都有实用价值。传统方式要么靠问卷让用户主动填,要么靠人工读文本打标签,前者门槛高、后者效率低。而社交媒体上的发言、转发、评论,本身就是用户性格的自然投射——问题是怎么把这些非结构化的文本变成可计算的信号。基于 DNN 深层神经网络的社交媒体用户性格分析,做的正是这件事:用深度网络从用户的文本行为中学习性格特征,输出 MBTI 或 Big Five(大五人格)的多标签预测结果。
DNN(Deep Neural Network,深层神经网络)之所以适合这个任务,是因为它不像逻辑回归那样对特征做线性组合,而是通过多层非线性变换自动捕捉“用词习惯”和“性格倾向”之间的高阶关联。例如“频繁使用第一人称代词”和“神经质”之间的相关性,在浅层模型里需要人工构造交叉特征,DNN 则可以在隐层中自行表示。本文会从特征工程、网络结构、训练参数到多标签评估,完整走一遍可复现的落地路径。适合有 Python 基础、想用深度学习处理文本分类或用户画像问题的工程师。
2. 先把用户文本变成 DNN 能吃进去的数值特征
2.1 为什么不能直接把原始文本丢给 DNN
社交媒体文本与新闻、论文不同,它短、口语化、噪声大,同一个人在不同时间线的表达风格差异也很明显。如果直接把分词后的 token 序列交给 DNN,网络需要从零开始学习语法和语义,训练数据量不够时很难收敛。所以第一步必须做特征工程,把文本转换成既能保留性格线索、又适合全连接网络输入的向量。
常见做法是构建三类特征拼接成一条样本向量:TF-IDF 统计特征、LIWC 心理语言学特征、以及词向量平均池化特征。TF-IDF 负责捕捉关键词权重,比如“绝对”“总是”这类绝对化词汇在神经质得分高的人群中更常见。LIWC(Linguistic Inquiry and Word Count)是一个词典工具,统计代词、情绪词、认知词等类别的占比。词向量平均池化则把整条文本的语义压缩成固定维度向量。这三者拼接后,每一条用户文本就变成了一个几百维到几千维的稠密向量,DNN 的输入层可以直接接住。
2.2 构建最小可用的特征矩阵
下面是一个基于 Python 的简化实现,输入是一批已分好词的社交媒体文本,输出是拼接后的特征矩阵。
import numpy as np import jieba from sklearn.feature_extraction.text import TfidfVectorizer from gensim.models import Word2Vec def build_user_features(texts, w2v_model, max_tfidf=500): # 1. TF-IDF 特征 tfidf_vec = TfidfVectorizer(max_features=max_tfidf, token_pattern=r'\S+') tfidf_matrix = tfidf_vec.fit_transform(texts).toarray() # 2. 词向量平均池化 def avg_embedding(text): words = jieba.lcut(text) vecs = [w2v_model.wv[w] for w in words if w in w2v_model.wv] if not vecs: return np.zeros(w2v_model.vector_size) return np.mean(vecs, axis=0) emb_matrix = np.array([avg_embedding(t) for t in texts]) # 3. 拼接:TF-IDF (500维) + 词向量 (100维) X = np.hstack([tfidf_matrix, emb_matrix]) return X这段代码的核心思路是把两种异构特征横向拼接。TF-IDF 是稀疏统计信号,词向量池化是稠密语义信号,两者互补。max_tfidf=500控制词表规模,防止维度爆炸;w2v_model需要预先用同领域语料训练,领域越接近,语义信号越准。实际项目中我还会加入 LIWC 特征,但需要词典文件,这里不再展开。特征矩阵准备好后,还需要做标准化——DNN 对输入尺度敏感,建议用StandardScaler或MinMaxScaler处理后再进网络。
2.3 标签怎么定:多标签二值化是关键
性格分类常用的是 Big Five 五维标签,每个维度是一个二分类(高/低),或者 MBTI 四个维度(I/E、N/S、T/F、J/P)。无论哪种,都是多标签问题,不是多分类。所以输出层不能用 softmax,而要用多个 sigmoid 单元,每个单元独立判断一个维度是否成立。
from sklearn.preprocessing import MultiLabelBinarizer # 每条样本的标签,例如 [1,0,1,1,0] 表示五个维度中的第 1、3、4 维为高分组 mlb = MultiLabelBinarizer() y = mlb.fit_transform(labels)这里的labels是原始标签列表,每一位代表一个维度的二值结果。多标签二值化之后,模型的目标就变成了“同时预测多个 0/1 标签”,每个标签独立参与损失计算。这个设计直接决定了 DNN 输出层的神经元数量和损失函数的选择,下文会详细讲。
3. DNN 网络结构设计与核心训练参数
3.1 从输入层到输出层:宽度和深度怎么选
社交媒体用户性格分析属于中等复杂度的表格型文本任务,网络不必太深。我一般从 3 层隐藏层起步:输入层接特征维度,第一层 256 个神经元,第二层 128 个,第三层 64 个,每层都跟 BatchNormalization 和 Dropout。宽度先大后小,是为了让网络先充分组合特征,再逐步压缩成高层次的性格表示。
激活函数统一用 ReLU,输出层用 Sigmoid。ReLU 能缓解梯度消失,Sigmoid 保证每个输出值在 0 到 1 之间,可以解释为该维度成立的概率。如果任务包含 5 个 Big Five 维度,输出层就是 5 个神经元;如果是 MBTI 四维,就是 4 个。这个设计里有一个常见误区:把多标签问题当成多分类做 softmax,那会强制所有维度概率之和为 1,与实际矛盾——一个人可以同时外向且尽责。
import tensorflow as tf def build_dnn(input_dim, label_num): model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(input_dim,)), tf.keras.layers.Dense(256, activation='relu'), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.4), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(label_num, activation='sigmoid') ]) return model3.2 损失函数、优化器与评估指标的搭配逻辑
多标签分类的默认损失是binary_crossentropy,它对每一个输出神经元独立计算二分类交叉熵再求平均。优化器用 Adam,学习率从 1e-3 起调。评估指标不能只看 accuracy,因为标签分布通常不均衡,比如“尽责性高”的人可能只占样本的 30%。我通常会同时监控每个标签的 AUC 以及整体的宏平均 F1。
model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='binary_crossentropy', metrics=['AUC'] )这里的AUC会自动对每个输出维度计算后取宏观平均,能够更真实地反映模型在正负样本不均衡时的区分能力。如果某个维度的 AUC 明显偏低,说明该维度的文本信号不足或样本量太少,需要回看特征层面有没有覆盖相关线索。
3.3 训练流程中的关键参数参考表
以下参数基于典型的 5 万条左右用户文本、3000 维特征的场景,可在实际项目中作为起点调整。
| 参数项 | 推荐值 | 调整方向 |
|---|---|---|
| Batch Size | 128 | 样本量大可到 256,过小时训练震荡 |
| Epochs | 30 | 配 EarlyStopping,patience 设为 5 |
| Dropout 比例 | 0.2~0.4 | 过拟合加大,欠拟合减小 |
| 隐藏层宽度 | 256→128→64 | 特征维度高时 512 起步 |
| 学习率 | 1e-3 | 收敛慢可降到 1e-4 |
| 验证集比例 | 0.2 | 按用户分组切分,避免同人多条文本泄漏 |
按用户分组切分值得专门强调。如果同一个用户的几十条推文既出现在训练集又出现在验证集,模型实际上是记忆了用户 ID 对应的风格,验证分数会虚高,上线后打回原形。所以我一般先按用户 ID 去重分组,再切分数据。
4. 多标签训练与性格预测的完整落地步骤
4.1 数据切分与模型训练的完整代码骨架
将前面各步骤串起来,形成一条端到端的训练流程。这里用StratifiedKFold做交叉验证,并在每个 fold 内按用户分组确保数据不泄漏。
import pandas as pd from sklearn.model_selection import GroupKFold from sklearn.preprocessing import StandardScaler df = pd.read_csv('social_users.csv') X = build_user_features(df['text'].tolist(), w2v_model) y = df[['openness', 'conscientiousness', 'extraversion', 'agreeableness', 'neuroticism']].values # 标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 按用户 ID 分组切分 gkf = GroupKFold(n_splits=5) for train_idx, val_idx in gkf.split(X_scaled, y, groups=df['user_id'].values): X_train, X_val = X_scaled[train_idx], X_scaled[val_idx] y_train, y_val = y[train_idx], y[val_idx] model = build_dnn(X_scaled.shape[1], y.shape[1]) early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_auc', mode='max', patience=5, restore_best_weights=True ) model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=30, batch_size=128, callbacks=[early_stop], verbose=1 )这段代码中的GroupKFold是防止数据泄漏的关键,groups=df['user_id'].values确保同一个用户的所有文本只会出现在同一个折里。StandardScaler在训练折上拟合后再转换验证折,避免验证信息流入训练过程。早停监控的是val_auc而不是val_loss,因为 AUC 对不均衡数据更稳定。
4.2 预测结果的阈值选择与概率解读
训练完成后,模型输出的不是一个确定的类别,而是每个标签的概率。把概率变成 0/1 标签时,阈值的设定直接影响召回率和精确率的平衡。不同性格维度的正样本比例不同,一刀切用 0.5 不是最优解,我一般会在验证集上对每个标签搜索最优阈值。
from sklearn.metrics import precision_recall_curve import numpy as np def find_optimal_threshold(y_true, y_prob): precisions, recalls, thresholds = precision_recall_curve(y_true, y_prob) f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-9) return thresholds[np.argmax(f1_scores)] optimal_thresholds = [] for i in range(y_val.shape[1]): thr = find_optimal_threshold(y_val[:, i], model.predict(X_val)[:, i]) optimal_thresholds.append(thr)这里的逻辑是:对每个性格维度,画出精确率-召回率曲线,找到令 F1 最高的分割点,作为该维度的判定阈值。它比统一 0.5 更能适应标签分布差异。比如“开放性”正样本占比高时阈值可能低到 0.3,而“神经质”可能要到 0.6。
4.3 失败时先看这几个信号
训练过程中最常见的三种异常信号和处理思路如下。
第一,验证 AUC 始终在 0.5 附近徘徊,说明模型没有学到有效信号。先检查特征矩阵是否有大量零值或标准差为零的列,再确认词向量是否基于同领域语料预训练。
第二,训练 AUC 接近 1.0 而验证 AUC 很低,这是典型的特征泄漏或过拟合。除分组切分外,还要检查是否把标签相关的词(如“抑郁”“开心”)直接放进了 TF-IDF 词表。
第三,训练 loss 震荡不下降。降低学习率到 1e-4,同时确认输入数据经过标准化,避免大数值特征主导梯度更新。
5. 从分析到应用:把用户性格特征接入业务系统
5.1 用少量新样本来验证模型可用性
在把模型接入线上系统之前,我习惯先用人工标注的样本做一个“最小可行验证”。准备 50 条新用户的微博或推特文本,让经过培训的 annotator 按 Big Five 打分,再跟模型预测结果比较一致率。如果一致率低于 60%,不要急着调参,先回看标注指南是否统一——不同人对“外向”的理解差异很大。
5.2 推理接口的轻量封装实践
模型训练完成后,部署讲究轻量。实际应用中不必保留训练代码,只需要导出模型权重和特征工程的预处理参数。下面的代码展示了一个完整的推理函数,输入一条用户文本,输出性格标签与概率。
import pickle import tensorflow as tf import jieba import numpy as np class PersonalityPredictor: def __init__(self, model_path, w2v_path, scaler_path, tfidf_path): self.model = tf.keras.models.load_model(model_path) self.w2v = __import__('gensim').models.Word2Vec.load(w2v_path) self.scaler = pickle.load(open(scaler_path, 'rb')) self.tfidf = pickle.load(open(tfidf_path, 'rb')) def predict_one(self, text): tfidf_vec = self.tfidf.transform([text]).toarray() words = jieba.lcut(text) vecs = [self.w2v.wv[w] for w in words if w in self.w2v.wv] emb_vec = np.mean(vecs, axis=0) if vecs else np.zeros(self.w2v.vector_size) x = np.hstack([tfidf_vec, emb_vec.reshape(1, -1)]) x = self.scaler.transform(x) probs = self.model.predict(x, verbose=0)[0] return probs predictor = PersonalityPredictor('model.h5', 'w2v.model', 'scaler.pkl', 'tfidf.pkl') probs = predictor.predict_one("今天又加班到深夜,不过项目终于上线了,开心!")这个封装的关键在于:特征构造逻辑必须与训练时完全一致,tfidf.pkl和scaler.pkl保存的是训练时拟合好的对象,推理时不能重新拟合。词向量模型加载后需要保留完整的vector_size信息,确保emb_vec的维度与训练时一致。verbose=0是为了避免推理时输出多余日志。
5.3 上线后的监控维度
模型上线不是终点,性格分析这类任务尤其依赖持续的反馈闭环。第一个要监控的是线上输入分布漂移,例如新用户大量使用表情包和短视频文案,与训练语料的文本风格差异变大。第二个是预测结果分布,如果某个标签的正样本比例在两周内明显偏移,需要警惕数据漂移。第三个是人工复核机制,对预测置信度处于 0.4 到 0.6 之间的模糊样本,定期抽样人工标注并回填训练集。
这套流程跑通后,模型输出的性格向量就可以用于个性化推荐策略制定、社区内容分组管理或舆情用户画像构建。接入方式可以直接调用predictor.predict_one(),也可以用 Flask 包成 HTTP 接口。每一步都不复杂,但组件的对齐和验证细节决定了最终效果。先在一个小范围用户群上跑通最小闭环,再逐步放开流量,是成本最低的稳妥路径。
本文还有配套的精品资源,点击获取