基於Hadoop與Spark大數據平臺的個性化圖書推薦系統搭建學習總結

前言:這兩個月來一直在處理接手實驗室師兄的一個圖書推薦項目,期間從讀懂其用python構建的簡易推薦系統到在spark上寫pyspark、scala程序來實現一個基於大數據平臺的分佈式推薦系統,對於我這樣一個無人指點的小白着實是費了一番功夫,現在做記錄如下。

一、在spark分佈式平臺運到的坑

1、 如何在spark ui上監聽到spark的歷史運行記錄

利用spark UI 調試和監控運行的spark程序非常的方便,它可以讓我們很直觀的看到正在運行的stages、某stage運行的時間和executors的運行狀況等,但有個比較不便的是沒用經過特殊設置,spark UI 在spark程序運行完就不能再連接上了。市場上常見的spark書籍在教搭建spark平臺時都不會教怎麼配置spark環境變量以使我們可以看到spark平臺運行的歷史記錄。在網上進行搜索一番後發現如何配置環境也都是衆說紛紜,不同的人配置的方式也不用。
經過一番比較後,我配置spark的環境變量以使其可以看到spark的歷史運行記錄的方式如下:
在環境變量文件spark-defaults.conf中添加如下幾行:

spark.history.ui.port=Master:18080   //設置監聽歷史記錄的端口號爲18080
spark.eventLog.enabled=true   //設置爲ture,表示設置記錄Spark事件,用於應用程序在完成後重構webUI
spark.eventLog.dir=hdfs://Master:9000/tmp/spark/events   //設置hdfs緩存歷史的任務目錄,目錄應在hdfs中相應的創建好
spark.history.retainedApplications=30  //設置可查看的最大歷史記錄數

在環境變量文件spark-env.sh中添加如下幾行:

export SPARK_HISTORY_OPTS="-Dspark.history.ui.port=18080 -Dspark.history.retainedApplications=3 -Dspark.history.fs.logDirectory=hdfs://Master:9000/tmp/spark/events" //設置後相應hdfs目錄需相應的創建好

參考連接官方文檔博客1
在上述兩個環境變量文件中添加好相應代碼後,在啓動spark集羣時同時打開spark-history-server.sh腳本即可在spark UI中監聽到spark 的歷史運行記錄了。

2、 用spark-submit提交pyspark代碼沒有按預期運行分佈式模式

異常描述: 分佈式平臺(hadoop+spark)配置好後,我在spark-shell master spark://Mastter:7070入口進入交互式環境下和以spark-submit爲入口在yarn集羣和spark standalone 集羣模式上提交sbt打包的scala程序都是以運行分佈式模式運行程序的。但在spark-submit爲入口,按書上的提示在yarn集羣和spark standalone 集羣模式上提交pyspark.py程序欲使其運行分佈式時就出現了問題,在運行記錄中看到的是提交的pyspark代碼是以localhost模式形式運行的,也就是說跑的是單機。這個問題困擾我兩天,因爲我的spark和hadoop是照着幾本書配置的,也參考了師兄配置的集羣且提交打包好的jar程序運行時並無異常。期間在網上查閱許久,也把spark和hadoop的配置文件檢查了數遍,及在一些技術羣裏詢問但都沒有得到答案,最後問題的解決是來自於一個無意的嘗試。平常在spark-submit提交的pyspark程序的格式是 “spark-submit /opt/data/LibraryProject/programSet/base_user_submit.py(程序名) --master yarn --deploy-mode client --queue default --executor-memory 5g --num-executors 3 “後來改爲“spark-submit --master yarn --deploy-mode client --queue default --executor-memory 4g --num-executors 3 --executor-cores 4 --driver-memory 1G /opt/data/LibraryProject/programSet/base_user_submit.py”即將代碼的的位置放在提交指令的最後一欄就可以如願在yarn上運行分佈式程序了。
  異常分析:經過分析後發現,spark-submit提交方式應該是讀到所提交的程序文件就不會再讀後面如–master yarn --deploy-mode client…等提交的參數設置了,而我所提交的程序中在創建spark實例的時候剛好沒有進行任何給參數的操作(我創建spark的方式是spark=SparkSession(context),context=SparkContext()),所以出現這樣spark平臺即以默認提交方式localhost形式運行程序了。這真的是一個大坑,現階段介紹pyspark的書比較少,且介紹的較淺,儘管python在越來越多的應用到編寫spark程序實現分佈式任務當中。

3、spark在執行程序中出現too much file open 的錯誤

寫在我的另一個博客中
Uncaught exception while reverting partial writes to file …(Too many open files)

4、運行pyspark程序運到python版本問題

運行錯誤警告

問題分析:因爲在集羣中存在python官網中下載安裝的python和安裝的anaconda帶的python兩個版本,程序在執行時不知道使用哪個版本python的解釋器。
  
  問題解決:
在環境變量文件spark-env.sh中添加如下設置:

