知识表示学习

网络上已经存在了大量知识库(KBs),比如OpenCyc,WordNet,Freebase,Dbpedia等等。

这些知识库是为了各种各样的目的建立的,因此很难用到其他系统上面。为了发挥知识库的图(graph)性,也为了得到统计学习(包括机器学习和深度学习)的优势,我们需要将知识库嵌入(embedding)到一个低维空间里(比如10、20、50维)。我们都知道,获得了向量后,就可以运用各种数学工具进行分析。它为许多知识获取任务和下游应用铺平了道路。
总的来说,废话这么多,所谓知识表示学习,就是将知识库给映射成向量,同时满足属于同一个三元组的(h,t,l)满足h+l≈t,而不是一个三元组的不满足这个条件。

TransE思路

TranE是一篇Bordes等人2013年发表在NIPS上的文章提出的算法。它的提出,是为了解决多关系数据(multi-relational data)的处理问题。我们现在有很多很多的知识库数据knowledge bases (KBs),比如Freebase、 Google Knowledge Graph 、 GeneOntology等等。
TransE的直观含义,就是TransE基于实体和关系的分布式向量表示,将每个三元组实例(head,relation,tail)中的关系relation看做从实体head到实体tail的翻译(其实我一直很纳闷为什么叫做translating,其实就是向量相加),通过不断调整h、r和t(head、relation和tail的向量),使(h + r) 尽可能与 t 相等,即 h + r = t。

损失函数是

TransE代码逻辑梳理

首先注明,该代码不是出自我手,但由于最近需要使用并修改TransE,故从github上找到一个还不错的TransE实现,对其进行阅读,并梳理其逻辑,为后续工作做好铺垫。贴上其github链接,感谢前人辛苦付出。https://github.com/wuxiyu/transE/blob/master/tranE.py
下面对其代码进行分析。
首先,这里将整个代码封装成了一个类,该类的构造方法(由于平常用的语言是java,python只当做工具语言,没有系统学过语法,所以用词可能不当,见谅)中需要的参数如下所示:

        :param entityList: 实体列表,读取文本文件,实体+id:param relationList: 关系列表,读取文本文件,关系+id:param tripleList: 三元组列表,读取文本文件,实体+实体+关系:param margin: 表示正负样本之间的间距,是一个超参数,也就是公式中Loss里的γ:param learingRate: 学习率,其实就是梯度下降中的步长:param dim: 向量维度,即h,t,l向量的维度是1*dim,因为最终我们所有的实体和关系都是要表示为向量:param L1: 距离公式采用矩阵1范数还是矩阵2范数

首先,我们将目光放到main方法,从main方法开始整个TransE的旅程。

    dirEntity = "C:\\data\\entity2id.txt"entityIdNum, entityList = openDetailsAndId(dirEntity)dirRelation = "C:\\data\\relation2id.txt"relationIdNum, relationList = openDetailsAndId(dirRelation)dirTrain = "C:\\data\\train.txt"tripleNum, tripleList = openTrain(dirTrain)print("打开TransE")transE = TransE(entityList,relationList,tripleList, margin=1, dim = 100)print("TranE初始化")transE.initialize()transE.transE(15000)transE.writeRelationVector("c:\\relationVector.txt")transE.writeEntilyVector("c:\\entityVector.txt")

首先是通过三个Open方法分别获取实体数量和实体列表、关系总数量和关系列表、三元组总数量和三元组列表。获取需要的数据。
例如,其中entityList是一个list,其样式就为[05451384,04958634,00620424,....];
而relationList样式为["_member_of_domain_topic","_member_meronym"...];
而tripleList例如[(03964744,04371774,_hyponym), (....)....],其中全是三元组,都是(h,t,l)的格式。
至于那些Num们,都只是用于计数?并没发现用在哪里,也不用管

然后就是实例化TransE这个类了,将实体列表,关系列表,和三元组列表放进去,设置间距γ为1(这个是超参数,可以调),然后对于输出向量,其维度设为100(这个也可以自己指定)。

