决策树(Decision Tree)
引言
决策树是机器学习中一种经典的分类与回归算法。决策树模型呈树形结构,在分类问题中,可以认为是if-then
规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。决策树学习通常包括3个步骤:特征选择、决策树生成、决策树剪枝 。决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,其基本流程遵循简单且直观的"分而治之"策略。
决策树的生成是一个递归的过程,在决策树基本算法中,有三种情形会导致递归的返回:(1)当前结点包含的样本全部属于同一类别,无需划分;(2)当前属性集为空,或是所有样本在属性上取值相同,无法划分;(3)当前结点包含的样本集合为空,不能划分。
原理
基本概念
决策树将特征空间划分为互不相交的单元,在每个单元定义一个类的概率分布,构成一个条件概率分布。
- 决策树的每一条路径对应于划分中的一个基本单元。
- 设某个单元 S \mathbb{S} S内部有 N S {N_S} NS个样本点,则可以定义一个条件概率分布 p ( y ∣ x ) , x ∈ S p(y|x),x\in \mathbb{S} p(y∣x),x∈S,则单元的类别可以表示为 a r g m a x y p ( y ∣ x ) , x ∈ s arg{max}_yp(y|x),x\in \mathbb{s} argmaxyp(y∣x),x∈s
信息增益
在信息论和概率统计中,熵(Entropy)表示随机变量不确定的度量。设X
是一个取有限个值的离散随机变量,其概率分布为:
P X = x i = p i , i = 1 , 2... , n P{X=x_i}=p_i,i=1,2...,n PX=xi=pi,i=1,2...,n
则随机变量的熵定义为:
H ( X ) = − ∑ i = 1 n p i log p i H(X)=-\sum_{i=1}^n p_i \log p_i H(X)=−i=1∑npilogpi
若 p i = 0 p_i = 0 pi=0,则定义 0 log 0 = 0 0\log 0 =0 0log0=0。通常,式中的对数以2为底或以自然数e为底。由定义可知,熵只依赖于x的分布,与x的取值无关,所以可以将x的熵记作:H(p)
H ( p ) = − ∑ i = 1 n p i log p i H(p) = - \sum_{i=1}^n p_i \log p_i H(p)=−i=1∑npilogpi
熵越大,随机变量的不确定性就越大。
当随机变量只取0、1两个值的时候,分布就变成
P ( x = 1 ) = p , P ( X = 0 ) = 1 − p , 0 ≤ p ≤ 1 P(x=1)=p,P(X=0)=1-p,0\leq p \leq 1 P(x=1)=p,P(X=0)=1−p,0≤p≤1
熵就变为:
H ( p ) = − p log p − ( 1 − p ) l o g ( 1 − p ) H(p) = -p\log p -(1-p)log(1-p) H(p)=−plogp−(1−p)log(1−p)
这个时候和二分类的损失函数就类似了,此时如果p为1或者0,即可以确定类别为正类/负类时,熵最小,p为0.5,即正类负类置信度相同时,熵最大
信息增益 表示得知特征x的信息而使得类y的信息的不确定性减少的程度。
特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)
一般地,熵 H ( Y ) H(Y) H(Y)与条件熵之差 H ( Y ∣ X ) H(Y|X) H(Y∣X)称为互信息。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。
信息增益依赖于特征,不同的特征往往具有不同的信息增益,信息增益大的特征具有更强的分类能力。
根据信息增益准则的特征选取方法是:对训练数据集D,计算其每个特征的信息增益,并比较大小,选择信息增益最大的特征**
信息增益比
信息增益的大小是相对于数据集而言的,并没有绝对意义。在训练数据集的经验熵大的时候,信息增益值会偏大,反之则会偏小。使用信息增益比可以进行校正。
信息增益比定义为:
g R ( D , A ) = g ( D , A ) H ( D ) g_R(D,A)=\frac{g(D,A)}{H(D)} gR(D,A)=H(D)g(D,A)
剪枝
决策树对于训练数据的分类很准确,但是对于测试数据的分类却没有那么准确,会出现过拟合。解决这个问题的方法是考虑决策树的复杂度,对已生成的决策树进行简化(即进行剪枝)。
决策树的剪枝往往通过极小化决策树的整体损失函数或代价函数来实现。设树T的叶结点个数为 ∣ T ∣ |T| ∣T∣,t是树T的叶结点,该叶结点有 N t N_t Nt个样本,其中k类的样本点有 N t k N_{tk} Ntk个, H t ( T ) H_t(T) Ht(T)为叶结点上的经验熵,则损失函数可以定义为
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ C_{\alpha}(T) = \sum_{t=1}^{|T|}N_tH_t(T)+\alpha|T| Cα(T)=t=1∑∣T∣NtHt(T)+α∣T∣
其中经验熵为:
H t ( T ) = − ∑ k N t k N t log N t k N t H_t(T) = - \sum_k \frac{N_{tk}}{N_t}\log {N_tk}{N_t} Ht(T)=−k∑NtNtklogNtkNt
在损失函数中,记:
C ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 K N t k log N t k N t C(T) = \sum_{t=1}^{|T|} N_tH_t(T) = -\sum_{t=1}^{|T|}\sum_{k=1}^K N_{tk}\log\frac{N_{tk}}{N_t} C(T)=t=1∑∣T∣NtHt(T)=−t=1∑∣T∣k=1∑KNtklogNtNtk
这是就有
C α ( T ) = C ( T ) + α ∣ T ∣ C_\alpha(T) = C(T)+\alpha|T| Cα(T)=C(T)+α∣T∣
上式中,C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度,|T|表示模型复杂度,也称正则项,参数a>0控制两者之间的影响。较大的a促使选择较简单的模型(树),较小的a促使选择较复杂的模型(树)。a=0意味着只考虑模型与训练数据的拟合程度,不考虑模型的复杂度。
剪枝就是当 α \alpha α确定时,选择损失函数最小的模型。当 α \alpha α值确定时,子树越大,往往与训练数据的拟合越好,模型复杂度也会越高;相反,子树越小,模型的复杂度就越低,与训练数据的拟合越不好。
决策树生成只考虑了通过提高信息增益(或信息增益比)对训练数据进行更好的拟合。而决策树剪枝通过优化损失函数还考虑了减小模型复杂度。决策树生成学习局部的模型,而决策树剪枝学习整体的模型。
代码
下面对titanic幸存者以及西瓜数据集2.0分别用sklearn库和python代码进行决策树的构建使用。
python
#使用SKlearn决策树进行titanic幸存者预测
# 首先导入需要的包
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.feature_extraction import DictVectorizer
import pandas as pd
def descision():
"""
决策树对泰坦尼克号进行预测生死
:return:None
"""
titan = pd.read_csv("https://hbiostat.org/data/repo/titanic.txt")
# 处理数据,找出特征值和目标值
x = titan[['pclass', 'age', 'sex']]
y = titan['survived']
#print(x)
# 缺失值处理
x['age'].fillna(x['age'].mean(), inplace=True)
# 分割数据集到训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25)
# 进行处理(特征工程)
dict = DictVectorizer(sparse=False)
x_train = dict.fit_transform(x_train.to_dict(orient="records"))
#print(dict.get_feature_names_out())
x_test = dict.transform(x_test.to_dict(orient="records"))
#print(x_train)
# 用决策树进行预测
dec = DecisionTreeClassifier()
dec.fit(x_train, y_train)
# 预测准确率
print("预测的准确率为:", dec.score(x_test, y_test))
# 导出决策树的结构
export_graphviz(dec, out_file="./tree.dot", feature_names=['age', 'pclass=1st', 'pclass=2nd', 'pclass=3rd', 'sex=female', 'sex=male'])
if __name__=="__main__":
descision()
预测的准确率为: 0.7993920972644377
python针对西瓜数据集构建决策树
python
## python针对西瓜数据集2.0手搓决策树
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
#计算信息熵
def cal_information_entropy(data):
data_label = data.iloc[:,-1]
label_class =data_label.value_counts() #总共有多少类
Ent = 0
for k in label_class.keys():
p_k = label_class[k]/len(data_label)
Ent += -p_k*np.log2(p_k)
return Ent
#计算给定数据属性a的信息增益
def cal_information_gain(data, a):
Ent = cal_information_entropy(data)
feature_class = data[a].value_counts() #特征有多少种可能
gain = 0
for v in feature_class.keys():
weight = feature_class[v]/data.shape[0]
Ent_v = cal_information_entropy(data.loc[data[a] == v])
gain += weight*Ent_v
return Ent - gain
#获取标签最多的那一类
def get_most_label(data):
data_label = data.iloc[:,-1]
label_sort = data_label.value_counts(sort=True)
return label_sort.keys()[0]
#挑选最优特征,即信息增益最大的特征
def get_best_feature(data):
features = data.columns[:-1]
res = {}
for a in features:
temp = cal_information_gain(data, a)
res[a] = temp
res = sorted(res.items(),key=lambda x:x[1],reverse=True)
return res[0][0]
##将数据转化为(属性值:数据)的元组形式返回,并删除之前的特征列
def drop_exist_feature(data, best_feature):
attr = pd.unique(data[best_feature])
new_data = [(nd, data[data[best_feature] == nd]) for nd in attr]
new_data = [(n[0], n[1].drop([best_feature], axis=1)) for n in new_data]
return new_data
#创建决策树
def create_tree(data):
data_label = data.iloc[:,-1]
if len(data_label.value_counts()) == 1: #只有一类
return data_label.values[0]
if all(len(data[i].value_counts()) == 1 for i in data.iloc[:,:-1].columns): #所有数据的特征值一样,选样本最多的类作为分类结果
return get_most_label(data)
best_feature = get_best_feature(data) #根据信息增益得到的最优划分特征
Tree = {best_feature:{}} #用字典形式存储决策树
exist_vals = pd.unique(data[best_feature]) #当前数据下最佳特征的取值
if len(exist_vals) != len(column_count[best_feature]): #如果特征的取值相比于原来的少了
no_exist_attr = set(column_count[best_feature]) - set(exist_vals) #少的那些特征
for no_feat in no_exist_attr:
Tree[best_feature][no_feat] = get_most_label(data) #缺失的特征分类为当前类别最多的
for item in drop_exist_feature(data,best_feature): #根据特征值的不同递归创建决策树
Tree[best_feature][item[0]] = create_tree(item[1])
return Tree
#{'纹理': {'清晰': {'根蒂': {'蜷缩': 1, '稍蜷': {'色泽': {'青绿': 1, '乌黑': {'触感': {'硬滑': 1, '软粘': 0}}}}, '硬挺': 0}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}
def predict(Tree , test_data):
first_feature = list(Tree.keys())[0]
second_dict = Tree[first_feature]
input_first = test_data.get(first_feature)
input_value = second_dict[input_first]
if isinstance(input_value , dict): #判断分支还是不是字典
class_label = predict(input_value, test_data)
else:
class_label = input_value
return class_label
if __name__ == '__main__':
#读取数据
data = pd.read_csv('./data/西瓜数据集2.0.txt')
data = data.iloc[:,1:]
#统计每个特征的取值情况作为全局变量
column_count = dict([(ds, list(pd.unique(data[ds]))) for ds in data.iloc[:, :-1].columns])
#创建决策树
dicision_Tree = create_tree(data)
print(dicision_Tree)
#测试数据
test_data_1 = {'色泽':'青绿','根蒂':'蜷缩','敲声':'浊响','纹理':'稍糊','脐部':'凹陷','触感':'硬滑'}
test_data_2 = {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑'}
result = predict(dicision_Tree,test_data_1)
print('分类结果为'+('好瓜'if result == '是' else '坏瓜'))
{'纹理': {'清晰': {'根蒂': {'蜷缩': '是', '稍蜷': {'色泽': {'浅白': '是', '青绿': '是', '乌黑': {'触感': {'硬滑': '是', '软粘': '否'}}}}, '硬挺': '否'}}, '稍糊': {'触感': {'软粘': '是', '硬滑': '否'}}, '模糊': '否'}}
分类结果为坏瓜
sklearn针对西瓜数据集构建决策树
python
#使用SKlearn决策树进行西瓜数据集决策树构建预测
# 首先导入需要的包
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree
import pandas as pd
from matplotlib import pyplot as plt
def descision():
data = pd.read_csv("./data/西瓜数据集2.0.txt")
feature_names = ['色泽','根蒂','敲声','纹理','脐部','触感']
# 处理数据,找出特征值和目标值
x = data[["色泽","根蒂","敲声","纹理","脐部","触感"]].copy()
y = data['好瓜'].copy()
x = x.copy()
for i in ["色泽","根蒂","敲声","纹理","脐部","触感"]:
for j in range(len(x)):
if(x[i][j] == "青绿" or x[i][j] == "蜷缩" or data[i][j] == "浊响" \
or x[i][j] == "清晰" or x[i][j] == "凹陷" or x[i][j] == "硬滑"):
x[i][j] = 1
elif(x[i][j] == "乌黑" or x[i][j] == "稍蜷" or data[i][j] == "沉闷" \
or x[i][j] == "稍糊" or x[i][j] == "稍凹" or x[i][j] == "软粘"):
x[i][j] = 2
else:
x[i][j] = 3
y = y.copy()
for i in range(len(y)):
if(y[i] == "是"):
y[i] = int(1)
else:
y[i] = int(0)
X = pd.DataFrame(x).astype(int)
y = pd.DataFrame(y).astype(int)
# 分割数据集到训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3 , random_state = 1234)
#print(X_train)
#print(y_train)
# 创建决策树分类器
clf = DecisionTreeClassifier(criterion = 'entropy',random_state = 1234)
# 训练模型
clf.fit(X_train, y_train)
#预测测试结果
y_pred = clf.predict(X_test)
print(y_pred)
# 预测准确率
#print(X_test)
#print(y_test)
print("预测的准确率为:", clf.score(X_test, y_test))
# 可视化决策树
font = {'family' : 'SimHei',
'weight' : 'bold',
'size' : '4'}
plt.rc('font', **font)
fig = plt.figure(figsize=(25,20))
_ = tree.plot_tree(
clf,
feature_names=feature_names,
class_names=["好瓜","坏瓜"],
filled=True
)
plt.show()
# Save picture
#fig.savefig("decistion_tree.png")
if __name__=="__main__":
descision()
[1 0 1 0 0 1]
预测的准确率为: 0.8333333333333334
决策树剪枝
python
import pandas as pd
import numpy as np
##将数据转化为(属性值:数据)的元组形式返回,并删除之前的特征列
def drop_exist_feature(data, best_feature):
attr = pd.unique(data[best_feature])
new_data = [(nd, data[data[best_feature] == nd]) for nd in attr]
new_data = [(n[0], n[1].drop([best_feature], axis=1)) for n in new_data]
return new_data
# 预测单条数据
def predict(Tree , test_data):
first_feature = list(Tree.keys())[0]
second_dict = Tree[first_feature]
input_first = test_data.get(first_feature)
input_value = second_dict[input_first]
if isinstance(input_value , dict): #判断分支还是不是字典
class_label = predict(input_value, test_data)
else:
class_label = input_value
return class_label
#测试很多案例,话返回准确率
def predict_more(Tree, test_data, test_label):
cnt = 0
#计算如果该节点不剪枝的准确率
for i in range(len(test_data)):
after_data = test_data.reset_index().loc[i].to_dict()
pred = predict(Tree, after_data)
if pred == test_label[i]:
cnt += 1
return cnt / len(test_label)
#用于预测节点剪枝后的预测正确数
def equalNums(label, featPreLabel):
res = 0
for l in label:
if l == featPreLabel:
res += 1
return res
# 后剪枝
def post_prunning(tree , test_data , test_label , names):
newTree = tree.copy() #copy是浅拷贝
names = np.asarray(names)
# 取决策节点的名称 即特征的名称
featName = list(tree.keys())[0]
# 取特征的列
featCol = np.argwhere(names == featName)[0][0]
names = np.delete(names, [featCol]) #删掉使用过的特征
newTree[featName] = tree[featName].copy() #取值
featValueDict = newTree[featName] #当前特征下面的取值情况
featPreLabel = featValueDict.pop("prun_label") #如果当前节点剪枝的话是什么标签,并删除_vpdl
# 分割测试数据 如果有数据 则进行测试或递归调用:
split_data = drop_exist_feature(test_data,featName) #删除该特征,按照该特征的取值重新划分数据
split_data = dict(split_data)
for featValue in featValueDict.keys(): #每个特征的值
if type(featValueDict[featValue]) == dict: #如果下一层还是字典,说明还是子树
split_data_feature = split_data[featValue] #特征某个取值的数据,如"脐部"特征值为"凹陷"的数据
split_data_lable = split_data[featValue].iloc[:, -1].values
# 递归到下一个节点
newTree[featName][featValue] = post_prunning(featValueDict[featValue],split_data_feature,split_data_lable,split_data_feature.columns)
# 根据准确率判断是否剪枝,注意这里的准确率是到达该节点数据预测正确的准确率,而不是整体数据集的准确率
# 因为在修改当前节点时,走到其他节点的数据的预测结果是不变的,所以只需要计算走到当前节点的数据预测对了没有即可
ratioPreDivision = equalNums(test_label, featPreLabel) / test_label.size #判断测试集的数据如果剪枝的准确率
#计算如果该节点不剪枝的准确率
ratioAfterDivision = predict_more(newTree, test_data, test_label)
if ratioAfterDivision < ratioPreDivision:
newTree = featPreLabel # 返回剪枝结果,其实也就是走到当前节点的数据最多的那一类
return newTree
if __name__ == '__main__':
#读取数据
train_data = pd.read_csv('./data/train_data.csv')
test_data = pd.read_csv('./data/test_data.csv')
test_data_label = test_data.iloc[:, -1].values
names = test_data.columns
dicision_Tree = {"脐部": {"prun_label": 1
, '凹陷': {'色泽':{"prun_label": 1, '青绿': 1, '乌黑': 1, '浅白': 0}}
, '稍凹': {'根蒂':{"prun_label": 1
, '稍蜷': {'色泽': {"prun_label": 1
, '青绿': 1
, '乌黑': {'纹理': {"prun_label": 1
, '稍糊': 1, '清晰': 0, '模糊': 1}}
, '浅白': 1}}
, '蜷缩': 0
, '硬挺': 1}}
, '平坦': 0}}
print('剪枝前的决策树:')
print(dicision_Tree)
print('剪枝前的测试集准确率: {}'.format(predict_more(dicision_Tree, test_data, test_data_label)))
print('-'*20 + '剪枝' + '-'*20)
new_tree = post_prunning(dicision_Tree,test_data , test_data_label , names)
print('剪枝后的决策树:')
print(new_tree)
print('剪枝后的测试集准确率: {}'.format(predict_more(new_tree, test_data, test_data_label)))
剪枝前的决策树:
{'脐部': {'prun_label': 1, '凹陷': {'色泽': {'prun_label': 1, '青绿': 1, '乌黑': 1, '浅白': 0}}, '稍凹': {'根蒂': {'prun_label': 1, '稍蜷': {'色泽': {'prun_label': 1, '青绿': 1, '乌黑': {'纹理': {'prun_label': 1, '稍糊': 1, '清晰': 0, '模糊': 1}}, '浅白': 1}}, '蜷缩': 0, '硬挺': 1}}, '平坦': 0}}
剪枝前的测试集准确率: 0.42857142857142855
--------------------剪枝--------------------
剪枝后的决策树:
{'脐部': {'凹陷': 1, '稍凹': {'根蒂': {'稍蜷': {'色泽': {'青绿': 1, '乌黑': 1, '浅白': 1}}, '蜷缩': 0, '硬挺': 1}}, '平坦': 0}}
剪枝后的测试集准确率: 0.7142857142857143
python
#西瓜数据集2.0 (可直接复制为txt)
编号,色泽,根蒂,敲声,纹理,脐部,触感,好瓜
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,是
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,是
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,是
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,是
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,是
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,是
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,是
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,是
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,否
10,青绿,硬挺,清脆,清晰,平坦,软粘,否
11,浅白,硬挺,清脆,模糊,平坦,硬滑,否
12,浅白,蜷缩,浊响,模糊,平坦,软粘,否
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,否
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,否
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,否
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,否
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,否]
#西瓜数据集3.0 (可直接复制为txt)
Color,Pedicle,Stroke,Texture,Umbilicus,Touch,Density,Sugar content,Good melon
0,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.460,是
1,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是
2,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是
3,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是
4,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是
5,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是
6,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是
7,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是
8,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否
9,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否
10,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否
11,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否
12,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否
13,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否
14,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.360,0.370,否
15,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.042,0.042,否
16,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.103,0.103,否
#剪枝数据集
#train (csv格式)
色泽,根蒂,敲声,纹理,脐部,触感,好瓜
青绿,蜷缩,浊响,清晰,凹陷,硬滑,1
乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,1
乌黑,蜷缩,浊响,清晰,凹陷,硬滑,1
青绿,稍蜷,浊响,清晰,稍凹,软粘,1
乌黑,稍蜷,浊响,稍糊,稍凹,软粘,1
青绿,硬挺,清脆,清晰,平坦,软粘,0
浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0
乌黑,稍蜷,浊响,清晰,稍凹,软粘,0
浅白,蜷缩,浊响,模糊,平坦,硬滑,0
青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0
#test (csv格式)
色泽,根蒂,敲声,纹理,脐部,触感,好瓜
青绿,蜷缩,沉闷,清晰,凹陷,硬滑,1
浅白,蜷缩,浊响,清晰,凹陷,硬滑,1
乌黑,稍蜷,浊响,清晰,稍凹,硬滑,1
乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0
浅白,硬挺,清脆,模糊,平坦,硬滑,0
浅白,蜷缩,浊响,模糊,平坦,软粘,0
青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0