资讯动态

从SVM到DNN:社交媒体性格分析中的特征工程与模型设计

发布时间:2026/9/10 16:22:43 来源:尧图企业网站定制
简介这套资源是一份基于深度神经网络DNN的社交媒体用户性格分析完整项目面向人工智能、深度学习方向的研究者与开发者适合用于NLP文本分类、用户画像及性格预测等场景。包内实现了从数据爬取、预处理、特征构建到模型训练、评估与推理的完整流程包含TF-IDF、Doc2Vec、情感词典、词向量等特征处理方法并覆盖DNN与SVM对比实验可帮助读者理解性格多分类任务的落地思路。资源共54个文件以Python脚本、npy数据文件为主另有Markdown说明文档、文本配置、CSV数据及gitignore配置压缩包整体约18.34MB。目录区分为数据处理、模型训练、评估推理等模块结构清晰便于按步骤复现实验。项目结合五大人格特质模型展示如何将深度学习应用于社交媒体文本分析对个性化推荐、广告定向等领域具有参考价值。已有136人学习下载适合具备一定Python与深度学习基础、希望从零搭建文本性格分析系统的读者深入研究。1. 文本性格分析从传统机器学习转向DNN瓶颈不在模型而在特征形态做社交媒体用户性格分析很多团队第一版用的是SVM加TF-IDF跑出来的准确率也在可接受范围内但一旦把数据规模从几千条推到几万条SVM的核函数计算和稀疏矩阵存储会先把内存压垮调参窗口也越来越窄。这个项目把特征工程和DNN训练放到同一条流水线里等于把“特征设计”和“分类器训练”解耦让模型自己去组合高阶特征而不是靠人工拼特征。它的核心价值不在于网络有多深而在于提供了从文本清洗、特征抽取、模型训练到推理落地的完整链路。对刚入门的工程师来说能直接看到每一层输入输出的形状变化对做过多年的开发者也能在特征选择和数据增强的策略上找到值得对比的细节。项目代码基于Python实现模型部分用TensorFlow或PyTorch均可复现数据集指向社交媒体文本标注的五大人格维度适合NLP方向的算法工程师、数据挖掘岗面试者、以及对用户画像建模感兴趣的研究人员。2. 数据清洗与特征工程222维特征是如何从原始文本里挤出来的2.1 原始语料的预处理链路项目根目录下的process_data1.py负责把社交媒体原始文本转换成结构化数据。单条用户动态比如“今天天气真好啊哈哈哈哈”经过管道后变成词汇序列、词性标记和情感极性三路输出。这个多路并行设计的目的是后续可以同时构建词汇级和句子级特征避免单一表示丢失语义层次。# process_data1.py 核心逻辑示意代码 import re import jieba from emotion_lexicon import load_lexicon def clean_text(raw: str) - str: # 1. 去除URL和提及 text re.sub(rhttp\S|www\.\S, , raw) text re.sub(r\w, , text) # 2. 统一数字和英文大小写 text re.sub(r\d, NUM , text) text text.lower() return text def tokenize(text: str) - list: # 中文按jieba精确模式分词保留长度大于1的词 return [w for w in jieba.lcut(text) if len(w.strip()) 1] def extract_emotion_scores(text: str, lexicon: dict) - dict: # 统计文本中正/负情感词命中数返回极性得分 pos_score sum(1 for w in tokenize(text) if w in lexicon[pos]) neg_score sum(1 for w in tokenize(text) if w in lexicon[neg]) return {pos: pos_score, neg: neg_score}clean_text里的正则顺序有讲究——先删URL再处理数字能避免把URL里的数字误归一化成NUM标记否则下游TF-IDF会把“链接”和“数字”混成同一个语义槽。tokenize这里用len(w.strip()) 1过滤单字是因为社交媒体口语里单字语气词“哈”“啊”对人格预测的判别力极低且会拉高特征维度保留反而增加过拟合风险。情感词典的命中统计走的是朴素计数路线词频信息后续会和TF-IDF特征做拼接而不是单独建模这样设计的原因是情感极性本身已经压缩了语义不需要再让DNN从头学。2.2 三类特征的无缝拼接222 features目录下存的是已经抽取完成的特征矩阵每行对应一个用户每列对应一个特征维度。这222维特征不是单一来源而是分为三组词法统计类token数量、平均句长、标点密度、大写比例等、TF-IDF类对分词结果做TfidfVectorizer降维到150维、Doc2Vec类用doc2vec模型把用户全部动态聚合成一条文档向量取维度70。# 特征拼接逻辑示意代码 from sklearn.feature_extraction.text import TfidfVectorizer from gensim.models.doc2vec import Doc2Vec, TaggedDocument # 1. TF-IDF特征ngram_range(1,2)控制维度不超过150 tfidf_vec TfidfVectorizer(max_features150, ngram_range(1,2), sublinear_tfTrue, min_df3) tfidf_mat tfidf_vec.fit_transform(all_docs) # 2. Doc2Vec特征dm0表示使用DBOW模式训练更快 d2v_model Doc2Vec(documents[TaggedDocument(doc.split(), [i]) for i, doc in enumerate(all_docs)], vector_size70, window5, min_count2, dm0, epochs20, workers4) d2v_mat [d2v_model.dv[i] for i in range(len(all_docs))] # 3. 手工特征统计每个用户的动态条数/平均长度/情感极性 manual_feat extract_manual_features(user_texts) # 假设返回(样本数, 2)TF-IDF的max_features150属于结构化压缩原本文本空间可能有几万维截断到150维后虽然损失长尾词信息但配合Doc2Vec的70维语义向量恰好覆盖了“关键词命中”和“全局语义”两个互补维度。Doc2Vec使用dm0DBOW而非dm1DM原因是训练语料只有社交媒体短文本DM模式对上下文窗口的平滑要求更高短文本下DBOW的泛化能力更稳。workers4在多核机器上能跑满但对小数据集低于1万条反而会有线程切换开销建议改为2。2.3 特征矩阵写入与内存边界控制特征拼接完成后write_feature_matrix函数负责把稀疏矩阵和稠密向量统一成.npy格式落盘。这里有一个容易踩的坑TF-IDF输出的是scipy.sparse矩阵直接np.save会报错必须用toarray()显式转成稠密格式但5000用户×222维的稠密矩阵内存约9MB不影响如果用户量到10万级建议只存稀疏格式并让DNN从稀疏输入层开始训练。import numpy as np from scipy import sparse def write_feature_matrix(tfidf_mat, d2v_mat, manual_feat, output_path): # 合并三类特征横向堆叠 dense_tfidf tfidf_mat.toarray() if sparse.issparse(tfidf_mat) else tfidf_mat all_features np.hstack([dense_tfidf, d2v_mat, np.array(manual_feat).reshape(-1, 2)]) # 输出形状为 (样本数, 222) np.save(output_path, all_features) print(fFeature matrix saved with shape {all_features.shape})output_path建议按数据集版本命名比如feat_v3_222.npy因为FB数据集的标签分布在不同采集批次下会有偏移版本号能帮你快速回溯是哪一版特征跑出的指标。np.hstack拼接时要求三组数据的样本数一致如果某用户没有Doc2Vec向量因为分词后词数少于min_count需要在更早环节滤掉该用户否则拼接阶段直接报维度不匹配错误。3. DNN模型设计与训练从222维输入到五维输出的映射3.1 网络结构和参数量计算character_dnn目录下的character_train.py定义了模型主体。输入层为222个特征经过三层全连接后输出层为10个节点。这里输出为什么不是5而是10因为项目把每个性格维度建模成独立的二分类问题——比如“开放性”分高低两档而不是直接做五分类这样每个维度的阈值可以单独校准避免不同性格维度的分数分布差异互相干扰。# character_train.py 核心模型定义示意代码 import tensorflow as tf from tensorflow.keras import layers, models def build_dnn_model(input_dim222, hidden_units[128, 64, 32]): model models.Sequential(namecharacter_dnn) model.add(layers.InputLayer(input_shape(input_dim,), namefeatures)) # 隐藏层ReLU BatchNorm Dropout 交替 for units in hidden_units: model.add(layers.Dense(units, activationrelu, kernel_regularizertf.keras.regularizers.l2(5e-4))) model.add(layers.BatchNormalization()) model.add(layers.Dropout(rate0.3)) # 输出层sigmoid每个维度独立二分类 model.add(layers.Dense(10, activationsigmoid, namepersonality_output)) model.compile(optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy, tf.keras.metrics.AUC(nameauc)]) return model三层隐藏层设为128-64-32是一个从宽到窄的漏斗结构。输入222维特征第一层用128个神经元先做宽泛组合第二层64个神经元压缩模式第三层32个神经元提炼高级抽象。这种结构比直接222→10的单层网络多了非线性表达能力又比深层ResNet更容易训练——毕竟文本统计特征不像图像像素那样有天然的空间局部性过深的网络反而会把已经稠密的特征打散。L2正则化系数5e-4是经验值太大超过1e-2会把权重压得过于稀疏导致模型退化到线性Dropout率0.3对222维输入来说是中等强度社交媒体文本特征之间本身有相关性dropout率太高会随机屏蔽整组相关特征训练不稳定。3.2 训练配置与早停策略训练循环里需要特别关注的是早停和模型checkpoint的配合。character_train.py里设置了验证集上AUC不再提升连续5轮就停止训练并保存验证集上AUC最高的权重文件。# character_train.py 训练配置示意代码 checkpoint tf.keras.callbacks.ModelCheckpoint( filepathmodel/best_model.weights.h5, monitorval_auc, modemax, save_best_onlyTrue, verbose1) early_stop tf.keras.callbacks.EarlyStopping( monitorval_auc, patience5, restore_best_weightsTrue, verbose1) history model.fit( x_train, y_train, validation_data(x_val, y_val), epochs100, batch_size64, callbacks[checkpoint, early_stop], verbose1 )monitorval_auc比val_accuracy更可靠。人格分析数据集的标签分布通常不平衡——比如“神经质”维度低分组可能占70%模型全部预测为低分组也能有70%准确率但AUC会掉到0.5左右能真实反映模型区分能力。patience5意味着如果连续5轮验证集AUC没创新高就回滚到最优权重这个值适配小数据集因为小数据集验证集噪声大patience太小比如2容易被单轮的随机波动骗到提前停止。注意restore_best_weightsTrue保证训练结束后模型加载的是历史最优权重而不是最后一轮的权重。有些版本ModelCheckpoint和EarlyStopping同时启用时如果EarlyStopping先触发restore_best_weights会覆盖掉checkpoint加载的权重需要确认TF版本行为建议统一使用EarlyStopping的恢复机制。3.3 与SVM基线模型的对比实验项目里保留了character_svm目录里面有svm_tfidf_test.py和svm_dbow_test.py两个脚本——前者只用TF-IDF特征后者用DBOW向量。做对比实验的意义在于DNN的收益到底来自特征组合能力还是网络结构本身如果不跑SVM基线这个结论说不清。# svm_tfidf_test.py 对比实验示意代码 from sklearn.svm import SVC from sklearn.multiclass import OneVsRestClassifier # SVM用RBF核C1.0gammascale每个维度独立训练 svm_model OneVsRestClassifier( SVC(kernelrbf, C1.0, gammascale, probabilityTrue, random_state42), n_jobs-1) svm_model.fit(x_train, y_train) y_pred_proba svm_model.predict_proba(x_val)OneVsRestClassifier包了一层SVC和DNN的10节点sigmoid输出在数学上是等价的——都是每个维度做独立二分类。C1.0是默认值RBF核下这个值偏保守如果你在SVM上把C调到10以上可能缩小和DNN的差距但训练时间会指数级增长。gammascale会根据特征维数自动计算带宽222维特征下等价于gamma1/222是一个合理的初始值。跑完对比记录每个维度的AUC如果DNN比SVM提升不足1%说明特征工程已经做到位网络再深也难有突破如果提升超过3%说明特征之间的非线性交互确实被DNN捕获了。4. 模型评估与推理落地character_eval和character_inference的完整逻辑4.1 评估指标如何细粒度拆分character_eval.py不是简单地打印整体准确率而是输出10个标签各自的精确率、召回率、F1和AUC并额外计算五个维度的高低分组各自的正负样本比例。这个设计解决了机器学习里关键的“平均指标掩盖局部失败”问题——整体准确率75%可能意味着“尽责性”维度的低分组全对了“开放性”维度的高分组全错了平均值一拉看起来还不错但业务上完全没法用。# character_eval.py 各维度评估示意代码 from sklearn.metrics import classification_report, roc_auc_score import numpy as np label_names [open_low, open_high, cons_low, cons_high, extra_low, extra_high, agree_low, agree_high, neur_low, neur_high] # 对每个输出节点做独立评估 for idx, name in enumerate(label_names): y_true_dim y_val[:, idx].astype(int) y_pred_binary (y_val_pred[:, idx] 0.5).astype(int) auc roc_auc_score(y_true_dim, y_val_pred[:, idx]) report classification_report(y_true_dim, y_pred_binary, output_dictTrue, zero_division0) print(fDimension {name}: AUC{auc:.4f}, fF1{report[1][f1-score]:.4f}, fPrecision{report[1][precision]:.4f})roc_auc_score接收的是预测概率而不是二值标签这一点比accuracy_score更严格——如果只传y_pred_binaryAUC会因离散化丢失大量概率排序信息算出的值也偏低。zero_division0处理极端情况当某个维度在预测时完全没有输出正样本比如所有预测概率都低于0.5classification_report按定义会除零设成0避免训练中断。如果某个维度的高分组F1明显低于低分组需要先看该组的样本量——社交媒体文本对“开放性”这类心理特质的信号本身就比较弱模型倾向保守预测这种情况优先考虑给少数类加权而不是急着改网络结构。4.2 评估集划分中容易被忽视的泄漏源目录里utils文件下的split_data函数承担了划分逻辑。真正容易导致评估结果虚高的泄漏源不是随机种子没设而是同一用户的多次动态被拆到了训练集和验证集。假设一个用户发了200条推文模型见过其中150条验证集里剩下50条和训练集内容高度相关预测自然准确但换新用户就露馅——这个在代码里需要按用户ID而不是按文本条目切分。# utils/split_data.py 按用户划分示意代码 from sklearn.model_selection import GroupShuffleSplit def group_split(user_ids, X, y, test_size0.2, random_state42): gss GroupShuffleSplit(n_splits1, test_sizetest_size, random_staterandom_state) train_idx, val_idx next(gss.split(X, y, groupsuser_ids)) return X[train_idx], X[val_idx], y[train_idx], y[val_idx]GroupShuffleSplit的groups参数是关键它保证同一个用户的样本全部落在同一侧。如果不用分组切分评估的AUC虚高0.05到0.1是很正常的尤其在Doc2Vec特征下因为同用户的文本训练出的向量天然就有嵌入记忆。嵌套写法next(gss.split(...))只取第一折因为这里只需要一个验证集不需要K折交叉验证——K折在小数据集上会显著增加训练时间且每个fold的模型都不一样最后没法直接对单条新样本做推理。4.3 推理脚本的输入输出设计character_inference.py是新用户文本在线预测的入口。它加载保存好的权重文件对新用户的所有动态按时间排序拼接成单个文档经过和训练阶段完全相同的预处理、TF-IDF变换、Doc2Vec推断后输出10个概率值。# character_inference.py 推理逻辑示意代码 def predict_user(model, tfidf_vectorizer, d2v_model, user_texts): # 1. 拼接用户动态并按训练时的流程清洗 merged_text .join(user_texts) clean clean_text(merged_text) # 2. TF-IDF必须用训练时拟合的vectorizer不能重新fit tfidf_vec tfidf_vectorizer.transform([clean]) # 3. Doc2Vec推理用dbow_wordsFalse避免更新词向量 doc_vec d2v_model.infer_vector(tokenize(clean), epochs50) # 4. 特征拼接后输入DNN features np.hstack([tfidf_vec.toarray(), doc_vec.reshape(1, -1), extract_manual_feature(clean)]) proba model.predict(features)[0] # 5. 输出为五维性格得分每维取high档概率 return {openness: proba[1], conscientiousness: proba[3], extraversion: proba[5], agreeableness: proba[7], neuroticism: proba[9]}tfidf_vectorizer必须直接复用训练时保存的对象而不是新建一个TfidfVectorizer再fit——重新fit得到的词表和新数据分布一致但和训练阶段的词表空间不对齐transform出来特征的位置全错乱了。infer_vector的epochs50是Gensim的默认值对短文本聚合文档建议保持过少小于20语义向量不够稳定过多大于100会把训练阶段的向量空间拉偏。proba[1]代表openness维度high档的概率因为输出节点索引顺序是低档在前高档在后这个映射关系要和训练时标签编码保持一致否则预测结果正好反向。5. 用混淆矩阵定位特征失效的边界条件性格分析的最后一个实战技巧是把混淆矩阵按特征来源拆分来看——不是看四个象限数字而是看“哪些样本被预测正确、哪些被预测错误”对应的特征模式差异。常见的定位场景验证集上“外向性”维度F1为0.61明显低于其他维度。直接改网络结构可能无效正确的排查是把错误预测的样本单独抽出来检查他们的Doc2Vec向量在空间中的分布位置。# 特征错误模式分析示意代码 import matplotlib.pyplot as plt from sklearn.decomposition import PCA # 取预测错误的样本 errors_idx np.where((y_val[:, 5] 0.5) ! (y_val_pred[:, 5] 0.5))[0] # 降维并可视化错误样本的Doc2Vec空间位置 pca PCA(n_components2) d2v_reduced pca.fit_transform(d2v_val[errors_idx]) plt.figure(figsize(8, 6)) plt.scatter(d2v_reduced[:, 0], d2v_reduced[:, 1], cy_val[errors_idx, 5], cmapcoolwarm, alpha0.7) plt.title(Distribution of error samples in Doc2Vec space) plt.savefig(error_analysis_extra.png, dpi150)errors_idx获取的是预测错误样本的索引这一行在调试阶段的价值是让你能回溯原始文本直接看模型到底在哪些表达上犯了错。如果错误样本在Doc2Vec空间里聚成明显的两团说明该性格维度在文档向量这个特征族上存在语义盲区常见做法是回到process_data1.py增加一个基于句法模式的特征——比如“我经常/我从不”这类行为陈述句对“尽责性”有强信号。dpi150足以看清散点分层不需要更高。如果你的业务场景不是社交媒体而是电商评论、客服工单或问卷开放题这套222维特征加DNN的流水线不需要改动架构只替换数据清洗阶段的正则规则和分词词典即可。特征维度越高DNN相对SVM的优势越显眼那是因为DNN的参数组合能力在稠密特征空间里更容易发挥。做实验时建议固定随机种子最多跑三轮取均值——文本数据的方差本身就大单轮结果不具备参考意义。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价