数据挖掘日常笔记

Zoe的博客 | Zoe Blog


  • Home

  • Tags

  • Categories

  • Archives

  • Schedule

序列推荐综述

Posted on 2019-10-29 | Post modified: 2019-10-31 | In 推荐
Words count in article: 2.6k | Reading time ≈ 11

A Survey on Session-based Recommender Systems

本文是ACM’18关于session-based recommender systems(SBRS)的综述,文章对SBRS的推荐范式进行了说明,从理论和实践上证明了SBRS的价值;对当前对SBRS进行了系统分类,并从技术及研究的角度对其进行说明;最后总述了SBRS目前的进展及其复杂性和挑战。

推荐系统分类

推荐系统 输入 核心假设 工作机制 优势 劣势
基于内容 user和item content 根据用户的 喜好做推荐 根据item content建模 简单直接 可以解决用户的冷启动 用户兴趣会变化,跟现实场景会有出入
协同过滤(CF) User-item交互数据 根据用户喜好 User-item交互数据建模 有效且相对简单 容易陷入数据稀疏带来的问题, 冷启动问题
上下文可感知的RS User, item, 上下文,user-item交互数据 用户在不同上下文环境下有不同的喜好 对User-item 上下文 交互信息进行建模 User-item交互数据建模 可用数据及数据稀疏的问题
SBRS Session的数据 用户的喜好随着session的变化而变化 推荐出现在相似的session环境中的items 考虑到用户喜好的变化,更好的符合现实场景 会忽略用户长期的更general的喜好

SBRS相关定义及符号表示

定义如下符号表示:

用户:u

用户集合: $U=\left\{u_{1}, u_{2}, \ldots, u_{|U|}\right\}$

物品: i

物品集合:$I=\left\{i_{1}, i_{2}, \ldots, i_{|I|}\right\}$

session: $s=\left\{i_{1}, i_{2}, \ldots, i_{|s|}\right\}$

session集合: $S=\left\{s_{1}, s_{2}, \dots, s_{|S|}\right\}$

待预测/推荐物品为: t

Intra-session context: $C^{I a}$

Inter-seesion context: $C^{I e}$

session的定义: 一段时间内用户消费/收集的一系列事件内的item集合

SBRS定义: SBRS的任务是给定session的部分信息对未知/未来的信息进行预测。

Intra-session context定义: $s_{n}$为当前待推荐session,则$C^{I a}=\left\{i | i \in s_{n}, i \neq i_{t}\right\}$

Inter-session context定义: $s_{n}$为当前待推荐session,$C^{I e}=\left\{s_{n-1}, s_{n-2}, \dots, s_{\left|c^{I e}\right|}\right\}$

session-based recommendation task定义: 给定一个session语境C,session-based recommendation是学习一个从C到t的函数f使得$t: t \Leftarrow f(C)$。在SBRS中,session context是首要的信息,其他如用户或item信息也可以添加到模型中。

Next-items recommendation: 给定当前session $s_{n}$和intra-session context$C^{Ia}$预测$i_{t}$

Next-sessopm(next-basket) recommendations: 给定当前session$s_{n}$和inter-session context $C^{Ie}$预测下一个session的items。

SBRS的意义,复杂性和挑战

  • 意义和价值: SBRS在学术和商业上都具有重要意义。
  • 复杂性:

    • 数据的复杂性

      如下图所示,数据可以分为如下五个等级,SBRS需要的是中间核心的三个(item feature level, item level 和session level)。
      1

  • 挑战: 上图右侧描述了每层的一些挑战,按照层级可以分为 Inner-session,Inter-session, Outer-session的挑战。也可以按照类别分为异质性,共生性,复杂性和相互作用四大类。

Overview of SBRS

SBRS的发展可以分为两阶段: 1990s到2010s的model-free阶段,主要依赖pattern或规则去做。第二阶段是从2010s到现在的model-based阶段,主要采用统计和机器学习算法来做,包括时序相关的马尔可夫链,rnn等。

SBRS的分类和总结

从研究问题的角度做分类

  • 按照推荐什么做分类:

    • Next-item: 有明显session划分的预测下一个
      • Incorporating dwell time in session-based recommendations with recurrent Neural networks 2017
      • Recurrent Latent Variable Networks for Session-Based
        Recommendation
        2017
      • A Variational Recurrent Neural Network for Session-Based
        Recommendations using Bayesian Personalized Ranking
        2017
      • Web path recommendations based on page ranking and
        markov models
        2005
      • Web page personalization based on weighted association rules 2009
      • Session-based recommendations using item embedding 2017
      • Recurrent Neural Networks with Top-k Gains for Session-based Recommendations 2017
      • Session-based recommendations with recurrent
        neural networks
        2015
      • Parallel recurrent neural network architectures
        for feature-rich session-based recommendations
        2016
      • Diversifying personalized recommendation
        with user-session context
        2017
      • When recurrent neural networks meet the neighborhood for session-based recommendation 2017
      • Session-based item recommendation in e-commerce: on short-term intents,reminders, trends and discounts 2017
      • Factorization meets the item embedding: Regularizing matrix
        factorization with item co-occurrence
        2016
      • Interacting Attention-gated Recurrent Networks
        for Recommendation
        2017
      • Personalizing Session-based Recommendations
        with Hierarchical Recurrent Neural Networks
        2017
      • Inter-Session Modeling for Session-Based Recommendation 2017
      • Improved recurrent neural networks for session-based recommendations2016
      • Perceiving the Next Choice with Comprehensive Transaction Embeddings for Online Recommendation 2017
      • Attention-based Transactional Context
        Embedding for Next-Item Recommendation 2018
      • Effective next-items recommendation via personalized sequential pattern mining 2012
      • Sequential
        Recommender System based on Hierarchical Attention Networks
        2018
    • Next-basket: 有明显session划分的预测下一个session
      • Factorizing personalized markov chains for next-basket
        recommendation
        2010
      • Next Basket Recommendation with Neural
        Networks
        2015
      • Learning hierarchical representation
        model for nextbasket recommendation
        2015
      • A dynamic recurrent model for next basket recommendation 2016
    • Next-event/action: 无明显session划分
      • Content-Aware Hierarchical Point-of-Interest
        Embedding Model for Successive POI Recommendation
        2018
      • Where You Like to Go Next: Successive Point-of-Interest Recommendation 2013
      • Addressing cold start for next-song recommendation 2016
      • Personalized Ranking Metric Embedding
        for Next New POI Recommendation
        2015
      • Collaborative filtering meets next check-in location prediction 2013
      • K-plet Recurrent Neural Networks for Sequential Recommendation 2018
      • Personalized next-song recommendation in
        online karaokes
        2013
  • 按照如何推荐做分类:

方法 进展 缺点
Item-level dependency modeling patten-based, 马尔可夫链模型, RNN, attention item 不平衡(如热门商品),长尾商品
Session-level dependency modeling 因子分解机, RNN等 长期依赖,上下文感知,inter-seesion依赖
Feature-level dependency modeling 因子分解机, RNN, CNN 特征的异构和依赖
Feature value-level dependency modeling 目前没有进展 特征值的异构和依赖,item-feature-value的交互
Domain-level dependency modeling 目前没有进展 Domain间的互补和交互

从技术的角度进行分类

model-free方法

model-free的方法主要包括:

  • 基于pattern/规则
  • 基于序列pattern

model-based 方法

model-based 方法包括:

  • 基于马尔可夫链的推荐,基于转移概率考虑一阶依赖。
  • 因子分解机,首先将共现矩阵或item-item转移矩阵进行分解,得到item的隐变量,基于隐变量做推荐。
  • 基于神经网络的方法,如考虑序列的RNN等。

model-free方法介绍

基于pattern/规则的推荐系统主要分三阶段:1.最高频的pattern挖掘,session匹配和item推荐。

常见的基于高频pattern的挖掘有Apriori,FR-Tree及相关变形。
基于session pattern的方法和高频pattern 的挖掘相似,但是有一下两个不同点:1.主要考虑inter-seesion的信息而不是intra-session的信息。2.是对序列信息进行挖掘的。

model-based 方法介绍

基于马尔可夫链的推荐方法

马尔可夫链模型可以描述为$\left\{S T, P_{t}, P_{0}\right\}$,其中ST为状态空间,$P_{t}$为状态转移矩阵,$P_{0}$为初始状态概率,一阶状态转移概率为:

