Dawn's Blogs

分享技术 记录成长

0%

Java 反射机制

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

反射主要用到的 API:

  • java.lang.Class:代表一个类。
  • java.lang.reflect.Method:类的方法。
  • java.lang.reflect.Field:类的成员变量。
  • java.lang.reflect.Constructor:类的构造器。
  • 等等

Class 类

加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。这个对象就像一面镜子,透过这个镜子看到类的结构。

常用方法

Class 类常用方法如下:

image-20230206105907091

反射的举例:

通过反射,可以访问类的私有结构(构造器、属性、方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 实例化 Class 类对象
String str = "xxx.Person";
Class c = Class.forName(str);
// 获取构造器
Constructor cons = c.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Tom", 18);
// 获取 name 属性并修改
Field field = c.getField("name");
field.set(obj, "Dawn");
Obj name = field.get(obj);
System.out.println(name);
// 调用方法
Method show = c.getDeclearedMethod("show");
show.invoke(obj);
// 访问修改私有属性
Field age = c.getDeclearedField("age");
age.setAccessible(true);
age.set(obj, 20);
阅读全文 »

Java 网络编程

InetAddress 类

InetAddress 类用于表示网络地址,没有公共构造器,而是提供了如下几个静态方法来获取 InetAddress 实例:

  • public static InetAddress getLocalHost()
  • public static InetAddress getByName(String host)

InetAddress 有几个常用方法:

  • **public String getHostAddress()**:返回 IP 地址字符串(以文本表现形式)。
  • public String getHostName():获取此 IP 地址的主机名。
  • public boolean isReachable(int timeout):测试是否可以达到该地址。

Socket 类

Socket 类的常用构造器:

  • public Socket(InetAddress address,int port)
  • public Socket(String host,int port)

TCP 网络编程

服务器端的工作过程:

  • 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上开始监听。
  • 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
  • 调用套接字对象的 getOutputStream() 和 getInputStream():获取输出流和输入流,开始网络数据的发送和接收。
  • 关闭 ServerSocket 和 Socket 对象:客户端访问结束,关闭通信套接字。

客户端的流程:

  • 创建 Socket:根据指定服务端的 IP 地址(域名)和端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
  • 打开连接到服务器的输入输出流: 使用 getInputStream() 方法获得输入流,使用 getOutputStream() 方法获得输出流,进行数据传输。
  • 关闭 Socket:断开客户端到服务器的连接。

UDP 网络编程

UDP 网络编程步骤:

  • 调用 DatagramSocket 和 DatagramPacket:创建一个 UDP 连接套接字、以及 UDP 数据报。
  • 调用 UDP 套接字的 send 和 receive 方法:用于数据报的发送和接收。
  • 关闭 socket

URL URI URN 的区别:

  • URI(uniform resource identifier,统一资源标识符):用来唯一的标识一个资源。

  • URL(uniform resource locator,统一资源定位符):它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何定位这个资源。

  • URN(uniform resource name,统一资源命名):是通过名字来标识资源。

URI 是以一种抽象的,高层次概念定义统一资源标识,而 URL 和 URN 则是具体的资源标识的方式。URL 和 URN 都是一种 URI。

Consistent Representation Learning for Continual Relation Extraction

年份:2022

会议:ACL

作者:Kang Zhao, Hua Xu, Jianggong Yang, Kai Gao

机构:Tsinghua University, Beijing

motivation:连续关系抽取(Continual RE,CRE)目的是帮助模型学习新的关系,同时保持对旧关系的准确分类。以往的一些工作证明,存储一些新关系的典型样本并在学习新关系时重播,可以有效地避免遗忘。但是,这种方法有两个缺点:过拟合在不平衡的数据集上表现不佳。为了解决这个问题,论文提出了一种一致表示学习方法,该方法在回放记忆时采用对比学习知识蒸馏来保持 relation embedding 的稳定性。

GitHub:https://github.com/thuiar/CRL

当模型学习新任务时,持续的关系学习需要缓解旧任务的灾难性遗忘。因为神经网络在每次训练中都需要重新训练一组固定的参数,所以解决灾难性遗忘问题最有效的解决方案是存储所有的历史数据,并在每次出现一个新的关系实例时,用所有的数据重新训练模型。该方法在持续关系学习中取得最佳效果,但由于时间和计算功率成本的原因,没有在现实生活中被采用。

连续学习主要存在三个方法:

  • 基于正则化(Regurgitation-based)的方法:限制了神经权重的更新。
  • 动态架构(Dynamic architecture)方法:动态扩展模型架构,学习新任务,有效防止遗忘旧任务。然而,这些方法不适合NLP应用,因为模型的大小随着任务的增加而显著增加。
  • 基于记忆(Memory-based)的方法:从旧任务中保存一些样本,并在新任务中不断学习它们,以缓解灾难性遗忘

对比学习(CL)的目的是使相似样本的表示在嵌入空间中映射得更近,而不同样本的表示应该映射得更远

方法

CRL 包括三个主要步骤,算法如下图所示:

  • Init training for new task(4 ~ 11):通过监督对比学习,在新的数据集上训练 encoder 和 projector head(实际上是两层神经网络)的参数。
  • Sample selection(12 ~ 13):对于新数据集上的每一个关系,检索关系中的每一个 sample 作为一个 cluster。对每一种关系都应用 k-means 算法,选择最接近中心的关系 representation,并存储在 cluster 的 memory 中。
  • Consistent representation learning(16 ~ 23):为了在学习新关系后,保持历史关系嵌入在空间中的一致性,对 memory 中的 sample 进行了对比重放和知识精馏约束。

image-20230207145937703

image-20230207145958153

image-20230207150021216

Encoder

Encoder(BERT)的输入为一个句子和一对实体 E1 和 E2。使用四个保留关键字,用于标记实体在句子中的开始和结束位置。将两个实体对应位置上的输出连接起来,得到高维 relation representation(也可以认为是 entity pair representation)

image-20230207164011927

上述编码器记为 E。

然后,使用一个 projection head 获取低维度的 representation(用于分类),其中 Proj 表示两层的神经网络

image-20230207164124323

最后进行正则化后,得到的向量用于对比学习

image-20230207164316811

Initial training for new task

在开始训练新任务之前,首先利用 Encoder 提取新训练集中每句话的 relation representation,并且将之用于初始化临时 memory bank Mb:

image-20230207171620047

接着开始训练,对于每一个 batch 首先获取对应的 relation representation,接着通过监督对比学习聚类进行明确的约束(使得相同关系类型的 relation representation 尽量的接近,不同类型的 relation representation 距离远一些):

image-20230207171825293

在每一次 batch 反向传播结束后,去更新在 memory bank 中的相应 representation Mb。

Selecting Typical Samples for Memory

为了使模型在学习新任务时不忘记对旧任务的相关知识,需要在 memory bank Mr 中存储一些样本 Sample,旧任务中的样本被存储到 Mr 中。使用 k-means 算法对每一种关系进行聚类,其中聚类的数量是需要为每个类存储的样本的数量(memory size)。然后,选取最接近中心的 relation representation,并存储在记忆中。

Consistent Representation Learning

在学习完新任务后,旧关系在空间中的 representation 可能会发生变化。为了使编码器在学习新任务的同时不改变旧任务的知识,论文提出了两种重放策略来学习一致性表示来缓解这一问题:对比重放知识蒸馏

Contrastive Replay with Memory Bank

在学习过新的知识过后,通过重播在 memory bank Mk 中的样本,使用与之前相同的监督对比学习聚类的方法,来进一步训练 Encoder(这里的不同之处在于,每个 batch 都使用整个 memory bank Mk 中的所有样本进行对比学习):

image-20230207175906610

编码器可通过在记忆中回放样本,以减轻对之前学习到的知识的遗忘,同时巩固在当前任务中学习到的知识。

然而,对比重放允许编码器训练少量的样本,这有过拟合的风险。另一方面,它可能会改变前一个任务中关系的分布。因此,论文提出用知识蒸馏来弥补这一不足。

Knowledge Distillation for Relieve Forgetting

我们希望该模型能够保留历史任务中关系之间的语义知识。因此,在编码器训练任务之前,论文使用 relations in memory 之间的相似性度量作为记忆知识(Memory Knowledge),然后使用知识蒸馏来缓解模型对这些知识的遗忘。

具体来说,就是首先对记忆 Mk 中的样本进行编码,然后计算每个类的原型(每一个关系类型对应一个原型,原型的计算方法为这个关系类型中所有 relation representation 之和):

image-20230207210241593

然后,计算关系类型之间的余弦相似度来表示在记忆中学习到的知识:

image-20230207210526437

在执行记忆重放时,使用 KL 散度使编码器保留对旧任务的知识

image-20230207211024746

NCM for Prediction

为了预测测试样本 x 的标签,the nearest class mean(NCM)将 x 的嵌入与所有记忆原型进行比较,以最相似的原型对应的标签预测关系

image-20230207212854469

在预测过程中不需要额外的线性层,因此可以添加新的类别,而不需要结构的改变。

实验

实验是在两个基准数据集上进行的,训练、测试、验证比例为 3:1:1。

  • FewRel:它是一个包含 80 个关系的 RE 数据集,每个关系都有 700 个实例。
  • TACRED:它是一个大规模的RE数据集,包含 42 个关系(包括无关系)和 106264 个样本。与FewRel相比,TACRED 中的样品是不平衡的,所以每个关系的训练样本数量限制为 320 个,相关的测试样本数量限制为 40 个。

为了模拟不同的任务,论文将数据集的所有关系随机划分为 10 个集合来模拟 10 个任务。

image-20230207213700047

探究 memory size 对模型效果的影响,实验结果表明,memory size 对于模型的表现影响非常大,并且 memory size 越大效果越好

image-20230207214526663

Packed Levitated Marker for Entity and Relation Extraction

年份:2022

会议:ACL

作者:Deming Ye, YanKai Lin, Peng Li, Maosong Sun

机构:Tsinghua University, Beijing, China

motivation:现有的工作忽视了 span(以及 span pair)之间的相互关系。因此,论文提出了 Packed Levitated Marker(PL-Marker)的 span 表示法

GitHub:https://github.com/thunlp/PL-Marker

目前有三种 span representation 方法:

  • T-Concat:连接 span 边界的 representation(start 和 end tokens)。这种方式停留在 token level 获取有关信息,也忽略了边界 token 之间的关系。
  • Solid Marker:在 span 前后显式的插入两个实体标记,以在输入文本中突出显示 span。这种方法难以处理多对 object-subject 的情况,因为它无法在同一句子中区分不同的 object 和 subject,也不能处理 overlapping spans 的情况。
  • Levitated Marker:首先设置一对悬浮标记与跨度的边界标记共享相同的位置,然后通过定向注意机制将一对标记绑定。具体来说,就是一对标记在注意力 mask 矩阵中彼此可见,但对文本标记和其他标记对不可见

模型结构

image-20230205111907254

阅读全文 »

File 类

java.io.File 类:文件和文件目录路径的抽象表示形式,与平台无关

File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流

Java File 类提供一个常量,表示路径分隔符:separator。

IO 流

Java程序中,对于数据的输入/输出(IO)操作以流(stream)的方式进行。

  • 输入 input:读取外部数据(磁盘等)到程序(内存)中。
  • 输出 output:将程序(内存)数据输出到磁盘等外部设备中。

流的分类:

  • 按操作数据单位不同分为:字节流(8 bit)、字符流(16 bit)。
  • 按数据流的流向不同分为:输入流、输出流。
  • 按流的角色的不同分为:节点流、处理流。
    • 节点流:直接从数据源或目的地读写数据。
    • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

image-20230202221611516

阅读全文 »

泛型

为什么需要泛型

那么为什么要有泛型呢,直接 Object 不是也可以存储数据吗?

  • 解决元素存储的安全性问题,确定数据类型。
  • 解决获取数据元素时,需要类型强制转换的问题。

Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。同时,代码更加简洁、健壮。

image-20230201181245221

image-20230201181302622

阅读全文 »

Map 接口

Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap 和 Properties。

  • HashMap:
    • HashMap 是 Map 接口使用频率最高的实现类
    • 效率高,线程不安全的。
    • 可以存储 key 或 value 为 null 的键值对。
  • LinkedHashMap:
    • 是 HashMap 的子类。
    • 保证在遍历元素时,可以按照添加的顺序进行遍历(在原有 HashMap 的基础上,为 item 添加了双向链表的结构)。在频繁的遍历操作中,效率高于 LinkedHashMap。
  • TreeMap:
    • 底层使用红黑树,是有序的,根据 key 采用自然排序或者定制排序。
  • Hashtable:
    • 作为 Map 接口的古老实现类
    • 效率低,线程安全的。
    • 不可以存储 key 或 value 为 null 的键值对。
  • Properties:
    • 是 Hashtable 的子类。
    • 常用于处理配置文件,key 和 value 都是 String 类型

image-20230131164433486

阅读全文 »

Java 集合可分为 Collection 和 Map 两种体系:

  • Collection 接口:定义了存取一组对象的方法的集合。
    • List:元素有序、可重复的集合。
    • Set:元素无序、不可重复的集合。

image-20230131164401491

  • Map 接口:保存具有映射关系 key-value 对的集合。

image-20230131164433486

Collection 接口

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

JDK 不提供此接口的任何直接实现,而是提供更具体的子接口的实现。

阅读全文 »

枚举类

enum 定义枚举类

枚举类 enum 的使用说明:

  • 使用 enum 定义的枚举类默认继承了 java.lang.Enum 类

  • 枚举类的构造器只能使用 private 权限修饰符

  • 枚举类的所有实例必须在枚举类中显式列出,列出的实例系统会自动添加 public static final 修饰

  • 必须在枚举类的第一行声明枚举类对象,多个对象之间用逗号隔开,末尾用分号结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum SeasonEnum {
// 3.声明当前枚举类对象
SPRING("春天", "春风又绿江南岸"),
SUMMER("夏天", "映日荷花别样红"),
AUTUMN("秋天", "秋水共长天一色"),
WINTER("冬天", "窗含西岭千秋雪");

// 1.声明枚举类对象属性
private final String seasonName;
private final String seasonDesc;

// 2.枚举类对象构造器,用于创建枚举类对象
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}

Enum 类主要方法

Enum 类的主要方法:

  • values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  • valueOf(String str):可以把一个字符串转为对应的枚举类对象,这个字符串必须是枚举类对象的名字,否则会报出 IllegalArgumentException 异常。
  • toString():返回当前枚举类对象的名称
阅读全文 »

比较器

在 Java 中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。Java 实现对象排序的方式有两种:

  • 自然排序:java.lang.Comparable
  • 定制排序:java.util.Comparator

java.lang.Comparable

实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小

  • 如果当前对象大于形参 obj,则返回正数。
  • 如果当前对象小于形参 obj,则返回负数。
  • 如果当前对象等于形参 obj,则返回零。

实现 Comparable 接口的对象数组可以通过 Collections.sort 或 Arrays.sort 进行自动排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Goods implements Comparable {
private String name;
private double price;

Goods(String name, double price) {
this.name = name;
this.price = price;
}

@Override
public int compareTo(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods) o;
if (this.price > goods.price) {
return 1;
} else if (this.price < goods.price) {
return -1;
}
return 0;
}
throw new RuntimeException("输入类型不一致");
}

@Override
public String toString() {
return name + ":" + price;
}

public String getName() {
return name;
}

public double getPrice() {
return price;
}
}

public class ComparableTest {
public static void main(String[] args) {
Goods[] all = new Goods[4];
all[0] = new Goods("《红楼梦》", 100);
all[1] = new Goods("《西游记》", 80);
all[2] = new Goods("《三国演义》", 140);
all[3] = new Goods("《水浒传》", 120);

Arrays.sort(all);

System.out.println(Arrays.toString(all));
}
}
阅读全文 »