数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 MCMC 采样
- 机器学习方法概论
统计学习
深度学习
- 深度学习简介
- 深度前馈网络
- 反向传播算法
- 正则化
- 深度学习中的最优化问题
- 卷积神经网络
- CNN:图像分类
- 循环神经网络 RNN
- Transformer
- 一、Transformer [2017]
- 二、Universal Transformer [2018]
- 三、Transformer-XL [2019]
- 四、GPT1 [2018]
- 五、GPT2 [2019]
- 六、GPT3 [2020]
- 七、OPT [2022]
- 八、BERT [2018]
- 九、XLNet [2019]
- 十、RoBERTa [2019]
- 十一、ERNIE 1.0 [2019]
- 十二、ERNIE 2.0 [2019]
- 十三、ERNIE 3.0 [2021]
- 十四、ERNIE-Huawei [2019]
- 十五、MT-DNN [2019]
- 十六、BART [2019]
- 十七、mBART [2020]
- 十八、SpanBERT [2019]
- 十九、ALBERT [2019]
- 二十、UniLM [2019]
- 二十一、MASS [2019]
- 二十二、MacBERT [2019]
- 二十三、Fine-Tuning Language Models from Human Preferences [2019]
- 二十四 Learning to summarize from human feedback [2020]
- 二十五、InstructGPT [2022]
- 二十六、T5 [2020]
- 二十七、mT5 [2020]
- 二十八、ExT5 [2021]
- 二十九、Muppet [2021]
- 三十、Self-Attention with Relative Position Representations [2018]
- 三十一、USE [2018]
- 三十二、Sentence-BERT [2019]
- 三十三、SimCSE [2021]
- 三十四、BERT-Flow [2020]
- 三十五、BERT-Whitening [2021]
- 三十六、Comparing the Geometry of BERT, ELMo, and GPT-2 Embeddings [2019]
- 三十七、CERT [2020]
- 三十八、DeCLUTR [2020]
- 三十九、CLEAR [2020]
- 四十、ConSERT [2021]
- 四十一、Sentence-T5 [2021]
- 四十二、ULMFiT [2018]
- 四十三、Scaling Laws for Neural Language Models [2020]
- 四十四、Chinchilla [2022]
- 四十七、GLM-130B [2022]
- 四十八、GPT-NeoX-20B [2022]
- 四十九、Bloom [2022]
- 五十、PaLM [2022] (粗读)
- 五十一、PaLM2 [2023](粗读)
- 五十二、Self-Instruct [2022]
- 句子向量
- 词向量
- 传统CTR 预估模型
- CTR 预估模型
- 一、DSSM [2013]
- 二、FNN [2016]
- 三、PNN [2016]
- 四、DeepCrossing [2016]
- 五、Wide 和 Deep [2016]
- 六、DCN [2017]
- 七、DeepFM [2017]
- 八、NFM [2017]
- 九、AFM [2017]
- 十、xDeepFM [2018]
- 十一、ESMM [2018]
- 十二、DIN [2017]
- 十三、DIEN [2019]
- 十四、DSIN [2019]
- 十五、DICM [2017]
- 十六、DeepMCP [2019]
- 十七、MIMN [2019]
- 十八、DMR [2020]
- 十九、MiNet [2020]
- 二十、DSTN [2019]
- 二十一、BST [2019]
- 二十二、SIM [2020]
- 二十三、ESM2 [2019]
- 二十四、MV-DNN [2015]
- 二十五、CAN [2020]
- 二十六、AutoInt [2018]
- 二十七、Fi-GNN [2019]
- 二十八、FwFM [2018]
- 二十九、FM2 [2021]
- 三十、FiBiNET [2019]
- 三十一、AutoFIS [2020]
- 三十三、AFN [2020]
- 三十四、FGCNN [2019]
- 三十五、AutoCross [2019]
- 三十六、InterHAt [2020]
- 三十七、xDeepInt [2023]
- 三十九、AutoDis [2021]
- 四十、MDE [2020]
- 四十一、NIS [2020]
- 四十二、AutoEmb [2020]
- 四十三、AutoDim [2021]
- 四十四、PEP [2021]
- 四十五、DeepLight [2021]
- 图的表达
- 一、DeepWalk [2014]
- 二、LINE [2015]
- 三、GraRep [2015]
- 四、TADW [2015]
- 五、DNGR [2016]
- 六、Node2Vec [2016]
- 七、WALKLETS [2016]
- 八、SDNE [2016]
- 九、CANE [2017]
- 十、EOE [2017]
- 十一、metapath2vec [2017]
- 十二、GraphGAN [2018]
- 十三、struc2vec [2017]
- 十四、GraphWave [2018]
- 十五、NetMF [2017]
- 十六、NetSMF [2019]
- 十七、PTE [2015]
- 十八、HNE [2015]
- 十九、AANE [2017]
- 二十、LANE [2017]
- 二十一、MVE [2017]
- 二十二、PMNE [2017]
- 二十三、ANRL [2018]
- 二十四、DANE [2018]
- 二十五、HERec [2018]
- 二十六、GATNE [2019]
- 二十七、MNE [2018]
- 二十八、MVN2VEC [2018]
- 二十九、SNE [2018]
- 三十、ProNE [2019]
- Graph Embedding 综述
- 图神经网络
- 一、GNN [2009]
- 二、Spectral Networks 和 Deep Locally Connected Networks [2013]
- 三、Fast Localized Spectral Filtering On Graph [2016]
- 四、GCN [2016]
- 五、神经图指纹 [2015]
- 六、GGS-NN [2016]
- 七、PATCHY-SAN [2016]
- 八、GraphSAGE [2017]
- 九、GAT [2017]
- 十、R-GCN [2017]
- 十一、 AGCN [2018]
- 十二、FastGCN [2018]
- 十三、PinSage [2018]
- 十四、GCMC [2017]
- 十五、JK-Net [2018]
- 十六、PPNP [2018]
- 十七、VRGCN [2017]
- 十八、ClusterGCN [2019]
- 十九、LDS-GNN [2019]
- 二十、DIAL-GNN [2019]
- 二十一、HAN [2019]
- 二十二、HetGNN [2019]
- 二十三、HGT [2020]
- 二十四、GPT-GNN [2020]
- 二十五、Geom-GCN [2020]
- 二十六、Graph Network [2018]
- 二十七、GIN [2019]
- 二十八、MPNN [2017]
- 二十九、UniMP [2020]
- 三十、Correct and Smooth [2020]
- 三十一、LGCN [2018]
- 三十二、DGCNN [2018]
- 三十三、AS-GCN
- 三十四、DGI [2018]
- 三十五、DIFFPOLL [2018]
- 三十六、DCNN [2016]
- 三十七、IN [2016]
- 图神经网络 2
- 图神经网络 3
- 推荐算法(传统方法)
- 一、Tapestry [1992]
- 二、GroupLens [1994]
- 三、ItemBased CF [2001]
- 四、Amazon I-2-I CF [2003]
- 五、Slope One Rating-Based CF [2005]
- 六、Bipartite Network Projection [2007]
- 七、Implicit Feedback CF [2008]
- 八、PMF [2008]
- 九、SVD++ [2008]
- 十、MMMF 扩展 [2008]
- 十一、OCCF [2008]
- 十二、BPR [2009]
- 十三、MF for RS [2009]
- 十四、 Netflix BellKor Solution [2009]
- 推荐算法(神经网络方法 1)
- 一、MIND [2019](用于召回)
- 二、DNN For YouTube [2016]
- 三、Recommending What Video to Watch Next [2019]
- 四、ESAM [2020]
- 五、Facebook Embedding Based Retrieval [2020](用于检索)
- 六、Airbnb Search Ranking [2018]
- 七、MOBIUS [2019](用于召回)
- 八、TDM [2018](用于检索)
- 九、DR [2020](用于检索)
- 十、JTM [2019](用于检索)
- 十一、Pinterest Recommender System [2017]
- 十二、DLRM [2019]
- 十三、Applying Deep Learning To Airbnb Search [2018]
- 十四、Improving Deep Learning For Airbnb Search [2020]
- 十五、HOP-Rec [2018]
- 十六、NCF [2017]
- 十七、NGCF [2019]
- 十八、LightGCN [2020]
- 十九、Sampling-Bias-Corrected Neural Modeling [2019](检索)
- 二十、EGES [2018](Matching 阶段)
- 二十一、SDM [2019](Matching 阶段)
- 二十二、COLD [2020 ] (Pre-Ranking 模型)
- 二十三、ComiRec [2020](https://www.wenjiangs.com/doc/0b4e1736-ac78)
- 二十四、EdgeRec [2020]
- 二十五、DPSR [2020](检索)
- 二十六、PDN [2021](mathcing)
- 二十七、时空周期兴趣学习网络ST-PIL [2021]
- 推荐算法之序列推荐
- 一、FPMC [2010]
- 二、GRU4Rec [2015]
- 三、HRM [2015]
- 四、DREAM [2016]
- 五、Improved GRU4Rec [2016]
- 六、NARM [2017]
- 七、HRNN [2017]
- 八、RRN [2017]
- 九、Caser [2018]
- 十、p-RNN [2016]
- 十一、GRU4Rec Top-k Gains [2018]
- 十二、SASRec [2018]
- 十三、RUM [2018]
- 十四、SHAN [2018]
- 十五、Phased LSTM [2016]
- 十六、Time-LSTM [2017]
- 十七、STAMP [2018]
- 十八、Latent Cross [2018]
- 十九、CSRM [2019]
- 二十、SR-GNN [2019]
- 二十一、GC-SAN [2019]
- 二十二、BERT4Rec [2019]
- 二十三、MCPRN [2019]
- 二十四、RepeatNet [2019]
- 二十五、LINet(2019)
- 二十六、NextItNet [2019]
- 二十七、GCE-GNN [2020]
- 二十八、LESSR [2020]
- 二十九、HyperRec [2020]
- 三十、DHCN [2021]
- 三十一、TiSASRec [2020]
- 推荐算法(综述)
- 多任务学习
- 系统架构
- 实践方法论
- 深度强化学习 1
- 自动代码生成
工具
- CRF
- lightgbm
- xgboost
- scikit-learn
- spark
- numpy
- matplotlib
- pandas
- huggingface_transformer
- 一、Tokenizer
- 二、Datasets
- 三、Model
- 四、Trainer
- 五、Evaluator
- 六、Pipeline
- 七、Accelerate
- 八、Autoclass
- 九、应用
- 十、Gradio
Scala
- 环境搭建
- 基础知识
- 函数
- 类
- 样例类和模式匹配
- 测试和注解
- 集合 collection(一)
- 集合collection(二)
- 集成 Java
- 并发
八、类型参数化
8.1 类型参数化
类型参数化用于编写泛型的类和泛型的特质。泛型的意思是:我们用一个泛化的类或者特质来定义许多具体的类型。
xxxxxxxxxx
trait Queue[T]{ def head: T def tail: Queue[T] def enqueue(x: T): Queue[T] }这里的
Queue
是泛型的, 是一个泛型的特质,其定义为Queue[T]
。事实上
Queue
并不能当作一个类型来用,因此我们不能创建类型为Queue
的变量:xxxxxxxxxx
def f(q: Queue) = {}我们必须参数化
Queue
,指定参数化类型T
。如:Queue[Int],Queue[String],...
。xxxxxxxxxx
def f(q: Queue[Int]) = {}因此
Queue
也被称作类型构造方法,因为我们可以通过指定类型参数来构造一个类型。类型构造方法Queue
能够“生成”一组类型,如:Queue[Int],Queue[String],...
。
8.2 型变注解
泛型和子类型这两个概念放在一起,会产生一些非常有趣的问题。如:
Queue[String]
应不应该被当作Queue[AnyRef]
的子类型?更通俗的讲:如果
S
是类型T
的子类型,那么Queue[S]
是否应该作为Queue[T]
的子类型?如果
Queue[S]
可以作为Queue[T]
的子类型,则可以说特质Queue
在类型参数T
上是协变的convariant
。由于Queue
只有一个类型参数,因此可以简单的说Queue
是协变的。这意味着以下的调用是允许的:xxxxxxxxxx
def f(q: Queue[AnyRef]) = {} // 函数参数类型: Queue[AnyRef] val q1: Queue[String] = .... f(q1) // 实参类型: Queue[String]在
Scala
中,泛型类型默认的子类型规则是非协变的nonvariant
。也就是说,Queue[String]
不能当作Queue[AnyRef]
来使用。可以通过在类型参数前面加上
+
来表示协变的:xxxxxxxxxx
trait Queue[+T]{ ... }通过
+
,我们告诉Scala
我们要的效果是:Queue[String]
是Queue[AnyRef]
的子类型。编译器会检查Queue
的定义符合这种子类型关系的要求。也可以通过在类型参数前面加上
-
来表示逆协变的contravariance
:xxxxxxxxxx
trait MyTrait[-T]{ ... }如果
T
是类型S
的子类型,则MyTrait[S]
是MyTrait[T]
的子类型。也就是说,MyTrait[AnyRef]
是MyTrait[String]
的子类型。类型参数是协变的、逆变的、还是不变的,这被称作类型参数的形变
variance
。可以放在类型参数旁边的+
和-
被称作类型注解variance annotation
。在纯函数式的世界中,许多类型自然而然的是协变的。但是引入可变数据之后,情况就会发生变化。
xxxxxxxxxx
class Cell[+T](init: T) { // 实际上无法通过编译 private[this] var current = init def get = current def set(x: T) = { current = x } }假设
Cell
是协变的(实际上这段代码无法通过编译器的检查),则可以构建如下语句:xxxxxxxxxx
val c1 = new Cell[String]("hello") val c2: Cell[Any] = c1 // Cell[String] 是 Cell[Any] 的子类型 c2.set(1) // Int 是 Any 的子类型 val s: String = c1.get // current 现在是整数 1这四行代码的效果是:将整数
1
赋值给了字符串s
。这显然违背了类型约束。并不仅仅只有可变字段能让协变类型变得不可靠,泛型参数类型作为方法参数类型出现时,协变类型也不可靠。
xxxxxxxxxx
class Cell[+T](init: T) { // 实际上无法通过编译 private[this] var current = init def get = current def set(x: T) = { current = x } } class MyCell extends Cell[Int]{ override def set(x: Int) ={ println(math.sqrt(x)) super.set(x) } }因为
Cell[Int]
是Cell[Any]
的子类,而MyCell
又是Cell[Int]
的子类,因此现在我们可以这么做:xxxxxxxxxx
val c: Cell[Any] = new MyCell c.set("hello")c.set
会执行MyCell
的set
方法,而该方法要求的参数类型是Int
。事实上,可变字段能让协变类型变得不可靠只是如下规则的特例:用
+
注解的类型参数不允许应用于方法的参数类型。Java
中的数组是被当作协变来处理的。Java
在运行时会保存数组的元素类型。每当数组元素被更新时,都会检查新元素值是否满足类型要求。如果新元素不满足类型要求,则抛出ArrayStoreException
异常。xxxxxxxxxx
// Java 代码,能够编译成功,但是运行异常 String[] a1 = { "abc" }; Object[] a2 = a1; a2[0] = new Integer(17); String s = a1[0]Java
的这种设计是为了用一种简单的手段来泛化地处理数组。而在
Scala
中,数组是不变的。因此Array[String]
并不会被当作Array[Any]
的子类来处理。xxxxxxxxxx
val a1 = Array("abc") val a2: Array[Any] = a1 // 编译失败但是
Scala
允许我们将元素类型为T
的数组,经过类型转换成T
的任意超类型的数组:xxxxxxxxxx
val a1 = Array("abc") val a2: Array[Any] = a1.asInstanceOf[Array[Any]]这个类型转换在编译时永远合法,且在运行时也永远成功。因为
JVM
的底层runtime
模型对数组的处理都是协变的,跟Java
语言一样。但是你可能在这之后遇到ArrayStoreException
,就跟Java
一样:xxxxxxxxxx
val a1 = Array("abc") val a2: Array[Any] = a1.asInstanceOf[Array[Any]] a2(0) = 17 // 抛出 ArrayStoreException
8.3 逆变
类型系统设计的一个通用原则:如果在任何需要类型
U
的值的地方,都能够用类型T
的值替代,则可以安全的假定类型T
是类型U
的子类型。这称作李氏替换原则。如果
T
支持跟U
一样的操作,而T
的所有操作跟U
中对应的操作相比,要求更少且提供更多的话,该原则就成立。有些场景需要逆变。
xxxxxxxxxx
trait OutputChannel[-T]{ def write(x: T) }这里
OutputChannel
被定义为以T
逆变,因此一个OutputChannel[AnyRef]
是OutputChannel[String]
的子类。这是因为
OutputChannel[AnyRef]
和OutputChannel[String]
都支持write
操作,而这个操作在OutputChannel[AnyRef]
中的要求比OutputChannel[String]
更少。更少的意思是:前者只要求入参是AnyRef
,后者要求入参是String
。有时候逆变和协变会同时出现。一个典型的例子是
Scala
的Function
特质。当我们写下函数类型
A => B
时,Scala
会将其展开成Function1[A, B]
。标准类库中的Function1
同时使用了协变和逆变:函数入参类型A
上进行逆变,函数结果类型B
上进行协变。xxxxxxxxxx
trait Function1[-A, +B]{ def apply(x: A): B }这是因为对于函数调用,我们可以传入
A
的子类对象;而函数的返回值可以传递给B
的超类对象。
8.3 检查型变注解
Scala
编译器会检查你添加在类型参数上的任何型变注解。如:如果你尝试声明一个类型参数为协变的(添加一个+
),但是有可能引发潜在的运行时错误,则你的程序将无法通过编译。为了验证型变注解的正确性,
Scala
编译器会对类或者特质定义中的所有能够出现类型参数的地点进行归类,归类为:协变的positive
、逆变的negative
和不变的neutral
。所谓的“地点”指的是:类或特质中,任何一个可以用到类型参数的地方。
编译器会检查类型参数的每一次使用:
- 使用
+
注解的类型参数只能用在协变点。 - 使用
-
注解的类型参数只能用在逆变点。 - 没有型变注解的类型参数能够用在任何能够出现类型参数的点,因此这也是唯一的能用在不变点的类型参数。
- 使用
为了对类型参数点进行归类,编译器从类型参数声明开始,逐步深入到更深的嵌套层次。
声明该类型参数的类的顶层的点被归类为协变点。
更深的嵌套层次默认为跟包含它的层次相同,不过有一些例外情况归类会发生变化:
值函数的参数的点被归类为:方法外的翻转:
- 协变点的翻转是逆变点。
- 逆变点的翻转是协变点。
- 不变点的翻转仍然是不变点。
当前的归类在方法的类型参数上也会翻转。
xxxxxxxxxx
class C[-T,+U] { // T,U 在顶层都是协变点 def func(t:T, u:U){} // func 为方法,因此翻转:T,U 都为逆变点 }当前的归类在类的类型参数上也会翻转。
xxxxxxxxxx
class C[-T,+U] { // W 在顶层是协变点 def func[W](){} // W 为类型,因此翻转:W 为逆变点 }
要想跟踪型变点相当不容易。不过不用担心,
Scala
编译器会帮助你做这个检查。一旦归类被计算出来,编译器会检查每个类型参数只被用在了正确的归类点。
Scala
的型变检查对于对象私有定义private[this]
有一个特殊规则:在检查带有+
或-
的类型参数必须匹配相同型变归类点时,会忽略掉对象私有的定义。
8.4 上界/下界
对于例子中:
xxxxxxxxxx
trait Queue[T]{ def head: T def tail: Queue[T] def enqueue(x: T): Queue[T] }由于
T
是以enqueue
方法的参数出现,因此T
不能是协变的。事实上可以通过给
enqueue
一个类型参数,并对这个类型参数使用下界来实现多态:xxxxxxxxxx
trait Queue[T]{ def head: T def tail: Queue[T] def enqueue[U >: T](x: U): Queue[U] }新的定义给
enqueue
添加了一个类型参数U
,并且用U >: T
这样的语法定义了U
的下界为T
。这样一来U
必须是T
的超类。现在
enqueue
的参数类型为U
而不是T
,方法的返回值是Queue[U]
而不是Queue[T]
。超类和子类关系是反身的。即:一个类型同时是自己的超类和子类。对于
U >: T
,尽管T
是U
的下界,你仍然可以将一个T
传入enqueue
。上界的指定方式跟下界类似,只是不再采用表示下界的
>:
符号,而是采用<:
符号。对于
U <: T
,要求类型参数U
是T
的子类型。型变注解和上、下界配合得很好。它们是类型驱动设计得绝佳范例。
8.5 类型推断
对于方法调用
func(args)
,类型推断算法首先检查func
的类型是否已知。如果已知,则这个类型信息就被用于推断入参的预期类型。
如:
List(1,2,3,4).sortWith( _ > _)
中,调用对象的类型为List[Int]
,因此类型推断算法知道sortWith
的参数类型为:(Int,Int) => Boolean
,并且返回类型为List[Int]
。由于该参数类型已知,所以并不需要显式写出来。
如果未知,则类型推断算法无法自动推断入参类型。
xxxxxxxxxx
def msort[T](less:(T,T) => Boolean)(xs: List[T]): List[T] ={ ... } msort ( _ > _)(List(1,2,3,4)) // 错误 msort[Int]( _ > _)(List(1,2,3,4)) // 正确 msort[Int]( (x:Int, y:Int) => x > y )List(1,2,3,4)) // 正确msort
是一个经过科里化的、多态的方法类型,它接收一个类型为(T, T) => Boolean
的入参,产出一个从List[T]
到List[T]
的函数,其中T
是当前未知的某个类型。msort
需要先用一个类型参数实例化之后才能应用到它的入参上。由于
msort
确切实例类型未知,因此类型推断算法无法推断首个入参类型。此时类型推断算法尝试从入参类型来决定方法的正确实例类型。但是当它检查_ > _
时,无法得到任何类型信息。一种方案是:显式传入类型参数,如
msort[Int]( _ > _)(List(1,2,3,4))
。另一种方案是:交换两个参数列表的位置:
xxxxxxxxxx
def msort[T](xs: List[T])(less: (T,T) => Boolean): List[T] ={ ... }当类型推断算法需要推断一个多态方法的类型参数时,它会考虑第一个参数列表里的所有入参的类型,但是不会考虑第二个、第三个等等参数列表的入参。
因此,当我们设计一个接收非函数的入参和接受函数入参时,将函数入参单独放在最后一个参数列表中。这样一来,方法的正确实例类型可以从非函数入参推断而来,而这个类型又可以继续用于对函数入参进行类型检查。这样的效果是:编写函数字面量作为入参时可以更简洁。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论