本次作业分三个任务。
第一个是使用决策树预测隐形眼镜类型。数据集是非常经典的隐形眼镜数据集,使用时数据集是txt文本文件的形式。要求是手撕算法不能够直接调库。
第二个是根据用户采集的 WiFi信息采用决策树预测用户所在房间。这个数据集应该是老师以前在学校搞的一个手机的log。在后面编程作业中也经常使用到这个数据集。可以调库。
第三个是预测泰坦尼克幸存者。这个似乎也是很有名的一个项目,给出数据集用决策树的方法进行预测,得出结果上传到网站上和全球玩家比试看谁的预测精度高。
网站地址为https://www.kaggle.com/c/titanic/leaderboard
,可能需要魔法,数据集可以在网站上下载
首先上图,这个是西瓜书里面对决策树算法的伪码表示,写的还是比较清楚的,手撕代码也就是把这个伪码翻译成自己的代码。
训练集D中包含了 x i x_{i} xi和 y i y_{i} yi,分别是一个样本的属性值向量和样本对应的标签,属性集A也就只保存了当前循环中还没有用来生成决策树的属性的名字。决策树生成的算法中最核心的就是从A中划分最优属性。
采用最简单的方法:信息增益。选择信息增益最大的属性进行划分。
G a i n ( D , a ) = E n t ( D ) − ∑ v = 1 V D v D E n t ( D v ) Gain(D,a)=Ent(D)-\sum_{v=1}^{V} \frac{D^{v} }{D} Ent(D^{v}) Gain(D,a)=Ent(D)−∑v=1VDDvEnt(Dv)
E n t ( D ) = − ∑ k = 1 ∣ y ∣ p k l o g 2 p k Ent(D)=-\sum_{k=1}^{|y|} p_{k} log_{2}p_{k} Ent(D)=−∑k=1∣y∣pklog2pk
所以就能写出计算熵和计算信息增益的代码
def getent(Data): # 计算熵
datanum = len(Data)
ent = 0
dic = {}
for i in range(datanum):
if Data[i][-1] not in dic:
dic[Data[i][-1]] = 1
else:
dic[Data[i][-1]] += 1
for key in dic:
p = dic[key] / datanum
ent -= p * log(p, 2)
return ent
def getbestattr(Data):
# 计算maxGain(D,a)
attrnum = len(Data[0])
datanum = len(Data)
ent = getent(Data)
maxgain = 0
bestattr = None
for i in range(attrnum-1):
attrlist = [da[i] for da in Data]
dir = {}
for attr in attrlist:
if attr not in dir:
dir[attr] = 1
else:
dir[attr] += 1
attrs = dir.keys()
entattr = 0
for attr in attrs:
subdata = splitdata(Data, i, attr)
p = dir[attr]/datanum
entattr += p * getent(subdata)
gain = ent - entattr
if gain > maxgain:
maxgain = gain
bestattr = i
return bestattr
核心部分就这么多,剩下的都是读取txt文件生成数组或向量、数组、字典计数这些杂事
最终把伪码翻译成自己的代码
def treegenerate(Data, attr):
classlist = [da[-1] for da in Data]
if classlist.count(classlist[0]) == len(classlist):return classlist[0] # d中样本全属于同一类别C,返回叶节点
if not attr or len(Data[0]) == 1:return maxnumlist(classlist) # 返回叶节点
splitattr = getbestattr(Data) # 通过计算最大Gain得到的属性在Data[i]中的序号
attrname = attr[splitattr] # 获取属性名字
tree = {attrname: {}}
del attr[splitattr]
attrlist = [da[splitattr] for da in Data]
attrvs = set(attrlist)
for attrv in attrvs:
subdata = splitdata(Data, splitattr, attrv)
if subdata:tree[attrname][attrv] = treegenerate(subdata, copy.deepcopy(attr)) # 生成分支
else:tree[attrname][attrv] = maxnumlist(classlist) # Dv为空则叶节点标记为D中样本最多的类
return tree
数据集的每一条内容都包含了BSSIDLabel,RSSLabel,RoomLabel,SSIDLabel,finLabel
一个BSSIDLabel就是AP的一个MAC地址。
RSSLabel是信号强度大小,是一个连续值
RoomLabel是房间号,用来预测
SSIDLabel是WiFi名称,没啥用
finLabel是指纹,finLabel标号相同,表示这部分BSSID在同一时刻被采集到;我们将在同一时刻采集的所有BSSID及其相应RSS构成的矢量称为一个指纹 f i = [ B S S I D 1 : R S S 1 , B S S I D 2 : R S S 2 , . . . , R o o m L a b e l ] f_{i}=[BSSID_{1}:RSS_{1},BSSID_{2}:RSS_{2},...,RoomLabel] fi=[BSSID1:RSS1,BSSID2:RSS2,...,RoomLabel];由于BSSID的RSS在不同位置大小不同,因此指纹可以唯一的标识一个位置。
数据集是csv文件,需要用pandas库进行读取。
读取完数据之后,用pandas库的groughby函数将相同finLabel的作为一组。
训练不需要手撕,直接用sklearn库中函数即可
# 处理原始数据
def dataprocess(fins, bssid):
x, y = [], []
for finlabel, v in fins:
# print(finlabel, len(list(v['BSSIDLabel'])))
# finlabellist = list(v['BSSIDLabel'])
# print(v['RSSLabel'])
finlabellist = list(v['BSSIDLabel'])
rsslabellist = list(v['RSSLabel'])
# fi = [0] * bssnums
fi = [-100] * bssnums
for bss in bssid:
if bss in finlabellist:
# fi[bssid.index(bss)] = 1
fi[bssid.index(bss)] = rsslabellist[finlabellist.index(bss)]
x.append(fi)
roomlabel = list(v['RoomLabel'])
# print(len(roomlabel))
y.append(roomlabel[0])
return x, y
if __name__ == "__main__":
# 读取与数据预处理
traindata_ori = pd.read_csv('TrainDT.csv', encoding='gbk') # utf-8编码读取会报错
testdata_ori = pd.read_csv('TrainDT.csv', encoding='gbk')
# print(testdata_ori.info())
# 感觉ssr的数值没有什么用,判断的时候直接用finLabel进行判断
# 补全缺失的ssr,设置值为100
# imp = SimpleImputer(missing_values=np.nan, strategy='constant', fill_value=-100)
# testdata = pd.DataFrame(imp.fit_transform(testdata_ori))
# traindata = pd.DataFrame(imp.fit_transform(traindata_ori))
# traindata.columns = traindata_ori.columns
# testdata.columns = testdata_ori.columns # 对齐
traindata = traindata_ori[['BSSIDLabel', 'RSSLabel', 'RoomLabel', 'finLabel']]
testdata = testdata_ori[['BSSIDLabel', 'RSSLabel', 'RoomLabel', 'finLabel']]
trainfins = traindata.groupby('finLabel') # 相同指纹作为一组
testfins = testdata.groupby('finLabel')
print(len(testfins))
bssid = list(set(traindata['BSSIDLabel']))
bssnums = len(bssid)
# x为bssid向量组,大小为bssnum*数据量,一条数据大小是bssnum*1。该条数据有存在的bss就为1
# y为room向量组,x中一条数据对应y的一个数据
trainx, trainy = dataprocess(trainfins, bssid)
testx, testy = dataprocess(testfins, bssid)
# 调库构建决策树并进行预测
wifitree = tree.DecisionTreeClassifier()
wifitree.fit(trainx, trainy)
predict = list(wifitree.predict(testx))
print('实际结果', np.array(testy))
print('预测结果', np.array(predict))
print('精度', accuracy_score(testy, predict))
可以直接复用任务2的代码,没什么多说的。主要就是选取属性不同会导致结果不同。
变量名 | 变量解释 |
---|---|
PassengerId | 乘客编号 |
Survived | 乘客是否存活(0=NO, 1=Yes) |
Pclass | 乘客所在的船舱等级(1=1st, 2=2nd, 3=3rd; 1st = Upper, 2nd = Middle, 3rd = Lower) |
Name | 乘客姓名 |
Sex | 乘客性别 |
Age | 乘客年龄(Age is fractional if less than 1. If the age is estimated, is it in the form of xx.5)[有缺失值] |
SibSp | 乘客的兄弟姐妹/配偶数量 |
Parch | 乘客的父母/子女数量 |
Ticket | 票的编号 |
Fare | 票价 |
Cabin | 座位号[有缺失值] |
Embarked | 乘客登船码头[有缺失值] |
实际训练时不可能把所有的属性都用上,必选的属性有Pclass,Sex,Age。其他属性自由搭配。我达到的最高准确率是0.78229 。
写blog写的比我实验报告还长我是没有想到的,可能是因为贴了代码?
我打算代码再打包上传一遍。
放假在家,效率好低啊,写一篇用了我一下午,其他时间都在刷手机。。。