spark2
simhash
simhash
分词→hash→加权→合并→降维
两个simhash对应二进制(01串)取值不同的数量称为这两个simhash的海明距离(异或运算)
分表存储策略:
将一个64位的simhash签名拆分成4个16位的二进制码
编码
import org.apache.spark.sql.SparkSession
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
class Strawberry extends Serializable{
var argsMap: Map[String,String] = _
def run(args:Array[String]):Unit={
//参数初始化
arsMap = initArgs(args) //通过args(0)传参
val ss = SparkSession.builder().enableHiveSupport().getOrCreate()
try{
val sparkContext=ss.sparkContext
val outHdfs = org.apache.hadoop.fs.FileSystem.get(
new java.net.URI("hdfs://apple"),
new org.apache.hadoop.conf.Configuration())
val blackWordSet:Set[String] = Set.empty[String]
val blackWordFilePath=arsMap("blackWordFilePath")//比如hdfs://apple/blackWord.txt
if(outHdfs.exists(new Path(blackWordFilePaht))){//判断hdfs文件是否存在
//获取hdfs文件的最后修改时间
val modifyTime=outHdfs.getFileStatus(new Path(blackWordFilePath)).getModificationTime
}
}finally{
if(null != ss){
ss.stop()
}
}
}
//传入的参数通过一个map统一管理
def initArgs(args:Array[String]):Map[String,String]={
val argsMap=Map(
"appName" -> args(0),
"checkPoint" -> args(1),
"topics" -> args(2),
"blackWordFilePath" -> args(3)
)
}
}
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
/**
* 热加载:判断文件最后修改时间是否有变化
*/
val fs = FileSystem.get(new Configuration())
val inputPath="/apple.txt"
val input_files = fs.listStatus(new Path(inputPath))
var last_modification_time = input_files.map(m => m.getModificationTime.toString).toList
val fs2 = FileSystem.get(new Configuration())
val input_files2 = fs.listStatus(new Path(input_files))
if (input_files2.map(m => m.getModificationTime.toString).toList != last_modification_time){
//file update,do something
last_modification_time = input_files2.map(m => m.getModificationTime.toString).toList// update ModificationTime
}
class MyApple extends Serializable{
var args: Array[String] = _
private val DATA_INPUT_ARRAY=Array(
("name","jack")
,("age","12")
,("address","china")
)
def run(args:Array[String]):Unit ={
val ss=SparkSession.builder().enableHiveSupport().getOrCreate()
try{
}finally{
if(null !=ss){
ss.stop()
}
}
}
}
object MyApple{
def main(args:Array[String]):Unit ={
val myApple=new MyApple()
myApple.run(args)
}
}
编码优化
//对于很大的元组,可以使用case class或者 用数组定义字段
val ss = SparkSession.builder().enableHiveSupport().getOrCreate()
val sc = ss.sparkContext
val rdd1=sc.parallelize(List(("id01","jack",12,Set("piano","soccer")
,Map("beijing"->"china","newyork"->"usa"))))
case class Person(id:String,name:String,age:Int
,hobby:Set[String],address:Map[String,String])
//case class
val rdd2=rdd1.map(x => Person(x._1,x._2,x._3,x._4,x._5))
rdd2.map(p=>{
p.id
p.hobby
})
val PERSON_ARRAY=Array("id","name","age","hobby","address")
//数组按顺序定义字段,用indexof取位置,避免出现大量的x(0),x(1),难以理解
val rdd3=rdd1.map(x=>Array(x._1,x._2,x._3,x._4,x._5))
rdd3.map(x=>{
x(PERSON_ARRAY.indexOf("id")) //等价x(0)
x(PERSON_ARRAY.indexOf("hobby")) //等价x(3)
})
//常用方法
//字符串 转 Long
def stringToLongEmptyZero(ss: String): Long = {
try {
if (ss.isEmpty) {
0L
} else {
ss.toLong
}
} catch {
case e: Exception => 0L
}
}
// break 跳出循环
import scala.util.control.Breaks
val loop = new Breaks
loop.breakable{
for(x <- aArr){
if(flag==true){
loop.break
}else{
//do something
}
}
}
系统结构
冷启动用户:冷启动召回策略
不活跃重新活跃(非冷启动用户):短时兴趣和长时兴趣;但短时兴趣已经衰减很弱
长时兴趣召回策略:
兴趣模型相对固定
更新召回列表两个动机:①用户刷新频繁,召回列表的内容很快被推送完毕
②从召回列表生成已有比较长的时间,需要重新生成召回列表来体现新增内容
热搜名称+频道+热搜热度值+来源+获取时间
平均阅读速度: 900字/分钟=15字/秒
标准时长=总字数/平均阅读速度 +图片数*5
平均阅读时长=总阅读时长/点击数
文章质量分: (1-标题党异常分+exp(-用户投诉反馈次数和) +平均阅读时长/标准时长+CTR)/4
内容标签:
通过TF-IDF生成原始关键词和关键词对应tf-idf值,
用TextRank算法计算关键词和权重,对TF-IDF值进行加权
内容多样性: 偏好分频道存,在推荐策略中推荐多个频道,主题多样化,频道间要岔开
相似用户标签的召回策略:
根据聚类的用户分群,寻找一个用户相类似的用户群的标签,映射到文章,做召回推荐
基于用户协同的召回策略:
对一批流式用户做协同过滤生成召回推荐-根据与用户A特征最相似的N个用户,根据其他用户的兴趣来给用户A做推荐生成召回列表
热点惩罚:
热门推荐中,用户对某一类热点总是不点(或点击少到一定值)
兴趣试探与行为反馈存储:
Bandit类算法,对用户兴趣进行试探,快速反馈,得到用户超短期兴趣偏好
#############
########
这部分用的标签指关键词
非冷启动用户8个召回策略:
兴趣召回:
用户兴趣模型中取不超过3个兴趣标签,根据标签从内容倒排抽取30篇;
进行已推过滤,再每个标签匹配分数取top10
列表之间去重
对每个标签保留的文章进行ctr打分
兴趣扩展:
用户兴趣模型中抽取2个二级类别,根据二级类别标签从内容倒排抽取文章30篇;
进行已推过滤,再每个标签匹配分数取top10
列表之间去重
对每个标签保留的文章进行ctr打分
兴趣探索:
从用户兴趣模型之外的二级类别做探索,选定两个二级类别;
根据二级类别从内容倒排抽取文章,每个二级类别20篇;
进行已推过滤,按标签-文章分数取top10;
列表之间去重
ctr打分
兴趣协同:
根据用户所属群体,读取群体标签;
从协同标签集合中抽取至多1个协同标签;每个标签重内容倒排抽取20篇文章
进行已推过滤,按标签-文章分数取top10;
列表之间去重
ctr打分
地域召回:
根据用户请求LocalCode提取整个地域候选列表,进行已推过滤,按发布时间取top10;
列表间去重;
对文章ctr打分;
热点召回:
提取整个热门候选列表,进行已推过滤,按发布时间取top10;
列表间去重;
对文章ctr打分;
相似召回:
针对上一刷有点击/收藏/转发/分享行为的每篇文章,从相似内容列表按相似度随机抽取相似文章,各取30篇;
已推过滤,每个行为按相似度取top10;
列表之间进行去重;
对每个列表进行ctr打分;
默认召回:
兜底策略,如果其他召回无法凑齐10篇文章,则用默认召回补齐;
提取整个默认侯选池;
对默认召回 侯选池进行已推过滤后提取不超过30篇文章;
列表之间去重;
对保留文章进行ctr打分
默认侯选池是线下预先 生成的长度为1000的多样化文章集合
长时画像: 用户id, 标签|分值
生成布隆过滤器:
1.根据文章的发布时间从redis读取相应的bitmap
2.采用k个独立的hash算子处理NewsId生成k个偏移值
3.将bitmap总k个偏移值对应的bit位设置为1,结果写入redis
4.bitmap按周存储,发布时间一周的已推文章存储在一起;单独一个任务将超期(比如5周)的bitmap删除
已推过滤:
1.根据文章的发布时间读取redis相应的bitmap
2.采用k个独立的hash算子处理NewsId生成k个偏移值
3.检查bitmap中k个偏移值对应的bit位设置是否全为1,是则将文章从召回列表中删除
日历定期类事件的推荐召回
事件 | 分数 | 起始时间 | 终止时间 |
---|---|---|---|
圣诞 | 3 | 2020/12/25 | 2020/12/25 |
春节 | 10 | 2020/2/3 | 2020/2/10 |
标题与内容相关度:
利用Word2vec计算文章标题与内容相似度, 长文章采取文章摘要
用户画像标签体系
标签是某一种用户特征的符号表示
标签级别:
事实标签:通过对原始数据库的数据进行统计分析而来的
模型标签:以事实标签为基础,通过构建实际标签与业务问题之间的模型,进行模型分析得到
预测标签:在模型的基础上做预测
标签体系结构
读书笔记 |《推荐系统实践》- 个性化推荐系统总结
https://www.jianshu.com/p/319e4933c5ba
报错总结
当topic中,开始推数据的时候或者数据因为时间消失时,消费若没数据消失的速度快,则会报错:
原因: org.apache.kafka.clients.consumer.offsetOutofRangeException:Offsets out of range with no configured reset policy for partition:{xxx}
当我们的任务开始的时候,如果之前消费过某个topic,那么这个topic会在zk上设置offset,我们一般会去获取这个offset来继续从上次结束的地方继续消费,但是kafka定时清理日志的功能,比如定时一天一清理,那么如果你的offset是前天消费的offset,那么这个时候你再去消费,自然而然的你的offset肯定已经不在有效范围内,所以就报OffsetOutOfRangeException了
解决方法就是判断一下zk中的offset是否小于topic最小的offset,如果小于的话,就把最小的offset设置到zk中
当前有两个方法 1) 重新用一个新的groupid 然后 offset设置为latest 2) 用上面的命令 将已有的groupid 强制设置offset为latest 然后直接启动就ok
这个可以查出来 开始的 和结束的offset 然后强制设置下
但由于我们要消费全部日志,则可查看最小offset,将current offset设置成最小offset:
查看最小offset:
设置成最小offset:
待验证:是否无需最开始的那些操作,直接修改current offset为最小offset,即可实现kafka消息的重新消费。
推荐系统
基尼系数:
马太效应: 强者越强,弱者更弱
主流的推荐算法都有马太效应
新颖度: 喜欢周星驰,推荐了他一部早期鲜为人知的电影
惊喜度: 推荐了一部和周星驰没有关系的电影,你觉得很不错,这是惊喜度
预测准确度度量一个推荐系统或者推荐算法预测用户行为的能力
网站在提供推荐服务时,一般是给用户一个个性化的推荐列表,这种推荐叫做TopN推荐。 TopN推荐的预测准确率一般通过准确率(precision)/召回率(recall)度量
二.利用用户行为数据
基于用户行为分析的推荐算法是个性化推荐系统的重要算法,学术界一般将这种类型的算法称之为协同过滤算法.顾名思义,协同过滤就是指用户可以齐心协力,通过不断地和网站互动,使自己的推荐列表能够不断过滤掉自己不敢兴趣的物品,从而越来越满足自己的需求.
显性反馈行为
隐性反馈行为
隐语义模型(latent factor model)
基于图的随机有组算法(random walk on graph)
基于领域的方法(neighborhood-based) :
获得最广泛应用,主要分为两大类:
基于用户的协同过滤算法
基于物品的系统过滤算法
基于用户的协同过滤算法:
1.找到和目标用户兴趣相似的用户集合
2.找到集合中得用户喜欢的,且目标用户没有听说过的物品推荐给目标用户
关键是计算两个用户的兴趣相似度: 余弦相似度
对两两用户都计算相似度,时间复杂度高,用户基数大则非常耗时
物品-用户的倒排表,只对同时属于倒排表中k个物品的两个用户做计算
每个用户选出K个和他兴趣最相似的用户,然后推荐那K个用户感兴趣的物品
准确率和召回率: 选择合适K提高推荐的精度
流行度: K越大,推荐的就越热门,因为参考的人越多,结果就越趋近于全局热门物品
覆盖率: K越大覆盖率越低,因为k越大流行度越高,所以对长尾物品推荐越少
基于物品的协同过滤算法
计算物品之间的相似度
根据物品的相似度和用户的历史行为给用户生成推荐列表
用户—物品倒排表
物品相似度归一化:
增加推荐的准确度,提高推荐的覆盖率和多样性
UserCF的推荐结果着重于反映和用户兴趣相似的小群体的热点
ItemCF 的推荐结果着重于维系用户的历史兴趣
在新闻网站中,用户的兴趣不是特别细化,绝大多数用户都喜欢看热门的新闻.即使是个性 化,也是比较粗粒度的,比如体育,娱乐;个性化新闻推荐更加强调抓住 新闻热点,热门程度和时效性是个性化新闻推荐的重点,而个性化相对于这两点略显次要。因 此,UserCF可以给用户推荐和他有相似爱好的一群其他用户今天都在看的新闻,这样在抓住热 点和时效性的同时,保证了一定程度的个性化
用户没有过行为的物品叫做负样本
负样本采样:
对每个用户,要保证负样本的平衡(数目相似)
负样本采样时,选取哪些很热门,而用户却没有行为的物品
一般认为,很热门而用户却没有行为更加代表用户对这个物品不感兴趣。因为对于冷门的物
品,用户可能是压根没在网站中发现这个物品,所以谈不上是否感兴趣
冷启动问题:
标签:
一种无层次化结构的用来描述信息的关键词
标签应用分为两种:
让作者或专家给物品打标签;
让普通用户给物品打标签,UGC(User Generated Content)
排名模块:
1.新颖性:
给用户推荐他们不知道的长尾中的物品
2.多样性:
推荐结果中覆盖多种类型
3.时间多样性:
保证推荐系统的实时性,在用户新行为实时调整推荐结果以满足用户最近的需求
如果用户没有新行为,今天推荐结果列表中,把昨天或者更长时间看到的推荐结果降权