[Python笔记] 用LDA(隐含狄利克雷分布)抽取主题分布+用户特征生成

实习的时候有个任务,利用大样本关联多种特征二分类预测,其中有部分特征为文本特征,简单处理过后可取得一定收益,便考虑使用gensim库里的lda抽取样本在文本特征上的主题分布生成新的特征,具体实现如下:

  • gensim中lda包的使用请参考:pyLDA系列︱gensim中的主题模型(Latent Dirichlet Allocation)—— 悟乙己
  • lda原理请参考:通俗理解LDA主题模型 —— v_JULY_v

问题背景

在进行特征挖掘时,有一类表现尚好的特征为样本APP相关信息,其内容皆为文本,特征文本内容分为3种,apkname唯一标识、appname和apptags(以逗号分隔),欲将每个样本所包含的APP相关信息经清洗融合为单一文本,作为代表该样本的文本建立主题模型,最终用lda得到每个样本在top主题上的概率分布,作为样本新的特征使用。
特征的样式……编个例子,如下:

apkname appname apptags
com.bilibili.fgo.qihoo 命运-冠位指定-FGO 正版IP 网游 ACT 回合 二次元

数据样本量:约300万
数据中app种类:约28万

预处理

特征文本内容分为3种,apkname唯一标识、appname和apptags,为生成特征文本,欲将apkname以"."分隔,appname需进行分词以及特殊符号清洗将其融入tag。

import numpy as np
import pandas as pd
#import pyLDAvis.gensim
import json
import warnings
import gensim
import string
from gensim import corpora, models
import os, sys
import gc
import logging
from logzero import logger
logging.basicConfig(format="%(asctime)s:%(levelname)s:%(message)s",
                    level=logging.INFO)
warnings.filterwarnings('ignore')  # To ignore all warnings that arise here to enhance clarity

from gensim.models.coherencemodel import CoherenceModel
app = pd.read_csv('../input/user_apklabels.csv',nrows=None, usecols=['apklabels'])
print(app.shape[0] - app.isnull().sum())
app = app[~app.isnull()]
#app['is_num_apkname']=[s.isalnum() for s in app['apklabels'].astype(str).values]
#app = app[app['is_num_apkname']==False]
app.shape[0]
app['apklabels'] = [s.replace("[","").replace("]","").replace(" ","") for s in app['apklabels']]
app.head(20)
doc_clean = [c.split(',') for c in app['apklabels']]
del app
gc.collect()
logger.info('generating dictionary...')
dictionary = corpora.Dictionary(doc_clean)
dictionary = dictionary.filter_extremes(no_below=5, no_above=0.3, keep_n=100000)
dictionary = dictionary.compactify()
dictionary.save("lda_dictionary.dic")
logger.info('doc 2 bow...')
doc_term_matrix = [dictionary.doc2bow(doc) for doc in doc_clean]
del doc_clean
gc.collect()
corpora.MmCorpus.serialize('lda_app_corpus.mm', doc_term_matrix)  # store to disk, for later use

尽管如上代码在试验小数据集时可顺利运行,但在实际操作时,由于数据集过大造成开发机内存泄漏,本预处理于ETL中以HiveSql进行。以下知识点十分实用:

-- 合在一起得到app文本
concat_ws(',',collect_set(apkname_cleaned),collect_set(app_splited_cleaned),
  collect_set(tag_cleaned)) as app_text
-- group by之后选取其中一个数值就行,常用于group by userid的场景(一个用户所有数据中该栏目数据不变)
collect_set(is_apply_suc)[0]
-- 正则表达式替换字符
regexp_replace(regexp_replace(regexp_replace(apkname,'dkplugin|dkmodel|app|App|xposed|The|pro|Pro|apps|com.',''),'^.|$.',''),'.|.{2,}',',') as apkname_cleaned

预处理结束后的样本样式如下:

com,bilibili,fgo,qihoo,命运,冠位,指定,FGO,正版IP,网游,ACT,回合,二次元