$P_{t}(j, k)=P\left(i_{j} \rightarrow i_{k}\right)=\frac{f r e q\left(i_{j} \rightarrow i_{k}\right)}{\sum_{i_{t} \in I} f r e q\left(i_{j} \rightarrow i_{t}\right)}$

由上述公式我们可以得到$\{i_{1}\rightarrow i_{2} \rightarrow i_{3}\}$的概率为:
$P\left(i_{1} \rightarrow i_{2} \rightarrow i_{3}\right)=P\left(i_{1}\right) P\left(i_{2} | i_{1}\right) P\left(i_{3} | i_{2}\right)$

通过上述公式我们可以计算给定session 部分序列时某一item的概率从而做推荐。

除了上述基本的模型外,有其他一些变形,包括结合一阶二阶马尔可夫的模型,有结合隐马尔可夫的概率模型,还有结合因子分解做一些工作。

基于因子分解机的推荐方法

由上述可知,根据观测数据可以计算转移概率,从而形成转移概率矩阵$A^{u}$,需要注意的是上述转移概率矩阵是某一个用户的,因此所有用户的概率转移矩阵为$\mathfrak{A}^{|U| \times|I| \times|I|}$,最基本的因子分解模型是基于Tucker降维:

$\hat{\mathscr{H}}=C \times V_{U} \times V_{I_{j}} \times V_{I_{k}}$

其中C为核张量,$V_{U}$为用户特征矩阵,$V_{I_{j}}$,$V_{I_{k}}$分别为当前item的特征矩阵和下一个item的特征矩阵。

基于神经网络的推荐方法

基于神经网络的推荐方法主要分两大类:基于embedding的浅层的神经网络推荐模型和多层的深度神经网络推荐模型。

浅层神经网络

主要是受到word2vec的启发,这种方法将item序列理解为句子,item为词,使用同word2vec的方法可以学习得到高维空间中item的表示,从而item embedding的距离可以表示为他们的关系,基于此做推荐。其他的变形包括加入item feature或其他相关feature做embedding,或加入attention机制,都是自然语言中常见的几种方法。

深层神经网络

深层神经网络主要从2016年开始,主要包括基于RNN的,基于DNN的和基于CNN的。

  • 基于RNN: 更好得捕捉序列关系

    最基础的是GRU4REC模型,输入item序列,输出item的概率分布(同最基础的语言模型)。为提升GRU4REC的效果,Tan提出数据增广和embedding dropout两种策略训练GRU4REC模型,同时提出一个generalised distillation framework;Quadrana提出层次RNN结构来捕捉跨session 的信息;Tim Donkers提出结合用户属性的user-based GRU来产生用户个性化推荐。

    除了基于GRU4REC的推荐模型,还有基于RNN的一些变形模型,如Dynamic REcurrent bAsket Model (DREAM),通过每个时刻的隐状态学习用户变化的表示。

    其他的一些进展包括:1.RNN结合变分推断解决稀疏数据的不确定性和降低模型规模[2,3];2.结合更丰富的信息如上下文信息,位置,item feature等来提升模型的效果[4,5,6];3.加入attention机制7;4.结合传统的FM或KNN等模型

  • 基于DNN:常用于当一个session中无序列关系时,如一个购物车中的物品。

  • 基于CNN:同样适用于无严格序列关系的session数据,并且具有更强的局部特征的捕捉能力,可以跨区域捕捉RNN常常捕捉不到的联合依赖关系。

参考文献

  1. A Survey on Session-based Recommender Systems

  2. Recurrent Latent Variable Networks for Session-Based
    Recommendation

  3. A Variational Recurrent Neural Network for Session-Based
    Recommendations using Bayesian Personalized Ranking
  4. Latent Cross: Making Use of Context in
    Recurrent Recommender Systems
  5. Parallel Recurrent Neural Network Architectures for Feature-rich Session-based Recommendations
  6. Incorporating Dwell Time in Session-Based Recommendations with Recurrent Neural Networks
  7. Interacting Attention-gated Recurrent Networks for Recommendation

序列推荐

Posted on 2019-10-25 | Post modified: 2019-10-29 | In 推荐
Words count in article: 2.8k | Reading time ≈ 11

Session-Based Recommendations With Recurrent Neural Networks

这篇是2016 ICLR的一篇文章,考虑到现实生活中更多的推荐场景是基于短期session而不是长期的用户历史,因此如MF等方法就不是很准确,因此本文提出将RNN应用于基于session的推荐系统。

模型

1

输入对应session内的点击序列,采用one-hot编码,输出为预测的item被点击的概率。为适应推荐任务,作者做了以下几个优化:

session-parallel mini-batches

由于用户一个session内的行为差别很大,比如有的时候只有两个动作,有的时候会有上百个,而我们希望捕捉的是session内的行为模式,因此不希望通过截断来提高训练效率,因此作者提出来session-parrallel mini-batches,即拼接不同的session减少无效计算,具体如下图所示:
2
如上图mini-batch3所示,当session2结束后继续接入session4而不是通过padding实现,避免浪费计算。当接入新的session时,需要将对应的隐状态重置。

smapling on the output

由于item数量常常是巨大的,对每一个item在每一个step计算一个得分无疑是巨大的计算量,在实际应用中也会变得不可用。因此作者提出对output进行采样,只对采样得到的正样本和一些负样本计算得分。

对于采样方法,作者采用的是同一个mini-batch中其他sequence的下一个点击的item作为负样本。

ranking loss

排序可以分为pointwise,pairwise和listwise三种,pointwise ranking的损失函数是对单点预测结果和实际之间对差异。pairwise ranking的损失函数是对pair的预测效果和真实之间的差异。listwise可以通过pairwise实现。本文作者尝试了pointwise和pairwise的损失,发现基于pointwise的loss训练出的模型不稳定,因此采用了pairwise的损失。具体包含以下两种:

  • BPR(Bayesian Personalized Ranking) 一种矩阵分解的方法

    $L_{s} = -\frac{1}{N_{S}} \cdot \sum_{j=1}^{N_{S}} \log \left(\sigma\left(\hat{r}_{s, i}-\hat{r}_{s, j}\right)\right)$

    其中$N_{S}$为采样大小,$\hat{r}_{s, k}$为给定session下item k的得分。

  • TOP1 一种正则估计

    $L_{s}=\frac{1}{N_{S}} \cdot \sum_{j=1}^{N_{S}} \sigma\left(\hat{r}_{s, j}-\hat{r}_{s, i}\right)+\sigma\left(\hat{r}_{s, j}^{2}\right)$

总结:

文章主要贡献就是将RNN应用于推荐领域(虽然现在看没有什么新意),更具有参考意义的是作者提到的训练上优化的点。

Parallel Recurrent Neural Network Architectures for Feature-rich Session-based Recommendations

这是发表于RecSys‘16的一篇文章,主要提出结合item信息的基于session的RNN架构。

模型

3

文章所提出的模型架构如上图所示,主要包括以下几个架构:

  • Baseline architectures:
    • ID only是指只用session 中click item的ID经one-hot作为输入的RNN模型(就是第一篇paper中的模型)。
    • feature only是指只用item的image feature作为输入的模型(用item文本信息也是一样的道理)。
    • Concatenated input是将ID 和image feature concat作为输入送入模型,这也是大家都能想到的结合item feature的方法了。
  • p-RNN architectures:
    • Parallel 是将ID和image feature分别送入RNN,隐藏层concat之后做预测。
    • Parallel Shared-W 与parallel区别的是共享一个用于计算输出的权重矩阵,整个架构类似来自上下文感知因子分解的pariwise的模型。
    • Parallel interaction model 这个架构中 image feature的隐状态会和ID 的隐状态做逐元素相乘后再计算推荐结果(类似上下文感知偏好建模),其中计算输出的权重矩阵不共享。

模型用到的损失函数是上文提到的TOP1损失。

p-RNNs训练

对于p-RNNs的训练,作者提出了以下几个策略:

  • Simultaneous: 所有的子图同时训练
  • Alternating: 交替训练,如第一个epoch训练ID的子图,第二个epoch训练feature的子图,交替往复直到训练结束。
  • Residual: 子图一个接一个的训练,当ID子图训练完成后,feature的子图基于残差继续训练。
  • Inrerleaving: 每个mini-batch交替训练。

论文实验结果表示Parallel并行更新item ID和feature的模型效果最好,Parallel Shared-W和Parallel interaction model效果没有很好可能是重复的序列信息加重了模型训练的负担。

总结:

本文主要是提出了几种结合item feature的架构,虽然最终实验结果表明Parallel具有最好的结果,但是面对实际业务其他的架构及训练策略还是具有一定借鉴意义的。

