Posts /

搜狗用户画像

Twitter Facebook
31 Dec 2016

一、赛提说明及结果

a)竞赛简介

在现代广告投放系统中,多层级成体系的用户画像构建算法是实现精准广告投放的基础技术之一。其中,基于人口属性的广告定向技术是普遍适用于品牌展示广告和精准竞价广告的关键性技术。人口属性包括自然人的性别、年龄、学历等基本属性。

在搜索竞价广告系统中,用户通过在搜索引擎输入具体的查询词来获取相关信息。因此,用户的历史查询词与用户的基本属性及潜在需求有密切的关系。

举例如下:

1、 年龄在19岁至23岁区间的自然人会有较多的搜索行为与大学生活、社交等主题有关

2、 男性相比女性会在军事、汽车等主题有更多的搜索行为

3、 高学历人群会更加倾向于获取社会、经济等主题的信息

本题目提供用户历史一个月的查询词与用户的人口属性标签(包括性别、年龄、学历)做为训练数据,要求参赛人员通过机器学习、数据挖掘技术构建分类算法来对新增用户的人口属性进行判定。

b)初赛结果

说明:由于不确定提交的精度变低会不会使排名降低,因此我注册了几个号,只在精确度提高的时候才用 ZZC_Persona 提交(105),因此虽然只显示提交 4 次,实际提交次数大约 15 次。

c)复赛结果

如上图所示,我们组成功进入复赛,未能进入决赛。由于小组成员各自有其他比赛,未继续提交。

二、组员分工

我们组的三位成员中,张政 在比赛期间生病住院,因此无法参加比赛,陈小龙 因为有 ACM 竞赛,也并未在比赛中投入太多精力,因此本次 CCF 大数据竞赛基本由 朱本金 一人完成。在每个成员名字后面注明了工作量:

三、 算法说明 & 核心代码


Another try is to use LSTMs to predict user labels.

  1. read data, cut tokens then remove stop tokens.
  2. build the word-index trans table.
  3. specify the vocabulary_size, for example, 10000, and all the words not in the vocabulary is replaced with UNKNOWN_TOKEN.
  4. use One-Hot encoding to represent each token of a user’s query list. so the query list of each user become a matrix of len(query_list) by vocabulary_size.
  5. Build a 5 layers LSTM network, and input the above query matrix of each user and the corresponding labels vector into it to produce the predictions.