(如果样本内含多款APP,则相应apkname、appname、apptags会经预处理被分词后分别合在一起生成APP文本)
此处忽略取数过程,得到样本app文本,以每个用户的app文本作为文本输入生成主题模型。

训练lda

import pandas as pd
import gensim
from gensim import corpora
import nltk
from nltk.corpus import stopwords #停用词
from nltk.stem.wordnet import WordNetLemmatizer #NLTK的WordNet来对同一个单词进行变体还原
import string
from gensim.utils import simple_preprocess
import operator
from gensim import corpora, models
import os, sys
import gc
import logging
from logzero import logger
logging.basicConfig(format="%(asctime)s:%(levelname)s:%(message)s",
                    level=logging.INFO)
#这一步开始导入tag作为文本
logger.info('loading dictionary...')
dictionary = corpora.Dictionary.load("lda_dictionary.dic")
logger.info('loading doc 2 bow...')
doc_term_matrix = gensim.corpora.MmCorpus('lda_app_corpus.mm')
#用tf-idf处理:
logger.info('start tf-idf...')
tfidf = gensim.models.TfidfModel(doc_term_matrix)
corpus_tfidf = tfidf[doc_term_matrix]
del doc_term_matrix
gc.collect()
logger.info('start lda training...')
ldamodel = models.ldamulticore.LdaMulticore(corpus_tfidf, num_topics=100, id2word = dictionary, passes=5,iterations = 300,eval_every=50,workers = 10,chunksize=20000)
ldamodel.save('lda_app.model') 
logger.info('end lda training...')
topics = ldamodel.print_topics(num_topics=100, num_words=10)
logger.info('topics: %s', topics)
logger.info('end')

训练1pass大概耗时近1小时,尽管已经选择多核lda,总体耗时仍较长。建议nohup python 以.py脚本形式离线运行,并同时输出结果到日志,监控lda训练过程。
由于内存消耗过大,在此生成model之后先保存模型,转另一个脚本进行结果输出。

lda模型的评价

请参考:gensim LDA模型的优劣评估 —— Zsank

以稀疏矩阵输出结果

lda训练得到的结果是以corpus格式保存在模型中的,用get_document_topics()方法的话,会得到语料格式的返回主题分布,且会自动过滤概率小于默认值的主题,返回其余主题编号及用户在该主题上的概率。

model.get_document_topics(corpus[0])
>>> [(1, 0.13500942), (3, 0.18280579), (4, 0.1801268), (7, 0.50190312)]

为得到可应用于lightGBM的主题分布作为特征输入,需要将corpus转为稀疏矩阵。

#topic制作#
import numpy as np
import pandas as pd
import gensim
from gensim import corpora, models
import os, sys
import gc
from scipy import sparse
import logging
from logzero import logger
logging.basicConfig(format="%(asctime)s:%(levelname)s:%(message)s",
                    level=logging.INFO)
lda_model_try03 = models.ldamodel.LdaModel.load('../lda_app.model')
doc_term_matrix = gensim.corpora.MmCorpus('../lda_app_corpus.mm')
tfidf = gensim.models.TfidfModel(doc_term_matrix)
corpus = tfidf[doc_term_matrix]
del doc_term_matrix
gc.collect()
get_document_topics = lda_model_try03.get_document_topics(corpus,minimum_probability=0.3)
del corpus
gc.collect()
all_topics_csr = gensim.matutils.corpus2csc(get_document_topics,printprogress=20000)
all_topics_numpy = all_topics_csr.T.toarray()
user_topic = pd.DataFrame(all_topics_numpy)
userid = pd.read_csv('../user_labels.csv',nrows=None,usecols=['userid'])
user_topic['userid'] = userid
del userid
gc.collect()
user_topic.to_csv('user_topic_app.csv')
#get_document_topics(bow, minimum_probability=None,minimum_phi_value=None, per_word_topics=False)

结果

拿到的收益不大,仅当做一次lda及大数据集的练习,今后可能考虑使用Embedding或者概率图的方式再优化处理该特征数据。

你可能感兴趣的:(Python笔记)