Incorporating Dwell Time in Session-Based Recommendations with Recurrent Neural Networks

本文是RecSys‘17的一篇文章,主要提出将item停留时长考虑进去基于session 的RNN建模。

模型

模型架构与第一篇paper中的架构完全一样,只是在session的构建上,如下图所示:
4

作者认为,用户停留的时间越长表明越感兴趣,将用户在每个item上的停留时间按照单位时间t进行划分,得到如上图中的session。考虑到用户停留时长的session划分能够起到item boosting的效果。

总结

这篇文章短小而精悍,相比于第一篇paper只是考虑到用户时长将session进行划分的不同就可以得到非常大的提升。看到题目标题时,笔者也只是在考虑是将用户停留时长做feature一起送入模型,没想到是采用这样的方式,只是很奇妙的。

Personalizing Session-based Recommendations with Hierarchical Recurrent Neural Networks

这也是RecSys‘17的一篇文章,主要提出层级RNN结构能够捕获session内信息和跨session的信息(如用户兴趣的变迁等)从而做用户的个性化推荐。

模型

5

上图为本文提出的HRNN架构图,红框对应session-level GRU,蓝框对应user-level GRU。 session-level GRU输入为session内item ID的one-hot编码,输出为预测的item的score。训练时损失可以采用cross-entrop,BRP或TOP1.

对于一个用户多session时,当一个session结束时,用session-level GRU的输出来计算当前user-level GRU的表示:
$c_{m}=G R U_{u s r}\left(s_{m}, c_{m-1}\right), m=1, \ldots, M_{u}$,

user-levelGRU的表示来初始化下一个session的输入:$s_{m+1,0}=\tanh \left(W_{i n i t} c_{m}+b_{i n i t}\right)$

随着迭代,$s_{m+1, n}=G R U_{s e s}\left(i_{m+1, n}, s_{m+1, n-1}\left[, c_{m}\right]\right), n=1, \ldots, N_{m+1}-1 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ …(1)$

训练是端到端进行的,user-level GRU只有在session结束时才更新。

总结

本文提供了一种可以综合跨session信息的策略,从而可以捕捉到诸如用户兴趣变迁等信息。

When Recurrent Neural Networks meet the Neighborhood for Session-Based Recommendation

这也是RecSys‘17的一篇文章,本文主要在基于session-bases rnn model的基础上,结合了KNN,实验结果显示对推荐结果有了进一步对提升。

模型

如果一个item在和当前item相似的session中出现过,则这个item出现的可能性更大,基于这种考虑,作者提出了基于session-based KNN。

GRU4REC

与论文1中提出的session-based RNN一致

Session-based KNN

根据当前session找到最相近的K个历史session,item i在当前session出现的概率为:

$score_{KNN} ( i , s )=\Sigma_{n \in N_{s}} \operatorname{sim}(s, n) \times 1_{n}(i)$

其中sim(s,n)可以是各种衡量相似度的公式(作者实验得cosine取得最好的结果),$1_{n}(i)=1$指当session n中包含item i为1,否则为0.

Hybird Approach

两个方法综合的方式有switching,cascading和weighted hybirds。作者实验结果表明weighted hybirds方式可以取得最好的结果。

总结

本文提出了一种基于session-based KNN模型,KNN更多的是关注近期数据表现,RNN相对可以捕获更长的序列,混合模型对结果还是会有提升。

Improved Recurrent Neural Networks for Session-based Recommendations

DLRS’16的一篇文章,在GRU4REC(paper1 介绍的session-based RNN)基础上提出的两条提升模型效果的方法,并且提出了一个generalised distillation framework来替代原有模型。

提升模型效果的方法

GRU4REC是对$\mathbf{x}=\left[x_{1}, x_{2}, \ldots, x_{r-1}, x_{r}\right]$这样的session序列做预测,本质是一个分类模型。由于session内数据变化很大,而我们的任务是作出对任意长度都适用的模型。

Data Augument

给定一个session的输入序列$\mathbf{x}$,可以产生出多条训练数据如$\left(\left[x_{1}\right], V\left(x_{2}\right)\right),\left(\left[x_{1}, x_{2}\right], V\left(x_{3}\right)\right), \ldots\left(\left[x_{1}, x_{2}, \ldots, x_{n-1}\right], V\left(x_{n}\right)\right)$,同时用户能存在误点击的操作,因此可以引入dropout的方式来做泛化。总体结果如下图所示:
6

Adapting to temporal changes

机器学习算法的一个基本假设是数据独立同分布的,对于推荐的数据,显然并不是严格成立的:当item被显示时用户才有可能点击,并且用户的行为是随时时间变化的。推荐的任务是根据用户当前的行为预测下一个点击的item,因此应该更关注的是当前的行为序列而不是更早之前的行为。而训练通常会采用全历史行为记录去做模型,因此可能引入偏差。

为缓解上述问题,可以简单的设置一个空间上的门限,大概门限值的记录就可以舍弃。另一种是采用预训练的方法,首先用全量数据做训练,之后再用近期的数据做fine-tune。

Use of privileged information

这部分对应作者提出的generalised distillation framework。给定的序列$x_{1},x_{2}\ldots x_{r-1}$预测$x_{r}$,作者称$x_{r+1},\ldots x_{n}$为privileged information,其对应的privileged sequence 为$x^{}=\left[x_{n}, x_{n-1} \ldots x_{r+2}\right]$。其中n为原始序列的总长度。首先可以以privileged sequence训练teacher model,然后再tune student model,其loss function为:
$(1-\lambda)
L\left(M(\mathbf{x}), V\left(x_{n}\right)\right)+\lambda L\left(M(\mathbf{x}), M^{}\left(\mathbf{x}^{}\right)\right)$。其中$\lambda \in[0,1],$M对应student model,$M^{}$为teacher model。

Output embeddings for faster predictions

作者提出可以对item embedding向量做预测,使测试结果更具有泛化意义。loss function此时可以定义为embedding的cosine相似度。此时计算量也可以下降: 从原始的HN变为HD,H为hidden layer nums,N为item个数,D为item的embedding size。

总结

作者提出了2中策略提升原GRU4REC的效果,另外提出了generalised distillation framework来训练一个新的模型,同时将item预测的任务变为预测item embedding。对于直接对item embedding的预测,作者最后也提到,需要优质的item embedding源做label,虽然可以重用上述模型的产出,但是此时的效果又会如何呢?

参考文献

  1. SESSION-BASED RECOMMENDATIONS WITH RECURRENT NEURAL NETWORKS
  2. Parallel Recurrent Neural Network Architectures for Feature-rich Session-based Recommendations
  3. Incorporating Dwell Time in Session-Based Recommendations with Recurrent Neural Networks
  4. Personalizing Session-based Recommendations with
    Hierarchical Recurrent Neural Networks
  5. When Recurrent Neural Networks meet the Neighborhood for
    Session-Based Recommendation
  6. Improved Recurrent Neural Networks for Session-based Recommendations

spark_with_tfrecords

Posted on 2019-09-24 | Post modified: 2019-09-24
Words count in article: 944 | Reading time ≈ 5

spark-tensorflow-connector

spark-tensorflow-connector是tensorflow提供的解决如何利用spark生成tfrecord文件的解决方案

安装与配置

  1. git clone https://github.com/tensorflow/ecosystem
  2. 安装并配置maven

    2.1 下载maven压缩包 http://maven.apache.org/download.cgi

    2.2 解压缩到指定目录

    2.3 配置~/.bash_profile

    1
    2
    3
    export M2_HOME=/Users/xxx/apache-maven-3.3.3

    export PATH=$PATH:$M2_HOME/bin

    2.4 source ~/.bash_profile使配置生效

    2.5 mvn -v 查看maven配置成功

    (如果失败,请配置JAVA_HOME)

  3. intellij打开 xx/ecosystem/hadoop 工程, 执行maven clean & install

    若出现一下错误

    1
    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:2.9:jar (attach-javadocs) on project template.querylist: MavenReportException: Error while creating archive: Unable to find javadoc command: The environment variable JAVA_HOME is not correctly set. -> [Help 1]

    可以修改POM配置

    1
    2
    3
    <properties>
    <javadocExecutable>${java.home}/../bin/javadoc</javadocExecutable>
    </properties>
  4. intellij打开 xx/ecosystem/spark/spark-tensorflow-connector工程, 执行maven clean & install 得到最终到spark-tensorflow-connector_2.11-1.10.0.jar

使用

官方给出的scala example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.xxx