export PYSPARK_PYTHON=/opt/app/anaconda2/bin/python

二、 基於讀者與讀者的圖書推薦實現

1、基於用戶的協同過濾的基本思想是:根據用戶對物品的愛好找到相鄰鄰居用戶,然後將鄰居用戶偏愛的物品中但是當前用戶又沒有購買的推薦給他。它的原理就是將某個用戶對全部物品的愛好作爲一個向量,計算出各個用戶之間的相似度,找到鄰居後,依據鄰居們的相似度及購物歷史,預測當前用戶可能會喜歡的但尚未購買的物品,計算得出按照一定排列順序的物品推薦類表給用戶。
2、基於用戶的算法在很多方面有着非常成功的運用,但是隨着推薦系統規模的日益擴充,特別是用戶數和項目數的日益增多,該算法存在的弱點開始體現出來了。
a、稀疏性問題。據研究結果表明,當用戶評價項目數少於總項目數的,就很容易造成評價矩陣數據相當稀疏,導致算法難以找到一個用戶的偏好相似鄰居。
b、冷啓動問題。基於用戶協同過濾是建立在有大量用戶對某個產品的評價上的,由於在新產品開始階段沒有人購買,也沒有對其進行評價,那麼在開始階段也將無法對其進行推薦。
c、算法擴展性問題。隨着物品數尤其是用戶數的劇烈增加,最近鄰居算法的計算量也相應增加,所以不太適合數據量大的情況使用,所以推薦系統性能也會大大受影響,而現在的推薦系統幾乎是結構,沒有快速的相應速度,對網絡用戶來說無法忍受的,因此這在某種程度上限制了基於用戶協同過濾在推薦系統中的使用。
 d、特殊用戶問題。在生活中,往往有一部分人的偏好是比較特殊,他沒有相對固定的興趣愛好,而這剛好是基於用戶協同過濾的前提,那麼系統很難爲他找出鄰居,也就是很難給出比較精確的推薦信息了。
3、基於讀者與讀者的協同過濾如何實現
a、構建讀者-圖書評價矩陣
b、利用餘弦相似度計算公式,以每個讀者的借閱評分向量來計算不同用戶之間的相似度。
c、制定推薦策略,爲每一位讀者推薦圖書
讀者-圖書評價矩陣

4、在實現過程中遇到的理論問題
  圖書館沒有對圖書進行借閱打分的機制,因而在構建用戶-圖書評價矩陣時缺失讀者借閱評分數據,只能以簡單的是否借閱某書來代替讀者對某書的評分。其中借閱過表示爲1,代表完全滿意;沒有借閱過表示爲0,代表完全不滿意。這種無可奈何的辦法會導致不能準確計算讀者的相似度,且容易造成推薦熱門借閱圖書,難以對讀者進行個性化推薦。所以如果想要實現對讀者進行個性化推薦,後期圖書館應該制定一個可以讓讀者對其所借閱的圖書進行評分的機制。

三、 代碼中覺得值得記錄的操作

1、讀取csv文件並將其轉化爲dataframe的操作指令

user_book=spark.read.csv('logSet/user_book.csv',inferSchema='true',header="true")//pyspark版,讀取時設置爲自動推斷列的類型,取第一列爲列名
var user_book_list=spark.read.format("csv").option("header","true").load("dataSet/user_book_id.csv")//scala版本,讀取時設置爲自動推斷列的類型,取第一列爲列名

2、保存datafram到hdfs(df.repartition(1)表示將數據聚合在一個分區上進行保存,這樣保存的數據不會別切分成多個)

book_list.write.repartition(1).csv('test_book.csv',header='true',mode='overwrite')//pyspark版,保存時時設置未取第一列爲列名,如有相同文件名則進行覆蓋
user_sim.repartition(1).write.format("csv").option("header","true").option("mode","overwrite").save("spark_result/user_Sim3.csv")scala版,保存時時設置未取第一列爲列名,如有相同文件名則進行覆蓋

3、創建dataframe的幾種方式
pyspark版

schema = StructType([StructField("user_id", IntegerType(), True), StructField("sim_id", IntegerType(), True),StructField("sim", FloatType(), True)])
df=spark.createDataFrame([(user_id,i,sim_values)],schema)

scala版
a、創建帶制定格式的dataframe

case class userSim(user_id:String,sim_id:String,sim_value:Double)
var df_tmp = List(userSim(user_id,user_i,sim_value))
var user_sim=sc.parallelize(df_tmp).toDF()
或者:
var list_tmp = List(userSim(user_book_cv_list(0)(0).toString,  user_book_cv_list(0)(0).toString, 1.0)).toBuffer//預設df的格式
list_tmp += userSim(user_book_cv_list(i)(0).toString,  user_book_cv_list(j)(0).toString, cosineSim)
val user_sim = sc.parallelize(list_tmp).toDF()//將得出的讀者相似度list創建爲df(user_id,sim_id,sim_value)

b、不帶格式的dataframe

