基於內容的推薦演算法(推薦系統)(二)
距離上次更新已經不知道有多久了,因為過幾日就是中期答辯了,為了不太監開始堅持把這個專案往後做一做。
這次我們要做的是什麼呢,要先搭建整個開發環境,目前用到的如下:mysql,idea,IKAnalyzer2012_u6(一個開源的分詞包,完全夠用了)
這次我計劃先完成最簡單的一個推薦系統的設計,目的只為了完成通過餘弦相似性來計算文字的相似性,提取特徵值採用資料庫中最好拆解分析的“原料”列
餘弦相似度和tf-idf的參考文章 http://www.ruanyifeng.com/blog/2013/03/cosine_similarity.html
需要用到的jar包:
上面兩個在上面那個下載的資料夾裡,匯入即可,jdbc的java和mysql的連線用的jar包自己搜著下載吧。
用到的資料庫:已經放到我百度雲裡了https://pan.baidu.com/s/1nv4klM5,格式是mdb也就是微軟的access,我使用的是navicat將其轉換為mysql的。
資料格式:
我們先上一個現在達到的最終效果
接下來講如何實現。
首先我設計了三個類,分別是JDBC類用於連線資料庫, similarity類用於計算相似度,split類用來完成分詞。
先將最重要的部分,similarity類是如何工作的。
我們採用預設的分詞方法,隨便分一行中的原料列看看效果
菠菜|400克|熟火腿|20克|雞蛋|50克|海米|20克|熟|冬筍|50克|水|發|冬菇|50克|胡蘿蔔|50克|
裡面有兩個方法,getSimilarDegree計算相似度,delUseless是將無用的“50克”這樣的刪掉。
下面我們舉一個實際的例子:
鮮|豆腐|香菇|黑木耳|西紅柿|黃瓜|蛋清| 豆腐|小蔥| 上面是兩樣菜譜的原料分詞並且去除無用詞後,我們怎麼算他們兩個的相似度呢? 使用餘弦的演算法我們都很清楚公式, 這個夾角越小,也就是上面的值越逼近1說明兩者相似度越高。 注意我們要怎麼樣構造這個向量空間呢:Map<String, int[]> vectorSpace = new HashMap<String, int[]>();這是重點,這個map的key是上面分出來的詞,後面value是長度為2的int型陣列,上面的的例子轉換到這個向量空間就是:
鮮 | 1 | 0 |
豆腐 | 1 | 1 |
香菇 | 1 | 0 |
黑木耳 | 1 | 0 |
西紅柿 | 1 | 0 |
黃瓜 | 1 | 0 |
蛋清 | 1 | 0 |
小蔥 | 0 | 1 |
下面是similarity類的程式碼:
package com.company;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by 陽 on 2016/12/5.
*/
public class similarity {
/*
* 計算兩個字串(英文字元)的相似度,簡單的餘弦計算,未添權重
*/
public double getSimilarDegree(ArrayList<String> str1, ArrayList<String> str2)
{
//建立向量空間模型,使用map實現,主鍵為詞項,值為長度為2的陣列,存放著對應詞項在字串中的出現次數
Map<String, int[]> vectorSpace = new HashMap<String, int[]>();
int[] itemCountArray = null;//為了避免頻繁產生區域性變數,所以將itemCountArray宣告在此
//動態陣列轉為陣列
int size1=str1.size();
String[] strArray1 = (String[])str1.toArray(new String[size1]);
for(int i=0; i<strArray1.length; ++i)
{
if(vectorSpace.containsKey(strArray1[i]))
++(vectorSpace.get(strArray1[i])[0]);
else
{
itemCountArray = new int[2];
itemCountArray[0] = 1;
itemCountArray[1] = 0;
vectorSpace.put(strArray1[i], itemCountArray);
}
}
int size2=str2.size();
String[] strArray2 = (String[])str2.toArray(new String[size2]);
for(int i=0; i<strArray2.length; ++i)
{
if(vectorSpace.containsKey(strArray2[i]))
++(vectorSpace.get(strArray2[i])[1]);
else
{
itemCountArray = new int[2];
itemCountArray[0] = 0;
itemCountArray[1] = 1;
vectorSpace.put(strArray2[i], itemCountArray);
}
}
//計算相似度
double vector1Modulo = 0.00;//向量1的模
double vector2Modulo = 0.00;//向量2的模
double vectorProduct = 0.00; //向量積
Iterator iter = vectorSpace.entrySet().iterator();
while(iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
itemCountArray = (int[])entry.getValue();
vector1Modulo += itemCountArray[0]*itemCountArray[0];
vector2Modulo += itemCountArray[1]*itemCountArray[1];
vectorProduct += itemCountArray[0]*itemCountArray[1];
}
vector1Modulo = Math.sqrt(vector1Modulo);
vector2Modulo = Math.sqrt(vector2Modulo);
//返回相似度
return (vectorProduct/(vector1Modulo*vector2Modulo));
}
/*
計算原料列相似度的時候去除數字和“克”這些無用詞
用正則表示式
*/
public boolean delUseless(String value){
Pattern pattern = Pattern.compile("^\\pN");
Matcher isNum = pattern.matcher(value);
if(isNum.find()){
return true;
}else
return false;
}
}
最後這個正則要多說幾句:
\un |
匹配 n,其中 n 是以四位十六進位制數表示的 Unicode 字元。例如,\u00A9 匹配版權符號 (©)。 |
然後是split,基本上就是照著給的例子寫一個就能用:
package com.company;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.wltea.analyzer.lucene.IKAnalyzer;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
/**
* Created by 陽 on 2016/12/5.
*/
public class split {
similarity sim=new similarity();
//體現出分詞效果
public void splitWord(String s) throws IOException {
String text = s;
Analyzer analyzer = new IKAnalyzer(true);// 建構函式當為 true時,分詞器採用智慧切分
StringReader reader = new StringReader(text);
TokenStream ts = analyzer.tokenStream("", reader);
CharTermAttribute term=ts.getAttribute(CharTermAttribute.class);
while(ts.incrementToken()){
if(!sim.delUseless(term.toString())){
System.out.print(term.toString() + "|");
}
}
System.out.println();
analyzer.close();
reader.close();
}
//將分詞結果存到陣列中去,在這裡面處理掉無用詞
public ArrayList<String> splitWordtoArr(String s) throws IOException {
String text = s;
ArrayList List = new ArrayList();
Analyzer analyzer = new IKAnalyzer(true);// 建構函式當為 true時,分詞器採用智慧切分
StringReader reader = new StringReader(text);
TokenStream ts = analyzer.tokenStream("", reader);
CharTermAttribute term=ts.getAttribute(CharTermAttribute.class);
while(ts.incrementToken()){
if(!sim.delUseless(term.toString())){
List.add(term.toString());
}
}
analyzer.close();
reader.close();
return List;
}
}
然後放上最沒有技術含量的jdbc:
package com.company;
import java.sql.*;
import java.util.ArrayList;
import java.util.Properties;
/**
* Created by sunyang on 16/9/25.
*/
public class JDBC {
// JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/recipe";
// Database credentials
static final String USER = "root";
static final String PASS = "";
public static void main(String[] args) {
/*
新建split物件,然後用jdbc讀取資料用splitWrod處理
*/
split sw=new split();
similarity sim=new similarity();
//初始化jdbc
Connection conn = null;
Statement stmt = null;
try{
//STEP 2: Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
//STEP 3: Open a connection
Properties props = new Properties();
props.setProperty("characterEncoding", "utf-8");
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
// sql = "SELECT count(*) FROM recipe_copy";
sql="SELECT `菜譜`.`原料` FROM `菜譜` WHERE `菜譜`.`菜譜ID`=969 OR `菜譜`.`菜譜ID`=1000";
ResultSet rs = stmt.executeQuery(sql);
//STEP 5: Extract data from result set
ArrayList[] result=new ArrayList[2];//暫存每條資料分詞後的結果,用於傳入計算相似度
int k=0;
while(rs.next()){
//Retrieve by column name
String handle = rs.getString("原料");
//Display values
sw.splitWord(handle);
result[k]=sw.splitWordtoArr(handle);
k++;
}
System.out.println(sim.getSimilarDegree(result[0],result[1]));//計算相似度
//STEP 6: Clean-up environment
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
}//end main
}//end FirstExample
目前先做到這這一步,往後就是考慮對整個菜譜的相似度計算的問題。以及如何對個人生成個性化的推薦(也就是如何刻畫每個人人的人像)