1. 程式人生 > >基於內容的推薦演算法(推薦系統)(二)

基於內容的推薦演算法(推薦系統)(二)

距離上次更新已經不知道有多久了,因為過幾日就是中期答辯了,為了不太監開始堅持把這個專案往後做一做。

這次我們要做的是什麼呢,要先搭建整個開發環境,目前用到的如下: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 匹配版權符號 (©)。

Unicode中0-9:\u0030-\u0039   還有一種更簡單的方法,^\\pN 就是上面這種,\p 其中的小寫 p 是 property 的意思,表示 Unicode 屬性,用於 Unicode 正表示式的字首。 大寫N代表 Unicode 字符集七個字元屬性之一數字,再用^取頭部, 通過這個正則就可以過濾掉“100克”這樣的無用單詞了。
然後是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



目前先做到這這一步,往後就是考慮對整個菜譜的相似度計算的問題。以及如何對個人生成個性化的推薦(也就是如何刻畫每個人人的人像)