之后调用transE的initialize()方法,进行初始化。这里初始化具体做了什么呢?答曰初始化向量,构建字典集合,分别来装实体向量们和关系向量们。那么问题就来了,这个向量如何生成呢,之前我们手里只有05451384这串数字来代表实体,但是,并没有向量啊。这里采用的方式就是···随机生成,对于个100维的向量,随机生成它,方式为每一个数字都是在-6/(dim**0.5), 6/(dim**0.5)之间随机生成,然后构成一个100个元素的列表,即代表这个实体的向量,同时,将这个实体和其对应的随机生成的向量放入新创建的字典entityVectorList中去,同理对于关系也是如此操作。当然,在向量生成之后对其做一个归一化,保证它是单位向量,做法就是每个元素除以元素总和的平方和的开平方,具体见norm方法,这个很简单。

        entityVectorList = {}relationVectorList = {}for entity in self.entityList:n = 0entityVector = []while n < self.dim:ram = init(self.dim)entityVector.append(ram) #注意到这里的ram和entity是毫无关系的,是一个随机的值,所以这里append之后,就是一个dim个元素的列表n += 1entityVector = norm(entityVector)#归一化entityVectorList[entity] = entityVector

至此,我们便为每个关系和实体生成了一个向量,向量是一个100维的列表。
然后我们将entityList和relationList赋值成这两个字典,也就是我们最初的entityList是列表,而经过初始化之后却变成了字典,字典的样式为{实体名:对应向量,…}

之后,下一步就是进行训练了。调用transE的transE()方法,其中输入的15000意为迭代的次数。

        for cycleIndex in range(cI):#迭代cI次Sbatch = self.getSample(150) #随机获取150个三元组Tbatch = []#元组对(原三元组,打碎的三元组)的列表 :[((h,r,t),(h',r,t'))]for sbatch in Sbatch:#遍历获取到的元组,并获取它们的打碎三元组,从而获得<=150个元组对(防止重复)tripletWithCorruptedTriplet = (sbatch, self.getCorruptedTriplet(sbatch)) #将sbatch传入,获取打碎的三元组,然后构成一个元组对if(tripletWithCorruptedTriplet not in Tbatch):Tbatch.append(tripletWithCorruptedTriplet)self.update(Tbatch)#对整个集合进行更新if cycleIndex % 100 == 0:print("第%d次循环"%cycleIndex)print(self.loss)self.writeRelationVector("c:\\relationVector.txt")self.writeEntilyVector("c:\\entityVector.txt")self.loss = 0

这里cI参数就是迭代次数。
首先是调用getSample()方法,该方法作用为在tripleList中随机选取size个三元组并返回。所以这里的Sbatch就是随机获取的150个三元组。
然后Tbatch是一个新创建的列表,用于存储元组(元组是tuple,是python中一种数据结构,而三元组是知识图谱的一种结构,不要搞乱了),其中的样式为[((h,r,t),(h',r,t'))]
下面就是对Sbatch进行遍历,遍历每一个三元组,调用getCorruptedTriplet()方法来获取某个三元组的打碎的三元组,也就是在上面算法中提到的,对一个三元组,我们假定它是h+l=t的,此时我们创建一个范例,一个绝对不满足假设的,如何创建呢,任意用别的h或t来替换掉我们这里的h或t,从而得到一个错误的三元组,即打碎的三元组(我也不知道为啥叫打碎,不过挺有意思哈哈哈)。将打碎的三元组和正确的三元组放在一起组成一个新的元组,然后将其放入Tbatch列表中,当然这里有个去重的判断,很简单,就不说了哈。
下面的操作就是最重要的了,进行更新。
首先,要明确,这里的更新,只是针对我们随机选出来的150个三元组进行更新。然后,更新什么呢?当然是更新它们的向量,所以假设我们的h,t都互不相同,那么这里最多也就更新了300个实体的向量,(关系因为数量肯定没那么多,就不举例了)。然后更新的方式是什么,那就是通过梯度下降法来求得损失函数的最小值,从而获得一个最优的向量们。

