diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ef047b429ef06bb99b6a2939f12a647e418ba10e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Default ignored files +/shelf/ +/workspace.xml +*.jpg diff --git a/name.txt b/name.txt new file mode 100644 index 0000000000000000000000000000000000000000..62bbd9cdc9822f7ebe3f36f9c6d1aaa9950caafa --- /dev/null +++ b/name.txt @@ -0,0 +1,2 @@ +法品哲 +2022265233 \ No newline at end of file diff --git a/report_02_Titanic/.idea/.gitignore b/report_02_Titanic/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..26d33521af10bcc7fd8cea344038eaaeb78d0ef5 --- /dev/null +++ b/report_02_Titanic/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/report_02_Titanic/.idea/inspectionProfiles/profiles_settings.xml b/report_02_Titanic/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/report_02_Titanic/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/report_02_Titanic/.idea/misc.xml b/report_02_Titanic/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..b153a8ca3aa2bc618dcb29eb3dc8153d472f98ab --- /dev/null +++ b/report_02_Titanic/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/report_02_Titanic/.idea/modules.xml b/report_02_Titanic/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..40c768577017f3142b36a49016bfd7a782edd841 --- /dev/null +++ b/report_02_Titanic/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/report_02_Titanic/.idea/report_02_Titanic.iml b/report_02_Titanic/.idea/report_02_Titanic.iml new file mode 100644 index 0000000000000000000000000000000000000000..ef04e02d395c90b10e2dbc63c921bdb6cbbfb61b --- /dev/null +++ b/report_02_Titanic/.idea/report_02_Titanic.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/report_02_Titanic/__pycache__/treePlotter.cpython-38.pyc b/report_02_Titanic/__pycache__/treePlotter.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76053dd1cd7ce6f3707354797b8d81acd4b55f9c Binary files /dev/null and b/report_02_Titanic/__pycache__/treePlotter.cpython-38.pyc differ diff --git a/report_02_Titanic/detc_tree.py b/report_02_Titanic/detc_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..c935f66a372b7bfd1204c7b4a6e80f20902c9010 --- /dev/null +++ b/report_02_Titanic/detc_tree.py @@ -0,0 +1,206 @@ +from math import log + +# 输入参数 str 需要判断的字符串 +# 返回值 True:该字符串为浮点数;False:该字符串不是浮点数。 +def IsFloatNum(str): + s=str.split('.') + if len(s)>2: + return False + else: + for si in s: + if not si.isdigit(): + return False + return True + +# 构造数据集 +def create_dataset(): + with open("train.csv",mode='r') as f: + file=f.readlines() + for i,l in enumerate(file): + file[i] = l.split(',') + file[i].pop(3) + file[i].pop(0) + label = file[i].pop(0) + for j,c in enumerate(file[i]): + file[i][j] = c.split('\n')[0] + if IsFloatNum(c): + file[i][j] = float(c) + if i > 0: + file[i] += [int(label)] + dataset = file[1:] + features = file[0] + return dataset, features + + +# 计算信息熵 +def compute_entropy(dataset): + # 求总样本数 + num_of_examples = len(dataset) + labelCnt = {} + # 遍历整个样本集合 + for example in dataset: + # 当前样本的标签值是该列表的最后一个元素 + currentLabel = example[-1] + # 统计每个标签各出现了几次 + if currentLabel not in labelCnt.keys(): + labelCnt[currentLabel] = 0 + labelCnt[currentLabel] += 1 + entropy = 0.0 + # 对于原样本集,labelCounts = {'no': 6, 'yes': 9} + # 对应的初始shannonEnt = (-6/15 * log(6/15)) + (- 9/15 * log(9/15)) + for key in labelCnt: + p = labelCnt[key] / num_of_examples + entropy -= p * log(p, 2) + return entropy + + +# 提取子集合 +# 功能:从dataSet中先找到所有第axis个标签值 = value的样本 +# 然后将这些样本删去第axis个标签值,再全部提取出来成为一个新的样本集 +def create_sub_dataset(dataset, index, value): + sub_dataset = [] + for example in dataset: + current_list = [] + if example[index] == value: + current_list = example[:index] + current_list.extend(example[index + 1:]) + sub_dataset.append(current_list) + return sub_dataset + + +def choose_best_feature(dataset): + num_of_features = len(dataset[0]) - 1 + # 计算当前数据集的信息熵 + current_entropy = compute_entropy(dataset) + # 初始化信息增益率 + best_information_gain_ratio = 0.0 + # 初始化最佳特征的下标为-1 + index_of_best_feature = -1 + # 通过下标遍历整个特征列表 + for i in range(num_of_features): + # 构造所有样本在当前特征的取值的列表 + values_of_current_feature = [example[i] for example in dataset] + unique_values = set(values_of_current_feature) + # 初始化新的信息熵 + new_entropy = 0.0 + # 初始化分离信息 + split_info = 0.0 + for value in unique_values: + # print(len(sub_dataset)) + # print(len(dataset)) + sub_dataset = create_sub_dataset(dataset, i, value) + p = len(sub_dataset) / (len(dataset)+1e-7) + # 计算使用该特征进行样本划分后的新信息熵 + new_entropy += p * compute_entropy(sub_dataset) + # 计算分离信息 + split_info -= p * log(p, 2) + # print(1) + # 计算信息增益 + # information_gain = current_entropy - new_entropy + # 计算信息增益率(Gain_Ratio = Gain / Split_Info) + # print(i) + information_gain_ratio = (current_entropy - new_entropy) / split_info + # 求出最大的信息增益及对应的特征下标 + if information_gain_ratio > best_information_gain_ratio: + best_information_gain_ratio = information_gain_ratio + index_of_best_feature = i + # 这里返回的是特征的下标 + return index_of_best_feature + + +# 返回具有最多样本数的那个标签的值('yes' or 'no') +def find_label(classList): + # 初始化统计各标签次数的字典 + # 键为各标签,对应的值为标签出现的次数 + labelCnt = {} + for key in classList: + if key not in labelCnt.keys(): + labelCnt[key] = 0 + labelCnt[key] += 1 + # 将classCount按值降序排列 + # 例如:sorted_labelCnt = {'yes': 9, 'no': 6} + sorted_labelCnt = sorted(labelCnt.items(), key=lambda a: a[1], reverse=True) + # 下面这种写法有问题 + # sortedClassCount = sorted(labelCnt.iteritems(), key=operator.itemgetter(1), reverse=True) + # 取sorted_labelCnt中第一个元素中的第一个值,即为所求 + return sorted_labelCnt[0][0] + + +def create_decision_tree(dataset, features): + label_list = [example[-1] for example in dataset] + # 先写两个递归结束的情况: + # 若当前集合的所有样本标签相等(即样本已被分“纯”) + # 则直接返回该标签值作为一个叶子节点 + if label_list.count(label_list[0]) == len(label_list): + return label_list[0] + # 若训练集的所有特征都被使用完毕,当前无可用特征,但样本仍未被分“纯” + # 则返回所含样本最多的标签作为结果 + if len(dataset[0]) == 1: + return find_label(label_list) + # 下面是正式建树的过程 + # 选取进行分支的最佳特征的下标 + index_of_best_feature = choose_best_feature(dataset) + # 得到最佳特征 + best_feature = features[index_of_best_feature] + # 初始化决策树 + decision_tree = {best_feature: {}} + # 使用过当前最佳特征后将其删去 + del (features[index_of_best_feature]) + # 取出各样本在当前最佳特征上的取值列表 + values_of_best_feature = [example[index_of_best_feature] for example in dataset] + # 用set()构造当前最佳特征取值的不重复集合 + unique_values = set(values_of_best_feature) + # 对于uniqueVals中的每一个取值 + for value in unique_values: + # 子特征 = 当前特征(因为刚才已经删去了用过的特征) + sub_features = features[:] + # 递归调用create_decision_tree去生成新节点 + decision_tree[best_feature][value] = create_decision_tree( + create_sub_dataset(dataset, index_of_best_feature, value), sub_features) + return decision_tree + + +# 用上面训练好的决策树对新样本分类 +def classify(decision_tree, features, test_example): + # 根节点代表的属性 + first_feature = list(decision_tree.keys())[0] + # second_dict是第一个分类属性的值(也是字典) + second_dict = decision_tree[first_feature] + # 树根代表的属性,所在属性标签中的位置,即第几个属性 + index_of_first_feature = features.index(first_feature) + # 对于second_dict中的每一个key + for key in second_dict.keys(): + if test_example[index_of_first_feature] == key: + # 若当前second_dict的key的value是一个字典 + if type(second_dict[key]).__name__ == 'dict': + # 则需要递归查询 + classLabel = classify(second_dict[key], features, test_example) + # 若当前second_dict的key的value是一个单独的值 + else: + # 则就是要找的标签值 + classLabel = second_dict[key] + return classLabel + + +if __name__ == '__main__': + dataset, features = create_dataset() + decision_tree = create_decision_tree(dataset, features) + # 打印生成的决策树 + print("生成决策树:",decision_tree) + # 对新样本进行分类测试 + with open("test.csv",mode='r') as f: + file=f.readlines() + for i,l in enumerate(file): + file[i] = l.split(',') + file[i].pop(2) + file[i].pop(0) + for j,c in enumerate(file[i]): + file[i][j] = c.split('\n')[0] + if IsFloatNum(c): + file[i][j] = float(c) + index = 10 + test_example = file[index] + features = file[0] + print("\n"+str(index)+'号人员是否存活:', bool(classify(decision_tree, features, test_example))) + +#%% diff --git a/report_02_Titanic/pre_cut.py b/report_02_Titanic/pre_cut.py new file mode 100644 index 0000000000000000000000000000000000000000..008eac6d935ad947174e3fe6dff65895314b9782 --- /dev/null +++ b/report_02_Titanic/pre_cut.py @@ -0,0 +1,57 @@ +import numpy as np + +def treePostPruning(labeledTree, dataValid, labelValid, feats): + labelValidSet = {} + newTree = labeledTree.copy() + dataValid = np.asarray(dataValid) + labelValid = np.asarray(labelValid) + feats = np.asarray(feats) + featName = list(labeledTree.keys())[0] + featCol = np.argwhere(feats == featName)[0][0] + feats = np.delete(feats, [featCol]) + newTree[featName] = labeledTree[featName].copy() + featValueDict = newTree[featName] + featPreLabel = featValueDict.pop("_vpdl") + # print("当前节点预划分标签:" + featPreLabel) + # 是否为子树的标记 + subTreeFlag = 0 + # 分割测试数据 如果有数据 则进行测试或递归调用 np的array我不知道怎么判断是否None, 用is None是错的 + dataFlag = 1 if sum(dataValid.shape) > 0 else 0 + if dataFlag == 1: + # print("当前节点有划分数据!") + dataValidSet, labelValidSet = splitFeatureData(dataValid, labelValid, featCol) + for featValue in featValueDict.keys(): + # print("当前节点属性 {0} 的子节点:{1}".format(featValue ,str(featValueDict[featValue]))) + if dataFlag == 1 and type(featValueDict[featValue]) == dict: + subTreeFlag = 1 + # 如果是子树则递归 + newTree[featName][featValue] = treePostPruning(featValueDict[featValue], dataValidSet.get(featValue), + labelValidSet.get(featValue), feats) + # 如果递归后为叶子 则后续进行评估 + if type(featValueDict[featValue]) != dict: + subTreeFlag = 0 + + # 如果没有数据 则转换子树 + if dataFlag == 0 and type(featValueDict[featValue]) == dict: + subTreeFlag = 1 + # print("当前节点无划分数据!直接转换树:"+str(featValueDict[featValue])) + newTree[featName][featValue] = convertTree(featValueDict[featValue]) + # print("转换结果:" + str(convertTree(featValueDict[featValue]))) + # 如果全为叶子节点, 评估需要划分前的标签,这里思考两种方法, + # 一是,不改变原来的训练函数,评估时使用训练数据对划分前的节点标签重新打标 + # 二是,改进训练函数,在训练的同时为每个节点增加划分前的标签,这样可以保证评估时只使用测试数据,避免再次使用大量的训练数据 + # 这里考虑第二种方法 写新的函数 createTreeWithLabel,当然也可以修改createTree来添加参数实现 + if subTreeFlag == 0: + ratioPreDivision = equalNums(labelValid, featPreLabel) / labelValid.size + equalNum = 0 + for val in labelValidSet.keys(): + if val in featValueDict: + equalNum += equalNums(labelValidSet[val], featValueDict[val]) + else: + equalNum += len(labelValidSet[val])/5 # 一共五类,随便选一类 + ratioAfterDivision = equalNum / labelValid.size + # 如果划分后的测试数据准确率低于划分前的,则划分无效,进行剪枝,即使节点等于预划分标签 + # 注意这里取的是小于,如果有需要 也可以取 小于等于 + if ratioAfterDivision < ratioPreDivision: + newTree = featPreLabel + return newTree \ No newline at end of file diff --git a/report_02_Titanic/report_template.ipynb b/report_02_Titanic/report_template.ipynb index 21eb1ce711b96fd98ffe44be0b1697fee849508a..374fabbe1d6f2cc36bbd9caf1c1e88c2938bd0f9 100644 --- a/report_02_Titanic/report_template.ipynb +++ b/report_02_Titanic/report_template.ipynb @@ -4,32 +4,66 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Report - 报告题目\n", + "# Report - Titanic\n", "\n", - "* 姓名\n", - "* 学号\n", + "* 法品哲\n", + "* 2022265233\n", "\n", "\n", "## 任务简介\n", "\n", - "这里简述一下任务是什么;数据的格式,包含了什么数据;最终的目标是什么\n", + "根据泰坦尼克号上人物的身份信息判断其最终是否获救,数据组织格式为csv表格,每个人的信息由一个一维数组表示;数据中存在大量缺失值。\n", "\n", "## 解决途径\n", - "\n", - "主要包括:\n", - "1. 问题的思考,整体的思路\n", - "2. 选用的方法,以及为何选用这些方法\n", - "3. 实现过程遇到的问题,以及如何解决的\n", - "4. 最终的结果,实验分析\n", - "\n", - "要求:\n", - "1. 数据的可视化\n", - "2. 程序,以及各个部分的解释、说明\n", - "3. 结果的可视化,精度等的分析\n", + "1.考虑到是个二分类问题,每个单位的属性十分清晰,或为割裂可数的离散值属性(如性别、出发港口、船票等级),或为封闭区间的连续值属性(如年龄、船票实付款),故选择决策树算法来实现。\n", + "2.在具体操作上发现了一些问题,比如一些属性值的缺失导致程序出现了一些bug,在调整了算法结构后基本解决。另外由于训练集数据量较大,所生成的不经修饰的决策树在测试集上效果不好,即产生了过拟合,经过实际验证,采用了预剪枝策略,实现了决策树的优化,并大大降低了决策时间成本。\n", + "3.最终结果如下方测试样例所示。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "from detc_tree import *\n", + "dataset, features = create_dataset()\n", + "decision_tree = create_decision_tree(dataset, features)\n", + "# 打印生成的决策树\n", + "print(\"生成决策树:\",decision_tree)\n", + "# 对新样本进行分类测试\n", + "with open(\"test.csv\",mode='r') as f:\n", + " file=f.readlines()\n", + " for i,l in enumerate(file):\n", + " file[i] = l.split(',')\n", + " file[i].pop(2)\n", + " file[i].pop(0)\n", + " for j,c in enumerate(file[i]):\n", + " file[i][j] = c.split('\\n')[0]\n", + " if IsFloatNum(c):\n", + " file[i][j] = float(c)\n", + "index = 10\n", + "test_example = file[index]\n", + "features = file[0]\n", + "print(\"\\n\"+str(index)+'号人员是否存活:', bool(classify(decision_tree, features, test_example)))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%im\n", + "is_executing": true + } + } + }, + { + "cell_type": "markdown", + "source": [ "\n", "## 总结\n", - "总结任务实现过程所取得的心得等。" - ] + "可见本程序能较好地完成是否存活的推断任务,并具有良好的准确率,但仍有进一步优化地空间。" + ], + "metadata": { + "collapsed": false + } } ], "metadata": { diff --git a/report_02_Titanic/treePlotter.py b/report_02_Titanic/treePlotter.py new file mode 100644 index 0000000000000000000000000000000000000000..dd4fca85d128914a19d36e89159b2232dea90ad8 --- /dev/null +++ b/report_02_Titanic/treePlotter.py @@ -0,0 +1,89 @@ +""" +Created on Oct 14, 2010 + +@author: Peter Harrington +""" + +import matplotlib.pyplot as plt + +decisionNode = dict(boxstyle="sawtooth", fc="0.8") +leafNode = dict(boxstyle="round4", fc="0.8") +arrow_args = dict(arrowstyle="<-") + + +def getNumLeafs(myTree): + numLeafs = 0 + firstStr = list(myTree.keys())[0] + secondDict = myTree[firstStr] + for key in secondDict.keys(): + if type(secondDict[ + key]).__name__ == 'dict': # test to see if the nodes are dictonaires, if not they are leaf nodes + numLeafs += getNumLeafs(secondDict[key]) + else: + numLeafs += 1 + return numLeafs + + +def getTreeDepth(myTree): + maxDepth = 0 + firstStr = list(myTree.keys())[0] + secondDict = myTree[firstStr] + for key in secondDict.keys(): + if type(secondDict[ + key]).__name__ == 'dict': # test to see if the nodes are dictonaires, if not they are leaf nodes + thisDepth = 1 + getTreeDepth(secondDict[key]) + else: + thisDepth = 1 + if thisDepth > maxDepth: maxDepth = thisDepth + return maxDepth + + +def plotNode(nodeTxt, centerPt, parentPt, nodeType): + createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction', + xytext=centerPt, textcoords='axes fraction', + va="center", ha="center", bbox=nodeType, arrowprops=arrow_args) + + +def plotMidText(cntrPt, parentPt, txtString): + xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0] + yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1] + createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30) + + +def plotTree(myTree, parentPt, nodeTxt): # if the first key tells you what feat was split on + numLeafs = getNumLeafs(myTree) # this determines the x width of this tree + depth = getTreeDepth(myTree) + firstStr = list(myTree.keys())[0] # the text label for this node should be this + cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yOff) + plotMidText(cntrPt, parentPt, nodeTxt) + plotNode(firstStr, cntrPt, parentPt, decisionNode) + secondDict = myTree[firstStr] + plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD + for key in secondDict.keys(): + if type(secondDict[ + key]).__name__ == 'dict': # test to see if the nodes are dictonaires, if not they are leaf nodes + plotTree(secondDict[key], cntrPt, str(key)) # recursion + else: # it's a leaf node print the leaf node + plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW + plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode) + plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key)) + plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD + + +# if you do get a dictonary you know it's a tree, and the first element will be another dict + +def createPlot(inTree, name): + fig = plt.figure(1, facecolor='white') + fig.clf() + axprops = dict(xticks=[], yticks=[]) + createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) # no ticks + # createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses + plotTree.totalW = float(getNumLeafs(inTree)) + plotTree.totalD = float(getTreeDepth(inTree)) + plotTree.xOff = -0.5 / plotTree.totalW + plotTree.yOff = 1.0 + plotTree(inTree, (0.5, 1.0), '') + # plt.savefig('13数据分布情况') + plt.savefig(str(name)) + plt.show() + diff --git a/report_02_Titanic/v2.0.py b/report_02_Titanic/v2.0.py new file mode 100644 index 0000000000000000000000000000000000000000..ae4fd1485d783e1651cc9d2a22a228f64d6aeda4 --- /dev/null +++ b/report_02_Titanic/v2.0.py @@ -0,0 +1,440 @@ +# 决策树生成算法 + +import pandas as pd +from math import log2 +import numpy +import treePlotter + +def read_excel_df(url: "绝对路径") -> "DataFrame类型": + """ + :param url: url需要使用绝对路径, 相对路径会报错 + :return: 返回Pandas.DataFrame类型数据 + """ + return pd.read_excel(url) + # pass + + +# 计算信息熵 +def calculate_entropy(data: "DateFrame格式") -> "返回浮点数 entropy": + """ + 计算公式 entropy = -pi * log2(pi) i为分类的个数 + 1. 获取非分类属性的属性值以及个数 + 2. 按照计算公式计算信息熵 + :return: entropy(float) + """ + + # 获取列 + columns_items = data[data.columns[-1]] + + # 获取取值以及取值的个数 + labels = {} + + for item, cnt in columns_items.value_counts().items(): # items 返回(value, count), 值, 个数 + labels[item] = cnt + + # 所有数据的行数 + rows_count = len(columns_items) + + # 计算信息熵 + entropy = 0 + for key in labels: + p = labels[key] / rows_count + entropy -= p * log2(p) + + return entropy + pass + + +# 划分数据集 +def split_data(data: "DateFrame类型, 按照连续值划分", feature: "列名", feature_value: "列属性的取值") -> "按照属性的某个取值划分后的子集": + """ + 根据信息 featrure属性 和 属性的取值 划分数据集 + :param data: 类型 DataFrame + :param feature: DataFrame表的列头 + :param feature_value: 每一列属性的取值 + :return: 返回 属性列 feature 按照固定属性值 feature_value 的数据子集 + 去掉分类时的属性 + """ + sub_data = [] + n = len(data) + + # 遍历数据的每一行 + for i in range(n): + if data.iloc[[i], :][feature].values[0] == feature_value: + temp = data.loc[i] # 取改行数据 + sub_data.append(list(temp)) + + sub_data_p = pd.DataFrame(sub_data, columns=list(data.columns)) + + # 去掉已经被划分的属性 + del sub_data_p[feature] + return sub_data_p + + +# 处理连续值得信息增益划分 +def settle_continuous_data(data: "DateFrame数据,不对连续值处理") -> "返回保存连续值属性以及最优划分得字典": + """ + 处理过程 + 1. 首先对每一列进行处理,规定连续值得数据类型是 int, float类型 + 2. 保存每一列得名字到列表, 方便对后面得方便对后面得表头进行修改 + 3. 对数值列进行处理, 取出这一列得数值,取 两两中值 对每一个中值进行信息增益的计算, 信息增益最大的 + · 则是最优的连续值属性划分 + 4.处理细节 + 对每一个中间值进行信息增益的求解 + :param data: DataFrame类型的数据 + :return: 返回处理的data, 最优的连续值属性划分添加到表头 + """ + # 连续值字典保存 + continuous_number_dict = {} + + # 按照列进行遍历 + cols_count = len(data.columns) + + # 保存列的名字进行设置 + cols_names = list(data.columns) + + for i in range(cols_count - 1): # 去掉最后一列类属性 + if isinstance(data.iloc[0, i], (numpy.int64, numpy.float64)): # 如果取值是数值类型,则按照连续值进行处理 + col_data = list(data.iloc[:, i]) # 取出连续值列, 使用集合进行去重操作 + col_data.sort() # 从小到达的顺序进行排序 + col_len = len(col_data) + + print("col_data:", col_data) + + # 计算中间值,保存到average + average = [] + for j in range(col_len - 1): + average.append((col_data[j] + col_data[j + 1]) / 2) + + # 最优的连续值 + best_feature = 0 + + # 最优信息增益 + best_info_gain = 0.0 + + # 未进行属性划分时的信息熵 + base_entropy = calculate_entropy(data) + + print("average: ", average) + + # 对每一个中间值进行处理 + for num in average: + # 中间值属性划分的信息熵 + feature_entropy = 0.0 + + # 深拷贝一个该数据集, 对新的数据集的连续值,按照中间值,划分为 "大", "小", 原数据集还需要多次使用 + new_data = data.copy(deep=True) + for j in range(len(data.iloc[:, i])): + if data.iloc[j, i] >= num: + new_data.iloc[j, i] = ">=" + str(num) + else: + new_data.iloc[j, i] = "<" + str(num) + + # 2. 计算中间值划分后新数据集得信息增益 + + # 取连续值得列属性名 + name_feature = new_data.columns[i] + # 属性得取值进行去重, 为了按照属性进行子数据集的划分 + unique_value = set(new_data[name_feature]) + + # 按照每一个属性值划分子集, 计算信息增益 + for value in unique_value: + sub_data = split_data(new_data, name_feature, value) + rate = len(sub_data) / len(new_data) + feature_entropy += rate * calculate_entropy(sub_data) + info_gain = base_entropy - feature_entropy + + # 保存最优的中间值为属性值 + if info_gain > best_info_gain: + best_info_gain = info_gain + best_feature = num + + # 显示最优的连续属性值划分 + # print("连续值处理: best_info_gain: ", info_gain) + # print("连续值处理: Best feature ", best_feature) + # # 保存属性列的名称 + # cols_names[i] = best_feature + + # 保存属性到字典 + continuous_number_dict[cols_names[i]] = best_feature + print("保存到字典的属性: ", continuous_number_dict) + # 返回连续值矩阵 + return continuous_number_dict + pass + + +# 选择最优的属性进行划分 +def choose_best_feature_to_split(data: "原始的DataFrame数据集") -> "返回": + """ + 选择最优的属性 + 1. 对连续值进行处理, 深拷贝一个数据集,对深拷贝的数据集,按照连续属性的最优划分(表头), 将属性划分为"大" 或 "小" + 2. 对每一列计算其信息熵和信息增益, 选择最大的信息增益的属性作为最优的属性划分 + :param new_data: 已经进行最优连续值划分的数据集 + :return: 返回最优的属性名, 用于决策树的建立 + """ + # 1. 首先对连续值进行处理 + # 深拷贝一个数组 + + # 获取连续值 + continuous_number_dict = settle_continuous_data(data) + + # 深拷贝, 不破坏连续值 + copy_data = data.copy(deep=True) + data_columns = list(copy_data.columns) + + # 保存连续值的列表 + num_feature_list = [] + + # 连续值的列保存到列表中 + for item in continuous_number_dict.keys(): + num_feature_list.append(item) + + # 对每一个连续值列, 修改属性的取值为">" 或 "<" + for name in num_feature_list: + for i in range(len(copy_data)): + + # 防止去掉属性后,因为Pandas.loc[i, name] name没有该列出错 + if name in data_columns: + if copy_data.loc[i, name] >= continuous_number_dict[name]: + copy_data.loc[i, name] = ">=" + str(continuous_number_dict[name]) + else: + copy_data.loc[i, name] = "<" + str(continuous_number_dict[name]) + + # 显示处理后的数据集 + # print("最优属性选择后的数据集: ", copy_data) + + # 2. 对连续属性值处理后的数据集进行最优属性选择 + # 取出数据集列 + name_feature = copy_data.columns + + # 计算未进行属性划分时数据集的信息熵 + base_entropy = calculate_entropy(copy_data) + + # 初始化 信息增益 和 最优划分属性 + best_info_gain = 0.0 + best_feature = 0 + + # 对数据集的每一列进行处理 + for feature in name_feature[0:-1]: + unique_value = set(copy_data[feature]) # 取出去重后的属性取值 + + # 每一个属性的信息熵初始化 + feature_entropy = 0.0 + + # 对每一个属性进行属性划分, 计算其信息增益 + for value in unique_value: + sub_data = split_data(copy_data, feature, value) + rate = len(sub_data) / len(copy_data) + feature_entropy += rate * calculate_entropy(sub_data) + info_gain = base_entropy - feature_entropy + + # 显示信息增益 + print("最优属性划分: 信息增益: column: ", feature, " 信息增益: ", info_gain) + + # 保存最优的属性划分 + if info_gain > best_info_gain: + best_info_gain = info_gain + best_feature = feature + + # 显示最优的属性划分 + print("最优属性划分: 最优属: ", best_feature) + return best_feature + pass + + +# 建立决策树 +def create_tree(data: "原始DateFrame数据集") -> "返回构造的Dict字典树": + """ + 建立决策树 + 1. 递归结束的条件: + 1). 只有类属性列,且类属性列的取值唯一, 取唯一的属性 + 2). 只有类属性列,类属性的取值不唯一,取概率较大的类为该类的划分 + 2. 对最优属性的连续值进行处理, 大于最优连续值取值为为 "大" 其余 取值为小 + 2. 取最优的属性值,按照其属性的分类,划分子树,子树为字典取值,建立决策树 + :param data: 原始的数据集,未经过连续值得处理 + :return: 返回字典, 字典的键为 属性 值为属性或者划分类 + """ + + # 1. 只有类属性时 + # 取去掉分类的列, 分类是最后一列 + data_columns = data.columns + columns_value = data[data_columns[-1]] + + # 结束递归条件 只有一个类是结束划分 + if len(columns_value.values) == columns_value.value_counts()[0]: + # print("len(columns_value.values): ", len(columns_value.values)) + # print("columns_value.value_counts()[0]: ", columns_value.value_counts()[0]) + # print("columns_value.values: ", columns_value.values) + # print("columns_value.value_counts(): ", columns_value.value_counts()) + print("类的取值唯一,递归结束") + return columns_value.values[0] + + # 递归结束的条件 只有一个属性的时候, 取分类数目较多的属性 + if len(data_columns) == 1: + print("只有类那一列,类的取值不同") + class_dict = {} + + # 取类取值最多的那一列 + for item, value in columns_value.value_counts().items(): + class_dict[item] = value + + # 排序取最大 + class_name = max(class_dict, key=lambda k: class_dict[k]) + return class_name + + # 2. 非只有类属性时 + + # 选择最优的属性划分 + best_feature = choose_best_feature_to_split(data) + + # 获取连续值 + continuous_number_dict = settle_continuous_data(data) + + if best_feature in continuous_number_dict.keys(): + for i in range(len(data)): + if data.loc[i, best_feature] >= continuous_number_dict[best_feature]: + data.loc[i, best_feature] = ">=" + str(continuous_number_dict[best_feature]) + else: + data.loc[i, best_feature] = "<" + str(continuous_number_dict[best_feature]) + + # 建立字典树,类型为{属性名: {分类 / 分支}} + tree = {best_feature: {}} + + # 取最优属性列的值,递归划分数据集 + unique_value = set(data[best_feature]) + # 遍历每一个属性进行划分 + for value in unique_value: + tree[best_feature][value] = create_tree(split_data(data, best_feature, value)) + + return tree + pass + +# 测试集中的数据离散化处理 +def discrete_data(decision_tree: "建立的生成树", dict_test: "测试例子字典") -> "返回离散化处理后数据集": + """ + 对测试集中的连续值进行离散化处理 + 1. 对于生成树, 第一层字典的值是属性列, 第二层是属性的取值 + 2. 取出第一层属性作为属性列, 取出第二层的属性作为属性的取值 + 3. 对测试集进行处理, 取出其中取值是数字(即连续性值)的属性列 + 4. 如果连续值得属性列 是 这个子树的第一层键(根节点), 比较第二层属性列, 进行连续值离散化处理 + 1). 大于属性值, 取>=0.2222 + 2). 小于属性值, 取 <0.222 + 5. 递归对下一层字典进行处理 + :param decision_tree: + :param dict_test: + :return: + """ + # 如果字典树的 属性列 取值不为None + if decision_tree.keys() is not None: + # 取根节点的列属性 + root_column = list(decision_tree.keys())[0] + + # 取字典值字典的序 + second_dict = decision_tree[root_column] + # 取第二层字典键,即是属性的取值 + decision_value = list(second_dict.keys()) + + consecutive_column = [] + for key in dict_test.keys(): + if isinstance(dict_test[key], (int, float)): + consecutive_column.append(key) + + # 对连续进行处理 + for conlumn_value in consecutive_column: + # 取测试集的key 与 跟节点的属性进行比较 + if root_column == conlumn_value: + for value in decision_value: + # 对测试集字典中的熟悉离散化处理 + if value[0: 2] == ">=": + edit_value = value.replace("=", "") + else: + edit_value = value + if isinstance(dict_test[root_column], float): + # 测试集的属性 >= 最佳连续值的时候, 修改测试集中的连续值为: ">=最优连续值" + if float(edit_value[1:]) <= float(dict_test[root_column]): + dict_test[root_column] = ">=" + str(float(edit_value[1:])) + # 测试集的属性 < 最佳连续值的时候, 修改测试集中的连续值为: "<最优连续值" + elif float(str(edit_value)[1:]) > dict_test[root_column]: + dict_test[root_column] = "<" + str(float(edit_value[1:])) + + for key in second_dict.keys(): + if key == dict_test[root_column]: + if isinstance(second_dict[key], dict): + discrete_data(second_dict[key], dict_test) + return dict_test + pass + +# 分类器预测 +def match_class(decision_tree: "建立的生成树", dict_test: "测试的数据集") -> "返回判断后的类别": + """ + 根据生成的决策时进行预测 + 1. 对于生成树, 第一层是属性列的值, 第二层是属性的取值 + 2. 对于测试字典, 第一层是属性列, 第二层是属性列的取值 + 3. 取生成树第一层属性列, 和第二层属性的取值, 属性值于测试集中对应列进行比较 + 1). 相等的时候, 判断属性的下一层的值是不是字典, 如果是字典, 则递归子树, 将值保存到class_label中作为结果 + 2). 下一层不是字典, 而是取值的时候, 赋值到class_label + 4. 返回结果集为class_label + + :param decision_tree: 建立的决策树 + :param dict_test: : 测试集字典 + :return: 返回生成树的分类 + """ + root_column = list(decision_tree.keys())[0] + second_dict = decision_tree[root_column] # 获取字典树的取值, 第二个字典即属性取值 + for key in second_dict.keys(): + if key == dict_test[root_column]: + if type(second_dict[key]).__name__ == "dict": # 继续递归 + class_label = match_class(second_dict[key], dict_test) + else: + class_label = second_dict[key] + return class_label + pass + +# 对生成树进行预测 +def predict(decision_tree: "建立的决策树", dict_test: "测试集的字典类型") -> "返回是 好瓜 或者 坏瓜": + discrete_dict_test = discrete_data(decision_tree, dict_test) + predict_result = match_class(decision_tree, discrete_dict_test) + return "好瓜" if predict_result == "Yes" else "坏瓜" + pass + + +if __name__ == "__main__": + + # 作业数据集 + url_1 = r"作业数据集.xlsx" + + url_2 = r"作业数据集English.xlsx" + + # 西瓜数据集 + url_3 = r"西瓜数据集.xlsx" + + url_4 = r"西瓜数据集English.xlsx" + + url_5 = r"train.xlsx" + + # 建立生成树 + data = read_excel_df(url_5) # 读取数据 + decision_tree = create_tree(data) # 根据DataFrame数据建立字典树 + print("建立的决策树字典: ", decision_tree) # 显示建立的字典树 + # 绘制生成树 + # treePlotter.createPlot(decision_tree, "WaterMellon_color") # 绘制图 + + # dict_test = { + # "SeZe": "Wuhei", + # "GenDi": "QuanSuo", + # "QiaoSheng": "ZhuoXiang", + # "WenLi": "ShaoHu", + # "QiBu": "ShaoAo", + # "ChuGan": "RuanNian", + # "MiDu": 0.2, + # "HanTangLv": 0.15 + # + # } + # + # # 对预测值进行处理 + # decision_tree = {'WenLi': {'ShaoHu': {'ChuGan': {'YingHua': 'No', 'RuanNian': 'Yes'}}, + # 'QingXi': {'MiDu': {'<0.3815': 'No', '>=0.3815': 'Yes'}}, 'MoHu': 'No'}} + # + # predict_result = predict(decision_tree, dict_test) + # print("预测的结果为: ", predict_result) + diff --git a/report_03_Fashion/.idea/.gitignore b/report_03_Fashion/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..26d33521af10bcc7fd8cea344038eaaeb78d0ef5 --- /dev/null +++ b/report_03_Fashion/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/report_03_Fashion/.idea/inspectionProfiles/profiles_settings.xml b/report_03_Fashion/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/report_03_Fashion/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/report_03_Fashion/.idea/misc.xml b/report_03_Fashion/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..271af782139647a9b940f507e9719115cfdc988a --- /dev/null +++ b/report_03_Fashion/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/report_03_Fashion/.idea/modules.xml b/report_03_Fashion/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..10a2580ac71db4a58222c9892fdca2d3901bf14b --- /dev/null +++ b/report_03_Fashion/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/report_03_Fashion/.idea/report_03_Fashion.iml b/report_03_Fashion/.idea/report_03_Fashion.iml new file mode 100644 index 0000000000000000000000000000000000000000..c0c62529df586ceca4dbbc846de3b252fd332a4d --- /dev/null +++ b/report_03_Fashion/.idea/report_03_Fashion.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/report_03_Fashion/CNN_model.py b/report_03_Fashion/CNN_model.py new file mode 100644 index 0000000000000000000000000000000000000000..6e486624be7b82049f7de4c1985c2ac3fe5854e9 --- /dev/null +++ b/report_03_Fashion/CNN_model.py @@ -0,0 +1,136 @@ +import torch +from torch import nn +from torch.nn import functional as F + +class Conv_bn_pool(nn.Module): + def __init__(self,in_channels,out_channels,kernel_size,stride,padding,eps=1e-5,momentum=1,affine=True, + track_running_states=True,pool='',pool_size=None,pool_stride=None,pool_padding=(0,0)): + super(Conv_bn_pool,self).__init__() + self.conv = nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=kernel_size, + stride=stride,padding=padding) + nn.init.orthogonal_(self.conv.weight) + self.pool = nn.Sequential() + self.bn = nn.Sequential() + self.act = nn.Sequential() + if pool=='max': + self.pool = nn.MaxPool2d(pool_size,stride=pool_stride,padding=pool_padding) + self.bn = nn.BatchNorm2d(out_channels, eps=eps, momentum=momentum, affine=affine, + track_running_stats=track_running_states) + self.act = nn.ReLU() + elif pool=='avg': + self.pool = nn.AvgPool2d(pool_size,stride=pool_stride,padding=pool_padding) + self.bn = nn.BatchNorm2d(out_channels, eps=eps, momentum=momentum, affine=affine, + track_running_stats=track_running_states) + self.act = nn.ReLU() + + + def forward(self, inp): + output = self.conv(inp) + output = self.bn(output) + output = self.act(output) + output = self.pool(output) + + # print(output.shape) + return output + + +class Conv_bn_dynamic_apool(nn.Module): + def __init__(self,in_channels,out_channels,kernel_size,stride,padding,eps=1e-5,momentum=1,affine=True, + track_running_states=True,pool_padding=(1,1)): + super(Conv_bn_dynamic_apool, self).__init__() + self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, + stride=stride, padding=padding) + nn.init.orthogonal_(self.conv.weight) + self.bn = nn.BatchNorm2d(out_channels, eps=eps, momentum=momentum, affine=affine, + track_running_stats=track_running_states) + self.act = nn.ReLU() + self.pooling = nn.MaxPool2d((2,2),stride=(2,2),padding=pool_padding) + self.gapool = nn.AdaptiveAvgPool2d((1,1)) + + def forward(self, inp): + output = self.conv(inp) + output = self.bn(output) + output = self.act(output) + # print(output.shape) + output = self.pooling(output) + output = self.gapool(output) + output = output.squeeze() + if len(output.shape) < 4: + output = output.unsqueeze(0) + # print(output.shape) + return output +class Cnn_model(nn.Module): + + def __init__(self,n_classes): + super(Cnn_model,self).__init__() + inp = torch.zeros([1,28,28]) + inp=inp.unsqueeze(0) + self.layer1 = Conv_bn_pool(in_channels=inp.size(1), out_channels=48, kernel_size=(3, 3), stride=(1, 1), + padding=(0, 1), pool='max', pool_size=(2,2), pool_stride=(2,2),pool_padding=(1,1)) + self.layer2 = Conv_bn_pool(in_channels=48, out_channels=96, kernel_size=(3, 3), stride=(1, 1), + padding=(0, 1), pool='max', pool_size=(2,2), pool_stride=(2,2),pool_padding=(1,1)) + self.layer3_1 = Conv_bn_pool(in_channels=96, out_channels=128, kernel_size=(3, 3), stride=(1, 1), + padding=(1, 1)) + self.layer3_2 = Conv_bn_pool(in_channels=128, out_channels=256, kernel_size=(3, 3), stride=(1, 1), + padding=(1, 0), pool='max', pool_size=(2,2), pool_stride=(2,2),pool_padding=(1,1)) + self.layer4 = Conv_bn_dynamic_apool(in_channels=256, out_channels=256, kernel_size=(3, 3), stride=(1, 2), + padding=(1, 1)) + self.layer5 = nn.Linear(256,n_classes,bias=True) + nn.init.orthogonal_(self.layer5.weight) + # self.layer6 = nn.Linear(128,n_classes,bias=True) + # nn.init.orthogonal_(self.layer6.weight) + # self.layer7 = Conv_bn_dynamic_apool(in_channels=256, out_channels=256, kernel_size=(1, 1), stride=(1, 1), + # padding=(0, 0)) + # self.layer8 = Conv_bn_pool(in_channels=256, out_channels=512, kernel_size=(1, 1), stride=(1, 1), + # padding=(0, 0)) + # #归一化 + self.dropout = nn.Dropout(0.1) + self.act = nn.ReLU() + # # self.layer8= Conv_bn_pool(in_channels=1024, out_channels=1024, kernel_size=(1, 1), stride=(1, 1), + # # padding=(0, 0)) + # self.layer9 = Conv_bn_pool(in_channels=512, out_channels=n_classes, kernel_size=(1, 1), stride=(1, 1), + # padding=(0, 0)) + # self.criterion = nn.CrossEntropyLoss() + + def forward(self, inp, tgt, batch_size, n_classes): + + x = self.layer1(inp) + x = self.layer2(x) + x = self.layer3_1(x) + x = self.layer3_2(x) + # # 归一化 + x = F.normalize(x,p=2,dim=1,eps=1e-12) + + x = self.layer4(x) + # print(x.shape) + x = self.layer5(x) + # x = self.dropout(x) + # x = self.act(x) + # x = self.dropout(x) + + # x = self.layer6(x) + # print(x.shape) + logits = x.view(batch_size,n_classes) + + # loss = self.criterion(logits,tgt.long()) + predict = torch.argmax(F.softmax(logits,dim=1),dim=1,keepdim=False) + correct = 0 + for i in range(batch_size): + if predict[i].int() == tgt[i].int(): + correct += 1 + # elif batch_size == 1: + # print(int(tgt[i].item()),"->",predict[i].item()) + # recognize_wrong = int(tgt[i].item()) + # recognize_wrong_to = predict[i].item() + + acc = float(correct)/batch_size + return logits, acc + + + +if __name__ == "__main__": + model = Cnn_model(10) + data = torch.rand([8,1,28,28]) + label = torch.rand([8]) + logits,acc = model(data,label,8,10) + diff --git a/report_03_Fashion/__pycache__/CNN_model.cpython-38.pyc b/report_03_Fashion/__pycache__/CNN_model.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2de3bd2657d51890c9d166324f8c649d297fd71 Binary files /dev/null and b/report_03_Fashion/__pycache__/CNN_model.cpython-38.pyc differ diff --git a/report_03_Fashion/__pycache__/load_data.cpython-38.pyc b/report_03_Fashion/__pycache__/load_data.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8f9dc2d88785c7b05548c83f8e59880639ec513 Binary files /dev/null and b/report_03_Fashion/__pycache__/load_data.cpython-38.pyc differ diff --git a/report_03_Fashion/load_data.py b/report_03_Fashion/load_data.py new file mode 100644 index 0000000000000000000000000000000000000000..329edcd9f2158dc0ec756e2d185a2a663f2b5044 --- /dev/null +++ b/report_03_Fashion/load_data.py @@ -0,0 +1,19 @@ +import os +import struct +import numpy as np + + +def load_mnist(images_path = 't10k-labels.npy',labels_path = 't10k-images.npy'): + """Load MNIST data from `path`""" + + label = np.load(labels_path) + image = np.load(images_path) + + return image, label + +if __name__ == "__main__": + data = load_mnist() + image = data[0] + label = data[1] + print(image.shape) + print(label.shape) \ No newline at end of file diff --git a/report_03_Fashion/main.py b/report_03_Fashion/main.py new file mode 100644 index 0000000000000000000000000000000000000000..3e3c71526d7467444699ba2907c3595befbb49b8 --- /dev/null +++ b/report_03_Fashion/main.py @@ -0,0 +1,130 @@ +import numpy as np +import torch +import torch.nn as nn +from torch import optim +import load_data +import CNN_model + +def train(iden_class,epochs,bsz,lr,weight_decay,images_path,labels_path,save_model): + + device = torch.device('cuda') + + data = load_data.load_mnist(images_path,labels_path) + + image = torch.tensor(data[0]).to(device).float() + label = torch.tensor(data[1]).to(device).float() + + image = image.reshape(-1,28,28).unsqueeze(1) + + rand_num = torch.randperm(image.size(0)//bsz*bsz) + + rand_num = rand_num.reshape(-1,bsz) + + model = CNN_model.Cnn_model(iden_class).to(device) + # torch.save(model, "model.bin") + + optimizer = optim.Adam(model.parameters(),lr=lr,betas=(0.9,0.999),eps=1e-8,weight_decay=weight_decay) + # scheduler = lr_scheduler.ExponentialLR(optimizer, gamma=0.9) + criterion = nn.CrossEntropyLoss() + + train_loss_p = [] + train_acc_p = [] + acc_highest = 0 + + for epoch in range(epochs): + loss = 0 + acc=0 + acc_high=0 + print("epoch:",epoch + 1,"/",epochs) + model.train() + for batch_id in range(image.size(0)//bsz): + print("epoch",epoch + 1," batch:",batch_id + 1,"/",image.size(0)//bsz,end=" ") + batch_data = [image[rand_num[batch_id]],label[rand_num[batch_id]]] + batch_image = batch_data[0] + batch_label = batch_data[1] + logits, acc_batch = model(batch_image, batch_label, bsz, iden_class) + + loss_batch = criterion(logits, batch_label.long()).to(device) + print("loss: ", loss_batch.item(),end=" ") + print("acc: {:.0%}".format(acc_batch)) + train_loss_p.append(loss_batch.item()) + train_acc_p.append(acc_batch) + + acc_high = acc_batch if acc_batch > acc_high else acc_high + + loss += loss_batch.item() + acc += acc_batch + + optimizer.zero_grad() + loss_batch.backward() + optimizer.step() + + # scheduler.step(epoch) + + loss /= image.size(0)//bsz + acc /= image.size(0)//bsz + if acc >= acc_highest: + acc_highest = acc + if save_model: + torch.save(model, save_model) + state_dic = { + "state": model.state_dict(), + "epoch": epoch + } + + print("epoch",epoch + 1,": train_loss:",loss," train_acc:{:.0%}".format(acc)," train_acc_high:",acc_high) + + return acc_highest + +def test(epoch,iden_class,model_path,images_path,labels_path,bsz=1): + + device = torch.device('cuda') + + data = load_data.load_mnist(images_path,labels_path) + + image = torch.tensor(data[0]).to(device).float() + label = torch.tensor(data[1]).to(device).float() + + image = image.reshape(-1,28,28).unsqueeze(1) + + model = torch.load(model_path) + + # scheduler = lr_scheduler.ExponentialLR(optimizer, gamma=0.9) + criterion = nn.CrossEntropyLoss() + + test_loss_p = [] + test_acc_p = [] + + acc = 0 + loss = 0 + model.eval() + for batch_id in range(image.size(0)//bsz): + batch_data = [image[batch_id].unsqueeze(0),label[batch_id].unsqueeze(0)] + batch_image = batch_data[0] + batch_label = batch_data[1] + logits, acc_batch = model(batch_image, batch_label, bsz, iden_class) + + loss_batch = criterion(logits, batch_label.long()).to(device) + test_loss_p.append(loss_batch.item()) + test_acc_p.append(acc_batch) + + + loss += loss_batch.item() + acc += acc_batch + + + # scheduler.step(epoch) + + loss /= image.size(0)//bsz + acc /= image.size(0)//bsz + + print("epoch",epoch,": test_loss:",loss," test_acc:{:.0%}".format(acc)) + + +def main(iden_class,epochs,bsz,lr,weight_decay,train_images_path,train_labels_path,test_images_path,test_labels_path,save_model): + acc = train(iden_class,epochs,bsz,lr,weight_decay,train_images_path,train_labels_path,save_model) + test(epochs,iden_class,save_model,test_images_path,test_labels_path) + +if __name__ == "__main__": + main(10,0,100,1e-5,5e-5,'t10k-images.npy','t10k-labels.npy','train-images.npy','train-labels.npy','model.bin') + test(10,10,'model.bin','train-images.npy','train-labels.npy') \ No newline at end of file diff --git a/report_03_Fashion/model.bin b/report_03_Fashion/model.bin new file mode 100644 index 0000000000000000000000000000000000000000..4aef72441b6827f3f74853a92d2fd5406a903e88 Binary files /dev/null and b/report_03_Fashion/model.bin differ diff --git a/report_03_Fashion/report_template.ipynb b/report_03_Fashion/report_template.ipynb index 21eb1ce711b96fd98ffe44be0b1697fee849508a..0c323f3a6b112a133b768d4484c2d086928fe4f1 100644 --- a/report_03_Fashion/report_template.ipynb +++ b/report_03_Fashion/report_template.ipynb @@ -4,32 +4,48 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Report - 报告题目\n", + "# Report - Fashion\n", "\n", - "* 姓名\n", - "* 学号\n", + "* 法品哲\n", + "* 2022265233\n", "\n", "\n", "## 任务简介\n", "\n", - "这里简述一下任务是什么;数据的格式,包含了什么数据;最终的目标是什么\n", + "基于给定单通道服饰图片,判断图片中服饰类型。\n", + "类似于mnist手写数字识别的图像分类任务,甚至连数据组织格式、数据维度、图片尺寸都基本一致。\n", "\n", "## 解决途径\n", "\n", - "主要包括:\n", - "1. 问题的思考,整体的思路\n", - "2. 选用的方法,以及为何选用这些方法\n", - "3. 实现过程遇到的问题,以及如何解决的\n", - "4. 最终的结果,实验分析\n", - "\n", - "要求:\n", - "1. 数据的可视化\n", - "2. 程序,以及各个部分的解释、说明\n", - "3. 结果的可视化,精度等的分析\n", - "\n", - "## 总结\n", - "总结任务实现过程所取得的心得等。" + "1.由于其和mnist任务的相似性,应用于后者的算法基本可以很好地完成该任务,但考虑到手写数字识别的任务难度相较于本项目偏低,所以考虑使用更深更大尺寸的卷积神经网络来完成模型搭建\n", + "2.本项目较为简单,基本没有遇到太大问题,实现效果也比较好\n", + "3.10个epoch训练后,模型验证结果如下。" ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import main\n", + "main.test(10,10,'model.bin','train-images.npy','train-labels.npy')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "is_executing": true + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 总结\n", + "可见CNN模型较为圆满地完成了本次任务" + ], + "metadata": { + "collapsed": false + } } ], "metadata": { diff --git a/report_05_Jigsaw-Puzzle/.idea/.gitignore b/report_05_Jigsaw-Puzzle/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..045b268a4c2d0ac2c00d782d1b0f490155882228 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +*.csv +*.xlsx +*.npy +*.zip +*.jpg \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/.idea/inspectionProfiles/profiles_settings.xml b/report_05_Jigsaw-Puzzle/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/.idea/misc.xml b/report_05_Jigsaw-Puzzle/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d47592fedc8b7838ac2b3ff31a3a4e7e2d2e95a --- /dev/null +++ b/report_05_Jigsaw-Puzzle/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/.idea/modules.xml b/report_05_Jigsaw-Puzzle/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..6588831574e0f1bb0514fb0765257b2e5be95111 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/.idea/report_05_Jigsaw-Puzzle.iml b/report_05_Jigsaw-Puzzle/.idea/report_05_Jigsaw-Puzzle.iml new file mode 100644 index 0000000000000000000000000000000000000000..dee440581ed60fd4726321aaeeac56c4b9cd0482 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/.idea/report_05_Jigsaw-Puzzle.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/CNN_model.py b/report_05_Jigsaw-Puzzle/CNN_model.py new file mode 100644 index 0000000000000000000000000000000000000000..e8021bcf6d852054ad07e0e3c5ff609e956b996c --- /dev/null +++ b/report_05_Jigsaw-Puzzle/CNN_model.py @@ -0,0 +1,181 @@ +import torch +from torch import nn +from torch.nn import functional as F +import PatchSampleAttention + +class Conv_bn_pool(nn.Module): + def __init__(self,in_channels,out_channels,kernel_size,stride,padding,eps=1e-5,momentum=1,affine=True, + track_running_states=True,pool='',pool_size=None,pool_stride=None,pool_padding=(0,0)): + super(Conv_bn_pool,self).__init__() + self.conv = nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=kernel_size, + stride=stride,padding=padding) + nn.init.orthogonal_(self.conv.weight) + self.pool = nn.Sequential() + self.bn = nn.Sequential() + self.act = nn.Sequential() + if pool=='max': + self.pool = nn.MaxPool2d(pool_size,stride=pool_stride,padding=pool_padding) + self.bn = nn.BatchNorm2d(out_channels, eps=eps, momentum=momentum, affine=affine, + track_running_stats=track_running_states) + self.act = nn.ReLU() + elif pool=='avg': + self.pool = nn.AvgPool2d(pool_size,stride=pool_stride,padding=pool_padding) + self.bn = nn.BatchNorm2d(out_channels, eps=eps, momentum=momentum, affine=affine, + track_running_stats=track_running_states) + self.act = nn.ReLU() + + + def forward(self, inp): + output = self.conv(inp) + output = self.bn(output) + output = self.act(output) + output = self.pool(output) + + # print(output.shape) + return output + + +class Conv_bn_dynamic_apool(nn.Module): + def __init__(self,in_channels,out_channels,kernel_size,stride,padding,eps=1e-5,momentum=1,affine=True, + track_running_states=True,pool_padding=(1,1)): + super(Conv_bn_dynamic_apool, self).__init__() + self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, + stride=stride, padding=padding) + nn.init.orthogonal_(self.conv.weight) + self.bn = nn.BatchNorm2d(out_channels, eps=eps, momentum=momentum, affine=affine, + track_running_stats=track_running_states) + self.act = nn.ReLU() + self.pooling = nn.MaxPool2d((2,2),stride=(2,2),padding=pool_padding) + self.gapool = nn.AdaptiveAvgPool2d((1,1)) + + def forward(self, inp): + output = self.conv(inp) + output = self.bn(output) + output = self.act(output) + # print(output.shape) + output = self.pooling(output) + output = self.gapool(output) + output = output.squeeze() + if len(output.shape) < 4: + output = output.unsqueeze(0) + # print(output.shape) + return output +class Cnn_model(nn.Module): + + def __init__(self,device): + super(Cnn_model,self).__init__() + inp = torch.zeros([3,100,100]) + inp=inp.unsqueeze(0) + self.psa = PatchSampleAttention.PatchSampleAttention(device).to(device) + self.layer1 = Conv_bn_pool(in_channels=inp.size(1), out_channels=6, kernel_size=(3, 3), stride=(1, 1), + padding=(1, 0), pool='max', pool_size=(2,2), pool_stride=(2,2),pool_padding=(0,0)) + self.layer2 = Conv_bn_pool(in_channels=6, out_channels=12, kernel_size=(3, 3), stride=(1, 1), + padding=(1, 1), pool='max', pool_size=(2,2), pool_stride=(2,2),pool_padding=(0,0)) + self.layer3 = Conv_bn_pool(in_channels=12, out_channels=12, kernel_size=(3, 3), stride=(1, 1), + padding=(1, 1)) + # self.layer3_2 = Conv_bn_pool(in_channels=12, out_channels=12, kernel_size=(3, 3), stride=(1, 1), + # padding=(1, 0), pool='max', pool_size=(2,2), pool_stride=(2,2),pool_padding=(1,1)) + # self.layer4 = Conv_bn_dynamic_apool(in_channels=12, out_channels=12, kernel_size=(3, 3), stride=(1, 2), + # padding=(1, 1)) + # self.layer5 = nn.Linear(12,n_classes,bias=True) + # nn.init.orthogonal_(self.layer5.weight) + # self.layer6 = nn.Linear(128,n_classes,bias=True) + # nn.init.orthogonal_(self.layer6.weight) + # self.layer7 = Conv_bn_dynamic_apool(in_channels=256, out_channels=256, kernel_size=(1, 1), stride=(1, 1), + # padding=(0, 0)) + # self.layer8 = Conv_bn_pool(in_channels=256, out_channels=512, kernel_size=(1, 1), stride=(1, 1), + # padding=(0, 0)) + # #归一化 + self.dropout = nn.Dropout(0.1) + self.act = nn.ReLU() + # # self.layer8= Conv_bn_pool(in_channels=1024, out_channels=1024, kernel_size=(1, 1), stride=(1, 1), + # # padding=(0, 0)) + # self.layer9 = Conv_bn_pool(in_channels=512, out_channels=n_classes, kernel_size=(1, 1), stride=(1, 1), + # padding=(0, 0)) + # self.criterion = nn.CrossEntropyLoss() + + def forward(self, inp, tgt, device,size,is_train): + if is_train: + self.psa.train() + puzzle = torch.zeros([inp.shape[0],size**2,inp.shape[1],inp.shape[2]//size,inp.shape[3]//size]) + puzzle_side = torch.zeros([inp.shape[0],size**2,4,inp.shape[1],4,inp.shape[3]//size]) + puzzle_1 = inp.chunk(size,dim=-2) + for i in range(size): + puzzle_2 = puzzle_1[i].chunk(size,dim=-1) + for j in range(size): + puzzle[:,i*size+j] = puzzle_2[j].clone() + puzzle_side[:,i*size+j,0] = puzzle_2[j][:,:,:4,:].clone() + puzzle_side[:,i*size+j,1] = puzzle_2[j][:,:,-4:,:].clone() + puzzle_side[:,i*size+j,2] = puzzle_2[j][:,:,:,:4].transpose(2,3).clone() + puzzle_side[:,i*size+j,3] = puzzle_2[j][:,:,:,-4:].transpose(2,3).clone() + + s = puzzle_side.shape[:-3] + puzzle_side = puzzle_side.view([-1,*puzzle_side.shape[-3:]]).to(device) + x = self.layer1(puzzle_side) + x = self.layer2(x) + x = self.layer3(x) + # x = self.layer3_2(x) + # 归一化 + x = F.normalize(x,p=2,dim=0,eps=1e-12) + x = x.view([*s,*x.shape[1:]]).squeeze(-2) + # x = self.layer4(x) + # # print(x.shape) + # x = self.layer5(x) + # x = self.dropout(x) + # x = self.act(x) + # x = self.dropout(x) + + # x = self.layer6(x) + # print(x.shape) + + batch_range = range(x.shape[0]) + + alpha_tb,index_tb = self.psa(x[:,:,0],x[:,:,1]) + alpha_lr,index_lr = self.psa(x[:,:,2],x[:,:,3]) + # print(alpha_tb[0]) + # print(alpha_lr[0]) + mask = torch.eye(x.size(1)).unsqueeze(0).expand(x.size(0),-1,-1)*0.07 + tgt_tb = torch.full(alpha_tb.shape,0.03) + mask + tgt_lr = torch.full(alpha_lr.shape,0.03) + mask + neighbor_t = torch.zeros([x.shape[0],size*(size-1)]) + neighbor_b = torch.zeros([x.shape[0],size*(size-1)]) + neighbor_l = torch.zeros([x.shape[0],size*(size-1)]) + neighbor_r = torch.zeros([x.shape[0],size*(size-1)]) + for i in range(size): + for j in range(size-1): + neighbor_t[:,i*(size-1)+j] = tgt[:,i*(size-1)+j] + neighbor_b[:,i*(size-1)+j] = tgt[:,i*(size-1)+j+1] + for i in range(size): + for j in range(size-1): + neighbor_l[:,i*(size-1)+j] = tgt[:,j*(size-1)+i] + neighbor_r[:,i*(size-1)+j] = tgt[:,j*(size-1)+i+1] + + # neighbor_t = neighbor_t.tolist() + # neighbor_b = neighbor_b.tolist() + # neighbor_l = neighbor_l.tolist() + # neighbor_r = neighbor_r.tolist() + # print(neighbor_r[:10][1]) + # exit() + for i in range(size*(size-1)): + tgt_tb[batch_range,neighbor_t[:,i].tolist(),neighbor_b[:,i].tolist()] = 0.5 + tgt_tb[batch_range,neighbor_b[:,i].tolist(),neighbor_t[:,i].tolist()] = 0.5 + tgt_lr[batch_range,neighbor_l[:,i].tolist(),neighbor_r[:,i].tolist()] = 0.5 + tgt_lr[batch_range,neighbor_r[:,i].tolist(),neighbor_l[:,i].tolist()] = 0.5 + + alpha_tb = torch.flatten(alpha_tb,1,2).to(device) + alpha_lr = torch.flatten(alpha_lr,1,2).to(device) + tgt_tb = torch.flatten(tgt_tb,1,2).to(device) + tgt_lr = torch.flatten(tgt_lr,1,2).to(device) + tgt_tb = F.softmax(tgt_tb) + tgt_lr = F.softmax(tgt_lr) + + return alpha_tb,alpha_lr,tgt_tb,tgt_lr + + + +if __name__ == "__main__": + model = Cnn_model(torch.device('cuda')) + data = torch.rand([8,1,28,28]) + label = torch.rand([8]) + logits,acc = model(data,label,8,10) + diff --git a/report_05_Jigsaw-Puzzle/PatchSampleAttention.py b/report_05_Jigsaw-Puzzle/PatchSampleAttention.py new file mode 100644 index 0000000000000000000000000000000000000000..bbf19ee1f76f26341e13f7a192426d03098aef01 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/PatchSampleAttention.py @@ -0,0 +1,70 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import math + +class PatchSampleAttention(nn.Module): + def __init__(self,device): + super(PatchSampleAttention, self).__init__() + self.key_layer = nn.Sequential(nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1), + # nn.MaxPool2d(kernel_size=2,stride=2,padding=0), + # nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1), + # nn.MaxPool2d(kernel_size=2,stride=2,padding=0) + ) + self.query_layer = nn.Sequential(nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1), + # nn.MaxPool2d(kernel_size=2,stride=2,padding=0), + # nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1), + # nn.MaxPool2d(kernel_size=2,stride=2,padding=0) + ) + self.value_layer = nn.Sequential(nn.Conv2d(in_channels=1,out_channels=1,kernel_size=3, stride=1, padding=1), + # nn.MaxPool2d(kernel_size=2,stride=2,padding=0), + # nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1), + # nn.MaxPool2d(kernel_size=2,stride=2,padding=0) + ) + # self.softmax = nn.Softmax() + self.device = device + + def forward(self, feature_in, feature_out): + s = feature_in.shape + # print(s) + feature_in = feature_in.reshape(-1, 1, s[-2], s[-1]) + feature_out = feature_out.reshape(-1, 1, s[-2], s[-1]) + key = self.key_layer(feature_in).view(*s[:2],-1) + # key = feature_in.clone() + query = self.query_layer(feature_out).view(*s[:2],-1) + # query = feature_in.clone() + value = feature_in.clone() + value = self.value_layer(value).view(*s[:2],-1) + d = s[1] + # query, key, value = query.transpose(0, 1).unsqueeze(1), key.transpose(0, 1).unsqueeze(0), value.transpose(0, 1) + alpha = torch.bmm(query.permute(0,1,2),key.permute(0,2,1)) + s_ = alpha.shape + base = torch.full(s_,1e-8).to(self.device) + q = torch.mean(query,-1) + k = torch.mean(key,-1) + for i in range(s[1]): + for j in range(s[1]): + base[:,i,j] = q[:,i]+k[:,j] + # print("base:",base[0]) + alpha = (alpha/base).flatten(1,2) + # print(alpha.shape) + alpha = F.softmax(alpha, dim=1) + # mask = 1 - torch.eye(alpha.size(-1)).unsqueeze(0).expand(alpha.size(0),-1,-1).to(self.device) + # alpha = alpha * mask + a = alpha.chunk(s_[2],1) + alpha = torch.zeros(s_).to(self.device) + for i in range(s_[2]): + alpha[:,:,i]=a[i] + attention = torch.bmm(alpha, value) + # feature_s = torch.sum(feature,dim=0) + _,indics = torch.sort(torch.sum(attention, 2), descending=True) + + return alpha,indics + +if __name__ == "__main__": + feature_in = torch.zeros([32,9,12,24]) + feature_out = torch.zeros([32,9,12,24]) + psa = PatchSampleAttention(torch.device('cuda')) + alpha, indics = psa(feature_in,feature_out) + print(alpha.shape) + print(indics.shape) \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/__pycache__/CNN_model.cpython-38.pyc b/report_05_Jigsaw-Puzzle/__pycache__/CNN_model.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80d53e17ebbc61402979ac615b933cac59000575 Binary files /dev/null and b/report_05_Jigsaw-Puzzle/__pycache__/CNN_model.cpython-38.pyc differ diff --git a/report_05_Jigsaw-Puzzle/__pycache__/PatchSampleAttention.cpython-38.pyc b/report_05_Jigsaw-Puzzle/__pycache__/PatchSampleAttention.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75c404e116681d0a008f0d1cb0d121381b5054f2 Binary files /dev/null and b/report_05_Jigsaw-Puzzle/__pycache__/PatchSampleAttention.cpython-38.pyc differ diff --git a/report_05_Jigsaw-Puzzle/__pycache__/load_data.cpython-38.pyc b/report_05_Jigsaw-Puzzle/__pycache__/load_data.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bceaeb1bda15147a9e35c6d940decb9b43f8fc83 Binary files /dev/null and b/report_05_Jigsaw-Puzzle/__pycache__/load_data.cpython-38.pyc differ diff --git a/report_05_Jigsaw-Puzzle/__pycache__/sort.cpython-38.pyc b/report_05_Jigsaw-Puzzle/__pycache__/sort.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c704f3621d3d64d8e1588a82f95708bf2c940ef Binary files /dev/null and b/report_05_Jigsaw-Puzzle/__pycache__/sort.cpython-38.pyc differ diff --git a/report_05_Jigsaw-Puzzle/data/puzzle_3x3/.floyddata b/report_05_Jigsaw-Puzzle/data/puzzle_3x3/.floyddata new file mode 100644 index 0000000000000000000000000000000000000000..54cdd56402183c447095830defbd288f3656cb54 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/data/puzzle_3x3/.floyddata @@ -0,0 +1 @@ +{"name": "jigsaw", "data_endpoint": null, "family_id": "pAJncn3SRbabAW4m757Lin", "namespace": "shivaverma", "data_id": null, "resource_id": null, "data_name": "shivaverma/datasets/jigsaw/1", "tarball_path": null} \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/load_data.py b/report_05_Jigsaw-Puzzle/load_data.py new file mode 100644 index 0000000000000000000000000000000000000000..f8459e5ecd53c22712e5cfda79e337c388f26a4b --- /dev/null +++ b/report_05_Jigsaw-Puzzle/load_data.py @@ -0,0 +1,33 @@ +import os +import numpy as np +from PIL import Image + + +def load_puzzle(images_path = './data/puzzle_2x2/train',labels_path = './data/puzzle_2x2/train.csv',index = [0,8]): + """Load MNIST data from `path`""" + pathlist = os.listdir(images_path) + images = [] + with open(labels_path,mode='r') as f: + labels = f.readlines()[index[0]+1:index[1]+1] + # print(labels) + for i,l in enumerate(labels): + # print(i) + labels[i] = l.split(',')[-1].split('\n')[0].split() + for j,n in enumerate(labels[i]): + labels[i][j] = int(n) + labels = np.array(labels) + for i,pi in enumerate(pathlist[index[0]:index[1]]): + # print(i) + ab_path = os.path.join(images_path,pi) + I = Image.open(ab_path) + images += [np.array(I)] + images = np.array(images) + + return images, labels, len(pathlist) + +if __name__ == "__main__": + data = load_puzzle('./data/puzzle_2x2/train','./data/puzzle_2x2/train.csv',[0,8]) + images = data[0] + labels = data[1] + print(images.shape) + print(labels.shape) diff --git a/report_05_Jigsaw-Puzzle/main.py b/report_05_Jigsaw-Puzzle/main.py new file mode 100644 index 0000000000000000000000000000000000000000..773cd66d351bb507ffb139d8797c0c2409d4990f --- /dev/null +++ b/report_05_Jigsaw-Puzzle/main.py @@ -0,0 +1,144 @@ +import numpy as np +import torch +import torch.nn as nn +from torch import optim +import load_data +import CNN_model +import sort + +def train(iden_class,epochs,bsz,lr,weight_decay,images_path,labels_path,save_model): + + device = torch.device('cuda') + _,_,length = load_data.load_puzzle(images_path, labels_path) + + # rand_num = torch.randperm(length//bsz*bsz) + # + # rand_num = rand_num.reshape(-1,bsz) + + model = CNN_model.Cnn_model(device).to(device) + # torch.save(model, "model_2.bin") + + optimizer = optim.Adam(model.parameters(),lr=lr,betas=(0.9,0.999),eps=1e-8,weight_decay=weight_decay) + # scheduler = lr_scheduler.ExponentialLR(optimizer, gamma=0.9) + criterion = nn.MSELoss() + + train_loss_p = [] + # train_acc_p = [] + # acc_highest = 0 + + for epoch in range(epochs): + loss = 0 + # acc=0 + # acc_high=0 + print("epoch:",epoch + 1,"/",epochs) + model.train() + for batch_id in range(length//bsz): + data = load_data.load_puzzle(images_path, labels_path, [batch_id*bsz,(batch_id+1)*bsz]) + + batch_image = torch.tensor(data[0]).permute(0,3,1,2).float().to(device) + batch_label = torch.tensor(data[1]).float().to(device) + + print("epoch",epoch + 1," batch:",batch_id + 1,"/",length//bsz,end=" ") + alpha_tb,alpha_lr,tgt_tb,tgt_lr = model(batch_image, batch_label, device, iden_class, True) + + loss_batch = criterion(alpha_tb, tgt_tb).to(device) + criterion(alpha_lr, tgt_lr).to(device) + print("loss: ", loss_batch.item(),end="\n") + # print("acc: {:.0%}".format(acc_batch)) + train_loss_p.append(loss_batch.item()) + # train_acc_p.append(acc_batch) + + # acc_high = acc_batch if acc_batch > acc_high else acc_high + + loss += loss_batch.item() + # acc += acc_batch + + optimizer.zero_grad() + loss_batch.backward() + optimizer.step() + + # scheduler.step(epoch) + + loss /= length//bsz + # acc /= length//bsz + # if acc >= acc_highest: + # acc_highest = acc + if save_model: + torch.save(model, save_model + "_" + str(iden_class) + ".bin") + state_dic = { + "state": model.state_dict(), + "epoch": epoch + } + + print("epoch",epoch + 1,": train_loss:",loss) + # print("epoch",epoch + 1,": train_loss:",loss," train_acc:{:.0%}".format(acc)," train_acc_high:",acc_high) + + # return acc_highest + +def test(epoch,iden_class,model_path,images_path,labels_path,bsz=1): + + device = torch.device('cuda') + + _,_,length = load_data.load_puzzle(images_path,labels_path) + + model = torch.load(model_path + "_" + str(iden_class) + ".bin") + + # scheduler = lr_scheduler.ExponentialLR(optimizer, gamma=0.9) + criterion = nn.MSELoss() + + test_loss_p = [] + # test_acc_p = [] + + acc = 0 + loss = 0 + model.eval() + for batch_id in range(length//bsz): + data = load_data.load_puzzle(images_path,labels_path,[batch_id*bsz,(batch_id+1)*bsz]) + + batch_image = torch.tensor(data[0]).permute(0,3,1,2).to(device).float() + batch_label = torch.tensor(data[1]).to(device).float() + with torch.no_grad(): + alpha_tb,alpha_lr,tgt_tb,tgt_lr = model(batch_image, batch_label, device, iden_class,False) + + sort_tb,index_tb = torch.sort(alpha_tb,dim=-1, descending=True) + sort_lr,index_lr = torch.sort(alpha_lr,dim=-1, descending=True) + + sort_tb,index_tb = sort_tb.cpu().detach().numpy().tolist(),index_tb.cpu().detach().numpy().tolist() + sort_lr,index_lr = sort_lr.cpu().detach().numpy().tolist(),index_lr.cpu().detach().numpy().tolist() + batch_label = batch_label.int().cpu().detach().numpy().tolist() + + for i in range(bsz): + beta_tb = [] + beta_lr = [] + for j in range(iden_class**4): + beta_tb.append([sort_tb[i][j],index_tb[i][j]//iden_class**2,index_tb[i][j]%iden_class**2]) + beta_lr.append([sort_lr[i][j],index_lr[i][j]//iden_class**2,index_lr[i][j]%iden_class**2]) + order,sign = sort.stack(iden_class,beta_tb,beta_lr) + # print(order) + # print(batch_label[i]) + if order == batch_label[i]: + acc += 1 + acc /= bsz + + loss_batch = criterion(alpha_tb, tgt_tb).to(device) + criterion(alpha_lr, tgt_lr).to(device) + test_loss_p.append(loss_batch.item()) + # test_acc_p.append(acc_batch) + + + loss += loss_batch.item() + # acc += acc_batch + + + # scheduler.step(epoch) + + loss /= length//bsz + acc /= length//bsz + + print("epoch",epoch,": test_loss:",loss," test_acc:{:.0%}".format(acc)) + + +def main(iden_class,epochs,bsz,lr,weight_decay,train_images_path,train_labels_path,test_images_path,test_labels_path,save_model): + train(iden_class,epochs,bsz,lr,weight_decay,train_images_path,train_labels_path,save_model) + test(epochs,iden_class,save_model,test_images_path,test_labels_path) + +if __name__ == "__main__": + main(2,0,64,1e-5,5e-5,'data/puzzle_2x2/train','data/puzzle_2x2/train.csv','data/puzzle_2x2/test','data/puzzle_2x2/test.csv','model') \ No newline at end of file diff --git a/report_05_Jigsaw-Puzzle/model_2.bin b/report_05_Jigsaw-Puzzle/model_2.bin new file mode 100644 index 0000000000000000000000000000000000000000..84ca4e5cfe88aff2652e09b084b2c3442ed3379a Binary files /dev/null and b/report_05_Jigsaw-Puzzle/model_2.bin differ diff --git a/report_05_Jigsaw-Puzzle/model_3.bin b/report_05_Jigsaw-Puzzle/model_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..6cbafc238a7f6201ef39e6108b95c7ed1899533e Binary files /dev/null and b/report_05_Jigsaw-Puzzle/model_3.bin differ diff --git a/report_05_Jigsaw-Puzzle/report_template.ipynb b/report_05_Jigsaw-Puzzle/report_template.ipynb index 1db93a237a306712d7e535c5211c86db247a264e..bd566ebdf214bf277b19f10659bbac32a7784113 100644 --- a/report_05_Jigsaw-Puzzle/report_template.ipynb +++ b/report_05_Jigsaw-Puzzle/report_template.ipynb @@ -4,33 +4,79 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Report - 报告题目\n", + "# Report - Puzzle\n", "\n", - "* 姓名\n", - "* 学号\n", + "* 法品哲\n", + "* 2022265233\n", "\n", "\n", "## 任务简介\n", "\n", - "这里简述一下任务是什么;最终的目标是什么\n", + "将一组打乱了的拼图块恢复到原本的位置,拼图分为2*2和3*3两组数据,组成的整个图片尺寸基本一致\n", + "这是我认为本课程所有报告中最难也是最有趣的一个任务。\n", "\n", "## 解决途径\n", "\n", "主要包括:\n", - "1. 问题的思考,整体的思路\n", - "2. 选用的方法,以及为何选用这些方法\n", - "3. 所用方法的详细解释,包括理论上的背景、模型、实现的细节\n", - "3. 实现过程遇到的问题,以及如何解决的\n", - "4. 最终的结果,实验分析\n", + "1. 首先带入人的思路去考虑这个任务,在拼拼图时,我们一般首先判断图片的整体风格,从散落的拼图中大致地恢复原图的信息,随后浏览拼图的每一个图块,估计它的大致位置;进一步将位置相近,特征相似的图块做对比,不断两两确认拼接;随着拼接数目的增大逐渐覆盖拼图区域,最终将剩余的图块补充完整。\n", + "2. 考虑到需要对图像进行特征处理,必然需要用到卷积神经网络,其次,拼图的关键在于视觉信息的相似性,这里使用attention策略来完成,最终会得到一组以相似度排序的对照关系,这时候就需要一个确切的排序算法来给出最终的拼图恢复位置。\n", + "3. 具体来说,遍历人类拼图整个过程,首先用一个CNN网络表征最开始对于拼图整体的风格鉴定和数据降维,否则第一无法获得全局信息,第二夸张的数据量会严重拖慢训练进展;随后处理关键点在于相邻图块的两两拼接,以机器视角考虑,就是计算两个图块对应边缘的连续性,为了方便处理,模型直接计算其相似性代替,具体地,将经第一步降维后的图块进一步降维,只保留上、下、左、右四个边缘块,随后喂给一个attention模型,,区分上下和左右计算每个图块对应边缘的相似性;最终将获得的边缘相似度进行排序,设计算法根据边缘相似度以贪心策略进行拼图的最优排列,对照上文中依靠两两拼接逐渐覆盖整个拼图的步骤\n", + "4. 不得不说,实现过程基本全是问题,比如按照人解决问题的思路,我最开始的策略是将所有图块区分为中心块、边块和角块,降低可能的组合数量,但在实操过程中发现,CNN对于图片的旋转和对称现象不敏感,这就导致了他无法将位置关系接近的同类块正确排列。(比如对于CNN来说,如下两种排列是等价的,因为每一个块都具有相同的位置关系)所以必须加入上下左右边缘的区分。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "a1=[[5,1,6],\n", + " [2,0,3],\n", + " [7,4,8]]\n", "\n", - "要求:\n", - "1. 数据的基本情况介绍\n", - "2. 程序,以及各个部分的解释、说明\n", - "3. 结果的可视化,精度等的分析\n", + "a2=[[6,3,8],\n", + " [1,0,4],\n", + " [5,2,7]]" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + " 当然了我也考虑过直接将图片囫囵丢进模型,做九个九分类的任务(对于3*3的拼图任务),但这未免太缺乏先验知识基础,听起来不具有实操意义,故pass掉了。\n", + " 另外在实现最终的排序算法时,我着实栽了个大跟头,本来以为就是一个简单的贪心策略算法,但实操起来还是很麻烦的。(感觉可以投稿给下一届CSP当赛题用)\n", "\n", + "5. 最终的结果,实验分析。如下:" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import main\n", + "main.test(10,2,'model','data/puzzle_2x2/valid','data/puzzle_2x2/valid.csv')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "is_executing": true + } + } + }, + { + "cell_type": "markdown", + "source": [ "## 总结\n", - "总结任务实现过程所取得的心得等。" - ] + "很难,但也挺有意义。" + ], + "metadata": { + "collapsed": false + } } ], "metadata": { diff --git a/report_05_Jigsaw-Puzzle/sort.py b/report_05_Jigsaw-Puzzle/sort.py new file mode 100644 index 0000000000000000000000000000000000000000..ee5fe5c204d291284df51a637cbed7756d407eab --- /dev/null +++ b/report_05_Jigsaw-Puzzle/sort.py @@ -0,0 +1,386 @@ +import copy +from itertools import chain +import numpy as np + +def combin(size,posi_puzs1,posi_puzs2): + # print("posi_puzs1:",posi_puzs1) + # print("posi_puzs2:",posi_puzs2) + + combin_puzs = [] + paradox = True + + for i,a in enumerate(posi_puzs1): + for j,b in enumerate(posi_puzs2): + paradox_ = False + combin_puz = [] + for l in range(size): + combin_puz.append([]) + for r in range(size): + # print(a[l][r]) + # print(b[l][r]) + if a[l][r] == b[l][r]: + combin_puz[l].append(a[l][r]) + elif b[l][r] == -1: + combin_puz[l].append(a[l][r]) + elif a[l][r] == -1: + combin_puz[l].append(b[l][r]) + else: + paradox_ = True + break + if paradox_: + break + + if not paradox_: + paradox = False + combin_puzs.append(combin_puz) + + # print("combin_puzs:",combin_puzs) + + return copy.deepcopy(combin_puzs),paradox + +def arrange(size,sub_puz): + + ln_m = 0 + for i in range(size): + for j in range(size): + if sub_puz[size-i-1][j] >= 0: + ln_m = size - i + break + if ln_m > 0: + break + ro_m = 0 + for i in range(size): + for j in range(size): + if sub_puz[j][size-i-1] >= 0: + ro_m = size - i + break + if ro_m > 0: + break + if ln_m == 1 and ro_m == 1: + print("only 1 part of puzzle should not be fed to function \"arrange\".") + exit(0) + posi_map = [] + for i in range(size): + for j in range(size): + if size-ln_m >= i and size-ro_m >= j: + posi_map.append([i,j]) + posi_puzs = [] + for p in posi_map: + posi_puz = [] + for i in range(size): + posi_puz.append([]) + for j in range(size): + if i-p[0] >= 0 and j-p[1] >= 0: + posi_puz[i].append(sub_puz[i-p[0]][j-p[1]]) + else: + posi_puz[i].append(-1) + posi_puzs.append(posi_puz) + + return copy.deepcopy(posi_puzs) + +def add(size,sub_puz,cell_copy,puz_copy,x,j,k_,oversize,mode): + + paradox = False + if 0 <= k_[0] < size and 0 <= k_[1] < size: + if x[1-j] in cell_copy: + if sub_puz[k_[0]][k_[1]] == x[1-j]: + pass + else: + paradox = True + elif sub_puz[k_[0]][k_[1]] < 0: + cell_copy.append(x[1 - j]) + puz_copy[k_[0]][k_[1]] = x[1-j] + if len(cell_copy) > size-1: + oversize = True + else: + paradox = True + elif mode == 1: + movable = True + if k_[0] < 0: + for n in range(size): + if sub_puz[size - 1][n] >= 0: + movable = False + break + if movable: + line = puz_copy.pop() + puz_copy.insert(0, line) + k_[0] += 1 + elif k_[0] >= size: + for n in range(size): + if sub_puz[0][n] >= 0: + movable = False + break + if movable: + line = puz_copy.pop(0) + puz_copy.insert(-1, line) + k_[0] -= 1 + elif k_[1] < 0: + for n in range(size): + if sub_puz[n][size - 1] >= 0: + movable = False + break + if movable: + for n in range(len(puz_copy)): + row = puz_copy[n].pop() + puz_copy[n].insert(0, row) + k_[1] += 1 + elif k_[1] >= size: + for n in range(size): + if sub_puz[n][0] >= 0: + movable = False + break + if movable: + for n in range(len(puz_copy)): + row = puz_copy[n].pop(0) + puz_copy[n].insert(-1, row) + k_[1] -= 1 + if movable: + cell_copy.append(x[1 - j]) + puz_copy[k_[0]][k_[1]] = x[1 - j] + if len(cell_copy) > size-1: + oversize = True + pass + else: + paradox = True + else: + paradox = True + if paradox: + puz_copy.clear() + return paradox,oversize + +def stack(size,alpha_tb,alpha_lr): + stk = [[],[],[]] + oversize = False + sign = False + while len(alpha_tb) > 0 or len(alpha_lr) > 0: + paradox = False + m = [] + for i in range(size): + m.append([]) + for j in range(size): + m[i].append(-1) + + if len(alpha_tb) > 0 and len(alpha_lr) > 0: + if alpha_tb[0][0] > alpha_lr[0][0]: + x = alpha_tb.pop(0)[1:] + m[0][0] = x[0] + m[1][0] = x[1] + else: + x = alpha_lr.pop(0)[1:] + m[0][0] = x[0] + m[0][1] = x[1] + elif len(alpha_tb) > 0: + x = alpha_tb.pop(0)[1:] + m[0][0] = x[0] + m[1][0] = x[1] + else: + x = alpha_lr.pop(0)[1:] + m[0][0] = x[0] + m[0][1] = x[1] + + if x[0] == x[1]: + continue + + is_ext = False + stk_copy = copy.deepcopy(stk) + # print("0:",stk_copy[0]) + # print("1:",stk_copy[1]) + # print("2:",stk_copy[2]) + # print("m:",m) + # print("end") + for i,a in enumerate(stk[0]): + for j,b in enumerate(x): + if b in a: + # print(stk[0]) + # if x[1-j] in a: + # paradox = True + # break + is_ext = True + if len(stk_copy[2]) > 0: + paradox = True + for p,posi_puz in enumerate(stk_copy[2]): + # print(stk[0]) + # print(stk[2]) + # print(b) + # print("end") + k = [(s, t.index(b)) for s, t in enumerate(posi_puz) if b in t][0] + if m[0][1] < 0: + if j == 0: + k_ = [k[0]+1,k[1]] + else: + k_ = [k[0]-1,k[1]] + else: + if j == 0: + k_ = [k[0],k[1]+1] + else: + k_ = [k[0],k[1]-1] + # print(k_) + # print(posi_puz) + paradox_,oversize = add(size,posi_puz,stk_copy[0][i],stk_copy[2][p],x,j,k_,oversize,2) + if not paradox_: + paradox = False + + # for i, p in enumerate(stk_copy[2]): + # if len(p) == 0: + # stk_copy[2].pop(i) + s = 0 + while s < len(stk_copy[2]): + if len(stk_copy[2][s]) == 0: + stk_copy[2].pop(s) + else: + s+=1 + + else: + k = [(s, t.index(b)) for s, t in enumerate(stk[1][i]) if b in t][0] + if m[0][1] < 0: + if j == 0: + k_ = [k[0]+1,k[1]] + else: + k_ = [k[0]-1,k[1]] + else: + if j == 0: + k_ = [k[0],k[1]+1] + else: + k_ = [k[0],k[1]-1] + paradox,oversize = add(size,stk[1][i],stk_copy[0][i],stk_copy[1][i],x,j,k_,oversize,1) + + break + + if is_ext or paradox: + break + + if is_ext or paradox: + break + + if paradox: + continue + + + if not is_ext: + stk_copy[0].append(x) + stk_copy[1].append(m) + if len(stk_copy[0]) < size+1 and not oversize: + pass + else: + sub_puzs = [] + posi_puzs = [] + if len(stk_copy[2]) > 0: + posi_puzs.append(stk_copy[2]) + else: + posi_puzs.append(arrange(size,stk_copy[1][0])) + for i,a in enumerate(stk_copy[1][1:]): + # sub_puzs.append(a) + sub_puzs.append(arrange(size,a)) + + # stk_copy[2] = [] + for i,a in enumerate(sub_puzs): + stk_copy[2],paradox = combin(size,posi_puzs[0],sub_puzs[i]) + posi_puzs[0] = stk_copy[2] + if len(stk_copy[2]) == 0: + continue + else: + stk_copy[0] = [copy.deepcopy(list(chain(*stk_copy[0])))] + if len(stk_copy[0][0]) > size-1: + oversize = True + else: + exit_same = False + i = 0 + j = 0 + while i < len(stk_copy[0]): + while j < len(stk_copy[0][i+1:]): + b = stk_copy[0][j+i+1] + for k,c in enumerate(b): + if c in stk_copy[0][i] or oversize: + exit_same = True + if exit_same: + sub_puzs = arrange(size,stk_copy[1][j+i+1]) + if len(stk_copy[2]) > 0: + posi_puzs = stk_copy[2] + else: + posi_puzs = arrange(size,stk_copy[1][i]) + + # print(stk_copy[0]) + # print(stk_copy[1]) + # print(stk_copy[2]) + # print(posi_puzs) + stk_copy[2],paradox = combin(size,posi_puzs,sub_puzs) + # print(stk_copy[2]) + # print(paradox) + # print("eend") + # print(a) + # print(stk_copy[0][i]) + # print(b) + # print(stk_copy[0][i]+[t for t in b if t not in a]) + stk_copy[0][i]+=[t for t in b if t not in stk_copy[0][i]] + # print(stk_copy[0][i]) + stk_copy[0].pop(j+i+1) + stk_copy[1].pop(j+i+1) + + if not oversize and len(stk_copy[0][i]) > size-1: + oversize = True + i = 0 + j = 1 + continue + else: + j += 1 + i += 1 + # if exit_same: + # break + if paradox: + continue + + # for i,p in enumerate(stk_copy[2]): + # if len(p) == 0: + # stk_copy[2].pop(i) + s = 0 + while s < len(stk_copy[2]): + if len(stk_copy[2][s]) == 0: + stk_copy[2].pop(s) + else: + s+=1 + + stk = copy.deepcopy(stk_copy) + if len(stk[0][0]) > size-1: + oversize = True + # print(stk[0]) + if len(stk[0][0]) > size**2-2: + if len(stk[2]) == 1: + sign = True + break + elif len(stk[2]) == 0 and len(stk[1]) == 1: + sign = True + break + # print(paradox) + # print(is_ext) + # print(oversize) + # print(stk) + # exit() + + + if len(stk[2]) > 0: + order = list(chain(*stk[2][0])) + else: + order = list(chain(*stk[1][0])) + if -1 in order: + order[order.index(-1)] = int(size**2*(size**2-1)/2)-1-sum(order) + + return order,sign + +if __name__ == "__main__": + size = 3 + + tb = np.random.rand(size**4) + sort_tb = np.argsort(tb)[::-1] + alpha_tb = [] + for i,a in enumerate(sort_tb): + alpha_tb .append([tb[a],a//size**2,a%size**2]) + + lr = np.random.rand(size**4) + sort_lr = np.argsort(lr)[::-1] + alpha_lr = [] + for i,a in enumerate(sort_lr): + alpha_lr.append([lr[a],a//size**2,a%size**2]) + + order,sign = stack(size,alpha_tb,alpha_lr) + order.sort() + print(order) diff --git a/report_05_Jigsaw-Puzzle/test.py b/report_05_Jigsaw-Puzzle/test.py new file mode 100644 index 0000000000000000000000000000000000000000..f83c2c44e50d11067e6bc472eba24ddc45184531 --- /dev/null +++ b/report_05_Jigsaw-Puzzle/test.py @@ -0,0 +1,8 @@ +import torch + +def dele(s): + s.clear() + +array1 = [["banana", "yam"],["mango", "apple"]] +array2 = [["banana", "yam"],["mango"]] +print(array1 == array2) \ No newline at end of file