import scala.collection.JavaConversions._;
import scala.collection.JavaConverters._;
import collection.JavaConversions._
import org.apache.log4j.{ Level, Logger }
import org.apache.spark.SparkConf

import org.apache.spark.sql.SparkSession

import org.apache.spark.sql.{ DataFrame, Row }
import org.apache.spark.sql.catalyst.expressions.GenericRow
import org.apache.spark.sql.types._

object TFRecordsExample {
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

val spark = SparkSession.builder().master("local[4]").appName("tfrecords_examples").getOrCreate()

val path = "file/test-output.tfrecord"
val testRows: Array[Row] = Array(
new GenericRow(Array[Any](11, 1, 23L, 10.0F, 14.0, List(1.0, 2.0), "r1")),
new GenericRow(Array[Any](21, 2, 24L, 12.0F, 15.0, List(2.0, 2.0), "r2")))

val schema = StructType(List(
StructField("id", IntegerType),
StructField("IntegerCol", IntegerType),
StructField("LongCol", LongType),
StructField("FloatCol", FloatType),
StructField("DoubleCol", DoubleType),
StructField("VectorCol", ArrayType(DoubleType, true)),
StructField("StringCol", StringType)))

val rdd = spark.sparkContext.parallelize(testRows)

//Save DataFrame as TFRecords
val df: DataFrame = spark.createDataFrame(rdd, schema)
df.write.format("tfrecords").option("recordType", "SequenceExample").save(path)

//Read TFRecords into DataFrame.
//The DataFrame schema is inferred from the TFRecords if no custom schema is provided.
val importedDf1: DataFrame = spark.read.format("tfrecords").option("recordType", "SequenceExample").load(path)
importedDf1.show()

//Read TFRecords into DataFrame using custom schema
val importedDf2: DataFrame = spark.read.format("tfrecords").schema(schema).load(path)
importedDf2.show()

}

}

pom文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.xxx.ai</groupId>
<artifactId>tfrecordsProj</artifactId>
<version>1.0-SNAPSHOT</version>


<dependencies>
<dependency>
<groupId>org.tensorflow</groupId>
<artifactId>spark-tensorflow-connector_2.11</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.8</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>2.12.5</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-reflect</artifactId>
<version>2.11.8</version>
</dependency>
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-xml_2.12</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-parser-combinators_2.12</artifactId>

<version>1.1.0</version>
</dependency>
</dependencies>
</project>

官方给出的python example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pyspark.sql import SparkSession
from pyspark.sql.types import *


spark = SparkSession.builder.master("local[4]").appName("tfrecords_examples").getOrCreate()
path = '/Users/linglingsu/Documents/intellij_workspace/tfrecordsProj/file/test-output.tfrecord'
fields = [StructField("id", IntegerType()), StructField("IntegerCol", IntegerType()),
StructField("LongCol", LongType()), StructField("FloatCol", FloatType()),
StructField("DoubleCol", DoubleType()), StructField("VectorCol", ArrayType(DoubleType(), True)),
StructField("StringCol", StringType())]
schema = StructType(fields)
df = spark.read.format("tfrecords").option("recordType", "SequenceExample").load(path)
df_pandas = df.toPandas()
print(df_pandas.head())

运行命令如下:

1
2

pyspark-submit --jars xxx/ecosystem/spark/spark-tensorflow-connector/target/spark-tensorflow-connector_2.11-1.10.0.jar test.py

推荐系统-找相似

Posted on 2019-09-19 | Post modified: 2019-09-20 | In 推荐
Words count in article: 2.2k | Reading time ≈ 9

前言

最近做推荐,召回其中的一个策略是采用协同过滤的算法,本来觉得不就是简单的一通计算相似度取topK相似用户做推荐就好嘛!但是真正做起来就要踩“大数据”的坑。

传统的基于协同过滤,核心在于计算相似度找到topK相似用户/物品,无论哪种计算相似度的时间复杂度都会随着用户/物品规模的扩大而呈平方增长,对于实际应用中,用户/物品往往达到千万甚至更多,因此暴力计算相似度显然不可行,这时就需要采用近似计算的方法,损失一部分精度来换取计算的效率。

最开始准备采用Facebook的Faiss框架来计算相似度,但是由于准备用go做服务,Faiss没有官方提供go的接口需要手动编译,而且计算用户相似度时,数据是动态的,用起来不那么方便,因此弃用Faiss转向LSH来计算用户的相似度。下面详述Min hashing和LSH(local sensitive hashing)原理。

Hash Functions

大家应该经常能听到哈希,也用过java类或相似的包来计算哈希值,哈希函数根据hash-key可以产生一个bucket number作为其存储位置,因此可以快速查询。hash table就是根据key-value通过hash function(记为h)得到映射的值进行寻址直接访问的数据结构。

  1. 对于不同的key得到同样的散列地址,即$k_{1}≠ k_{2}$但$h_{1} = h_{2}$,我们称之为碰撞。
  2. 若对于所有的key经过hash function的映射到每一个地址的概率是相等的,则成为均匀散列函数。

Min Hashing

Jaccard相似度

Jaccard index用于计算相似度,是距离的一种度量方式,假设有向量$S_{1}$和$S_{2}$,我们定义

$Jaccard(S_{1}, S_{2})=\frac{S_{1}∩S_{2}}{S_{1}∪S_{2}}$

Minhash

Min Hashing是LSH的一种,可以用于快速估计两个向量的相似度。Min Hashing和Jaccard相似度有很大的关系:

对两个向量进行Min Hashing,产生的哈希值相同的概率等于两个向量的Jaccard相似度
                                                            -- (1)

通过MinHash得到映射分两步:

  • 首先对row进行permutation,每个向量做同样的操作
  • 向量的MinHash值对应permutation后,取值为非零的第一行的row index

考虑为什么通过Minhash可以达到上述(1)所说的效果呢?我们举例如下,有四个向量,每个向量有5行(维)(我们可以把列看作User,行看作Item,即User$S_{1}$购买过2,3商品…):

表1:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
0 1 0 0 1
1 0 0 1 0
2 0 1 0 1
3 1 0 1 1
4 0 0 1 0

我们看$S_{1}$和$S_{2}$,每一行的取值分三种情况:

  1. $S_{1}$和$S_{2}$同为1
  2. $S_{1}$和$S_{2}$一个为0一个为1
  3. $S_{1}$和$S_{2}$同为0

由于矩阵通常很稀疏(考虑实际中用户与购买物品的矩阵),因此大部分为情况3,但是1和2决定来Jaccard($S_{1}$,$S_{2}$)和$h\left(S_{1}\right)=h\left(S_{2}\right)$概率,假设情况1有x行,情况2有y行,则$\operatorname{Jaccard}\left(S_{1}, S_{2}\right)=\frac{x}{x+y}$(x相当于$S_{1} \cap S_{2}$,y相当于$S_{1} \cup S_{2}$),若Permutation是随机的,则$S_{1}$和$S_{2}$从上向下找第一个非零属于情况1的概括为$\frac{x}{x+y}$。

Min Hashing signatures

如何通过Min Hashing产生签名呢?

对向量做m次permutation(m一般为几百或更小,小于原向量长度n),每一次经过permutation得到minhash记为$h_{1}, h_{2}, \ldots, h_{m}$, 则向量获得的签名可以表示为:

$\operatorname{sig}(A)=\left[h_{1}(A), h_{2}(A), \ldots, h_{m}(A)\right]$

对于一个高维度的向量进行permutation也是非常耗时的,因此可以随机选择m个哈希函数代替permutation。具体做法如下:

  1. 取m个针对row index的哈希函数$h_{1},h_{2}, \ldots, h_{m}$将原始0,1…n-1映射到0,1…n-1上
  2. 记Sig(i,c)为第c列在第i个哈希函数下的MinHash值,初始值记做$\infty$
  3. 对于每一行r
    • 计算$h_{1}(r),h_{2}(r),\ldots,h_{m}(r)$
    • 对于每列c:
      • 如果c在r行取值为0,忽略
      • 如果c在r行取值为1,则对$i=1,2,…,m$计算Sig(i,c) = Min(Sig(i,c),$h_{i}(r)$)

对表1所示数据,我们举例说明Min Hashing签名生成过程,假设我们选择两个哈希函数对row index生成MinHash,如下表2所示:$h_{1}(x) = x+1 \mod 5$, $h_{2}(x) = 3x+1 \mod 5$

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$ x+1 mod 5 3x+1 mod 5
0 1 0 0 1 1 1
1 0 0 1 0 2 4
2 0 1 0 1 3 2
3 1 0 1 1 4 0
4 0 0 1 0 5 3