The above process will produce a qualified baseline, which means we can tune the details to gain improvements:

  1. clean more irrelevant tokens, for example, the most frequent tokens.
  2. add user dict to make more precision and meaningful cut result.
  3. to be continued … ``` 核心代码
  ## 分词
  def cut2rtn():
      fw = codecs.open('./data/output/queries_tokenized.csv', 'w', 'utf-8')
      # fw_ages = codecs.open('./data/output/ages.csv', 'w', 'utf-8')
      # fw_genders = codecs.open('./data/output/genders.csv', 'w', 'utf-8')
      # fw_educations = codecs.open('./data/output/educations.csv', 'w', 'utf-8')

      for queriesPerUser in userQueries:
          queryList = []  # query list per user.
          for query in queriesPerUser:
              qry_tks = jieba.lcut(query, cut_all=False)
              final = ''
              for tk in qry_tks:
                  if tk not in stop_tokens:
                      if tk != ' ':
                          queryList.append(tk)
                          final += tk + ','
              fw.write(final)
          fw.write('\n')
          queryLists.append(queryList)

      # Split  train set to train and validation set.
      trainQueryLists = queryLists[:20000]
      testQueryLists = queryLists[20000:]

      return userID, ages, genders, educations, trainQueryLists, testQueryLists

  ## 使用 LDA 进行训练
  import time
  import gensim
  from corpusLoader import cut2rtn, cutTest2Rtn
  import codecs
  import numpy as np

  topicNum = 200

  word2id = {}
  id2word = {}
  maxQID = 0
  # 加载语料的train或test子集,单词以句子为单位放入 orig_docs_words,类比放在 orig_docs_cat
    # setDocNum, orig_docs_words, orig_docs_name, orig_docs_cat, cats_docsWords, \
    # cats_docNames, category_names = loader(i)

    UIDs, ages, genders, educations, trainQueryLists, testQueryLists = cut2rtn()
    # testUIDs, testQueryLists = cutTest2Rtn()
    corpora = trainQueryLists + testQueryLists


    # 当前循环所处理的语料子集,是一个list的list。每个外层list元素对应一个文档
    # 每个内层list为一串 (word_id, frequency) 的pair
    # 这种格式是gensim的标准输入格式
    corpus = []

    # 保存原始文本,以供人查看
    orig_filename = "corpora.orig.txt"
    ORIG = codecs.open(orig_filename, 'w', 'utf-8')
    # 每个 wordsInSentences 对应一个文档
    # 每个 wordsInSentences 由许多句子组成,每个句子是一个list of words
    for queryList in corpora:
        # 统计当前文档的每个词的频率,也就是说,每一篇文档都有一个word-frequency对照表。
        doc_tid2freq = {}
        # 循环取当前文档的一个句子
        for queryToken in queryList:
      
            ORIG.write("%s " % queryToken)
      
            # 如果w已在word2id映射表中,映射成wid
            if queryToken in word2id:
                qid = word2id[queryToken]
            # 否则,把w加入映射表,并映射成新wid
            else:
                qid = maxQID
                word2id[queryToken] = maxQID
                id2word[maxQID] = queryToken
                maxQID += 1
      
            # 统计 wid 的频率
            if qid in doc_tid2freq:
                doc_tid2freq[qid] += 1
            else:
                doc_tid2freq[qid] = 1
      
        ORIG.write("\n")
        # 把文档中出现的wid按id大小排序
        sorted_qids = sorted(doc_tid2freq.keys())
        doc_pairs = []
        # 把 (wid, frequency) 的对追加到当前文档的list中
        for qid in sorted_qids:
            doc_pairs.append((qid, doc_tid2freq[qid]))
      
        # 当前文档的list已经完全生成,把它加入subcorpus,即语料子集的list中
        corpus.append(doc_pairs)

    ORIG.close()

    print "Training LDA... total %d docs..." %len(corpus)
    startTime = time.time()
    # LDA训练的时候是把train和test放一起训练的(更严格的办法应该是只用train集合来训练)
    lda = gensim.models.ldamodel.LdaModel(corpus=corpus, num_topics=topicNum, passes=2, iterations=500, update_every=20, minimum_probability=0.001, alpha='auto', eta='auto')

    endTime = time.time()
    print "Finished in %.1f seconds" % (endTime - startTime)

    lda_filename = "train.svm-lda.txt"
    LDA = codecs.open(lda_filename, 'w', 'utf-8')
    print "Saving topic proportions into '%s'..." % lda_filename

    # 拿出一个语料子集 (train或者test)
    # labels = ages
    ageLabels = []
    genderLabels = []
    educationLabels = []

    with codecs.open('./data/train.csv', 'r', 'utf-8') as fr:
        for line in fr.readlines():
            line = line.split('\t')
            ageLabels.append(line[1])
            genderLabels.append(line[2])
            educationLabels.append(line[3])
        fr.close()

    labels = zip(ageLabels, genderLabels, educationLabels)

    # 遍历子集中每个文档
    for d, doc_pairs in enumerate(corpus[:20000]):  # d is index, doc_pairs is the correspond value stored in corpus[d]
        # label = labels[d]
        LDA.write("%d %d %d" % (int(ageLabels[d]), int(genderLabels[d]), int(educationLabels[d])) )
      
        # 把当前文档作为输入,用训练好的LDA模型求“doc-topic比例”
        topic_props = lda.get_document_topics(doc_pairs)
      
        # 把K个比例保存成K个特征,svmlight格式
        for k, prop in topic_props:
            LDA.write(" %d:%.3f" % (k, prop))
        LDA.write("\n")
    LDA.close()

    test_lda_filename = "test.svm-lda.txt"
    testLDA = codecs.open(test_lda_filename, 'w', 'utf-8')
    print "Saving test topic proportions into '%s'..." % test_lda_filename

    for d, doc_pairs in enumerate(corpus[20000:]):
        label = '0 0 0'
        testLDA.write(label)
      
        # 把当前文档作为输入,用训练好的LDA模型求“doc-topic比例”
        topic_props = lda.get_document_topics(doc_pairs, minimum_probability=0.001)
      
        # 把K个比例保存成K个特征,svmlight格式
        for k, prop in topic_props:
            testLDA.write(" %d:%.3f" % (k, prop))
        testLDA.write("\n")
    testLDA.close()

    print "%d docs saved" % len(corpus)
该方法最终准确率可以达到 67%。


Twitter Facebook