目录
前言
一、字符串距离的几种计算方法
1、Levenshtein 距离
2、Overlap Coefficient计算
3、Q-gram Matching
4、余弦相似性计算
二、基于余弦相似性的基地名称对比
1、加载百科中的基地信息列表
2、设置忽略词列表
3、将数据库地名和Excel进行对比
三、总结
前言
在之前的一篇漂亮国的全球的基地博客中,我们曾经对漂亮国的全球基地进行了一些梳理。博文中使用的数据来源,重点是参考以为博主分享的KML的数据,同时针对其国内的基地部署信息,我们从互联网百科的数据中搜寻到一些。其实拿到这两份数据的时候,是存在一些问题的,比如,KML的数据它的基地名称都是英文的,而在互联网上公开的数据中,良莠不齐,有的是中文的,有的是英文,有的中英文都有,而且可能还存在同样的英文,它的描述不一样的情况,比如下面的场景:
XXX Air Base 空军基地
XXX Air Station 航空站
可能不同的信息来源,其主要的名称对应的真实位置表示的是同一个。再比如漂亮国驻小日子的基地列表。
Idesuna Jima Air Range
FAC 6078 出砂岛炸射练习场 Idesuna Jima Range 训练 冲绳渡名喜村
上面一行是从KML中解析出来的基地名称,而下面是从某百科中得到的数据。 当我们将句子的影响词(当出现在军事基地的领域中的某些特定词,如Air、Base、Station、Range等这些词去掉后)其主要的地点就是同一个,通过对比主要关键词的相似性,为我们实现将两个地名进行对齐,甚至可以设定一个阈值,当两个字符串的相似性超过多少(比如超过90%)就认为这是同一个地点,从而可以实现将基地的中文名和所驻地的国家、城市等信息进行一一匹配。从而减少人工对比的工作。
随着现代技术的快速发展,文本相似度计算有很多种方法,甚至可以用大数据、自然语言处理还可以结合人工智能的方法来实现精确的文本相似性判断。但是本文不想实现那么复杂的架构,只想简单快速的解决两个地址的快速匹配问题。博客以Java编程为例,讲解了在Java中求解两个字符串的几种方法。通过求解编辑距离、Q-gram Matching、还有余弦相似性计算,通过对比不同的方法,调用Apache 的Common-text中基于余弦的字符相似性得到了比较比错的结果。算法比较简单,主要想说明两个字符串的不同距离的计算,对于更复杂的模型,大家感兴趣的可以搜索相关的科研论文来学习。
一、字符串距离的几种计算方法
为了比较字符的相似性,我们通过通过对比距离(把两个字符串的距离计算出来,当然这个距离的计算是有讲究的,具体的算法就是我们说的距离算法,不同的距离算法,得到的值是不一样的)。因此这里我们打算讲将在Java当中,有哪些距离的计算方法以及如何编写相应的代码。
1、Levenshtein 距离
计算将一个字符串转换为另一个字符串所需的最少单字符编辑操作(插入、删除或替换)的数量。在下面这个示例中,levenshteinDistance方法使用动态规划来计算两个字符串之间的Levenshtein Distance。calculateNormalizedLevenshteinDistance方法则使用这个距离和两个字符串长度的最大值来计算标准化的Levenshtein Distance。 main方法中给出了两个示例字符串s1和s2,并调用calculateNormalizedLevenshteinDistance方法来计算并打印它们的Normalized Levenshtein Distance。根据这个得分,你可以设定一个阈值来判断两个字符串是否相似。例如,如果Normalized Levenshtein Distance小于0.2(即80%的字符是匹配的),你可以认为这两个字符串是相似的。这个阈值可以根据你的具体需求进行调整。
package com.yelang.project;public class NormalizedLevenshteinDistance {// 计算Levenshtein Distancepublic static int levenshteinDistance(String s1, String s2) {int[][] dp = new int[s1.length() + 1][s2.length() + 1];for (int i = 0; i <= s1.length(); i++) {dp[i][0] = i;}for (int j = 0; j <= s2.length(); j++) {dp[0][j] = j;}for (int i = 1; i <= s1.length(); i++) {for (int j = 1; j <= s2.length(); j++) {int cost = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? 0 : 1;dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + cost);}}return dp[s1.length()][s2.length()];}// 计算Normalized Levenshtein Distancepublic static double calculateNormalizedLevenshteinDistance(String s1, String s2) {int distance = levenshteinDistance(s1, s2);int maxLength = Math.max(s1.length(), s2.length());return (double) distance / maxLength;}public static void main(String[] args) {String s1 = "Camp Humphreys";String s2 = "United States Army Garrison-Humphreys";double normalizedDistance = calculateNormalizedLevenshteinDistance(s1, s2);System.out.println("Normalized Levenshtein Distance: " + normalizedDistance);}
}
运行上述程序后,得到的结果如下,表示两个字符串的相似度是0.67:
Normalized Levenshtein Distance: 0.6756756756756757
2、Overlap Coefficient计算
Overlap Coefficient是通过计算两个字符串的最长公共子序列的长度来求两者的相似度的。这种算法有弊有利,在主要的关键词不同而基地的信息完一致的情况下,比如xxx air base 和xx1 air base,通过计算得到的结果一定及其相似,因为这两个字符串的最长公共序列很长啊,计算机看起来就像是完全一致的。
package com.yelang.project;
public class OverlapCoefficient {// 计算最长公共子序列的长度public static int longestCommonSubsequence(String s1, String s2) {int len1 = s1.length();int len2 = s2.length();int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {if (s1.charAt(i - 1) == s2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}}}return dp[len1][len2];}// 计算Overlap Coefficientpublic static double calculateOverlapCoefficient(String s1, String s2) {int lcsLength = longestCommonSubsequence(s1, s2);int minLength = Math.min(s1.length(), s2.length());return (double) lcsLength / minLength;}public static void main(String[] args) {String s1 = "Camp Humphreys";String s2 = "United States Army Garrison-Humphreys";double overlapCoefficient = calculateOverlapCoefficient(s1, s2);System.out.println("Overlap Coefficient: " + overlapCoefficient);}
}
运行以上程序后,得到的结果是:
Overlap Coefficient: 0.8571428571428571
它判定是85%的相似度,貌似看起来比第一种距离很接近真相,你换个词去测试就发现,这种算法其实很蠢。
3、Q-gram Matching
Q-gram Matching是一种基于子串的字符串相似度计算方法,其中Q表示子串的长度。对于两个字符串,计算它们共有的Q-gram(长度为Q的连续子串)的数量,然后将这个数量除以两个字符串中Q-gram数量较少的那个, 得到相似度的比例。下面给出它的Java实现:
package com.yelang.project;
import java.util.HashSet;
import java.util.Set;
public class QGramMatching {/*** 在这个示例中,calculateQGrams方法接受一个字符串和一个整数Q,然后返回该字符串所有可能的Q-gram的集合。calculateQGramSimilarity方法接受两个字符串和一个整数Q,计算它们的Q-gram集合,并找出共有的Q-gram数量,然后计算相似度。main方法中给出了两个示例字符串s1和s2,以及Q-gram的长度Q,并调用calculateQGramSimilarity方法来计算并打印它们的Q-gram相似度。请注意,Q-gram的长度Q是一个参数,可以根据具体应用场景进行调整。较短的Q值可以捕捉到更细粒度的相似性,而较长的Q值则可以捕捉到更宽泛的相似性。此外,相似度的阈值可以根据实际需求设定,以判断两个字符串是否相似。*/// 计算字符串的Q-gram集合public static Set<String> calculateQGrams(String s, int q) {Set<String> qGrams = new HashSet<>();for (int i = 0; i <= s.length() - q; i++) {qGrams.add(s.substring(i, i + q));}return qGrams;}// 计算两个字符串的Q-gram相似度public static double calculateQGramSimilarity(String s1, String s2, int q) {Set<String> qGrams1 = calculateQGrams(s1, q);Set<String> qGrams2 = calculateQGrams(s2, q);// 计算两个集合的交集Set<String> intersection = new HashSet<>(qGrams1);intersection.retainAll(qGrams2);// 计算相似度double similarity = intersection.size() / (double) Math.min(qGrams1.size(), qGrams2.size());return similarity;}public static void main(String[] args) {String s1 = "Camp Humphreys";String s2 = "United States Army Garrison-Humphreys";// Misawa Air Range<==>Misawa Air Base:similar = 0.9058216273156766// Gun Training Area<==>Tsuken Jima Training Area:similar = 0.9202830114233174int q = 3; // Q-gram的长度double qGramSimilarity = calculateQGramSimilarity(s1, s2, q);System.out.println("Q-gram Similarity (Q=" + q + "): " + qGramSimilarity);}
}
运行以上的程序,得到以下的执行结果:
Q-gram Similarity (Q=3): 0.5833333333333334
4、余弦相似性计算
使用词嵌入(如word2vec或BERT)将单词转换为向量,并计算向量之间的余弦相似度。这里基于Apache 的common-text来进行余弦计算,看结果如何,首先在Pom.xml中引入以下资源。
<!-- 求解字符相似性 -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>1.10.0</version>
</dependency>
然后在Java中实现以下方法,请注意,这里是一种比较简单的方法,更精准的实现还要结合实际场景进行优化。
/**
*计算两个字符串的相似性
* @param args1
* @param args2
* @return
*/
private Double getSimilar(String args1,String args2) {Map<CharSequence, Integer> map = Arrays.stream(args1.split("")).collect(Collectors.toMap(c -> c, c -> 1, Integer::sum));Map<CharSequence, Integer> map2 = Arrays.stream(args2.split("")).collect(Collectors.toMap(c -> c, c -> 1, Integer::sum));CosineSimilarity cosineSimilarity = new CosineSimilarity();Double similar = cosineSimilarity.cosineSimilarity(map, map2);return similar;
}
基于余弦的方法运行结果:
similar = 0.6726637889454885
篇幅有限,这里不做很全面的覆盖测试,基于上面的代码,感兴趣的朋友和同学可以自己去做测试,根据不同的样例,得到的结果阈值比较令人想象不到,建议结合真实的数据进行测试,你会发现问题更大。如果要把这个应用于实战的话,建议不要采用上述模型,推荐采用TF-IDF(Term Frequency-Inverse Document Frequency)和余弦计算的方式。最后进行综合评分,将不同的相似度计算方法(如基于编辑的算法、基于令牌的方法、基于语义的方法)结合起来,并为每种方法分配一个权重,然后计算加权平均值。这样子计算出来的结果比较准确。更深入的内容,大家闲暇的时候可以自主实现。
二、基于余弦相似性的基地名称对比
这里我们讲解如何把数据库中的基地列表信息和通过Excel表格整理的基地信息进行综合对比,通过余弦相似性和设置忽略词来提高准确度。作为一种参考的实现方案供大家参考。
1、加载百科中的基地信息列表
要想将数据库中的基地信息和百科中的数据进行对比,首先要做的就是数据采集。通过把从百科采集的数据转换成excel,然后应用程序解析Excel数据,通过计算计算英文名称的相似性来进行对比。基础数据采集的方式,可以根据大家的熟悉程度,可以通过爬虫的形式进行介绍,也可以把表格直接复制到Exclel,直接进行Excle的读取成List列表后进行对比即可。这里我们以第二种方案来进行讲解。以下是从百科中节选的Excel中的数据列表。
管理设施代码 | 基地英文名称 | 基地中文名称 | 用途 | 驻地国家中文名 | 驻地国家英文名 | 驻地城市中文名 |
FAC 1054 | Camp Chitose | 千岁营 | 通信 | 日本 | Japan | 北海道千岁市 |
FAC 2001 | Misawa Air Base | 三泽空军基地 | 航空基地 | 日本 | Japan | 青森县三泽市 |
FAC 3013 | Yokota Air Base | 横田空军基地 | 航空基地 | 日本 | Japan | 东京都福生市 |
FAC 3016 | Fuchu Communications Station | 府中通讯站 | 通信 | 日本 | Japan | 东京都府中市 |
FAC 3019 | Tama Hills Recreation Center | 多摩之丘育乐中心 | 娱乐 | 日本 | Japan | 东京都稻城市 |
FAC 3048 | South Camp Drake AFN Transmitter Site | 南德雷克营美军电台转播站 | 兵营 | 日本 | Japan | 埼玉县和光市 |
FAC 3048 | Camp Asaka | 朝霞营 | 兵营 | 日本 | Japan | 埼玉县和光市 |
FAC 3049 | Tokorozawa Communications Station | 所泽通讯站 | 通信 | 日本 | Japan | 埼玉县所泽市 |
FAC 3056 | Owada Communication Site | 大和田通讯站 | 通信 | 日本 | Japan | 埼玉新座市 |
FAC 3162 | Yugi Communication Site | 由木通讯站 | 通信 | 日本 | Japan | 东京都八王子市 |
FAC 4100 | Sofu Communication Site | 祖生通讯站 | 通信 | 日本 | Japan | 山口县岩国市 |
FAC 5001 | Itazuke Auxiliary Airfield | 板付辅助飞行场 | 空运货站 | 日本 | Japan | 福冈市博多区 |
FAC 5073 | Sefurisan Liaison Annex | 脊振山辅助联络站 | 通信 | 日本 | Japan | 佐贺县神埼市 |
FAC 5091 | Tsushima Communication Site | 对马通讯站 | 通信 | 日本 | Japan | 长崎对马市 |
FAC 6004 | Okuma Rest Center | 奥间休息中心 | 娱乐 | 日本 | Japan | 冲绳国头村 |
FAC 6006 | Yaedake Communication Site | 八重岳通讯站 | 通信 | 日本 | Japan | 冲绳本部町 |
FAC 6022 | Kadena Ammunition Storage Area | 嘉手纳弹药储存区 | 存储 | 日本 | Japan | 冲绳恩纳村 |
FAC 6037 | Kadena Air Base | 嘉手纳空军基地 | 航空基地 | 日本 | Japan | 冲绳嘉手纳 |
FAC 6077 | Tori Shima Range | 鸟岛炸射练习场 | 训练 | 日本 | Japan | 冲绳久米岛 |
FAC 6078 | Idesuna Jima Range | 出砂岛炸射练习场 | 训练 | 日本 | Japan | 冲绳渡名喜村 |
FAC 6080 | Kume Jima Range | 久米岛炸射练习场 | 训练 | 日本 | Japan | 冲绳久米岛町 |
FAC 2070 | Shariki Communication Site | 车力通讯站 | 通信 | 日本 | Japan | 青森县津轻市 |
FAC 3004 | Akasaka Press Center (Hardy Barracks) | 赤坂新闻中心(哈迪军营) | 办公室 | 日本 | Japan | 东京都港区 |
FAC 3067 | Yokohama North Dock | 横滨北坞 | 港口设施 | 日本 | Japan | 神奈川县横滨市 |
上面只是一个示例,实际的表格有85条数据。
我们需要将Excel中的数据进行导入到应用程序中,首先需要定义一个实体类:
package com.yelang.project.extend.militarytopics.domain;
import java.io.Serializable;
import com.yelang.framework.aspectj.lang.annotation.Excel;
import com.yelang.framework.aspectj.lang.annotation.Excel.Type;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class WebSiteMilitaryBase implements Serializable{private static final long serialVersionUID = -4620277223946004351L;@Excel(name = "设施管理代码", type = Type.IMPORT)private String manageCode;@Excel(name = "基地英文名称")private String enName;@Excel(name = "基地中文名称")private String cnName;@Excel(name = "用途")private String useTo;@Excel(name = "驻地国家中文名")private String cnCountry;@Excel(name = "驻地国家英文名")private String enCountry;@Excel(name = "驻地城市中文名")private String cnCity;
}
然后定义Excel的读取方法,关键代码如下所示:
public void readJapanData() {try {File file = new File("C:/Users/Administrator/Desktop/基地/驻日美军基地.xlsx");FileInputStream fis = new FileInputStream(file);ExcelUtil<WebSiteMilitaryBase> util = new ExcelUtil<WebSiteMilitaryBase>(WebSiteMilitaryBase.class);List<WebSiteMilitaryBase> dataList = util.importExcel(fis);System.out.println(dataList.size());for(WebSiteMilitaryBase base : dataList) {System.out.println(base);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}
}
运行一下代码,测试一下是否读取成功,如果在控制台中看到以下信息说明解析成功。
2、设置忽略词列表
其实忽略词和权重打分的作用是差不多的,忽略词就是在字符串中没有什么影响的词,这里需要注意的是要结合场景来说明。比如当前是均事领域,类似与Air base等这些在相关基地中的字符串中就可以忽略掉。忽略词设置好之后,在进行字符串的相似性计算时,需要把两个字符串进行忽略词替换。关键代码如下:
/**
* 去掉需要忽略的关键词
* @param source
* @return
*/
private String ignoreKeyWords(String source) {// Barracks 军营 Service Area 服务中心 Air Station 航空站 Air Base 空军基地 Camp 营地 Recreation Center 娱乐中心 Communications Station 通信站// Transmitter Site 发射站 Communication Site 通信站点 Training 训练 Airfield机场 Ammunition Depot 弹药库 Port 港口 Pier 码头 Dependent Housing 家属区// Depot 仓库 Landing Field 着陆场 Corps 兵团 Naval 海军String [] keyWords = {"Barracks","Service Area","Station","Air","Base","Camp","Area","Recreation Center","Communications","Station","Transmitter","Site","Communication","Training","Airfield","Annex","Ammunition","Port","Pier","Dependent","Housing","Depot","Storage","Range","Center","Landing Field","Corps","Naval","Marine"};for(String keyword : keyWords) {source = source.replace(keyword, "").trim();}return source;
}
3、将数据库地名和Excel进行对比
在这里 ,我们需要将需要对比的数据从数据库中查询出来,然后跟Excel表格中的进行对比。经过忽略词的设置后,我们对比最后的计算结果,最后得到两个字符串的相似性。当两个字符串的相似度大于90%,我们即可认为是统一地点,当然这里最好需要人工审核。
@Test
public void evalJapanCosineSimilarity() {QueryWrapper<UsaMilitaryBase> queryWrapper = new QueryWrapper<UsaMilitaryBase>();queryWrapper.isNull("cn_name");//查询基地中文名为空的数据List<UsaMilitaryBase> dbList = this.militaryBaseService.list(queryWrapper);System.out.println(dbList.size());try {File file = new File("C:/Users/Administrator/Desktop/基地/驻日美军基地.xlsx");FileInputStream fis = new FileInputStream(file);ExcelUtil<WebSiteMilitaryBase> util = new ExcelUtil<WebSiteMilitaryBase>(WebSiteMilitaryBase.class);List<WebSiteMilitaryBase> excelDataList = util.importExcel(fis);System.out.println(excelDataList.size());int index = 0;for(WebSiteMilitaryBase base : excelDataList) {for(UsaMilitaryBase dbBase : dbList) {String dbEnName = dbBase.getEnName();dbEnName = ignoreKeyWords(dbEnName);String excelEnName = base.getEnName();excelEnName = ignoreKeyWords(excelEnName);Double similar = this.getSimilar(dbEnName, excelEnName);if(similar >= 0.85) {System.out.println(dbEnName + "<==>" + excelEnName + "\t similar = " + similar);System.out.println(base);index ++;}else {continue;}}}System.out.println(index);} catch (FileNotFoundException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}
}
下面我们来执行上述的代码,看看最终符合我们条件的有多少?
Misawa<==>Misawa similar = 0.9999999999999998
WebSiteMilitaryBase(manageCode=FAC 2001, enName=Misawa Air Base, cnName=三泽空军基地, useTo=航空基地, cnCountry=日本, enCountry=Japan, cnCity=青森县三泽市)
Idesuna Jima<==>Idesuna Jima similar = 1.0
WebSiteMilitaryBase(manageCode=FAC 6078, enName=Idesuna Jima Range, cnName=出砂岛炸射练习场, useTo=训练, cnCountry=日本, enCountry=Japan, cnCity=冲绳渡名喜村)
Kawakami<==>Kawakami similar = 1.0
WebSiteMilitaryBase(manageCode=FAC 4083, enName=Kawakami Ammunition Depot, cnName=川上弹药库, useTo=存储, cnCountry=日本, enCountry=Japan, cnCity=广岛县东广岛市)
Hiro<==>Hiro similar = 1.0
WebSiteMilitaryBase(manageCode=FAC 4084, enName=Hiro Ammunition Depot, cnName=广弹药库, useTo=存储, cnCountry=日本, enCountry=Japan, cnCity=广岛县吴市)
Kure 6<==>Kure No.6 similar = 0.8660254037844386
WebSiteMilitaryBase(manageCode=FAC 4152, enName=Kure Pier No.6, cnName=吴市6号码头, useTo=港口设施, cnCountry=日本, enCountry=Japan, cnCity=广岛县吴市)
Torii<==>Torii similar = 0.9999999999999999
WebSiteMilitaryBase(manageCode=FAC 6036, enName=Torii Communications Station, cnName=鸟居通讯站, useTo=通信, cnCountry=日本, enCountry=Japan, cnCity=冲绳县读谷村)
Naha<==>Naha similar = 1.0000000000000002
WebSiteMilitaryBase(manageCode=FAC 6064, enName=Naha Port, cnName=那霸港湾设施, useTo=港口设施, cnCountry=日本, enCountry=Japan, cnCity=冲绳县那霸市)
Fleet Activities Chinhae KS<==>United States Fleet Activities Yokosuka similar = 0.8653323061113575
WebSiteMilitaryBase(manageCode=FAC 3099, enName=United States Fleet Activities Yokosuka, cnName=横须贺美国舰队基地, useTo=港口设施, cnCountry=日本, enCountry=Japan, cnCity=神奈川县横须贺市)
Fleet Activities Chinhae KS (Yechon)<==>United States Fleet Activities Sasebo similar = 0.8560516500077061
WebSiteMilitaryBase(manageCode=FAC 5029, enName=United States Fleet Activities Sasebo, cnName=佐世保美国舰队基地, useTo=港口设施, cnCountry=日本, enCountry=Japan, cnCity=长崎县佐世保市)
Fleet Activities Chinhae KS<==>United States Fleet Activities Sasebo similar = 0.8994681014529727
WebSiteMilitaryBase(manageCode=FAC 5029, enName=United States Fleet Activities Sasebo, cnName=佐世保美国舰队基地, useTo=港口设施, cnCountry=日本, enCountry=Japan, cnCity=长崎县佐世保市)
Hiro<==>Hario similar = 0.8944271909999159
WebSiteMilitaryBase(manageCode=FAC 5119, enName=Hario Dependent Housing Area, cnName=针尾军眷住宅区, useTo=住宅, cnCountry=日本, enCountry=Japan, cnCity=长崎县佐世保市)
Fleet Activities Chinhae KS (Yongsan Garrison)<==>Makiminato ( Kinser) similar = 0.8663164754169591
WebSiteMilitaryBase(manageCode=FAC 6056, enName=Makiminato Service Area (Camp Kinser), cnName=牧港补给地区(金瑟营), useTo=后勤, cnCountry=日本, enCountry=Japan, cnCity=冲绳县浦添市)
San Vito Dei Normanni<==>Makiminato ( Kinser) similar = 0.8524007579586305
WebSiteMilitaryBase(manageCode=FAC 6056, enName=Makiminato Service Area (Camp Kinser), cnName=牧港补给地区(金瑟营), useTo=后勤, cnCountry=日本, enCountry=Japan, cnCity=冲绳县浦添市)
13
可以很直观的看到,经过计算,有的地名已经完全匹配,比如Hiro<==>Hiro similar = 1.0和Idesuna Jima<==>Idesuna Jima similar = 1.0。这些数据可以认为是一个地点了。
三、总结
以上就是本文的主要内容,博客以Java编程为例,讲解了在Java中求解两个字符串的几种方法。通过求解编辑距离、Q-gram Matching、还有余弦相似性计算,通过对比不同的方法,调用Apache 的Common-text中基于余弦的字符相似性得到了比较比错的结果。最后讲解了一个实际的案例,将之前我们采集的漂亮数据库数据和百科的数据进行对齐。为下一步做数据关联和挖掘打下坚实的基础。行文仓促,定有许多的不足之处,如有任何不当或者表达不准确的地方,还恳请各位专家和朋友在评论区留言平指正,不胜感激。