初始时,哈希签名如下:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ $\infty$ $\infty$ $\infty$ $\infty$
$h_{2}$ $\infty$ $\infty$ $\infty$ $\infty$

对于第0行,只有$S_{1}$ 和$S_{4}$为非零,且$h_{1}$和$h_{2}$均为1,则当前签名矩阵变为

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 $\infty$ $\infty$ 1
$h_{2}$ 1 $\infty$ $\infty$ 1

对于第1行,$S_{3}$为1,对于$h_{1}$和$h_{2}$为2和4,则签名矩阵变为:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 $\infty$ 2 1
$h_{2}$ 1 $\infty$ 4 1

继续至第2行,$S_{2}$ 和$S_{4}$为非零,对应$h_{1}$和$h_{2}为3和2,$S_{4}$目前的值均为1,小于$h_{1}$,$h_{2}$对应的3和2,则签名矩阵变为:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 3 2 1
$h_{2}$ 1 2 4 1

…

最后扫描完第4行,最后得到签名矩阵为:

row $S_{1}$ $S_{2}$ $S_{3}$ $S_{4}$
$h_{1}$ 1 3 0 1
$h_{2}$ 0 2 0 0

通过上述过程可以看到,实际的$Jaccard(S_{1},S_{4})=2/3$,但通过签名计算得到的$Jaccard(S_{1},S_{4})=1$,上述结果是由于示例只选择了两个哈希函数,在实际应用中,会选择更多的哈希函数并且向量数往往在百万千万级,这时就会比较相近了。

Local Sensitive Hashing(LSH)

经过Min Hashing,我们可以实现数据降维并保证降维后的数据保持原空间的相似度,降低了在物品维度很高时计算相似度的时间复杂度,但是当用户数巨大时,计算两两相似度仍旧需要很高的时间开销,如何进一步降低寻找相似用户的复杂度,就需要LSH这样一个近似算法来实现。

LSH常见的做法是经过多次哈希将相似的向量散列到相同的桶中,检索时,我们只需要考虑相同桶中的任意对作为候选集即可。我们称不相似的向量被散列到相同的桶样本为false positive,相似的向量被散列到不同到桶中的样本称为false negetive。

在有来Min Hashing签名的基础上,一个有效的办法是将签名分段,我们称之为band。如下图所示:
1

每个签名都被分成4段,向量的签名散列到对应的band上,如果band相同的越多,则其相似度越高。我们可以通过band获取candidate,计算candidate相似用户的相似度就好了。

如何选择band数?

假设我们有b和band,每个band有r行,即总的Min Hashing签名长度为b*r,假设两个向量的Jaccard相似度为s,则:

  1. 对于某一个band,所有行都相等的概率为:$s^r$
  2. 对于某一个band,至少有一行不同的概率为:$1 - s^r$
  3. 对所有band,至少有一行不同的概率为: $(1 - s^r)^b$
  4. 至少有一个band的所有行都相等的概率为: $1- (1 - s^r)^b$,即两个用户成为candidate的概率

上述概率在r,b不同值时为一个S曲线,如当r=6,b=100时的曲线如下图:

2

我们定义超过0.5的概率就有可能成为candidate,此时对应的相似度称为threshold:

$threshold \approx (\frac{1}{b})^{(\frac{1}{r})}$

在实际应用中,我们需要先确定最小相似度,即相似度大于多少我们认为可以作为candidate,然后确定哈希签名的长度进而可以确定b和r。我们需要考虑的是:

  • 我们想要尽可能少出现false negative,我们需要选择b和r使得thresh< 我们定义的最小相似度。
  • 如果要保证计算速度快并尽可能少出现false positive,我们需要选择b和r使得thresh>最小相似度。

参考文献:

  1. Mining of massive datasets; Jeffrey D. Ullman Anand Rajaraman, Jure Leskovec.
  2. 大规模数据的相似度计算:LSH算法

推荐之FM/FFM/Deep_FM

Posted on 2019-08-20 | Post modified: 2019-08-20 | In 推荐
Words count in article: 976 | Reading time ≈ 4

FM

FM常用于CTR估计,能够处理稀疏数据下的特征组合的问题。

FM原理

一般的线性模型不考虑特征之间的关系,公式定义如下:

$y=w_{0}+\sum_{i=1}^{n}x_{i}w_{i}$ ...(1)

为了表达特征之间的相关性,利用多项式函数,定义如下:
$y=w_{0}+\sum_{i=1}^{n}x_{i}w_{i}+\sum_{i=1}^{n}\sum_{j=i+1}^{n}w_{i,j }x_{i}x_{j}$ ...(2)

如上述公式,定义的二阶特征参数有$\frac{n*(n-1)}{2}$个,同时当特征非常稀疏且纬度高时,不仅时间复杂度在增加,同时由于稀疏的特征,很多二阶特征系数通常学习不到。
为了解决上述的问题,FM对每个特征引入辅助向量$V_{i} = [v_{i1},v_{i2},…v_{ik}]^{T}$,此时每个特征学习到一个k纬向量而不是一个值,从而解决数据稀疏下到特征组合问题。此时模型如下:
$y = w_{0}+\sum_{i=0}^{n}w_{i}x_{i} + \sum_{i=1}^{n}\sum_{j=i+1}^{n}x_{i}x_{j}$ ...(3)

其中k表示辅助向量的纬度,为超参数。上述表达式的时间复杂度为$kn^{2}$。为降低时间复杂度,对(3)式最后一项做如下变化:
$\sum _ { i = 1 } ^ { n } \sum _ { j = i + 1 } ^ { n } < V _ { i } , V _ { j } > x _ { i } x _ { j }$
$= \frac { 1 } { 2 } \sum _ { i = 1 } ^ { n } \sum _ { j = 1 } ^ { n } < V _ { i } , V _ { j } > x _ { i } x _ { j } - \frac { 1 } { 2 } \sum _ { i = 1 } ^ { n } < V _ { i } , V _ { i } > x _ { i } x _ { i }$
$=\frac{1}{2}(\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{f=1}^{k}v_{if}v_{jf}x_{i}x_{j} - \sum_{i=1}^{n}\sum_{f=1}^{k}v_{if}v_{if}x_ix_i)$
$=\frac{1}{2}\sum_{f=1}^{k}((\sum_{i=1}^{n}v_{if}x_{i})^2 - \sum_{i=1}^{n}(v_{if}x_i)^2)$

通过上述变化可以发现,将公式(3)的时间复杂度从$kn^{2}$降低为$kn$。

FM优点

  1. 通过引入辅助向量,能够解决稀疏数据的特征组合问题。
  2. 模型的时间复杂度为线性的(kn)。

FFM

FFM(Field-aware Factorization Machine)为FM的改进版,区别是FM的辅助向量为K维,而FFM的辅助向量为F*K维,其中F为field个数。对应的FFM模型如下:

$y = w_{0}+\sum_{i=0}^{n}w_{i}x_{i} + \sum_{i=1}^{n}\sum_{j=i+1}^{n}x_{i}x_{j}$ ...(3)

其中$f_{i,f_{j}}$为i特征属于$f_{j}$下的辅助向量。由于FFM不能按照(3)进行化简,因此时间复杂度为$kn^{2}$。

FFM优缺点

  1. 与FM相比FFM具有field感知能力,模型能力更强。
  2. 与FM相比,辅助向量变为F*K维学习参数变为FM的F倍。
  3. 与FM相比,时间复杂度高。

DeepFM

由于FM/FFM使用的是一阶和二阶特征,为了学习更高阶特征,Huifeng Guo等人提出了DeepFM。
与DeepFM相似的模型有FNN,PNN,Wide&deep,模型结构如下图所示:
1
FNN使用预训练好的权重作为NN的输入,只能得到高阶特征组合,PNN则是在embedding层和第一层隐藏层中加入了product操作。Wide&deep 的提出则是为了综合低阶特征组合和高阶特征组合,wide部分多为专家经过特征工程得到的低阶组合特征,而deep部分则通过NN网络学习到高阶特征。
DeepFM不仅可以达到Wide&deep同时学习低阶特征组合和高阶特征组合的效果,同时想比于Wide&deep网络的优点是,不需要专家经过特征组合筛选特征。网络结果如下:
2