好,下面我们来看这个更新操作,这里是调用update()方法,将刚才的Tbatch传入。
首先在该方法的开始,进行了两次拷贝,将实体列表(其实是实体-向量字典)和关系列表分别进行拷贝,目的是为了之后更新,不相互影响。然后关于deepcopy和copy的区别大家可以去查一下,简单来说就是前者copy的更彻底,列表或字典中的每个元素都单独拷贝了一份。
然后便是遍历这里的Tbatch,对每个元组进行操作。
首先是前面是一长串的赋值操作,选其中一个来说明。

headEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][0]]

首先我们知道tripletWithCorruptedTriplet的格式是这样的[((h,r,t),(h',r,t'))],那[0][0]就是获取其中的h实体,然后根据h实体在entityList字典中获取其对应的向量。如此便是,其余也皆是同理。
然后根据L1参数是否为true来使用矩阵1范数或矩阵2范数,因为不同范数它的梯度是不一样的。
我们接下来矩阵2范数即L1==false来进行说明。此时进行计算Loss损失函数的值,根据公式γ+d(h+l,t)−d(h′+l,t′)\gamma+d(h+l,t)-d(h'+l,t')γ+d(h+l,t)d(h+l,t)来计算,当然这里的d(h+l,t)d(h+l,t)d(h+l,t)要进行展开,就是普通的距离公式,展开之后的Loss函数为γ+(h+l−t)2−(h′+l−t′)2\gamma+(h+l-t)^{2}-(h'+l-t')^{2}γ+(h+lt)2(h+lt)2,等一下,是不是主要到这里和之前说的有些不同,对的,这里没有求和符号,因为这里相当于是把总的Loss给分开算的,所以没有求和符号了。累加起来便有。
然后当这个损失函数的值>0时,才进行更新,否则不进行更新。这里解释一下为什么这么操作。如此操作的原因在于我们喜欢正确的三元组的向量们满足h+l≈t,而打碎的三元组不满足,则正确三元组距离应该接近于0,而错误的应为一个不小的正值(因为是矩阵2范数),然后此时必然有损失函数值e<0的情况。当然,你也会说那假如两个值都不小,刚好前者小于后者呢,这种情况少,且没必要要求这么高,毕竟可以近似,同时这是算法层级的问题,这里不再讨论。
当e>0时,我们进行更新,这里更新的操作就是一个很简单的梯度下降方法。下面来介绍一下。首先损失函数Loss是γ+(h+l−t)2−(h′+l−t′)2\gamma+(h+l-t)^{2}-(h'+l-t')^{2}γ+(h+lt)2(h+lt)2,我们对其h进行求导得其梯度,则其结果是∂∂h=2(h+l−t)\frac{\partial }{\partial h} = 2(h+l-t)h=2(h+lt),则h更新为h∗=h−u∗∂∂h=h−u∗2∗(h+l−t)=h+u∗2∗(t−h−l)h^{*}=h-u*\frac{\partial }{\partial h}=h-u*2*(h+l-t)=h+u*2*(t-h-l)h=huh=hu2(h+lt)=h+u2(thl),这里的u是梯度下降的步长,也就是上面提到的学习率,同理,t的更新也是一样,t∗=t−u∗2∗(t−h−l)t^{*}=t-u*2*(t-h-l)t=tu2(thl),然后同理l也是一样l∗=l+u∗2∗(t−h−l)−u∗2∗(t′−h′−l)l^{*}=l+u*2*(t-h-l)-u*2*(t'-h'-l)l=l+u2(thl)u2(thl)
如此,进行更新,然后进行归一化,最终更新总的entityList和relationList。

至此,更新过程结束,至于后面的向量写入文件这里就不赘述了。

完整代码

这里代码我都加上了较为详细的注释,可以结合上面的代码梳理进行理解。