val df = Seq(("00",Array("機器學習","深度學習","spark機器學習"))).toDF("user_id","userName")
var arr =("大數據導論","快速大數據分析")
val df4 = Seq(("02",Array(arr))).toDF("user_id","userName")

4、合併兩個dataframe(在代碼實現過程的用到多次,很好用)

user_book=user_book.join(book_id,"bookName","left").cache()//pyspark版,以兩個df都有的col(bookName)進行合併,合併模式是"left",數據向df user_book傾斜
var user_book_list=user_book.join(book_list,$"bookName"===$"bookName1","left")//scala版
var user_sim_list = sim_list.join(all_user,sim_list("sim_id")===all_user("user_id"),"inner")//scala版,合併兩個df中col(sim_id)===col(user_id)相同的值,,join的模式是"inner",注意用的是三個“=”

5、將某列值相同的標籤聚合到同一行

var all_user = all_user.withColumn("bookName1", $"bookName").groupBy("user_id").agg(collect_list($"bookName1")).toDF("user_id","bookName").persist(StorageLevel.MEMORY_ONLY)/以讀者ID聚合,得到一個型爲(user_id,Array(user_all_book))的dataframe

6、刪除col(1)中包含col(0)中具有的值(這個在實現對每個讀者進行最終推薦圖書時非常好用,而且實現起來很簡單)

//自定義一個函數
val filterList = udf((a: Seq[String], b: Seq[String]) => a diff b)
//在col(recomBook)去除掉user_id用戶借閱過的書籍
var user_sim_recomBook=user_sim_book.withColumn("left", filterList($"recomBook", $"user_bookName")).show()

7、構建讀者-評分矩陣,(這裏用了一個取巧的方法–調用分詞函數,處理起來也很快,120萬行的數據只需要幾分鐘。實驗室用同學寫過類似的代碼,借鑑了一部分。不過這裏可以這麼用剛好是因爲除處理的圖書沒有評分數據,只能以借閱與否代表滿意度,否則不能直接用下示方法)

import org.apache.spark.ml.feature.CountVectorizer
import org.apache.spark.storage.StorageLevel

var user_book_list=spark.read.format("csv").option("header","true").load("dataSet/user_book_id.csv").select("id","bookName","book_id").toDF("user_id","bookName","book_id")//加載全部讀者的圖書借閱記錄
user_book_list.persist(StorageLevel.MEMORY_ONLY)//相當於調用cache()函數
var all_user=user_book_list.select("user_id","bookName").cache()//取用戶名和用戶借閱書名
val user_book = all_user.withColumn("bookName1", $"bookName").groupBy("user_id").agg(collect_list($"bookName1")).toDF("user_id","bookName").cache()//以讀者ID聚合,得到一個型爲(user_id,Array(user_all_book))的dataframe
val book_cv = new CountVectorizer().setInputCol("bookName").setOutputCol("TitleVectors")//以待分詞的列爲輸入創建分詞實例
val bookmodel = book_cv.fit(user_book)//調用fit實例
val user_book_cv = bookmodel.transform(user_book)//得到形爲(user_id,bookName,TitleVectors)的dataframe,其中的col(TitleVectors)爲每個讀者的讀者-評分矩陣

四、低效率編寫代碼的體現(spark平臺用的好的人可以實現快速計算數以Tb的數據,但代碼沒有優化好,寫出來的程序可能還不如用pyython單機來的快,這一點自己深有體會,以下是自己遇到的一些坑)

1、不會學會看spark官方文檔。
2、重複使用active操作,特別是在大的循環裏。
3、不會爲重複調用的rdd或者df設置緩存。
4、想實現某一功能,特別是spark官方文檔沒有直接寫到的,不是先googel而是自己硬着寫。
5、寫代碼之前沒有提前都構思好,想到什麼寫什麼,寫到後面才發現方向錯了。
6、能用scala實現的功能要用pyspark實現。官方指出,pyspark代碼執行的速度比scala慢一倍(rdd)。

五、項目處理過程中查閱的覺得不錯的博客

1、How to create an empty DataFrame? Why “ValueError: RDD is empty”?
2、How to create an empty DataFrame with a specified schema?
3、Spark DataFrame按某列降序排序](https://blog.csdn.net/dkl12/article/details/80961981)
4、Python pyspark.sql.SparkSession() Examples
5、Merging multiple data frames row-wise in PySpark
6、Extract column values of Dataframe as List in Apache Spark
7、spark - Converting dataframe to list improving performance
8、How do I add a new column to a Spark DataFrame (using PySpark)?
9、PySpark 學習筆記三
10、spark dataframe 一列分隔多列,一列分隔多行(scala)
11、PySpark︱DataFrame操作指南:增/刪/改/查/合併/統計與數據處理
12、Trying to create dataframe with two columns [Seq(), String] - Spark
13、how to convert rows into columns in spark dataframe, scala
14、Difference between columns of ArrayType in dataframe
15、python—pandas.merge使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章