参考文献

  1. DeepFM: A Factorization-Machine based Neural Network for CTR Prediction [https://arxiv.org/pdf/1703.04247.pdf]
  2. 推荐系统召回四模型之:全能的FM模型
    [https://zhuanlan.zhihu.com/p/58160982]
  3. CTR预估算法之FM, FFM, DeepFM及实践
    [https://blog.csdn.net/John_xyz/article/details/78933253]

TF的三种模型的保存与加载方式

Posted on 2019-08-15 | Post modified: 2019-08-15 | In Deeplearning , Tensorflow
Words count in article: 2.1k | Reading time ≈ 10

TF的三种模型的保存与加载方法

当我们训练完一波模型,准备把模型应用于生产环境时,我们需要在生产环境部署预测的model,此时就涉及到如何把我们训练好的model重新加载以及处于效率的考虑,用什么方法更好的部署模型。因此我们来谈谈常见的三种模型的保存与加载方法。

  • Checkpoint: 知道模型结构,单纯保存变量
  • SavedModel: 不知道模型结构,保存模型和变量
  • Freeze pb: 不需要再改变量,只要常量化的模型(“冻结”)

checkpoint

第一种必然是大家最为熟悉的checkpoint的方式,通常在模型训练时,我们通过saver=tf.train.saver()定义saver,在session中通过saver.save()保存模型中的变量。此时我们只是单纯保存了变量而没有对模型本身做任何保存,此时恢复模型需要有模型对应的源代码,因此当我们需要在C++中恢复模型,只能用C++把模型的代码复写一遍。
保存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf
a = tf.placeholder(dtype=tf.float32, shape=[2,2], name='a')
b = tf.placeholder(dtype=tf.float32, shape=[2,2], name='b')

w = tf.get_variable(name='w', shape=[2, 2], initializer=tf.ones_initializer())
c = tf.add(tf.add(a, b), w)

saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
result = sess.run(c, feed_dict={a:[[1,2],[2,3]], b:[[2,3],[4,5]]})
saver.save(sess, './saved_model/model.ckpt')

print(result)

1
2
[[4. 6.]
[7. 9.]]

加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
import tensorflow as tf
a = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='a')
b = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='b')

w = tf.get_variable(name='w', shape=[2, 2], initializer=tf.ones_initializer())
c = tf.add(tf.add(a, b), w)

saver = tf.train.Saver()
with tf.Session() as sess:
saver.restore(sess, './saved_model/model.ckpt')
result = sess.run(c, feed_dict={a: [[1, 2], [2, 3]], b: [[2, 3], [4, 5]]})

print(result)

1
2
[[4. 6.]
[7. 9.]]

可以看到,通过restore我们可以得到同样的结果。

SavedModel

TF官网介绍说SavedModel是一种独立于语言而且可以恢复的序列化格式,使较高级别的系统和工具可以创建,使用和转换Tensorflow模型。常见的两种与SavedModel交互的方式包括tf.saved_model API 和tf.estimator.Estimator。

tf.saved_model API

保存方法1:使用tf.saved_model.simple_save

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tensorflow as tf
a = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='a')
b = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='b')

w = tf.get_variable(name='w', shape=[2, 2], initializer=tf.ones_initializer())
c = tf.add(tf.add(a, b), w)

saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())

tf.saved_model.simple_save(session=sess,
export_dir='./saved_model/pb/1',
inputs={'a': a, 'b': b},
outputs={'c': c})

此时,我们可以在saved_model/pb下看到如下文件结构:

 --  saved_model
    |--  pb
        |-- 1
            |-- saved_model.pb
            |-- variables
                |-- variables.data-00000-of-00001
                |-- variables.index

保存方法2:通过SavedModelBuilder构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import tensorflow as tf


a = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='a')
b = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='b')

w = tf.get_variable(name='w', shape=[2, 2], initializer=tf.ones_initializer())
c = tf.add(tf.add(a, b), w)
saver =tf.train.Saver()
with tf.Session() as sess:
# sess.run(tf.global_variables_initializer())
saver.restore(sess, './saved_model/model.ckpt')
builder = tf.saved_model.builder.SavedModelBuilder("./saved_model/pb/2")
inputs = {'a': tf.saved_model.utils.build_tensor_info(a),
'b': tf.saved_model.utils.build_tensor_info(b)}
output = {'c': tf.saved_model.utils.build_tensor_info(c)}

prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(
inputs=inputs,
outputs=output,
method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
)
builder.add_meta_graph_and_variables(
sess,
[tf.saved_model.tag_constants.SERVING],
{tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: prediction_signature}
)
builder.save()

通过saved_model_cli命令查看SavedModel

1
saved_model_cli show --dir /Users/xxx/Documents/pycharm_workspace/test_python/saved_model/pb/1 --all

可以得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['a'] tensor_info:
dtype: DT_FLOAT
shape: (2, 2)
name: a:0
inputs['b'] tensor_info:
dtype: DT_FLOAT
shape: (2, 2)
name: b:0
The given SavedModel SignatureDef contains the following output(s):
outputs['c'] tensor_info:
dtype: DT_FLOAT
shape: (2, 2)
name: Add_1:0
Method name is: tensorflow/serving/predict

加载方法1: 通过tf.saved_model.loader.load加载

1
2
3
4
5
6
7
8
9
10
11
12

import tensorflow as tf

export_dir = './saved_model/pb/2'
with tf.Session() as sess:
meta_graph_def = tf.saved_model.loader.load(sess, ['serve'],
export_dir)
a = sess.graph.get_tensor_by_name('a:0')
b = sess.graph.get_tensor_by_name('b:0')

c = sess.graph.get_tensor_by_name('Add_1:0')
print(sess.run(c, feed_dict={a: [[1, 2], [2, 3]], b: [[2, 3], [4, 5]]}))

加载方法2: 通过tf.contrib.predictor.from_saved_model

1
2
3
4
5
6
7
8
9
10
11
import tensorflow as tf
export_dir = './saved_model/pb/2'
predictor_fn = tf.contrib.predictor.from_saved_model(
export_dir=export_dir,
signature_def_key="serving_default"
)

output = predictor_fn({'a': [[1, 2], [2, 3]],
'b': [[2, 3], [4, 5]]
})
print(output)

结合tf.estimator.Estimator使用

保存方法

1
2
3
4
5
6
7
8
9
10
11
12
13
def serving_input_receiver_fn():
feature_spec = {'a': tf.FixedLenFeature([2,2], tf.float32),
'b': tf.FixedLenFeature([2,2], tf.float32)}

serialized_tf_example = tf.placeholder(dtype=tf.string,
shape=[1],
name='input_example_tensor')
receiver_tensors = {'examples': serialized_tf_example}
features = tf.parse_example(serialized_tf_example, feature_spec)

return tf.estimator.export.ServingInputReceiver(features, receiver_tensors)

estimator.export_savedmodel('saved_model', serving_input_receiver_fn)