from random import uniform, sample
from numpy import *
from copy import deepcopyclass TransE:def __init__(self, entityList, relationList, tripleList, margin = 1, learingRate = 0.00001, dim = 10, L1 = True):''':param entityList: 实体列表,读取文本文件,实体+id:param relationList: 关系列表,读取文本文件,关系+id:param tripleList: 三元组列表,读取文本文件,实体+实体+关系:param margin: gamma,目标函数的常数:param learingRate: 学习率:param dim: 向量维度,也就是h,t,l向量的维度是1*dim:param L1: 距离公式'''self.margin = marginself.learingRate = learingRateself.dim = dim#向量维度self.entityList = entityList#一开始,entityList是entity的list;初始化后,变为字典,key是entity,values是其向量(使用narray)。self.relationList = relationList#理由同上self.tripleList = tripleList#理由同上self.loss = 0self.L1 = L1def initialize(self):'''初始化向量'''entityVectorList = {}relationVectorList = {}for entity in self.entityList:n = 0entityVector = []while n < self.dim:ram = init(self.dim)#初始化的范围entityVector.append(ram) #注意到这里的ram和entity是毫无关系的,是一个随机的值,所以这里append之后,就是一个dim个元素的列表n += 1entityVector = norm(entityVector)#归一化entityVectorList[entity] = entityVectorprint("entityVector初始化完成,数量是%d"%len(entityVectorList))for relation in self. relationList:n = 0relationVector = []while n < self.dim:ram = init(self.dim)#初始化的范围relationVector.append(ram)n += 1relationVector = norm(relationVector)#归一化relationVectorList[relation] = relationVectorprint("relationVectorList初始化完成,数量是%d"%len(relationVectorList))self.entityList = entityVectorListself.relationList = relationVectorListdef transE(self, cI = 20):print("训练开始")for cycleIndex in range(cI):#迭代cI次Sbatch = self.getSample(150) #随机获取150个三元组Tbatch = []#元组对(原三元组,打碎的三元组)的列表 :{((h,r,t),(h',r,t'))}for sbatch in Sbatch:#遍历获取到的元组,并获取它们的打碎三元组,从而获得<=150个元组对(防止重复)tripletWithCorruptedTriplet = (sbatch, self.getCorruptedTriplet(sbatch)) #将sbatch传入,获取打碎的三元组,然后构成一个元组对if(tripletWithCorruptedTriplet not in Tbatch):Tbatch.append(tripletWithCorruptedTriplet)self.update(Tbatch)#对整个集合进行更新if cycleIndex % 100 == 0:print("第%d次循环"%cycleIndex)print(self.loss)self.writeRelationVector("c:\\relationVector.txt")self.writeEntilyVector("c:\\entityVector.txt")self.loss = 0def getSample(self, size):'''随机选取部分三元关系 sbatch:param size::return:'''return sample(self.tripleList, size) #从tripleList中随机获取size个元素def getCorruptedTriplet(self, triplet):'''training triplets with either the head or tail replaced by a random entity (but not both at the same time)随机替换三元组的实体,h和t中任意一个被替换,但不同时替换。也就是构建损坏的三元组集合:param triplet::return corruptedTriplet:'''i = uniform(-1, 1)if i < 0:#小于0,打坏三元组的第一项while True:entityTemp = sample(self.entityList.keys(), 1)[0]if entityTemp != triplet[0]:breakcorruptedTriplet = (entityTemp, triplet[1], triplet[2])else:#大于等于0,打坏三元组的第二项while True:entityTemp = sample(self.entityList.keys(), 1)[0]if entityTemp != triplet[1]:breakcorruptedTriplet = (triplet[0], entityTemp, triplet[2])return corruptedTripletdef update(self, Tbatch):'''进行更新,更新的过程就是一个梯度下降:param Tbatch::return:'''copyEntityList = deepcopy(self.entityList) #copy和deepcopy的区别在于,copy只拷贝整体,若局部改变,则拷贝整体的局部也改变,而deepcopy则全部拷贝过去copyRelationList = deepcopy(self.relationList)for tripletWithCorruptedTriplet in Tbatch:#遍历整个元组,最多迭代150次# 这里的索引很好理解((h,t,l)(h',t',l)) 但是copyEntityList[h]# 懂了,这里EntityList是类似于字典的,有id与向量这两个东西,所以是输入id,获取向量headEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][0]]#tripletWithCorruptedTriplet是原三元组和打碎的三元组的元组tupletailEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][1]]relationVector = copyRelationList[tripletWithCorruptedTriplet[0][2]]headEntityVectorWithCorruptedTriplet = copyEntityList[tripletWithCorruptedTriplet[1][0]]tailEntityVectorWithCorruptedTriplet = copyEntityList[tripletWithCorruptedTriplet[1][1]]#下面的也是一模一样,感觉只是为了备份一份,进行比较headEntityVectorBeforeBatch = self.entityList[tripletWithCorruptedTriplet[0][0]]#tripletWithCorruptedTriplet是原三元组和打碎的三元组的元组tupletailEntityVectorBeforeBatch = self.entityList[tripletWithCorruptedTriplet[0][1]]relationVectorBeforeBatch = self.relationList[tripletWithCorruptedTriplet[0][2]]headEntityVectorWithCorruptedTripletBeforeBatch = self.entityList[tripletWithCorruptedTriplet[1][0]]tailEntityVectorWithCorruptedTripletBeforeBatch = self.entityList[tripletWithCorruptedTriplet[1][1]]if self.L1:#这L1啥意思···哦是L1范数distTriplet = distanceL1(headEntityVectorBeforeBatch, tailEntityVectorBeforeBatch, relationVectorBeforeBatch)distCorruptedTriplet = distanceL1(headEntityVectorWithCorruptedTripletBeforeBatch, tailEntityVectorWithCorruptedTripletBeforeBatch ,  relationVectorBeforeBatch)else:#否则L2范数distTriplet = distanceL2(headEntityVectorBeforeBatch, tailEntityVectorBeforeBatch, relationVectorBeforeBatch)distCorruptedTriplet = distanceL2(headEntityVectorWithCorruptedTripletBeforeBatch, tailEntityVectorWithCorruptedTripletBeforeBatch ,  relationVectorBeforeBatch)eg = self.margin + distTriplet - distCorruptedTriplet #损失函数 就跟论文上公式是一样的if eg > 0: #[function]+ 是一个取正值的函数  似乎是只有大于0时才进行更新,想一下,也确实,因为前一个距离应该为0,后一个不为0,然后,0-正<0则不用改,正-正>则需要改self.loss += egif self.L1:#这个学习率有点懵tempPositive = 2 * self.learingRate * (tailEntityVectorBeforeBatch - headEntityVectorBeforeBatch - relationVectorBeforeBatch)tempNegtative = 2 * self.learingRate * (tailEntityVectorWithCorruptedTripletBeforeBatch - headEntityVectorWithCorruptedTripletBeforeBatch - relationVectorBeforeBatch)tempPositiveL1 = []tempNegtativeL1 = []for i in range(self.dim):#不知道有没有pythonic的写法(比如列表推倒或者numpy的函数)?if tempPositive[i] >= 0:tempPositiveL1.append(1)else:tempPositiveL1.append(-1)if tempNegtative[i] >= 0:tempNegtativeL1.append(1)else:tempNegtativeL1.append(-1)tempPositive = array(tempPositiveL1)  tempNegtative = array(tempNegtativeL1)else:#这里学习率就是y?对,应该这里的学习率就是梯度下降中的步长#然后括号里是t-h-ltempPositive = 2 * self.learingRate * (tailEntityVectorBeforeBatch - headEntityVectorBeforeBatch - relationVectorBeforeBatch)tempNegtative = 2 * self.learingRate * (tailEntityVectorWithCorruptedTripletBeforeBatch - headEntityVectorWithCorruptedTripletBeforeBatch - relationVectorBeforeBatch)#进行更新headEntityVector = headEntityVector + tempPositive #h* = h + 增量tailEntityVector = tailEntityVector - tempPositive #t* = t - 增量relationVector = relationVector + tempPositive - tempNegtative #l* = l +y*2(t-h-l) -y*2(t'-h'-l)headEntityVectorWithCorruptedTriplet = headEntityVectorWithCorruptedTriplet - tempNegtative #同理tailEntityVectorWithCorruptedTriplet = tailEntityVectorWithCorruptedTriplet + tempNegtative #同理#只归一化这几个刚更新的向量,而不是按原论文那些一口气全更新了copyEntityList[tripletWithCorruptedTriplet[0][0]] = norm(headEntityVector)copyEntityList[tripletWithCorruptedTriplet[0][1]] = norm(tailEntityVector)copyRelationList[tripletWithCorruptedTriplet[0][2]] = norm(relationVector)copyEntityList[tripletWithCorruptedTriplet[1][0]] = norm(headEntityVectorWithCorruptedTriplet)copyEntityList[tripletWithCorruptedTriplet[1][1]] = norm(tailEntityVectorWithCorruptedTriplet)self.entityList = copyEntityList #进行更新self.relationList = copyRelationListdef writeEntilyVector(self, dir):print("写入实体")entityVectorFile = open(dir, 'w')for entity in self.entityList.keys():entityVectorFile.write(entity+"\t")entityVectorFile.write(str(self.entityList[entity].tolist()))entityVectorFile.write("\n")entityVectorFile.close()def writeRelationVector(self, dir):print("写入关系")relationVectorFile = open(dir, 'w')for relation in self.relationList.keys():relationVectorFile.write(relation + "\t")relationVectorFile.write(str(self.relationList[relation].tolist()))relationVectorFile.write("\n")relationVectorFile.close()def init(dim):'''向量初始化,随机生成值:param dim: 维度:return:'''return uniform(-6/(dim**0.5), 6/(dim**0.5)) #uniform(a, b)#随机生成a,b之间的数,左闭右开def distanceL1(h, t ,r):s = h + r - tsum = fabs(s).sum()return sumdef distanceL2(h, t, r):'''这里是对向量进行操作的,所以有个sum:param h: 这里的都是向量:param t::param r::return:'''s = h + r - tsum = (s*s).sum()return sumdef norm(list):'''归一化:param 向量:return: 向量/向量的能量'''var = linalg.norm(list)i = 0while i < len(list):list[i] = list[i]/vari += 1return array(list)def openDetailsAndId(dir,sp="\t"):idNum = 0list = []with open(dir) as file:lines = file.readlines()for line in lines:DetailsAndId = line.strip().split(sp)list.append(DetailsAndId[0])idNum += 1return idNum, listdef openTrain(dir,sp="\t"):num = 0list = []with open(dir) as file:lines = file.readlines()for line in lines:triple = line.strip().split(sp)if(len(triple)<3):continuelist.append(tuple(triple))num += 1return num, listif __name__ == '__main__':dirEntity = "C:\\data\\entity2id.txt"entityIdNum, entityList = openDetailsAndId(dirEntity)dirRelation = "C:\\data\\relation2id.txt"relationIdNum, relationList = openDetailsAndId(dirRelation)dirTrain = "C:\\data\\train.txt"tripleNum, tripleList = openTrain(dirTrain)print("打开TransE")transE = TransE(entityList,relationList,tripleList, margin=1, dim = 100)print("TranE初始化")transE.initialize()transE.transE(15000)transE.writeRelationVector("c:\\relationVector.txt")transE.writeEntilyVector("c:\\entityVector.txt")

