機器學習(hadoop實戰)01: 家電故障備件儲備預測分析

家電故障備件儲備預測分析

本例來源於《Hadoop大數據分析與挖掘實戰》第十二章家電故障備件儲備預測分析。

數據集:請留言,我會私發。

  • 問題:針對手機數據,要求從服務商代碼中提取出地區編碼,對每個地區進行故障率的預測,從而做到備件的提前儲備。

  • 問題分析:從問題描述可以看出,可以看作是對每個地區進行故障的推薦,地區編碼作爲用戶id,故障代碼作爲項目id,現有數據集中故障率作爲評分,使用協同過濾算法進行故障的推薦。

  • 難點:

  1. 需要把故障描述轉化爲故障代碼,故障描述是用戶對故障的描述,所以各種各樣,很難做到統一,需要提取其中的關鍵字。
  2. 現有數據集中故障率的計算。
  3. 在計算的時候,地區編碼、故障代碼都是字符串,但是協同過濾算法會把他們作爲long類型處理,會出現數據失真的情況。

本次主要針對手機故障進行分析。
分析流程:

  • 第一步:查看數據集。本次主要分析手機故障,所以只需要excel中Sheet2中的數據(見圖1.1),由於hadoop沒有讀取excel的InputFormat(我自己實現了一個,但是存在bug),所以採用了先把excel導出爲txt文件,然後再處理的辦法。數據集

    圖1.1

  • 第二步:寫mr程序計算故障率,map階段讀取文件。按照 \t 切分字符串,過濾掉不符合格式的數據(見代碼2.1),然後從中取出服務商代碼、故障描述。從服務商代碼中取出地區代碼,根據手機故障原因標準準則(見圖2.3),把故障描述轉化爲故障代碼(見代碼2.2),然後寫出。

代碼2.1

    /**
     * 產品大類 品牌 產品型號 序列號 內機編碼 服務商代碼 受理時間 派工時間 故障原因代碼 故障原因描述 維修措施 反映問題描述
     */
    static class ParseMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

        Text k = new Text();
        IntWritable v = new IntWritable();
        int sum = 0;

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 刪除空行
            if (!StringUtils.isBlank(value.toString())) {
                String[] fields = value.toString().split("\t");
                if (fields.length < 12) {
                    System.out.println(Arrays.toString(fields));
                    sum ++;
                }
                // 如果服務商代碼或者反映問題描述爲空,則刪除行
                if (fields.length >=12 && !StringUtils.isBlank(fields[5]) && !StringUtils.isBlank(fields[11])) {
                    // 服務商代碼
                    String districtCode = fields[5];
                    // 反映問題描述
                    String faultTxt = fields[11];

                    try {
                        if (districtCode.split("-").length >= 3) {
                            // 從服務商代碼中取出地區編碼
                            String district = districtCode.split("-")[2];
                            // 把反映問題描述轉化爲故障代碼
                            int convertCode = convertCode(faultTxt);
                            k.set(district);
                            v.set(convertCode);

                            context.write(k, v);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }

            }

        }

        @Override
        protected void cleanup(Context context) throws IOException, InterruptedException {
            System.out.println("sum: " + sum);
        }
    }

代碼2.2

    /**
     * 把反映問題描述轉化爲故障類型代碼
     * @param faultText 反映問題描述
     * @return 故障類型代碼
     */
    public static int convertCode(String faultText) {
        int code = 0;
        String fault = faultText.toLowerCase();
        if (fault.contains("lcd")) {
            code = 2;
        } else if (fault.contains("鍵") && !fault.contains("印錯")) {
            code = 3;
        } else if (fault.contains("網絡") || fault.contains("服務")
                || fault.contains("信號") || fault.contains("連接")
                || fault.contains("功率低")) {
            code = 5;
        } else if (fault.contains("通話") || fault.contains("聽筒")
                || fault.contains("送話")) {
            code = 4;
        } else if (fault.contains("燈")) {
            code = 6;
        } else if (fault.contains("藍牙")) {
            code = 7;
        } else if (fault.contains("不吃卡") || fault.contains("不識卡")
                || fault.contains("不讀卡")) {
            code = 8;
        } else if (fault.contains("電池") || fault.contains("耗電")) {
            code = 9;
        } else if (fault.contains("拍照") || fault.contains("照相")
                || fault.contains("攝像頭")) {
            code = 10;
        } else if (fault.contains("觸屏")) {
            code = 11;
        } else if (fault.contains("振動")) {
            code = 12;
        } else if (fault.contains("mp3") || fault.contains("音")
                && !fault.contains("鍵") && !fault.contains("喇叭")) {
            code = 13;
        } else if (fault.contains("喇叭") || fault.contains("耳機")) {
            code = 14;
        } else if (fault.contains("充電")) {
            code = 15;
        } else if (fault.contains("gps") || fault.contains("衛星")) {
            code = 16;
        } else if (fault.contains("殼") || fault.contains("螺絲")
                || fault.contains("縫隙") || fault.contains("印錯")) {
            code = 17;
        } else if (fault.contains("開機") || fault.contains("死機")
                || fault.contains("開關機") || fault.contains("開(關)機")) {
            code = 1;
        } else {
            code = 18;
        }

        return code;
    }

手機故障原因標準準則