通过tf-serving部署服务访问

  • 部署server端:

    • 基于Dockerfile 创建镜像:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      FROM ubuntu:18.04


      # Install general packages
      RUN apt-get update && apt-get install -y \
      curl \
      libcurl3-dev \
      unzip \
      wget \
      && \
      apt-get clean && \
      rm -rf /var/lib/apt/lists/*

      # Previous Installation of tensorflow-model-server (BROKEN RECENTLY)
      #RUN echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | tee /etc/apt/sources.list.d/tensorflow-serving.list \
      # && curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | apt-key add - \
      # && apt-get update && apt-get install tensorflow-model-server

      # New installation of tensorflow-model-server
      RUN TEMP_DEB="$(mktemp)" \
      && wget -O "$TEMP_DEB" 'http://storage.googleapis.com/tensorflow-serving-apt/pool/tensorflow-model-server-1.12.0/t/tensorflow-model-server/tensorflow-model-server_1.12.0_all.deb' \
      && dpkg -i "$TEMP_DEB" \
      && rm -f "$TEMP_DEB"

      # gRPC port
      EXPOSE 8500
      # REST API port
      EXPOSE 8501

      # Serve the model when the container starts
      CMD tensorflow_model_server \
      --port=8500 \
      --rest_api_port=8501 \
      --model_name="$MODEL_NAME" \
      --model_base_path="$MODEL_PATH"

    运行如下命令创建镜像:

    1
    docker build --rm -f Dockerfile -t tensorflow-serving-example:0.1 .
    • 创建临时目录保存savedModel

      1
      2
      mkdir -p ./saved_model/dkt/1
      cp -R ./saved_model/pb/* ./saved_model/test/1
    • 启动容器

      1
      docker run --rm -it -v /home/xxx/tf_serving/saved_model/:/models -e MODEL_NAME=test -e MODEL_PATH=/models/test -p 8500:8500 -p 8501:8501 --name tensorflow-serving-example tensorflow-serving-example:0.1

      至此,server已启动,运行client进行测试

  • client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import argparse
import time
import numpy as np

import grpc
import tensorflow as tf
from tensorflow.contrib.util import make_tensor_proto
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2


def run(host, port, model, signature_name):
channel = grpc.insecure_channel('{host}:{port}'.format(host=host, port=port))
stub = prediction_service_pb2.PredictionServiceStub(channel)
start = time.time()
# Call classification model to make prediction
request = predict_pb2.PredictRequest()
request.model_spec.name = model
request.model_spec.signature_name = signature_name

feature = {}
feature['a'] = tf.train.Feature(float_list=tf.train.FloatList(value=[[1,2],[3,4]]))
feature['b'] = tf.train.Feature(float_list=tf.train.FloatList(value=[[2,3],[4,5]]]))
example = tf.train.Example(
features=tf.train.Features(
feature=feature
)
)

request.inputs['examples'].CopyFrom(make_tensor_proto([example.SerializeToString()], shape=[1]))
result = stub.Predict(request, 10.0) # 10 secs timeout

end = time.time()
time_diff = end - start

# Reference:
# How to access nested values
# https://stackoverflow.com/questions/44785847/how-to-retrieve-float-val-from-a-predictresponse-object
# print(result)
result = result.outputs['predict'].float_val
print(result)
print('predict shape {}'.format(len(result)))
print('time elapased: {}'.format(time_diff))


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--host', help='Tensorflow server host name', default='10.8.8.71', type=str)
parser.add_argument('--port', help='Tensorflow server port number', default=8500, type=int)
parser.add_argument('--model', help='model name', default='dkt', type=str)
parser.add_argument('--signature_name', help='Signature name of saved TF model',
default='serving_default', type=str)

args = parser.parse_args()
run(args.host, args.port, args.model, args.signature_name)

Freeze pb

当不再需要改变变量,只要常量化当模型时,我们可以采用freeze pb的方式。可以用在不同语言部署的场景下,好处是除了可以冻结模型外,还可以指定剔除某些多余的节点。

冻结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
input_checkpoint = './saved_model'
output_graph = './saved_model/pb/4/saved_model.pb'
# 指定输出的节点名称,该节点名称必须是原模型中存在的节点
output_node_names = "Add_1"
# saver = tf.train.import_meta_graph(input_checkpoint + '.meta', clear_devices=True)

graph = tf.Graph() # 获得默认的图
with graph.as_default() as g:
a = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='a')
b = tf.placeholder(dtype=tf.float32, shape=[2, 2], name='b')

w = tf.get_variable(name='w', shape=[2, 2], initializer=tf.ones_initializer())
c = tf.add(tf.add(a, b), w)
# Define saver & Load checkpoint
saver = tf.train.Saver()

with tf.Session(graph=graph) as sess:
ckpt = tf.train.get_checkpoint_state(input_checkpoint)
if ckpt and ckpt.model_checkpoint_path:
print('restore True...')
saver.restore(sess, ckpt.model_checkpoint_path) # 恢复图并得到数据
# for op in graph.get_operations():
# print(op.name, op.values())
input_graph_def = graph.as_graph_def() # 返回一个序列化的图代表当前的图
output_graph_def = tf.graph_util.convert_variables_to_constants( # 模型持久化,将变量值固定
sess=sess,
input_graph_def=input_graph_def, # 等于:sess.graph_def
output_node_names=output_node_names.split(",")) # 如果有多个输出节点,以逗号隔开

with tf.gfile.GFile(output_graph, "wb") as f: # 保存模型
f.write(output_graph_def.SerializeToString()) # 序列化输出
print("%d ops in the final graph." % len(output_graph_def.node)) # 得到当前图有几个操作节点

运行上述程序,可在./saved_model/pb/4/下看到saved_model.pb文件。

加载pb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import tensorflow as tf
import numpy as np
dir = './saved_model/pb/4/saved_model.pb'
with tf.Session() as sess:
print("load graph")
with tf.gfile.FastGFile(dir, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
sess.graph.as_default()
tf.import_graph_def(graph_def, name='')
return_elements = [u'a:0', u'b:0', u'Add_1:0']
return_elements = tf.import_graph_def(graph_def,
return_elements=return_elements)
a, b, c = return_elements[0], return_elements[1], return_elements[2]

feed_dict_testing = {a: [[1,2],[3,4]],
b: [[2,3],[4,5]],

}

result = sess.run(c, feed_dict=feed_dict_testing)
print(result)

以上就是常见的TF的模型保存及对应的加载方法~

参考文献:

  1. tensorflow 模型的存档、保存、冻结、优化
  2. Introduction to RESTful API with Tensorflow Serving
  3. tensorflow-serving-example
  4. tensorflow 官方文档

word2vec

Posted on 2019-08-14 | Post modified: 2019-08-20 | In Deeplearning , NN
Words count in article: 0 | Reading time ≈ 1

doc2vec

Posted on 2019-08-13 | Post modified: 2019-09-20 | In Deeplearning
Words count in article: 950 | Reading time ≈ 3

Doc2vec

常用于短文本向量化的方法包括:

  • Bag of words
  • LDA
  • Average word vectors
  • tfidf-weighting word vector

上述方法存在公共的问题是没有考虑单词的顺序。而本文介绍的doc2vec是2014年谷歌的两位大牛Quoc Le 和 Tomas Mikolov提出的。文章提出一种无监督学习模型通过预测句子/段落中word来得到句子/段落/文档的向量表示。

模型

word2vec

文章提出的得到句子/段落/文档向量的方法是受到word2vec的启发,因此先回顾一下word2vec。如下图所示,word2vec是通过给定单词预测另一个单词,如CBOW根据前后词预测中间词,以及Skip-gram根据中间词预测前后词。
1
在模型中,每个词都用唯一的向量表示,是通过一个W矩阵通过index索引得到的每个词的向量表示,通过concat/average所有词的向量表示作为feature去预测另外一个词。在CBOW中,通过前后词预测中间词,模型最终优化的是最大化给定前后词的条件下当前词出现的概率,即:

$\frac{1}{T} \sum_{t=k}^{T-k} \log p\left(w_{t} | w_{t-k}, \ldots, w_{t+k}\right)$

最终模型通过softmax得到每个词出现的概率:

$p\left(w_{t} | w_{t-k}, \ldots, w_{t+k}\right)=\frac{e^{y_{w_{t}}}}{\sum_{i} e^{y_{i}}}$

上式中的$\boldsymbol{y}_{i}$是未归一化的每个词出现的概率:

$y=b+U h\left(w_{t-k}, \dots, w_{t+k} ; W\right)\qquad ...(1)$

原始论文中还提到使用层次softmax代替softmax来提升训练速度。详细细节见原文.

Paragraph Vector: A distributed memory model

受word2vec的启发,Paragraph Vector也是通过给出上下文预测下一个word来进行学习的,对应的框架如下:
2
每个段落/句子通过矩阵D映射到向量空间中,用D的一列代替,同样,每个单词也被映射到向量空间,用矩阵W的一列表示,然后通过段落/句子向量和词向量级连或求平均得到特征预测下一个单词。与word2vec唯一不同的是在计算$y_{i}$时的h是通过W和D average/concat 得到的。

对于D我们可以理解为其他的word,它相当于是上下文的记忆单元活着这个段落的主题,因此我们叫这种训练方法为Distributed Memory Model of Paragraph Vector(PV-DM)。在训练时,通常采用固定长度的滑动窗口得到训练集,段落/句子向量在上下文中是共享的。

总结doc2vec的过程主要是两步:

  • 训练阶段,在已知数据集上训练得到模型参数D,W,U,b
  • 预测阶段,得到未知段落的向量D即在固定W,U,b的情况下利用上述方法进行梯度下降,得到新的D(D中会加入表征新段落的column)

优点:

  • 使用无监督学习,不需要大量有标记数据
  • 能够解决bag-of-words模型的缺点:
    • 能够学到词之间的语义信息
    • 考虑到了词之间的顺序

Paragraph Vector without worf ordering: Distributed bag of words

上面提到的训练方法是要综合paragraph vector和word vector去预测下一个词,另一种训练方法可以忽略词的上下文来预测随机从段落/句子采样得到的一个词,具体来说就是每次迭代训练时,采样一个窗口的文本,然后从窗口文本中随机选一个词做预测。我们称这种方法为Distributed Bag of Words version of Paragraph Vector(PV-DBOW)。模型架构如下:
3


参考文献:

  1. Distributed Representations of Sentences and Documents
  2. https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf

Exercise-Enhanced-Sequential-Modeling-for-Student-Performance-Prediction

Posted on 2019-08-08 | Post modified: 2019-08-13 | In Deeplearning , LSTM , Embedding
Words count in article: 1.3k | Reading time ≈ 5

EKT: Exercise-aware Knowledge Tracing for Student Performance Prediction

这篇[文章][10]很早就看到过,但是到2019年6月份才发表出来,本文章认为目前的知识追踪的model都只用了学生的做题信息,而其他的如知识概念或习题内容相关的知识并没有在model中引入,而引入这些信息是能够为模型的预测精度带来增益的,因此作者提出Exercise-Enhanced Recurrent Neural Network(EERNN),在这个模型中,不仅使用了学生的做题记录,还引入了习题的文本信息。在EERNN中,作者使用RNN的隐变量来表征学生的学习轨迹,使用BiLSTM来学习习题的编码信息。在最终predict阶段,作者在EERNN基础上采用了两种策略,一种是EERNNM with Markov property,另一种是EERNNA with attention mechanism。最终为了追踪学生在各知识点上的掌握情况,作者将EERNN引入知识概念的信息引入得到Exercise-Aware Knowledge Tracing(EKT)。

模型:

模型主要分两部分来描述,一是做预测的EERNN(EERNNM & EERNNA),另一部分是追踪学生知识掌握情况的EKT。

EERNN

EERNN的提出主要用于做学生performance的预测,基于不同的策略又分为EERNNM和EERNNA。网络结果如下图所示:

EERNN

从上图可以看出:EERNNM和EERNNA的区别主要在prediction阶段,图中橘色框表示的是题目的embeddig,蓝色框表示的是学生的embedding。

Exercise embedding:

Exercise embedding的获取是通过双向LSTM得到的,结构如下:
EERNN

通过上述可以得到每个word的embedding $v_{m}=$ concatenate $\left(\vec{v}_{m}, \overleftarrow v_{m}\right)$,最终Exercise的embedding是通过max-pooling每个word的embedding得到,即$x_{i}=\max \left(v_{1}, v_{2}, \ldots, v_{M}\right) x_{i} \in \mathbb{R}^{2 d_{v}}$。

Student Embedding:

表征学生的向量应该跟题目和学生的回答有关,因此作者在上面获得$x_{i}$的基础上加入了表示学生作答情况的信息,通过RNN/LSTM得到student embedding,具体实现是首先将$r_{t}$表示成一个$2 d_{v}$维的$\mathbf{0}=(0,0, \ldots, 0)$,最终输入$\widetilde{x}_{t} \in \mathbb{R}^{4 d_{v}}$表示为:

$\widetilde{x}_{t}=\left\{\begin{array}{ll}{\left[x_{t} \oplus \mathbf{0}\right]} & {\text { if } r_{t}=1} \\ {\left[\mathbf{0} \oplus x_{t}\right]} & {\text { if } \quad r_{t}=0}\end{array}\right.$

Prediction
  • EERNNM:

    基于markov性,下一时刻状态的条件概率分布只与当前状态有关,因此对$\widetilde{r}_{T+1}$的预测只与$h_{T}$和$x_{T+1}$有关,因此计算公式如下:

    $\begin{aligned} y_{T+1} &=\operatorname{Re} L U\left(\mathbf{W}_{1} \cdot\left[h_{T} \oplus x_{T+1}\right]+\mathbf{b}_{1}\right) \\ \widetilde{r}_{T+1} &=\sigma\left(\mathbf{W}_{2} \cdot y_{T+1}+\mathbf{b}_{2}\right) \end{aligned}$ ... (1)

  • EERNNA
    如果序列很长的话,LSTM捕捉信息的能力会降低,因此为了改善上述问题,引入常用的Attention机制。
    经过attention后的表征当前状态的隐变量变为:

    $h_{a t t}=\sum_{j=1}^{T} \alpha_{j} h_{j} \\ \alpha_{j}=\cos \left(x_{T+1}, x_{j}\right)$

    将(1)式中的$h_{a t t}$替换$h_{T}$即可得到预测结果。

EKT: Exercise-aware Knowledge Tracing

EKT本质做的是将原始EERNN学习到的学生状态从$h_{t} \in \mathbb{R}^{d_{h}}$变换成$\dot{H}_{t} \in \mathbb{R}^{\dot{d}_{h} \times K}$,也就是说用一个向量来表征学生对某个知识的掌握情况。模型结构如下图所示:
EKT
与EERNN相比,除了使用到Exercise Embedding还用到了Knowledge Embedding(对应图中绿色的部分)。

Knowledge Embedding:

由于知识之间是相关的而非独立的,因此作者引入了memory module来计算当前的知识点与其他知识点的相关性,并最终影响到学生知识状态的隐变量,其中知识间的相关性是通过$\beta_{t}^{i}$实现的。如图中标注,k(K维,K表示所有知识点)表示当前时刻题目对应的知识点的one-hot编码,v($d_{k}$维)则是将k进行地位压缩后的编码向量$v_{t}=\mathbf{W}_{\mathbf{k}}^{\mathrm{T}} k_{t}$,通过memory module(本质上是一个$d_{k} \times K$的矩阵),最终$\beta_{t}^{i}$计算公式如下:

$\beta_{t}^{i}=\operatorname{Softmax}\left(v_{t}^{\mathrm{T}} \mathbf{M}_{i}\right)=\frac{\exp \left(v_{t}^{\mathrm{T}} \mathbf{M}_{i}\right)}{\sum_{i=1}^{K}\left(\exp \left(v_{t}^{\mathrm{T}} \mathbf{M}_{i}\right)\right)}$

最终隐状态表示为:
$H_{t}^{i}=L S T M\left(\widetilde{x}_{t}^{i}, H_{t-1}^{i} ; \theta_{H^{i}}\right)$,
其中$\widetilde{x}_{t}^{i}=\beta_{t}^{i} \hat{x}_{t}$。

结果:

EKT

思考:

  • EERNN模型为什么只引入题目信息和学生做题记录,为什么不引入知识结构信息?
  • EKT和EERNN本质是可以做相同的事情,并且结果表明EKT也由于EERNN相关模型。
  • 对应Exercise Embedding为什么采用预训练而不端到端统一到一个model中?

参考文献:

  1. EKT: Exercise-aware Knowledge Tracing for Student Performance Prediction

阿里云 cuda9.1+cudnn 7.1+pytorch 环境搭建

Posted on 2019-08-08 | Post modified: 2019-08-08 | In 环境搭建
Words count in article: 269 | Reading time ≈ 1

1.阿里云ECS 默认安装cuda 9.1版本

  • 确定cuda版本:
    • 首先使用nvidia -smi 查看显卡驱动运行状态
    • 使用nvcc -V查看cuda-toolkit安装是否成功
      • 若显示nvcc命令不存在:
        • 使用whereis cuda 查看cuda路径
        • 在~/.bashrc 中加入:
          1
          2
          export PATH=/usr/local/cuda/bin:$PATH 
          export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64/

source ~/.bashrc 命令使环境变量生效

2. 安装Anaconda:

https://repo.anaconda.com/archive/ 版本 Anaconda3-5.1.0-Linux-x86_64.sh

3. 安装cudnn:

从官网下载 https://developer.nvidia.com/cudnn
解压tar -xzvf cudnn-9.1-linux-x64-v7.1.tgz
copy到对应cuda文件夹下:

1
2
3
sudo cp cuda/include/cudnn.h /usr/local/cuda/include
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64
sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn*

查看cudnn版本:

1
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A5

4. 安装pytorch

1
conda install pytorch torchvision cuda91 -c pytorch

⚠️: 若不成功,可以查看pytorch 版本,可以更改pytorch版本:(实测可用)

1
2
pip install torch==0.4.1
pip install torchvision==0.2.2

5. 检查安装是否成功:

1
2
3
python
import torch
torch.cuda.is_available()

【参考文章】:

  1. https://blog.csdn.net/qq_29762941/article/details/80630585
  2. https://blog.csdn.net/qq_29762941/article/details/80630585
12
Zoe

Zoe

Every failure is leading towards success.

12 posts
9 categories
8 tags
RSS
0%
© 2019 Zoe | Site words total count: 19.6k
Powered by Hexo
|
Theme — NexT.Pisces v5.1.4