参考资料

https://blog.csdn.net/u011274209/article/details/50991385
https://blog.csdn.net/jiayalu/article/details/100543909
https://github.com/wuxiyu/transE/blob/master/tranE.py

知识表示学习 TransE 代码逻辑梳理 超详细解析相关推荐

  1. ResNeXt代码复现+超详细注释(PyTorch)

    ResNeXt就是一种典型的混合模型,由基础的Inception+ResNet组合而成,本质在gruops分组卷积,核心创新点就是用一种平行堆叠相同拓扑结构的blocks代替原来 ResNet 的三层 ...

  2. SENet代码复现+超详细注释(PyTorch)

    在卷积网络中通道注意力经常用到SENet模块,来增强网络模型在通道权重的选择能力,进而提点.关于SENet的原理和具体细节,我们在上一篇已经详细的介绍了:经典神经网络论文超详细解读(七)--SENet ...

  3. Android技能树 — 网络小结(6)之 OkHttp超超超超超超超详细解析

    前言: 本文也做了一次标题党,哈哈,其实写的还是很水,各位原谅我O(∩_∩)O. 介于自己的网络方面知识烂的一塌糊涂,所以准备写相关网络的文章,但是考虑全部写在一篇太长了,所以分开写,希望大家能仔细看 ...

  4. 单片机数字钟(调时,调时闪烁,万年历,年月日)超详细解析

    2019/07/13 单片机数字钟(调时,调时闪烁,万年历,年月日)超详细解析 发表日期:2019/07/13 单片机开发板:巫妖王2.0, 使用同款开发板可直接上板测试 文档说明: 实现功能 : 一 ...

  5. 计算机网络之交换机的工作原理---超详细解析,谁都看得懂!!

    在了解交换机的工作原理之前,我们先要了解几个概念. 一.相关概念  1.OSI七层模型是哪七层? 自上而下分别是: 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 交换机工作在数据链路层, ...

  6. 【大话GSM】物理信道与逻辑信道映射详细解析

    物理信道与逻辑信道映射详细解析 1. 频点.时隙.信道 在GSM中,无线路径上是采用时分多址TDMA方式.每一频点(频道.载频TRX)可分为8个时隙,每一时隙为一个信道,因此一个TRX最多可有8个移动 ...

  7. 【智能算法】粒子群算法(Particle Swarm Optimization)超详细解析+入门代码实例讲解...

    喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号[程序猿声] 01 算法起源 粒子群优化算法(PSO)是一种进化计算技术(evolutionary computation),1995 年由E ...

  8. 自动化测试学习步骤及路线(超详细)

    随着测试行业的不断发展,目前企业对测试人员要求越来越高,仅仅响应需求的功能测试人员基本饱和或是留给了校招生.而对于社招渠道的应聘者,企业越来越多地要求有一定的自动化或是代码经验,能解决工作过程中遇到的 ...

  9. 超详细解析python爬虫爬取京东图片

    超详细图片爬虫实战 实例讲解(京东商城手机图片爬取) 1.创建一个文件夹来存放你爬取的图片 2.第一部分代码分析 3.第二部分代码分析 完整的代码如下所示: 升级版代码: 爬取过程中首先你需要观察在手 ...

  10. VUE 钩子函数超详细解析

    点击上方蓝色字体关注我吧 一起学习,一起进步,做积极的人! 前言 Vue 实例在被创建时,会经过一系列的初始化过程,初始化过程中会运行一些函数,叫做生命周期钩子函数,通过运用钩子函数,用户在可以在Vu ...

