家電故障備件儲備預測分析
本例來源於《Hadoop大數據分析與挖掘實戰》第十二章家電故障備件儲備預測分析。
數據集:請留言,我會私發。
-
問題:針對手機數據,要求從服務商代碼中提取出地區編碼,對每個地區進行故障率的預測,從而做到備件的提前儲備。
-
問題分析:從問題描述可以看出,可以看作是對每個地區進行故障的推薦,地區編碼作爲用戶id,故障代碼作爲項目id,現有數據集中故障率作爲評分,使用協同過濾算法進行故障的推薦。
-
難點:
- 需要把故障描述轉化爲故障代碼,故障描述是用戶對故障的描述,所以各種各樣,很難做到統一,需要提取其中的關鍵字。
- 現有數據集中故障率的計算。
- 在計算的時候,地區編碼、故障代碼都是字符串,但是協同過濾算法會把他們作爲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;
}
- 第三步: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;
}
}
如有需要完整代碼,請留言。