基本概念K折交叉验证用于评估一个模型的泛化性能,那么网格搜索通过调参来提高模型的泛化性能.简单的网格搜索实现提前划分好训练集和测试集,简单通过两个for循环寻找最佳参数,评估效果直接使用模型自带的score.# naive grid search implementation from sklearn.svm import SVC X_train, X_test, y_train, y_test = train_test_split( iris.data, iris.target, random_state=0) print("Size of training set: {} size of test set: {}".format( X_train.shape[0], X_test.shape[0])) best_score = 0 for gamma in [0.001, 0.01, 0.1, 1, 10, 100]: for C in [0.001, 0.01, 0.1, 1, 10, 100]: # for each combination of parameters, train an SVC svm = SVC(gamma=gamma, C=C) svm.fit(X_train, y_train) # evaluate the SVC on the test set score = svm.score(X_test, y_test) # if we got a better score, store the score and parameters if score > best_score: best_score = score best_parameters = {'C': C, 'gamma': gamma} print("Best score: {:.2f}".format(best_score)) print("Best parameters: {}".format(best_parameters))验证集的引进上节实现的简单网格搜索存在一个致命问题:将用来调整参数的测试集用于评估模型的好坏!换句话说,我们是在测试集上选择出来的最佳参数,整个参数不能保证对于新数据也work well.所以需要引入一组新数据.那么数据总共就被分为三份:用于训练模型的训练集用于调整超参数的验证集用于评估模型的测试集测试集模型训练阶段从未见过,只在评估时使用.验证集用来衡量训练集训练出来的模型好坏最后评估时,模型会选择表现最好的超参,用训练集+验证集重新训练,再用测试集进行评估.这一步主要是充分利用验证集数据.from sklearn.svm import SVC # split data into train+validation set and test set X_trainval, X_test, y_trainval, y_test = train_test_split( iris.data, iris.target, random_state=0) # split train+validation set into training and validation sets X_train, X_valid, y_train, y_valid = train_test_split( X_trainval, y_trainval, random_state=1) print("Size of training set: {} size of validation set: {} size of test set:" " {}\n".format(X_train.shape[0], X_valid.shape[0], X_test.shape[0])) best_score = 0 for gamma in [0.001, 0.01, 0.1, 1, 10, 100]: for C in [0.001, 0.01, 0.1, 1, 10, 100]: # for each combination of parameters, train an SVC svm = SVC(gamma=gamma, C=C) svm.fit(X_train, y_train) # evaluate the SVC on the validation set score = svm.score(X_valid, y_valid) # if we got a better score, store the score and parameters if score > best_score: best_score = score best_parameters = {'C': C, 'gamma': gamma} # rebuild a model on the combined training and validation set, # and evaluate it on the test set svm = SVC(**best_parameters) svm.fit(X_trainval, y_trainval) test_score = svm.score(X_test, y_test) print("Best score on validation set: {:.2f}".format(best_score)) print("Best parameters: ", best_parameters) print("Test set score with best parameters: {:.2f}".format(test_score))带交叉验证的网格搜索上一节中人为分出了测试集\训练集\验证集,这钟方法对于数据的划分也比较敏感.因此可以在划分训练集/验证集时使用交叉验证,更好的评估模型的泛化能力.for gamma in [0.001, 0.01, 0.1, 1, 10, 100]: for C in [0.001, 0.01, 0.1, 1, 10, 100]: # for each combination of parameters, # train an SVC svm = SVC(gamma=gamma, C=C) # perform cross-validation scores = cross_val_score(svm, X_trainval, y_trainval, cv=5) # compute mean cross-validation accuracy score = np.mean(scores) # if we got a better score, store the score and parameters if score > best_score: best_score = score best_parameters = {'C': C, 'gamma': gamma} # rebuild a model on the combined training and validation set svm = SVC(**best_parameters) svm.fit(X_trainval, y_trainval)GridSearchCV 热图可视化from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]} grid_search = GridSearchCV(SVC(), param_grid, cv=5, return_train_score=True) X_train, X_test, y_train, y_test = train_test_split( iris.data, iris.target, random_state=0) grid_search.fit(X_train, y_train) scores = np.array(results.mean_test_score).reshape(6, 6) # plot the mean cross-validation scores mglearn.tools.heatmap(scores, xlabel='gamma', xticklabels=param_grid['gamma'], ylabel='C', yticklabels=param_grid['C'], cmap="viridis")不同的交叉验证策略嵌套交叉验证在交叉验证的网格搜索中,我们使用交叉验证来完成训练集/验证集的划分,测试集的划分我们还是手动的.嵌套交叉验证让我们测试集的划分也使用交叉验证.这会使结果更合理,当然需要训练模型的个数也upup~(比如36个模型超参数组合,嵌套交叉验证都是5折,那就需要训练900个模型)param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]} scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5), iris.data, iris.target, cv=5) print("Cross-validation scores: ", scores) print("Mean cross-validation score: ", scores.mean())
整体思路​ RoI-Header共由三部分组成：box_roi_pool:Multi-scale RoIAlign poolingbox_head:TwoMLPHeadbox_predictor:FastRCNNPredictorMultiScaleRoIAlign​ 该类与之前所述的RoIPooling不同，RoIAlign的定位能力更强。RoIPooling在计算过程中存在取整操作，从而引入了更多的定位误差，而Align不会进行取整操作。具体以后再展开~TwoMLPHead​ TwoMLPHead其实就是RoIPooling之后跟着的两个全连接层(还有一个Flatten层).FastRCNNPredictor​ FastRCNNPredictor也就是两个全连接层，分别预测每个proposal的类别和bbox的回归参数。注意输入的num_classes应该是实际类型+1,因为第0类是backgroundROIHeadsinit​ 保存一些需要用到的工具.box_similarity负责计算box_iouproposal_matcher负责正负样本的分配fg_bg_sampler负责正负样本的采样其他参数就是刚刚提到的类以及一些阈值参数了forward​ 训练模式下,首先会对proposal进一步采样,得到proposal样本和对应的label.​ 其次将proposal和features特征层送入roi_pool得到每个proposal的box_features.box_features的形状应该是[num_proposals,channel,7,7]​ 随后将box_features送入box_header提取出特征向量​ 最后将这些向量送入box_predictor得到类别和回归参数预测结果​ 最后一部分代码如下.如果是训练模式下将通过fastrcnn_loss计算损失;如果是预测模式则会对proposals进行预处理postprocess_detections.最后返回相应的结果select_training_samples该函数的功能是将RPN网络提供的Proposal进行采样,并计算这些Proposal的标签和regression参数(分配gtbox并计算,跟之前RPN网络内的操作类似)​ 如下图所示,源码将gt_boxes也拼到了proposal后面,这里可能考虑到了PRN训练初始无法提供有效的proposal,所以加入gt_boxes来训练FastRCNN网络部分.​ 接下来将调用assign_targets_to_proposals函数将proposals分配给gt_boxes.这个函数在之前的RPN网络提到过,这里不再赘述.​ 之后调用了subsample进行采样.得到一定比例的正负样本.​ 最后一步是遍历每张图片,首先找到正负样本(因为回归参数正负样本都参与计算)对应proposal的类别和proposal分配到的gt_box,再计算gt_box和proposal之间的回归参数(通过box_coder的encode方法,之前在RPN网络中有提到).注意这里负样本对应的gt_box是第0个gt_box,按道理来说负样本不参与边界回归参数损失的计算.但是为了防止matched_idxs下标越界,所以在计算match_idxs时将-1都置为了0,导致现在"负样本有对应的gt_box,且计算了回归参数",不过这个问题不大,因为label记录了负样本的位置,在计算损失时忽略这部分即可~subsample​ 该函数其实只是调用了fg_bg_sampler这个类对象,得到了每张图片里的正负样本索引,随后将每张图片的正负样本索引丢到sampled_inds列表里.fastrcnn_loss​ 刚开始将label和regression cat起来是把不同图片的labels和回归参数都摞起来,一起处理.​ 正负样本都会计算类别损失.​ 而回归参数损失只计算正样本的,所以这里需要用sampled_pos_inds_subset记录正样本的位置.同时还需要对box_regression进行reshape处理,因为regression参数针对每个类别都会有四个参数. 最后使用smoothL1Loss进行正样本的回归参数损失计算.postprocess_detections 在预测模式下，将通过此函数得到最后的预测结果。具体流程见下图；具体操作见源码(带注释)
整体思路​ 创建DataSet首先需要继承torch.utils.data.Dataset这个类，然后再init函数中完成数据的一些预处理，比如xml文件的解析/类与序号的映射/图片路径的存储等。​ 接下来需要重载__len__和__getitem__两个方法,分别返回数据长度和某个序号对应的图片(包括图片本身和标注)如果用到多GPU训练,按照Pytorch官方的建议,最好再实现get_height_and_wight这个方法,节约内存.(因为这样可以避免pytorch将所有图片读入计算宽高)源码细节1. xml解析​ 在init方法中调用了parse_xml_to_dict方法解析xml文件,获取其中的object信息.(物体的类别/位置/边界框)​ 而parse_xml_to_dict具体使用递归的方法遍历标签信息,返回字典类型的数据2.__getitem__方法​ 首先通过上述的给出的xml解析方法解析图片对应的xml文件,将结果存入data变量.图片也通过Image.open打开​ 接下来将data中的边界框和类别数据进行读取,丢到boxes和labels列表中.之后注意将这些数据转换成Tensor类型​ 最后将信息都整理到target中,作为整体的标签返回.最后还需要判断是否对图片进行data augmentation3.Transform​ transform有很多类型,这里简单介绍一下水平翻转的实现.需要注意的是图片翻转之后,边界框的标注位置也需要翻转.​ 对于水平翻转: y坐标不需要改变,xmax变为width-xmin,xmin变为width-xmax4.collate_fn​ 为了之后实现dataloaer,这里需要实现collate_fn函数.​ 不同于分类网络中dataset只返回一张图片和一个label(形式比较固定),目标识别网络中需要返回图片加标注,而标注是不等长的,使用默认的stack有可能出现问题.所以需要手动用collate_fn方法进行堆叠.下图是dataloader的实现,这里传入了collate_fn.不传入这个参数默认使用torch.stack()对__getitem__的每个返回值进行堆叠​
基本概念t-SNE(t-distributed stochastic neighbor embedding)是用于降维的一种机器学习算法，由 Laurens van der Maaten 等在08年提出。t-SNE 是一种非线性降维算法，非常适用于高维数据降维到2维或者3维，进行可视化。该算法可以将对于较大相似度的点，t分布在低维空间中的距离需要稍小一点；而对于低相似度的点，t分布在低维空间中的距离需要更远。tips1:TSNE将数据点之间的相似性转换为联合概率，并试图最小化低维嵌入和高维数据的联合概率之间的Kullback-Leibler差异。t-SNE的成本函数不是凸的，即使用不同的初始化，我们可以获得不同的结果。tips2:如果特征数量非常多，强烈建议使用另一种降维方法（例如，对于密集数据使用PCA或对于稀疏数据使用TruncatedSVD）将尺寸数量减少到合理的数量（例如50个）。这将抑制一些噪声并加快样本之间成对距离的计算。tips3:sklearn TSNE 源码tips4:t-SNE 原理及Python实例 - 知乎 (zhihu.com)优缺点优点：对于不相似的点，用一个较小的距离产生较大的梯度来排斥区分。这种排斥不会无限大(梯度中分母)，避免不相似的点距离太远。缺点：主要用于可视化，很难用于其他目的。比如测试集合降维，因为他没有显式的预估部分，不能在测试集合直接降维；比如降维到10维，因为t分布偏重长尾，1个自由度的t分布很难保存好局部特征，可能需要设置成更高的自由度。t-SNE倾向于保存局部特征，对于本征维数(intrinsic dimensionality)本身就很高的数据集，是不可能完整的映射到2-3维的空间（映射存在信息损失）t-SNE没有唯一最优解，且没有预估部分。如果想要做预估，可以考虑降维之后，再构建一个回归方程之类的模型去做。但是要注意，t-sne中距离本身是没有意义，都是概率分布问题。训练太慢。有很多基于树的算法在t-sne上做一些改进算法流程首先在高维空间,将样本之间的距离转换成概率分布.$$ P_{j|i}=\frac {exp(-||x_i-x_j||^2/2\sigma^2)}{\sum_{k \neq i} exp(-||x_i-x_k||^2/2\sigma^2)} $$在低维空间寻找类似的概率分布,并使用KL散度衡量高维空间/低维空间两概率分布的相似度.使用梯度下降最小化所有数据点上的KL散度总和$$ C = \sum_i KL(P_i||Q_i)=\sum_i \sum_j p_{j|i} \log {\frac {p_{j|i}}{q_{j|i}} } $$对 C 求偏导数(每个样本)获得每次更新的方向sklearnn_components : tsne最终降维的维度init : 初始化方法,可以使用pca or randomfrom sklearn.manifold import TSNE tsne=TSNE(n_components=1,init='pca', random_state=501) down_X = tsne.fit_transform(X) # train and get embadding
基本概念DBSCAN:Density-Based Spatial Clustering of Applications with Noise (具有噪声的基于密度的聚类方法）核心对象：若某个点内的密度达到算法设定的阈值则为核心点∈-邻域的距离阈值：设定的半径r直接密度可达：若点p在点q的r邻域内，且q是核心点则p-q直接密度可达密度可达：若有一个点的序列$q_0,q_1,...,q_k$，对任意$q_i,q_{i-1}$直接密度可达,那么称$q_0,q_k$密度可达。即直接密度可达的‘传播’。密度相连：若从核心点p出发，点q和点k都是密度可达的。那么点q和点k是密度相连的。(不要求q/k必须是核心点)边界点：属于某个类的非核心点噪声点：不属于任何一个类簇的点，从任何一个核心点出发都是密度不可达的。参数选择半径∈：可以根据K距离设定，即寻找突变点。K距离：给定数据集P = {p(i);i=0,1,...,n},计算点p(i)到集合D的子集S中所有点之间的距离d，按照距离从小到大排序,d(k)即K距离例如点s到其他点的距离数组$d_k$={0.1,0.11,0.12,0.3,0.5....}(已排序)，发现前三个距离相差不大，第四个距离0.3发生突变，那么半径取d(3)=0.12,MinPts取3MinPts：K-距离中的K值，一般从小开始取优缺点优点：可以处理任意形状的簇不需要指定簇的个数擅长找到离群点，用于异常检测任务缺点：高维数据处理困难(可以寻找降维方法)半径r和MinPts难以确定算法效率慢算法流程伪代码如下,类似宽度优先搜索进行簇的传播:bool visited[n]; memset(visited,false,sizeof visited); while(some point is unvisited){ choose p randomly which is unvisited; if(p ∈-field has more that MinPts points){ create a new cluster C; N = {q in p ∈-field}; for(auto &q:N){ if(visited[q]==false){ visited[q] = true; if(q ∈-field has more that MinPts points){ add {k in q ∈-field} to N; } add q to cluster C; } } }else{ add p to noise; } }可视化Visualizing DBSCAN Clustering (naftaliharris.com)sklearnfrom sklearn.cluster import DBSCAN dbscan = DBSCAN(eps=0.5,min_sample=5) dbscan.fit(X) # train dbscan.label_ # label label=-1 means noise descan.core_sample_indices_ # 核心样本在原始训练集中的位置
聚类概念属于无监督学习(无标签)顾名思义，就是将相似的东西分到一组难点在于如何评估聚类效果以及调整参数基本概念K代表最后需要得到的簇个数(比如二分类K=2)一个簇的质心计算：采用均值，即每个特征向量取平均值距离度量：经常使用欧几里得距离和余弦相似度(高维特征最好先执行一次标准化)优化目标：实现$min\sum_{i=1}^{k}\sum_{x∈C_i}dist(C_i,x)^2$优缺点优点：算法简单、快速适合常规数据集(就是一坨一坨的那种，没有特定形状)缺点：K值难以确定(一般是遍历K值取Inertia的拐点)算法复杂度与样本呈线性关系(一次迭代)很难发现任意形状的簇，比如二维特征的样本，一个圆环套一个球的形状KMeans就很难检测出来。算法流程首先随机选取K个样本作为K个簇的中心(在KMeans++中，使用最大距离进行初始化)KMeans++逐个选取K个簇中心，且离其它簇中心越远的样本点越有可能被选为下一个簇中心将每个样本分配到K个簇中，具体选择哪个簇取决于该样本和K个簇的距离，选择距离最近的簇(分配样本)此时所有样本都分配到对应的簇，在每个簇中更新簇中心。新的簇中心采用简单的均值计算(更新簇中心)回到第二步再次分配样本，直到达到迭代次数上限或损失指标小于某个阈值(损失指标有inertia/轮廓系数)KMeans代码实现import numpy as np class KMeans: def __init__(self,data,num_clusters): self.data = data self.num_clusters = num_clusters def train(self,max_iterations): # 1.choose k centers centroids = KMeans.centroids_init(self.data,self.num_clusters) # 2.begin train num_examples = self.data.shape[0] closest_centroids_ids = np.empty((num_examples,1)) for _ in range(max_iterations): # 3.get closest center id closest_centroids_ids = KMeans.centroids_find_closest(self.data,centroids) # 4.update k centers centroids = KMeans.centroids_compute(self.data,closest_centroids_ids,self.num_clusters) return centroids,closest_centroids_ids @staticmethod def centroids_init(self,data,num_clusters): num_examples = data.shape[0] random_ids = np.random.permutation(num_examples) centroids = data[random_ids[:num_clusters],:] return centroids @staticmethod def centroids_find_closest(self,data,entroids): num_examples = data.shape[0] num_centroids = centroids.shape[0] closest_centroids_ids = np.zeros((num_examlpes,1)) for example_index in range(num_examples): distance = np.zeros(num_centroids,1) for centroid_index in range(num_centroids): distance_diff = data[example_index,:] - centroids[centroid_index,:] distance[centroid_index] = np.sum(distance_diff**2) closest_centroids_ids[example_index] = np.argmin(distance) return closest_centroids_ids @staticmethod def centroids_compute(self.data,closest_centroids_ids,self.num_clusters): num_features = data.shape[1] centroids = np.zeros((num_clusters,num_features)) for centroid_id in range(num_clusters): closest_ids = closest_centroids_ids == centroid_id centroids[centroid_ids] = np.mean(data[closest_ids.flatten(),:],axis=0) return centroidssklearn接下来介绍sklearn库中的KMeans实现.文档见sklearn.cluster.KMeans — scikit-learn 1.2.2 documentationAttributes__init__n_clusters : 簇个数Krandom_state : 随机数种子init : K个簇中心的初始化方式n_init : 算法执行次数,取结果最好的一次max_iter : 单次算法迭代次数(分配簇/更新簇)from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=2,random_state=0,init='k-means++',n_init=10,max_iter=300)fitkmeans.fit(X) # input X kmeans.cluster_centers_ # center coordinate kmeans.label_ # every sample's label kmeans.inertia # losspredkmeans.predict(New_X) kmeans.label_tranformTransform(): Method using these calculated parameters apply the transformation to a particular dataset.返回值shape : (num_examples,num_clusters) 即每个样本和每个类的距离 通过这个方法也可以得到predict结果(argmin)from sklearn.preprocessing import StandardScaler sc = StandardScaler() sc.fit_tranform(X_train) sc.tranform(X_test) # 一定是tranform 保证train和test使用同一个转化标准可视化# draw cluster points def plot_data(feature,snr,label): df = pd.DataFrame(dict(x=snr, y=feature, color=label)) fig, ax = plt.subplots() colors = {0:'red', 1:'blue'} ax.scatter(df['x'], df['y'], c=df['color'].apply(lambda x: colors[x])) plt.show() # draw cluster centers def plot_centriods(snr,centroids,weights=None,circle_color='g',cross_color='k'): if weights is not None: centroids = centroids[weights>weights.max()/10] plt.scatter(snr,centroids[:], marker='o',s=30,linewidths=8, color=circle_color,zorder=10,alpha=0.9) plt.scatter(snr,centroids[:], marker='x',s=50,linewidths=50, color=circle_color,zorder=11,alpha=1)评估指标inertia指标inertia : 每个样本距离质心的距离score方法的结果是inertia的负值(绝对值越大分数越小)kmeans.inertia_ X_dist = kmeans.transform(X) np.sum(X_dist[np.arange(len(X_dist)),kmeans.labels_]**2) # inertia计算 kmeans.score(X) == -1*kmeans.inertia_轮廓系数某个样本的轮廓系数定义为:$$ s = \frac {disMean_{out} - disMean_{in}}{max(disMean_{out}-disMean_{in})} $$其中$disMean_{in}$为该点与本类其他点的平均距离，$disMean_{out}$为该点与非本类点的平均距离。该值取值范围为[−1,1]， 越接近1则说明分类越优秀。在sklearn中函数 silhouette_score() 计算所有点的平均轮廓系数，而silhouette_samples()返回每个点的轮廓系数。K值选取(拐点法)kmeans_per_k = [KMeans(n_clusters=k).fit(X) for k in range(1,10)] inertias = [model.inertia_ for model in kmeans_per_k] # draw fig plt.figure(figsize=(8,4)) plt.plot(range(1,10),inertias,'bo-') plt.show()半监督学习KMeans也可以用于半监督学习。比如在一个少样本训练场景下，相比于随机选取50个训练样本打标加入训练这种方式，可以先使用聚类挑出50个簇中心，每个簇选取一个样本作为典型样本；用这些样本打标训练，最后测试出来的acc会更高。除此之外，还可以通过标签传播(同一个簇内的标签一致)的方式扩大训练集。
OFD(Open Fixed-layout Document)OFD 是开放版式文档（Open Fixed-layout Document ）的英文缩写，是我国国家版式文档格式标准，通俗来说，也有人称这格式为国产PDF。但是在很多方面的性能优于PDF的同类文档。OFD也逐渐开始在电子发票、电子公文、电子证照等等的领域中应用。JJJYmmm目前有把OFD转换成txt的需求，但是网上大多是OFDPDF的方法。所以决定自己动手，丰衣足食(bushi)OFD格式OFD其实一个压缩文件，如果我们尝试解压，将会得到如下的目录结构。OFD.xml里面一般是文档的描述信息，比如创建时间、最迟修改时间、作者等等Doc_0里保存的是具体文档信息,我们主要关注Pages文件夹中的内容Content.xml,这里面保存的是文档主体内容,也就是我们需要提取的.观察Content.xml里的内容,可以发现 : 字符在\<ofd:TextCode\>字段中,而\<ofd:TextCode\>嵌套在\<ofd:TextObject\>对象里,众多TextObject封装在\<ofd:PageBlock\>,外层还有\<ofd:PageBlock\>/\<ofd:Layer\>等字段Python脚本处理xml接下来可以编写Python脚本来处理xml了!!!首先就是解压OFD,直接当成普通压缩文件解压即可.def unzip_file(zip_path, unzip_path=None): """ :param zip_path: ofd格式文件路径 :param unzip_path: 解压后的文件存放目录 :return: unzip_path """ if not unzip_path: unzip_path = zip_path.split('.')[0] with zipfile.ZipFile(zip_path, 'r') as f: for file in f.namelist(): f.extract(file, path=unzip_path) return unzip_path解析XML则借鉴之前FasterRCNN源码中的解析方法.因为TextObjcect对象和目标检测中的Bounding Box类似,有很多实例,故思路是一致的,即通过递归方法解析XML为dict,遇到TextObject对象时value值用列表保存.代码如下:def parse_xml_to_dict(prefix_ofd,xml): """ 将xml文件解析成字典形式，参考tensorflow的recursive_parse_xml_to_dict Args: xml: xml tree obtained by parsing XML file contents using lxml.etree Returns: Python dictionary holding XML contents. """ if len(xml) == 0: # 遍历到底层，直接返回tag对应的信息 return {xml.tag: xml.text} result = {} for child in xml: child_result = parse_xml_to_dict(prefix_ofd,child) # 递归遍历标签信息 if child.tag != prefix_ofd + 'TextObject': result[child.tag] = child_result[child.tag] else: if child.tag not in result: # 因为TextObject可能有多个，所以需要放入列表里 result[child.tag] = [] result[child.tag].append(child_result[child.tag]) return {xml.tag: result}这里存在一个 prefix_ofd 前缀,具体来说,这取决于xml第一个字段Page中的xmlns:ofd字段,在使用etree进行解析时,键值中的ofd:会被替换成xmlns:ofd字段,具体原理JJJYmmm暂时也不太清楚,是在debug时发现的()将XML解析成字典后,就可以遍历字典取出我们想要的结果了~具体代码如下,还是比较简单的pages_path = f"{file_path}/Doc_0/Pages" text = '' for page_path in os.listdir(pages_path): context_path = os.path.join(pages_path,page_path,'Content.xml') with open(context_path,'r',encoding='utf-8') as f: _text = f.read() xml = etree.fromstring(_text) prefix_ofd = '{http://www.ofdspec.org/2016}' # xml to dict data = parse_xml_to_dict(prefix_ofd,xml) # 遍历TextObject for textobj in data[prefix_ofd+'Page'][prefix_ofd+'Content'][prefix_ofd+'Layer'][prefix_ofd+'PageBlock'][prefix_ofd+'PageBlock'][prefix_ofd+'PageBlock'][prefix_ofd+'TextObject']: # print(textobj[prefix_ofd + 'TextCode']) text+= textobj[prefix_ofd+'TextCode']效果展示以一份政府公文为例,首先用OFD查看器查看内容.执行脚本,demo如下图.最后保存得到的txt如下.可以发现文章内容都提取出来力.TODO第一个问题是最后提取出来的txt没有换行符.这个问题在于OFD的xml文件里本来就没有保存换行符,是直接通过坐标进行渲染的(详见TextObject的Boundary字段).第二个小问题是没有封装成批量处理脚本,但是这个问题不大(单纯是不想写 咕了)
源码阅读|Faster RCNN(三)——GeneralizedRCNNTransform
整体思路​ GeneralizedRCNNTransform主要用在图像进入backbone网络前的预处理以及预测结果输出时的后处理两个阶段.主要工作是图像的标准化处理以及resize操作.函数细节__init__​ __init__函数主要输入图像的均值和方差,以及resize时图片的最小(大)边长范围normalize​ 最后一行通过添加None这个维度可以增加一维维度,再利用广播机制对image的每个像素都进行操作.resize​ 这个方法首先调用_resize_image使用双线性插值调整图片大小,再通过resize_boxes调整对应的box大小._resize_image​ 根据宽高限制来确定缩放比例,调用interpolate对图像进行双线性插值,这里在image又添加一个维度,是因为interpolate方法输入需要是4D图像resize_boxes​ 按照缩放比例调整box坐标即可.这里torch.stack()会在tensor最后新增一个维度,这里就是在最后一个维度摞起来batch_images​ 这个方法是将一个batch图像中再次resize到统一尺寸,加速训练.这个统一尺寸被调整为size_divisible的整数倍​ 具体实现时寻找一个batch中图片的高宽最大值,以此作为最大图像.其他图像跟该图像做左上角对齐,空余位置填充零.​ 这种方法的好处是保证了图像的比例.forward​ 对于每张图片,以此调用normalize和resize方法进行标准化和缩放.而在进行batch_images前,需要记录当前图像尺寸,存入image_sizes_list,最后与image打包成一个list,跟target标注一起返回.​ 之所以要这么做是因为经过batch_images后,图像变成统一尺寸,但是图像有效区域在原先的图片大小范围内,所以需要保存batch_resize前的图像大小.postprocess​ 这个方法是预测模式下最后的后处理操作.
整体思路​ 以上是Faster RCNN的整体网络框架图,首先需要构建一个基础的网络框架类FasterRCNNBase,随后在此基础上构建子类FasterRCNN,在这个类中进行各个模块如RPN/ROI Header的实例化.FasterRCNNBase​ init函数接收backbone/rpn/roi_heads/transform四个变量并初始化参数.​ 主要看forward函数.首先会对训练数据(bbox的tensor格式)进行检查.然后记录图片原始的大小.​ 接着进行预处理,丢到骨干网络中得到特征图(如果在多个特征图上预测,得到的特征图将是一个字典)​ 在特征图上使用RPN网络提取Proposal,并得到RPN网络的损失.​ 将RPN生成的Proposal,随同特征图与标注target丢入ROI_Header中得到预测结果和预测损失.最后还需要将预测结果进行预处理.​ 最后当然就是把RPN Loss和Fast RCNN Loss加起来反向传播~(如果是预测模式直接返回detections即可,eager_outpus就是干这事的)Faster RCNN​ FasterRCNN类继承自FastRCNNBase,输入由很多参数,具体参数含义见注释~​ __init__函数中首先检查anchor_generator和box_roi_pool是否是正确的类.​ 对于没有实例化的类,这里将进行实例化.如anchor生成器/RPN_Head/ROI_Head等.​ 最后还需要实例tranform类,进行图像归一化/翻转等操作各个模块的初始化参数AnchorsGenerator​ 参数1是生成anchor的大小,大小以tuple of tuple的类型传入,tuple第二个参数缺省.(这里是为了适配多尺度预测,不同特征层预测的尺度不一样)​ 第二个参数是生成anchor的宽高比例,这里乘以anchor数目.主要是为了多尺度预测处理方便.说白了第一维就是区分有几个特征层参与预测!!!5种大小,3种比例,那么特征图上每个像素将产生15个anchor.RPNHead​ RPN_Header就是利用3x3conv预测类别和边界框偏移那部分,参数1是backbone输出的维度,第二个是anchor的定位信息.ReginProposalNetwork​ 对anchor_generator和rpn_header两部分整合,形成完整的RPN网络.包括之后的NMS过滤等操作.MultiScaleRoIAlign​ featmap_names是多尺度预测时各个预测层的名字，output_size是最后proposal经过roi_pool层后的大小,这里和论文保持一致(7x7)TwoMLPHead​ 这部分接在roi_pool后,对其结果做展平处理和连接两个全连接层.FastRCNNPredictor​ 两个全连接层预测proposal的类别和边界框偏移.RoIHeads​ 将前面几个RoI组件组合起来.包括MultiScaleRoIAlign/TwoMLPHead/FastRCNNPredictor.
Faster RCNN框架图图源： deep-learning-for-image-processing/pytorch_object_detection/faster_rcnn at master · WZMIAOMIAO/deep-learning-for-image-processing (github.com)源码主要内容​ Faster R-CNN源码阅读将从以下几个方面展开，详见其他文档DataSet网络框架GeneralizedRCNNTransformRPNPredict Header正负样本划分与采样Loss函数PostProcessChange Backbone(with FPN)环境配置Python 3.6/3.7/3.8Pytorch>=1.6.0pycocotoolsUbuntu or CentosUse Gpu to train modelmore details see requirements.txt文件结构 ├── backbone: 特征提取网络，可以根据自己的要求选择 ├── network_files: Faster R-CNN网络（包括Fast R-CNN以及RPN等模块） ├── train_utils: 训练验证相关模块（包括cocotools） ├── my_dataset.py: 自定义dataset用于读取VOC数据集 ├── train_mobilenet.py: 以MobileNetV2做为backbone进行训练 ├── train_resnet50_fpn.py: 以resnet50+FPN做为backbone进行训练 ├── train_multi_GPU.py: 针对使用多GPU的用户使用 ├── predict.py: 简易的预测脚本，使用训练好的权重进行预测测试 ├── validation.py: 利用训练好的权重验证/测试数据的COCO指标，并生成record_mAP.txt文件 ├── coco.json: coco数据集标签文件 └── pascal_voc_classes.json: pascal_voc标签文件预训练权重MobileNetV2 weights(下载后重命名为mobilenet_v2.pth，然后放到bakcbone文件夹下): https://download.pytorch.org/models/mobilenet_v2-b0353104.pthResnet50 weights(下载后重命名为resnet50.pth，然后放到bakcbone文件夹下): https://download.pytorch.org/models/resnet50-0676ba61.pthResNet50+FPN weights: https://download.pytorch.org/models/fasterrcnn_resnet50_fpn_coco-258fb6c6.pth注意在源码中修改对应模型的路径与名称数据集(以PASCAL VOC2012为例)Pascal VOC2012 train/val数据集下载地址：http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar如果不了解数据集或者想使用自己的数据集进行训练，请参考:https://b23.tv/F1kSCK使用ResNet50+FPN以及迁移学习在VOC2012数据集上得到的权重: 链接:https://pan.baidu.com/s/1ifilndFRtAV5RDZINSHj5w 提取码:dsz8训练确保提前准备好数据集确保提前下载好对应预训练模型权重若要训练mobilenetv2+fasterrcnn，直接使用train_mobilenet.py训练脚本若要训练resnet50+fpn+fasterrcnn，直接使用train_resnet50_fpn.py训练脚本若要使用多GPU训练，使用python -m torch.distributed.launch --nproc_per_node=8 --use_env train_multi_GPU.py指令,nproc_per_node参数为使用GPU数量如果想指定使用哪些GPU设备可在指令前加上CUDA_VISIBLE_DEVICES=0,3(例如我只要使用设备中的第1块和第4块GPU设备)CUDA_VISIBLE_DEVICES=0,3 python -m torch.distributed.launch --nproc_per_node=2 --use_env train_multi_GPU.py注意事项在使用训练脚本时，注意要将--data-path(VOC_root)设置为自己存放VOCdevkit文件夹所在的根目录由于带有FPN结构的Faster RCNN很吃显存，如果GPU的显存不够(如果batch_size小于8的话)建议在create_model函数中使用默认的norm_layer， 即不传递norm_layer变量，默认去使用FrozenBatchNorm2d(即不会去更新参数的bn层),使用中发现效果也很好。训练过程中保存的results.txt是每个epoch在验证集上的COCO指标，前12个值是COCO指标，后面两个值是训练平均损失以及学习率在使用预测脚本时，要将train_weights设置为你自己生成的权重路径。使用validation文件时，注意确保你的验证集或者测试集中必须包含每个类别的目标，并且使用时只需要修改--num-classes、--data-path和--weights-path即可，其他代码尽量不要改动