最新文章

  1. 常考数据结构与算法:每k个节点反转链表
  2. python操作手机app_【Python】[技术博客] 一些使用Python编写获取手机App日志的操作...
  3. 简单理解Python中的if __name__ == '__main__'
  4. 高中计算机结业考试总结,高中考试总结
  5. Spring Data JPA 从入门到精通~Specification工作中的一些扩展
  6. 触发full gc的条件
  7. C语言拾遗(二):符号重载
  8. 不依赖jstack的java 线程dump和死锁检查工具
  9. pythoncad标注教程,AotuCAD国标字体和标注设置技巧图文教程
  10. 【CS224N-2019】斯坦福CS224N-2019自然语言处理----作业详解
  11. 在OpenCV里实现Prewitt算子
  12. 你是儒系程序名猿,还是佛系开发大神?
  13. 排列组合cn和an公式
  14. 大数据是普惠金融的未来!
  15. 基于itextpdf java pdf添加水印后,部分文档无法显示水印的问题
  16. AI留给教练的时间已经不多了
  17. 03-Linux-Shell
  18. Python 实现日志监控
  19. android4.0 去掉虚拟按键 教程,Android 4.0 隐藏虚拟按键(导航栏)的方法
  20. [HarekazeCTF2019]Avatar Uploader 1

热门文章

  1. DAVINCI DM6446 开发攻略——V4L2视频驱动和应用分析
  2. “搏一搏,单车变摩托
  3. CANoe_Demo_下载以及安装教程
  4. 百度开源的 71 个炸天项目
  5. linux卸载wine qq,ubuntu安装wineQQ
  6. 吐血推荐HZHControls:我见过最美的Winform控件库
  7. 金庸小说人物知识图谱构建——构建共现矩阵
  8. 百度文库复制内容,留作笔记
  9. 声网 视频通话SDK小结
  10. Android5.1开机LOGO与开机动画【转】