圖2.3(部分)

  • 第三步:reduce階段計算故障率。map階段寫出的時候把地區編碼作爲key,所以在reduce階段取數據的時候,會把相同地區編碼的數據作爲一組,一起拿過來。然後針對每個地區,用每種故障數量除以總故障數量,得到每個地區各種故障的故障率。應該除以該地區的總故障率,因爲我們使用協同過濾算法,研究的是各個地區之間故障率的相似度。計算完寫出到文件。
    static class RateReducer extends Reducer<Text, IntWritable, Text, DoubleWritable> {

        String districtCode = null;
        Map<Integer, Integer> map = new HashMap<>();
        double sum = 0D;

        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            districtCode = key.toString();
            for (IntWritable value : values) {
                int faultCode = value.get();
                // 如果map中沒有此值,則放入1,有,則在此基礎加1
                map.merge(faultCode, 1, Integer::sum);
                sum++;
            }

            // 寫出
            for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
                double rate = entry.getValue() / sum;
                Text outKey = new Text(districtCode + "\t" + entry.getKey());
                DoubleWritable value = new DoubleWritable(rate);
                context.write(outKey, value);
            }
            // 初始化值
            map.clear();
            sum = 0;
            districtCode = null;
        }

        @Override
        protected void cleanup(Context context) throws IOException, InterruptedException {

            map.clear();
            sum = 0;
            districtCode = null;
        }
    }
  • 第四步:根據現有故障率,使用mahout協同過濾算法進行故障率預測。
    在處理之前,先使用MemoryIDMigrator把地區編碼轉化成了long,防止模型把數據從string轉爲long,造成前面的0缺失的問題,預測的時候,再把地區編碼從long映射回string。具體代碼如下:
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.impl.model.MemoryIDMigrator;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;

import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author affable
 * @description 故障率預測的協同過濾算法
 * @date 2020-04-21 19:12
 */
public class FaultPredict {

    /**
     * 推薦的個數
     */
    private static final int RECOMMEND_NUM = 18;

    /**
     * 用戶即地區代碼
     * @param args 程序輸入參數
     */
    public static void main(String[] args) throws IOException, TasteException {
        // *******************************處理開始******************************************
        Map<Long, String> faultMap = loadFault();
        // 使用推薦模型之前,對數據的districtCode映射成long類型
        // 防止模型把districtCode轉爲long,出現數據異常
        String filePath = "data/faultRate/part-r-00000";
        File dealFile = new File("data/faultRateDeal.csv");
        MemoryIDMigrator memoryIDMigrator = new MemoryIDMigrator();
        FileWriter dealWriter = new FileWriter(dealFile, true);

        FileReader reader = new FileReader(filePath);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String line = null;
        while ((line = bufferedReader.readLine()) != null) {
            String[] fields = line.split("\t");
            long districtCodeLong = memoryIDMigrator.toLongID(fields[0]);
            memoryIDMigrator.storeMapping(districtCodeLong, fields[0]);
            dealWriter.write(districtCodeLong + "," + fields[1] + "," + fields[2] + "\n");
            dealWriter.flush();
        }
        dealWriter.close();
        bufferedReader.close();
        reader.close();
        // ***********************************處理完成**************************************

        // **********************************模型推薦開始************************************
        // 創建包含用戶評分的協同過濾模型
        FileDataModel dataModel = new FileDataModel(dealFile);
        // 指定使用歐式距離
        UserSimilarity userSimilarity = new EuclideanDistanceSimilarity(dataModel);
        // 指定臨近算法
        // 指定距離最近的一定百分比的用戶作爲鄰居
        // 百分比: 20%
        UserNeighborhood userNeighborhood = new ThresholdUserNeighborhood(0.2, userSimilarity, dataModel);
        // 創建推薦器
        Recommender recommender = new GenericUserBasedRecommender(dataModel, userNeighborhood, userSimilarity);

        // 獲取所有的用戶
        LongPrimitiveIterator userIDIter = dataModel.getUserIDs();
        StringBuilder recommendedRecord = new StringBuilder();
        while (userIDIter.hasNext()) {
            // 獲取針對每個用戶的推薦
            long userId = userIDIter.nextLong();
            List<RecommendedItem> recommendList = recommender.recommend(userId, RECOMMEND_NUM);
            for (RecommendedItem recommendedItem : recommendList) {
                recommendedRecord.append(String.format("%s,%s,%f\n", memoryIDMigrator.toStringID(userId), faultMap.get(recommendedItem.getItemID()), recommendedItem.getValue()));
            }
        }
        // **********************************模型推薦結束************************************

        // 寫出推薦結果到文件
        File recommendFile = new File("data/recommend.csv");
        FileWriter writer = new FileWriter(recommendFile, true);
        writer.write(recommendedRecord.toString());
        writer.flush();
        writer.close();


    }

    /**
     * 加載故障代碼對應的故障類型map
     * @return map
     */
    private static Map<Long, String> loadFault() {
        Map<Long, String> faultMap = new HashMap<>(18);
        faultMap.put(1L, "開機故障");
        faultMap.put(2L, "LCD顯示故障");
        faultMap.put(3L, "按鍵故障");
        faultMap.put(4L, "通話故障");
        faultMap.put(5L, "網絡故障");
        faultMap.put(6L, "燈故障");
        faultMap.put(7L, "藍牙機故障");
        faultMap.put(8L, "不讀卡");
        faultMap.put(9L, "電池故障");
        faultMap.put(10L, "拍照故障");
        faultMap.put(11L, "觸屏故障");
        faultMap.put(12L, "振動故障");
        faultMap.put(13L, "MP3、收音故障");
        faultMap.put(14L, "喇叭故障");
        faultMap.put(15L, "充電故障");
        faultMap.put(16L, "GPRS故障");
        faultMap.put(17L, "外觀故障");
        faultMap.put(18L, "其他故障");

        return faultMap;
    }

}

如有需要完整代碼,請留言。

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