2024年10月

设计模式

一、介绍

适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。

适配器可担任两个对象间的封装器,它会接收对于一个对象的调用,并将其转换为另一个对象可识别的格式和接口。

适配器模式在 PHP 代码中很常见。 基于一些遗留代码的系统常常会使用该模式。 在这种情况下, 适配器让遗留代码与现代的类得以相互合作。

应用场景:

(1)当你希望使用某个类,但是其接口与其他代码不兼容时,可以使用适配器类。

(2)如果您需要复用一些类,他们处于同一个继承体系,并且他们又有了额外的一些共同的方法,但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。

二、优缺点

优点:

  • 单一职责原则:你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则:只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点:

  • 代码整体复杂度增加,因为需要新增一系列接口和类。

三、核心结构

Adapter(适配器):最主要的就是这个适配器类,用于适配、扩展功能或接口。

四、代码实现

1、应用场景1

当你希望使用某个类,但是其接口与其他代码不兼容时,可以使用适配器类。

例如:有一台电脑显示器本身是VGA的数据线,但现在你手里只有HDMI的数据线,这个时候只有有个适配器(转接头)就可以使用。

(1)LcdInterface类本身相当于电脑显示屏,vga()方法相当于电脑显示屏自带的vga格式的接口

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/8/1
* Time: 15:47
*/namespace app\admin\service\mode\adapter\other;/**
* LcdInterface接口类本身相当于电脑显示屏
* vga()方法相当于电脑显示屏自带的vga格式的接口
*/ interfaceLcdInterface
{
public functionvga();
}

(2)适配器(转接头)

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/8/1
* Time: 15:54
*/namespace app\admin\service\mode\adapter\other;/**
* 适配器(转接头)
*/ class Adapter implementsLcdInterface
{
private NewDataCable $chinaPlug;public function __construct(NewDataCable $chinaPlug) {$this->chinaPlug = $chinaPlug;
}
/**
* 使用适配器,将HDMI接口转换为VGA格式
* 相当于显示器还是使用VGA接口,但是通过适配器,将HDMI接口转换为VGA接口
* @return void
* @Author: fengzi
* @Date: 2024/10/11 14:20
*/ public functionvga()
{
//TODO: Implement plug120V() method. $this->chinaPlug->hdmi();echo '使用适配器,将HDMI接口转换为VGA格式' . PHP_EOL;
}
}

(3)新的数据线类,支持HDMI接口。

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/8/1
* Time: 15:50
*/namespace app\admin\service\mode\adapter\other;/**
* 新的数据线类,支持HDMI接口
*/ classNewDataCable
{
public functionhdmi() {echo "HDMI接口" . PHP_EOL;
}
}

(4)调用方式

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/7/30
* Time: 11:29
*/namespace app\admin\controller\mode\adapter;useapp\admin\service\mode\adapter\other\Adapter;useapp\admin\service\mode\adapter\other\NewDataCable;classAdapterController
{
/**
* 当你希望使用某个类,但是其接口与其他代码不兼容时,可以使用适配器类。
* @return void
* @Author: fengzi
* @Date: 2024/10/10 15:41
*/ public functionchange()
{
$adapter = new Adapter(newNewDataCable());
$adapter->vga();
dd(
'转换结束,可以使用');
}

}

(5)结果展示

2、应用场景2

如果您需要复用一些类,他们处于同一个继承体系,并且他们又有了额外的一些共同的方法,但是这些共同的方法不是所有都在这一继承体系中的子类所具有的共性。

例如:有一个相同类型的类文件,里面有你要用的方法,但又不全面。这个时候就可以使用适配器继承老的接口类,再去实现没有的方法后去使用。

(1)公共的计算器接口类

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/7/30
* Time: 11:30
*/namespace app\admin\service\mode\adapter;/**
* 计算器的接口类
*/ interfaceCalculator
{
/**
* 加法
* @param string $a 加数
* @param string $b 加数
* @param int|null $scale 小数点后保留位数
* @return mixed
* @Author: fengzi
* @Date: 2024/7/30 11:37
*/ public function add(string $a, string $b, ?int $scale=null);/**
* 减法
* @param string $minuend 被减数
* @param string $subtrahend 减数
* @param int|null $scale 小数点后保留位数
* @return mixed
* @Author: fengzi
* @Date: 2024/7/30 11:39
*/ public function subtract(string $minuend, string $subtrahend, ?int $scale=null);/**
* 乘法
* @param string $a 乘数
* @param string $b 乘数
* @param int|null $scale 小数点后保留位数
* @return mixed
* @Author: fengzi
* @Date: 2024/7/30 11:41
*/ public function multiply(string $a, string $b, ?int $scale=null);/**
* 除法
* @param string $dividend 被除数
* @param string $divisor 除数
* @param int|null $scale 小数点后保留位数
* @return mixed
* @Author: fengzi
* @Date: 2024/7/30 11:44
*/ public function divide(string $dividend, string $divisor, int $scale=null);
}

(2)公共接口类的具体实现

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/7/30
* Time: 11:31
*/namespace app\admin\service\mode\adapter;/**
* 简单的计算类
*/ class SimpleCalculator implementsCalculator
{
/**
* 加法
* @param string $a 加数
* @param string $b 加数
* @param int|null $scale 小数点后保留位数
* @return string
* @Author: fengzi
* @Date: 2024/7/30 11:37
*/ public function add(string $a, string $b, int $scale=null): string{//TODO: Implement add() method. return bcadd($a, $b, $scale);
}
/**
* 减法
* @param string $minuend 被减数
* @param string $subtrahend 减数
* @param int|null $scale 小数点后保留位数
* @return string
* @Author: fengzi
* @Date: 2024/7/30 11:39
*/ public function subtract(string $minuend, string $subtrahend, ?int $scale=null): string{//TODO: Implement subtract() method. return bcsub($minuend, $subtrahend, $scale);
}
/**
* 乘法
* @param string $a 乘数
* @param string $b 乘数
* @param int|null $scale 小数点后保留位数
* @return string
* @Author: fengzi
* @Date: 2024/7/30 11:41
*/ public function multiply(string $a, string $b, int $scale=null): string{//TODO: Implement multiply() method. return bcmul($a, $b, $scale);
}
/**
* 除法
* @param string $dividend 被除数
* @param string $divisor 除数
* @param int|null $scale 小数点后保留位数
* @return string
* @throws \Exception
* @Author: fengzi
* @Date: 2024/7/30 11:44
*/ public function divide(string $dividend, string $divisor, int $scale=null): string{//TODO: Implement divide() method. if ($divisor == 0) {throw new \Exception("除数不能为零");
}
return bcdiv($dividend, $divisor, $scale);
}
}

(3)继承公共接口类后扩展了其他方法的接口类

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/7/30
* Time: 11:36
*/namespace app\admin\service\mode\adapter;/**
* 高级计算类
*/ interface AdvancedCalculator extendsCalculator
{
/**
* 指数
* @param mixed $base 底数
* @param mixed $exponent 指数
* @return mixed
* @Author: fengzi
* @Date: 2024/7/30 11:53
*/ public function power(mixed $base, mixed $exponent);/**
* 平方根
* @param float $number
* @return mixed
* @Author: fengzi
* @Date: 2024/7/30 11:55
*/ public function squareRoot(float $number);
}

(4)适配器适配所有方法

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/7/30
* Time: 11:59
*/namespace app\admin\service\mode\adapter;/**
* 适配器
*/ class CalculatorAdapter implementsAdvancedCalculator
{
private Calculator $simpleCalculator;public function __construct(SimpleCalculator $simpleCalculator)
{
$this->simpleCalculator = $simpleCalculator;
}
/**
* 指数运算
* @param mixed $base
* @param mixed $exponent
* @return float|int|mixed|object
* @Author: fengzi
* @Date: 2024/10/11 14:51
*/ public function power(mixed $base, mixed $exponent)
{
//TODO: Implement power() method. return pow($base, $exponent);
}
/**
* 平方根
* @param $number
* @return float|mixed
* @Author: fengzi
* @Date: 2024/10/11 14:52
*/ public function squareRoot($number)
{
//TODO: Implement squareRoot() method. return sqrt($number);
}
/**
* 加法
* @param string $a
* @param string $b
* @param int|null $scale
* @return mixed|string
* @Author: fengzi
* @Date: 2024/10/11 14:52
*/ public function add(string $a, string $b, ?int $scale = null)
{
//TODO: Implement add() method. return $this->simpleCalculator->add($a, $b, $scale);
}
/**
* 减法
* @param string $minuend
* @param string $subtrahend
* @param int|null $scale
* @return mixed|string
* @Author: fengzi
* @Date: 2024/10/11 14:52
*/ public function subtract(string $minuend, string $subtrahend, ?int $scale = null)
{
//TODO: Implement subtract() method. return $this->simpleCalculator->subtract($minuend, $subtrahend, $scale);
}
/**
* 乘法
* @param string $a
* @param string $b
* @param int|null $scale
* @return mixed|string
* @Author: fengzi
* @Date: 2024/10/11 14:52
*/ public function multiply(string $a, string $b, ?int $scale = null)
{
//TODO: Implement multiply() method. return $this->simpleCalculator->multiply($a, $b, $scale);
}
/**
* 除法
* @param string $dividend
* @param string $divisor
* @param int|null $scale
* @return mixed|string
* @throws \Exception
* @Author: fengzi
* @Date: 2024/10/11 14:53
*/ public function divide(string $dividend, string $divisor, int $scale = null)
{
//TODO: Implement divide() method. return $this->simpleCalculator->divide($dividend, $divisor, $scale);
}
}

(5)适配器调用方式

<?php/**
* Created by PhpStorm
* Author: fengzi
* Date: 2024/7/30
* Time: 11:29
*/namespace app\admin\controller\mode\adapter;useapp\admin\service\mode\adapter\CalculatorAdapter;useapp\admin\service\mode\adapter\SimpleCalculator;classAdapterController
{
public functionindex()
{
$calculatorAdapter = new CalculatorAdapter(newSimpleCalculator());$res = $calculatorAdapter->squareRoot(4);
dd(
$res);
}
}

(6)结果展示

C++类与对象

class

class基本语法

class ClassName {
public:
    // 公有成员
    Type memberVariable;         // 数据成员
    ReturnType memberFunction(); // 成员函数声明

private:
    // 私有成员
    Type privateMemberVariable;  // 数据成员
    ReturnType privateMemberFunction(); // 成员函数声明

protected:
    // 保护成员
    Type protectedMemberVariable; // 数据成员
    ReturnType protectedMemberFunction(); // 成员函数声明
};

Important points

  • public
    :公有访问权限,类的外部可以访问

  • private
    :私有访问权限,只有类的内部可以访问。

  • protected
    :保护访问权限,只有类的内部和派生类可以访问。

  • 注意,默认权限为
    private

这里可以实现成员变量的被操作权限

在class中,声明数组大小时,如果声明大小使用的变量为class内的变量时,应当如下

class Map{
    public:
        const static int maxn =4343;
        int next[maxn]={};
};

或者建议使用
array
或者
vector

关于class中的static修饰词警示后人

在class中访问没有static修饰的函数与变量都是需要一个已经创建的对象才可以访问。

但是有了static修饰以后便会有所不同。有static修饰的变量和函数仅仅属于这个类本身,不属于某个特定的对象,但是其仍然拥有访问权限的设置!!

其可以直接被如下方式访问

class classname{
	public:
		static functionname(){/*content*/}
		static cnt;
};

int main (){
	classname::functionname();
	cout<<classname::cnt<<endl;
}

构造函数与析构函数

构造函数语法:
类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法:
~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

注:
这两个函数会被设置访问权限,可以限制类的创建和销毁!!

构造函数分类与调用

两种分类方式:

​ 按参数分为: 有参构造和无参构造

​ 按类型分为: 普通构造和拷贝构造

三种调用方式:

​ 括号法 ``````

​ 显示法

​ 隐式转换法

深拷贝与浅拷贝的差别:

浅拷贝(Shallow Copy)

浅拷贝是指复制对象时,新的对象和原始对象共享相同的内存地址。换句话说,浅拷贝仅复制对象的值(即指针和基本数据类型的值),而不是指针所指向的实际对象。

特点:

  1. 共享引用
    : 如果对象包含指针或引用,浅拷贝只复制指针的值,因此原始对象和拷贝对象的指针会指向同一块内存区域。
  2. 性能
    : 浅拷贝比深拷贝通常更快,因为它只需要复制指针和基本数据类型的值,而不需要递归地复制所有引用的对象。
  3. 潜在问题
    : 由于原始对象和拷贝对象共享相同的内存区域,当一个对象修改了指针指向的内容时,另一个对象的内容也会受到影响。此共享可能导致悬挂指针(如果一个对象释放了共享内存,另一个对象会变成悬挂状态)或数据不一致的问题。

深拷贝(Deep Copy)

深拷贝则是创建一个新对象,并递归地复制原始对象所引用的所有对象。换句话说,深拷贝不仅复制对象的值,还复制对象内包含的所有指针指向的对象,从而创建一个完全独立的新对象。

特点:

  1. 独立性
    : 原始对象和深拷贝对象之间没有共享的内存区域,修改一个对象不会影响另一个对象。
  2. 性能
    : 深拷贝可能较慢,因为它涉及递归地复制所有指针所指向的内容。
  3. 内存管理
    : 深拷贝通常需要编写额外的代码来确保正确管理内存,例如在析构函数中释放分配的内存,避免内存泄漏。

构造函数的初始化列表

class Person {
public:

	////传统方式初始化
	//Person(int a, int b, int c) {
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
	void PrintPerson() {
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

类对象作为类成员的构造和析构函数调用顺序

C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员

//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反

静态成员

静态成员变量

  • 所有对象共享同一份数据

  • 在编译阶段分配内存

  • 类内声明,类外初始化(一定要记着初始化,不然编译错误不显示!!!)

    class Example {
    public:
        static int staticVar; // 声明静态成员变量
    };
    
    int Example::staticVar = 0; // 定义并初始化静态成员变量
    

静态成员函数(static)

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量(其实只需要在参数上加入一个对象就可以访问对象的成员变量)

静态函数可以在类里面声明,在类外定义!!

常成员(const)

const 修饰成员函数

  • 保证不会改变对象的状态,但是可以调用更改 static 修饰的变量

  • 只可以调用其他const成员函数

    为了保证在const成员函数中,不会对对象进行改变

    可以调用静态函数!!!

  • 可以返回一个值,但是如果返回的是指针or引用,则必须加上const

    class Type{
    	int a;
        const int* GO() const{//int const* GO() const 这样也可以
            return &a;
    	}
    };
    

    这一条并不适用于 static变量:
    当返回的值为static变量的地址或引用时,可以不用static

    注意:在这里在再次区分一下
    const int *
    int const *
    int * const


    前两者是一样的,定义的指针不可以修改对应的地址

    第三者是指该指针所指的地址不可修改,但是可以通过指针修改内容

  • 成员变量加上
    \(mutable\)
    就可以不受以上规则限制,而可被const函数修改和返回非常量指针

总结:
常函数保证了不会通过其对对象有任何形式的修改包括指针与引用,但是对应于
\(static,mutabl\)
修饰的变量例外

const关键字的使用方式:

const int* () const{}

前一个
\(const\)
用于修饰返回值,后一个用于修饰函数为静态函数

注意:函数可以反回
\(const\)
的值,但是没有意义,因为其返回的值是拷贝。但是返回
\(const\)
的指针与引用是有意义的。

const MyClass& getObject() const {
    static MyClass obj;
    return obj;
}
OR
const MyClass* getObject() const {
    static MyClass obj;
    return obj;
}

常对象

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
  • 常对象可以修改静态变量,调用静态函数,修改
    \(mutable\)
    变量

友元

通过声明友元的方式,可以使C++中某个类的private和protected的变量和函数被其他类和函数访问

友元类

友元类函数

友元全局函数

class People{
    friend class Dorm; //友元类
    friend void get_password(const People&,const Dorm&); //友元函数
    public:
    const int ID;
    void Change_drompassword(const int&,const int&,const int &,Dorm&);
    public:
    People(int id,int Password): ID(id),password(Password){
        cout<<"creat a new person\n";
    }
    private:
    int password;
};

class Dorm{
    friend class People;
    friend void get_password(const People & a,const Dorm & b);
    friend void People::Change_drompassword(const int&,const int&,const int &,Dorm&); //友元类函数
    private:
    int get_number(){
        return dorm_number;
    }
    int dorm_number;
    int dorm_password;
    public:
    Dorm(int number,int password):dorm_number(number),dorm_password(password){
        cout<<"creat a drom!\n";
    }
};

Attention:

  • 在声明友元类函数之前,一定要保证该类已经被声明完成(提前声明不可以,因为complier只是知道了有这个类而不知道这个类的具体内容)。例如:将以上代码的两个类的定义交换位置为导致 $ incompleting $ 错误
  • 在声明有友元函数时,参数列表不用写参数名称,但是
    \(const~,\&~\)
    不可拉下

友元的注意事项

  1. 不具有继承关系
    :友元关系不会被继承。例如,如果类B是类A的友元,类C继承了类A,类C不会自动成为类B的友元。
  2. 友元不具有传递性
    :如果类A是类B的友元,类B不是自动成为类A的友元。
  3. 友元关系是单向的
    :即使类A是类B的友元,类B并不能自动访问类A的私有成员,除非类B也显式地声明类A为友元。

\(C++\)
中的输入输出流

输入流

  • 用于从外部源(如键盘或文件)读取数据。

  • 主要的输入流对象是
    std::cin
    ,它表示标准输入流。

  • cin.fail()
    用于检查输入是否成功

  • cin.ignore()
    函数


    • 忽略指定数量的字符

      std::cin.ignore(count);//count 代表数量
      
    • 忽略直到特定字符或 EOF:

      std::cin.ignore(count, delimiter);//delimiter 代表分隔符,这个分隔符也会被舍去
      std::cin.ignore(numeric_limits<std::streamsize>::max(), '\n')://忽略输入流中的特定字符或直到遇到换行符。
      

输出流

  • 用于将数据输出到外部目标(如显示器或文件)。
  • 主要的输出流对象是
    std::cout
    ,它表示标准输出流。
  • cout.flush()
    刷新输出流,确保所有缓冲区的数据被输出到终端。

错误流

  • 用于输出错误信息。

  • 主要的错误流对象是
    std::cerr
    ,它用于打印错误信息。

日志流(Log Stream)

  • 用于输出警告或日志信息。
  • 主要的日志流对象是
    std::clog

注:
以上四个标准流的使用方法都是一样的

int x;
std::cin >> x;
std::cout << "Hello, World!" << std::endl;
std::cerr << "An error occurred!" << std::endl;
std::clog << "This is a log message." << std::endl;

文件流

除了标准流,C++还支持文件流,用于从文件中读取数据或将数据写入文件。文件流的主要类有:

  • std::ifstream
    :用于输入文件流。
  • std::ofstream
    :用于输出文件流。
  • std::fstream
    :用于读写文件流(即同时支持输入和输出)。
#include <iostream>
#include <fstream> // 引入文件流

int main() {
    // 写入文件
    std::ofstream outFile("example.txt");
    if (outFile.is_open()) {
        outFile << "Hello, File!" << std::endl;
        outFile.close();
    }

    // 读取文件
    std::ifstream inFile("example.txt");
    std::string line;
    if (inFile.is_open()) {
        while (getline(inFile, line)) {
            std::cout << line << std::endl;
        }
        inFile.close();
    }

    return 0;
}

文件打开模式

首先在打开文件流是可以设置打开模式的,设置方法如下

std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::ate);

注:
当要使用多个打开模式时,可以用
|
将其链接

接下来介绍文件打开模式

  • std::ios::in
    :用于读操作。

  • std::ios::out
    :用于写操作,文件内容会被覆盖(如果文件存在)。

  • std::ios::app
    :用于追加操作,所有写入的数据会追加到文件末尾。

  • std::ios::ate
    :文件打开时将文件指针定位到文件末尾,适用于需要在文件末尾开始读写的情况。

  • std::ios::trunc
    :如果文件已经存在,会清空文件内容,通常与
    std::ios::out
    一起使用。

  • std::ios::binary
    以二进制的方式打开文件,不会进行仍何文本转化(比如换行符转化)

    #include <iostream>
    #include <fstream>
    #include <vector>
    
    int main() {
        // 文件名
        const char* filename = "example.bin";
        
        // 打开文件以二进制模式
        std::ifstream file(filename, std::ios::binary);
        
        // 检查文件是否成功打开
        if (!file) {
            std::cerr << "无法打开文件: " << filename << std::endl;
            return 1;
        }
        
        // 移动文件指针到文件末尾以获取文件大小
        file.seekg(0, std::ios::end);
        std::streamsize size = file.tellg();
        file.seekg(0, std::ios::beg);
        
        // 使用 std::vector 存储文件内容
        std::vector<char> buffer(size);
        
        // 读取文件内容到 buffer 中
        if (file.read(buffer.data(), size)) {
            // 处理文件内容(示例中仅输出文件大小)
            std::cout << "文件大小: " << size << " 字节" << std::endl;
            // 这里可以根据需要处理 buffer 中的数据
        } else {
            std::cerr << "读取文件失败" << std::endl;
        }
        
        // 关闭文件
        file.close();
        
        return 0;
    }
    /*
    文件名:你需要指定你想要读取的文件名。示例中使用的是 "example.bin"。
    
    打开文件:std::ifstream file(filename, std::ios::binary); 这行代码打开了指定的文件,并以二进制模式进行读取。
    
    获取文件大小:使用 seekg 和 tellg 方法来确定文件的大小。
    
    读取内容:file.read(buffer.data(), size); 这行代码将文件内容读取到 buffer 中。
    
    错误检查:在打开文件和读取文件后,检查是否成功执行这些操作。
    
    关闭文件:使用 file.close(); 关闭文件。
    */
    

以上打开方式在
ifstream
ofstream
中使用是没有问题的,只不过要注意不要把输出的文件打开方式安在输入文件流上面了,
会导致文件流无法正常打开的问题

最后让我们来介绍一下在
fstream
中使用这些文件打开方式会出现的一些问题

  • 单独使用
    app,ate,trunc
    都是不行的,因为他们没有给出文件的读写模式,所以要加上
    in
    out

  • 有个作死的玩法

    fstream IN(".in", std::ios::out);
    cout<<IN.is_open()<<endl;
    int a;
    IN>>a;
    IN<<1111<<endl;
    

    然后你就会发现输出了个寂寞

定位输出指针
std::ios::beg //开头指针
std::ios::cur //当前指针
std::ios::end //文件末尾

搭配函数
seekg()
使用

    // 将读指针移动到文件开头
inFile.seekg(0, std::ios::beg);
    // 将读指针移动到当前指针位置向后偏移5个字符的位置
inFile.seekg(5, std::ios::cur);

\(istream\)

\(ostream\)

这两是流的两种类型,是最基础的,一个是输入流,一个是输出流

重载运算符的时候就是用的这两

//笔者摆烂了,BF5见!

运算符重载

重载函数的两种形式

重载函数和其他函数一样都会存在访问权限问题!!!

友函数重载
class Grade{
    friend ostream& operator<<(ostream& out,const Grade & P);
    private:
    int grade_Chnese,grade_program,grade_math;
    Grade(int a,int b,int c,bool OP):grade_Chnese(a),grade_program(b),grade_math(c){
        if(OP) cout<<"insert grade succesfully!\n";
    }
    Grade(){}
};

ostream& operator <<(ostream & OUTT,const Cnt & b){
    OUTT<<b.cnt<<endl;
    return OUTT;
}

注:
在友函数重载中,两个参数分别代表左右操作符(其实也可以不用加
friend
如果不用访问
private

protected
的话)

成员函数重载
class Grade{
    public:
    Grade operator + (const Grade& A) const{
       return Grade(A.grade_Chnese+grade_Chnese,A.grade_program+grade_program,A.grade_math+grade_math,0);
    }
}

注:
在成员函数重载中对象本身会作为左操作数,参数作为右操作数

运算符重载实例

注:
+ - * / > < >= <= ==
都比较简单,参考结构体重构一样的

注:
建议在定义参数时使用常变量+引用,防止意外的更改以及加快速度

左移右移符号(输入输出流操作符)

由于在左移右移符号中,对象始终处于右操作数,所以只可以使用友函数的方法

class Grade{
    friend ostream& operator<<(ostream& out,const Grade & P);
    friend class People;
    private:
    int grade_Chnese,grade_program,grade_math;
    Grade(int a,int b,int c,bool OP):grade_Chnese(a),grade_program(b),grade_math(c){
        if(OP) cout<<"insert grade succesfully!\n";
    }
    Grade(){}
    public:
    Grade operator + (const Grade& A) const{
        return Grade(A.grade_Chnese+this->grade_Chnese,A.grade_program+this->grade_program,A.grade_math+this->grade_math,0);
    }
};

ostream& operator<<(ostream& Gut,const Grade & P){
    Gut<<P.grade_Chnese<<" "<<P.grade_program<<" "<<P.grade_math<<endl;
    return Gut;
}
自增自减符号
class Cnt{
    public:
    double cnt;
    Cnt(long double a=0){cnt=a;}
    Cnt& operator ++(){
        (this->cnt)+=1;
        return *this;
    }//先修改,后返回引用
    Cnt operator ++(int){//这个int用于占位,是C++编译器用于区分这两个重载的标志,无实际意义!!
        Cnt a=*this;
        cnt+=1;
        return a;
    }//先返回值,后修改
};

继承

继承的基本概念:

  1. 基类(Base Class)
    :被继承的类,提供共有的属性和方法。
  2. 派生类(Derived Class)
    :从基类继承的类,可以重用基类的成员,并且可以扩展或修改这些成员。

继承的类型

  1. 公有继承(Public Inheritance)


    • 最常用的继承方式,表示派生类“是一个”基类的特殊类型。
    • 基类的公有成员在派生类中保持公有,基类的保护成员在派生类中保持保护。
  • 基类的私有成员不能直接访问。

    class Base {
    public:
        int pubValue;
    protected:
        int protValue;
    private:
        int privValue;
    

};

class Derived : public Base {
public:
void accessMembers() {
pubValue = 1; // 可以访问公有成员
protValue = 2; // 可以访问保护成员
// privValue = 3; // 不能访问私有成员
}
};

2. **保护继承(Protected Inheritance)**:


    - 基类的公有和保护成员在派生类中都变成保护成员。
 - 不允许外部代码通过派生类访问这些成员,但派生类内部可以访问。
 
    ```cpp
    class Derived : protected Base {
    public:
        void accessMembers() {
            pubValue = 1;   // 可以访问公有成员(现在是保护成员)
            protValue = 2;  // 可以访问保护成员
            // privValue = 3; // 不能访问私有成员
        }
    };
 ```

3. **私有继承(Private Inheritance)**:
- 基类的公有和保护成员在派生类中都变成私有成员。
- 外部代码不能通过派生类访问这些成员,但派生类内部可以访问。

```cpp
class Derived : private Base {
public:
    void accessMembers() {
        pubValue = 1;   // 可以访问公有成员(现在是私有成员)
        protValue = 2;  // 可以访问保护成员
        // privValue = 3; // 不能访问私有成员
    }
};

注意:
虽然说基类
private
的成员是无法在派生类中被调用的,但是实际上他是被继承过来了的,只是被编译器隐藏了。

继承的特点

  1. 构造函数和析构函数


    • 派生类的构造函数会调用基类的构造函数。基类的构造函数先执行,派生类的构造函数后执行。
    • 派生类的析构函数会调用基类的析构函数。派生类的析构函数先执行,基类的析构函数后执行。
class Base {
public:
    Base() { std::cout << "Base Constructor\n"; }
    virtual ~Base() { std::cout << "Base Destructor\n"; }
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived Constructor\n"; }
    ~Derived() { std::cout << "Derived Destructor\n"; }
};
   
   如果你想向基类的构造函数中输入参数,可以用以下方式:
   
   ```C++
   class People{
       public:
       string Name;
       People(){cout<<"creat a people\n";}
       People(const string& name,const string& id,const string& phone_num):Name(name),ID(id),Phone_num(phone_num){cout<<"creat a new people\n";}
       protected:
       string ID;
       string Phone_num;
   };
   
   class Student: public People{
       public:
       Student(){cout<<"creat a new student\n";}
       Student(const string& name,const string& id,const string& phone_num):
       People(name,id,phone_num){//在这里,使用参数列表的方式注入参数
           cout<<"creat a new student\n";
       }
   };
   

这样写是不对的:

class People{
    public:
    string Name;
    People(){cout<<"creat a people\n";}
    People(const string& name,const string& id,const string& phone_num):Name(name),ID(id),Phone_num(phone_num){cout<<"creat a new people\n";}
    protected:
    string ID;
    string Phone_num;
};

class Student: public People{
    public:
    Student(){cout<<"creat a new student\n";}
        Student(const string& name,const string& id,const string& phone_num):
    People::Name(name),People::ID(id),People::Phone_num(phone_num){
        cout<<"creat a new student\n";
    }
};
  1. 变量名冲突:

    多继承中如果父类中出现了同名情况,子类使用时候要加作用域

    class People{
        public:
        string Name;
        People(){cout<<"creat a people\n";}
        People(const string& name,const string& id,const string& phone_num):Name(name),ID(id),Phone_num(phone_num){cout<<"creat a new people\n";}
        void Print();
        protected:
        string ID;
        string Phone_num;
    };
    
    class Student: public People{
        public:
        string ID;
        Student(){cout<<"creat a new student\n";}
        Student(const string& name,const string& id,const string& phone_num,const string & IDD):People(name,id,phone_num),ID(IDD){
            cout<<"creat a new student\n";
        }
        void OKK(){
            cout<<People::ID<<" "<<ID<<endl;//这里,加入作用域就ok啦
        }
    };
    
  2. 虚继承


    • 用于解决菱形继承(钻石继承)问题,确保基类只被初始化一次。
    • 通过在基类前加上
      virtual
      关键字来声明虚继承。

    class Base {
    public:
        int value;
    };
    
    class Derived1 : virtual public Base {};
    class Derived2 : virtual public Base {};
    
    class Final : public Derived1, public Derived2 {};//在Final 类中就只会有一个 Base::value 避免了冗余和二义性
    
  3. 多重继承


    • C++支持一个类从多个基类继承。这种方式允许一个类同时继承多个类的功能,但需要小心避免命名冲突和不一致的问题。

    class A {
    public:
        void funcA() {}
    };
    
    class B {
    public:
        void funcB() {}
    };
    
    class C : public A, public B {
    public:
        void funcC() {}
    };
    

使用继承的注意事项

  • 继承的正确性
    :确保使用继承关系能够表达类之间的实际关系,避免使用继承来简单地复用代码。
  • 封装性
    :使用保护或私有继承可以减少对基类实现细节的依赖。
  • 多态性
    :利用虚函数(virtual functions)和动态绑定(dynamic binding)来实现运行时多态。

继承是C++的一个强大特性,但合理地使用它对于维护代码的可读性和可维护性是非常重要的。

多态

C++中的多态主要有两种类型:

  1. 编译时多态(静态多态)
  2. 运行时多态(动态多态)

编译时多态(静态多态)

编译时多态发生在编译阶段,主要通过函数重载(Function Overloading)和运算符重载(Operator Overloading)来实现。

运行时多态(动态多态)

虚函数
:在基类中声明为
virtual
的成员函数,允许派生类重写,并在运行时通过基类指针或引用调用派生类的实现。

class Base {
public:
    virtual void show() const {
        std::cout << "Base class show function" << std::endl;
    }

    virtual ~Base() {} // 虚析构函数,确保正确释放派生类资源
};

class Derived : public Base {
public:
    void show() const override { // 重写基类的 show 函数
        std::cout << "Derived class show function" << std::endl;
    }
};

这里
override
表示这是一个重写的函数,如果是
override final
指明派生类中某个虚函数不仅是重写了基类的虚函数,而且不允许进一步重写

在基类中也可以采用纯虚函数的写法

纯虚函数语法:
virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为
抽象类

抽象类特点

  • 无法实例化对象(就是无法声明出对象)
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

\(Attention:\)

当派生类的成员占据了堆的空间时(就是派生类是被
new
函数弄个出来的时候),基类的析构函数必须虚函数(纯虚函数也OK),不然会导致在
delete
派生对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,导致出现内存泄漏。

#include <iostream>
//Wrong
class Base {
public:
    Base() { std::cout << "Base constructor\n"; }
    ~Base() { std::cout << "Base destructor\n"; } // 非虚析构函数
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructor\n"; }
    ~Derived() { std::cout << "Derived destructor\n"; }
};

int main() {
    Base* basePtr = new Derived();
    delete basePtr; // 只调用 Base 的析构函数
    return 0;
}
#include <iostream>
//corect
class Base {
public:
    Base() { std::cout << "Base constructor\n"; }
    virtual ~Base() { std::cout << "Base destructor\n"; } // 虚析构函数
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructor\n"; }
    ~Derived() { std::cout << "Derived destructor\n"; }
};

int main() {
    Base* basePtr = new Derived();
    delete basePtr; // 现在会调用 Derived 的析构函数,然后调用 Base 的析构函数
    return 0;
}

撒花完结!!!

Meteor
是一个功能强大且易用的全栈 JavaScript 框架,它简化了现代 Web 应用的开发流程,从
数据库

用户界面
都可以通过一套统一的技术栈来实现。无论你是构建实时应用还是想要一个快速的原型开发工具,
Meteor
都能为你提供完整的解决方案。本文将详细介绍
Meteor
的显著特性、使用方式及其适用场景,并分析为什么它值得成为你的开发工具选择。

简要介绍

Meteor
是一个 JavaScript 框架,诞生于 2012 年,由 Meteor Development Group (MDG) 维护。其核心优势是提供了一个
端到端
的开发平台,允许开发者用
一套技术栈
构建
前端

后端

数据库层
,无需频繁切换语言或框架。它与
Node.js

MongoDB
集成良好,支持实时数据更新,自动化开发流程,开发体验极为顺畅。

显著特性

  1. 实时数据更新

Meteor
最大的特点是内置的实时数据同步机制。无论是客户端还是服务器端,数据更新可以立即反映在用户界面上,而无需手动编写复杂的
WebSocket

AJAX
逻辑。这种无缝的实时性使得
Meteor
在实时聊天、协作工具和社交应用中表现出色。

  1. 全栈开发统一化

Meteor
提供了一个完整的开发栈,前端使用
React

Vue

Blaze
,后端使用
Node.js
,数据库层默认支持
MongoDB
。你可以在同一环境中开发应用的各个部分,减少了切换语言和工具的复杂性。

  1. 强大的生态系统

Meteor
拥有一个广泛的生态系统,提供大量的包(Meteor Packages)和集成工具。你可以通过
Meteor
的集成包管理器(Atmosphere)找到和使用成千上万的现成解决方案,帮助你快速添加功能,例如用户验证、支付集成等。

  1. 快速原型开发

得益于
Meteor
的实时特性和一站式全栈平台,它非常适合快速开发原型。通过简单的命令行工具和自动更新功能,开发者能够在数分钟内搭建一个功能齐全的应用,极大提高开发效率。

  1. 跨平台支持

Meteor
不仅支持 Web 应用的开发,还可以通过与
Cordova
集成,轻松构建
iOS

Android
的移动应用,成为一个真正的跨平台开发工具。

  1. 服务器端渲染(SSR)

Meteor
还支持服务器端渲染(SSR),为开发者提供更好的
SEO
支持和更快的首屏加载体验。这在构建需要搜索引擎优化的应用时,尤其重要。

使用方式

  1. 安装
npm install -g meteor
  1. 初始化项目
meteor create my-app
  1. 使用
import { Meteor } from 'meteor/meteor';
import { TasksCollection } from './db/TasksCollection';

Meteor.startup(() => {
  if (TasksCollection.find().count() === 0) {
    TasksCollection.insert({ text: 'First task' });
  }
});

在开发过程中,
Meteor
会自动监听代码的改动,实时编译并重新加载应用。开发者可以选择使用
React

Vue
或原生的
Blaze
作为前端框架,与
MongoDB
进行数据交互时,也可以使用
Meteor
提供的方便的
Minimongo
客户端数据库。

适用场景

Meteor
非常适合快速构建需要实时功能的 Web 应用和移动应用,以下是一些具体的适用场景:

  1. 实时数据密集型应用

如果你正在构建类似
聊天应用

协作工具

游戏或社交媒体平台
,这些需要频繁同步数据的场景非常适合使用
Meteor

Meteor
内置的实时数据更新机制让数据交互变得异常简单。

  1. 需要快速开发的原型或 MVP

对于初创公司和独立开发者而言,快速迭代产品是成功的关键。
Meteor
简化了从后端到前端的开发流程,能在短时间内构建出可用的最小化产品(MVP),加速验证产品想法。

  1. 跨平台应用开发

如果你想同时发布 Web 应用和移动应用,
Meteor

Cordova
的集成为你提供了轻松的跨平台支持。你可以用同一套代码开发
iOS

Android

Web 应用
,节省大量时间和开发成本。

  1. 中小型企业应用

对于中小型企业来说,使用
Meteor
能帮助他们快速开发内部工具、后台系统或客户管理系统。这些系统通常需要快速上线、低维护成本和良好的扩展性,而
Meteor
完全符合这些要求。

  1. 教育平台或实时互动平台

Meteor
在教育科技(EdTech)和实时互动平台领域也表现优异。由于
Meteor
的内置实时性和数据同步功能,构建直播课程、实时讨论平台或在线学习工具非常合适。

为什么选择 Meteor?

Meteor
是一个
全栈 JavaScript 框架
,它不仅简化了开发者的工作流,还提供了强大的实时数据同步功能。这意味着你不再需要为不同的开发环境学习多种语言,所有功能都可以通过 JavaScript 实现。同时,
Meteor
的生态系统也非常成熟,开发者可以利用大量的第三方包轻松扩展项目功能。

无论是
原型开发

跨平台应用
,还是
实时交互场景

Meteor
都能帮助你快速构建、部署和扩展应用。此外,
Meteor
强大的社区支持和文档资源,也让它成为一个易于学习和使用的框架。

总结

Meteor
作为全栈 JavaScript 框架,凭借其简洁的 API、强大的实时功能和统一的开发体验,成为了众多开发者构建应用的理想选择。特别是在需要实时更新和快速开发的场景中,
Meteor
的优势尤为突出。如果你正在寻找一个能够加速开发流程、简化全栈开发的工具,
Meteor
无疑是一个值得选择的框架。


该框架已经收录到我的全栈前端一站式开发平台 “前端视界” 中(浏览器搜 前端视界 第一个),感兴趣的欢迎浏览使用!

尽管文本到图像的扩散模型已被证明在图像合成方面达到了最先进的结果,但它们尚未证明在下游应用中的有效性。先前的研究提出了在有限的真实数据访问下为图像分类器训练生成数据的方法。然而,这些方法在生成内部分布图像或描绘细粒度特征方面存在困难,从而阻碍了在合成数据集上训练的分类模型的泛化能力。论文提出了
DataDream
,一个合成分类数据集的框架,在少量目标类别示例的指导下,更真实地表示真实数据分布。

DataDream
在生成训练数据之前,对图像生成模型的
LoRA
权重进行微调,使用少量真实图像。然后,使用合成数据微调
CLIP

LoRA
权重,以在各种数据集上改善下游图像分类性能,超越先前的方法。

通过大量实验展示了
DataDream
的有效性,在
10
个数据集中的
7
个数据集上,以少量示例数据超越了最先进的分类准确率,同时在其他
3
个数据集上也表现竞争力。此外,论文提供了关于多种因素的影响的见解,例如真实图像和生成图像的数量以及微调计算对模型性能的影响。

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: DataDream: Few-shot Guided Dataset Generation

Introduction


文本到图像生成模型的出现,例如稳定扩散(
Stable Diffusion
),不仅能够创建照片真实感的合成图像,还为增强下游任务提供了机会。一个潜在的应用是在合成数据上训练或微调特定任务的模型。这在真实数据获取有限的领域尤其有用,因为生成模型提供了一种经济高效的方式来生成大量训练数据。论文研究了合成训练数据在低样本设置下对图像分类任务的影响,即当每个类别只有少量图像可用,但收集整个数据集的成本将是难以承受的。

之前的研究主要集中在使用给定数据集的类名称来指导数据生成过程。具体来说,他们使用文本到图像扩散模型生成图像,将类名称作为条件输入。为了更好地引导模型生成目标对象的准确描绘,他们将每个类的文本描述纳入提示中,这些描述来自语言模型或人工标注的类描述。尽管这些方法直观,但导致一些生成的图像缺乏所关注的对象。例如,来自
ImageNet
数据集的类名称“
clothes iron
”的真实图像显示的是用于熨烫衣物的电器,而
FakeIt
生成的图像大多描绘的是金属熨斗或由其制成的任意物体(见图
1
,左侧)。这种情况发生在生成模型误解类名称的模糊性或稀有类别时。现实图像与合成图像之间的这种不一致限制了生成图像在图像分类中的信息价值,并阻碍了性能的提升。

为了弥合真实图像与合成图像之间的差距,真实图像可以更好地为生成模型提供有关真实数据分布特征的信息。例如,正在同时开发的
DISEF
方法在生成合成数据集时,从部分带噪声的真实图像开始,将少量样本作为条件输入到预训练的扩散模型中。它还使用预训练的图像描述模型来多样化文本到图像的提示。虽然这种方法改善了真实数据和合成数据分布的对齐,但有时未能捕捉到细粒度特征。例如,尽管航空数据集中“
DHC-3-800
”类名称的真实图像在机翼前包含一个螺旋桨,但
DISEF
生成的合成图像缺乏这个细节(见图
1
,右侧)。准确表示类区分特征对分类任务来说可能至关重要,尤其是在细粒度数据集中。

为此,论文提出了一种新方法
DataDream
,旨在利用少量真实数据来适应生成模型。受到个性化生成建模方法的启发,这些方法通过少量描绘相同对象的真实图像对生成模型进行微调,该方法侧重于将生成模型对齐到一个具有多类和每类多样化对象的目标数据集。这与之前的少量样本数据集生成方法不同,后者并未探索微调生成模型的可能性。

具体来说,通过两种方式基于
LoRA
来调整
Stable Diffusion

\(\text{DataDream}_{\text{cls}}\)
,为每个类训练
LoRA
,以及
\(\text{DataDream}_{\text{dset}}\)
,为所有类训练一个
LoRA
。论文是首个提出使用少量样本数据来适应生成模型以生成合成训练数据的方法,而不是利用已冻结的预训练生成模型。在训练之后,使用相同的提示生成图像,该提示用于微调
DataDream
,生成的图像描绘了所关注的对象(例如衣物熨斗)或细粒度特征(例如
DHC-3-800
飞机的螺旋桨),如图
1
的最后一行所示。

通过大量实验验证了
DataDream
的有效性,只使用合成数据时,在所有数据集中达到了最先进的水平,并且在同时使用真实少量样本和合成数据进行训练时,在
10
个数据集中有
7
个获得了最佳性能。为了理解该方法的有效性,论文分析了真实数据与合成数据之间的对齐情况,揭示了该方法在与真实数据分布的对齐方面优于基线方法。最后,通过增加合成数据点和真实样本的数量,探讨了该方法的可扩展性,显示了更大数据集的潜在好处。

总之,论文的贡献如下:

  1. 引入了
    DataDream
    ,一种新颖的少量样本方法,该方法改进了
    Stable Diffusion
    ,以生成更好的同类分布图像,从而用于下游训练。在
    10
    个数据集中,
    DataDream

    7
    个上超过了最先进的少量样本分类表现,其余
    3
    个数据集的表现则相当。

  2. 强调仅使用合成数据报告结果的重要性。证明当仅使用合成数据训练分类器时,论文的方法能够取得更优的性能,在某些情况下甚至超过了仅使用真实少量样本图像训练的分类器,这表明论文的方法生成的图像能够从少量真实数据中提取出更具洞察力的信息。

  3. 通过分析合成数据与真实数据之间的分布对齐情况来研究论文方法的有效性。在少量样本的指导下,该方法生成的合成数据与真实数据的对齐效果最佳。

Methodology


Preliminaries

  • Latent diffusion model

论文的方法基于
Stable Diffusion
实现,这是一种概率生成模型,通过文本提示学习生成真实的图像。给定数据
\((x,c) \in {\mathcal{D}}\)
,其中
\(x\)
是一幅图像,
\(c\)
是描述
\(x\)
的标题,该模型通过逐渐去噪潜在空间中的高斯噪声来学习条件分布
\(p(x|c)\)
。给定一个预训练的编码器
\(E\)
,它将图像
\(x\)
编码为潜在变量
\(z\)
,即
\(z=E(x)\)
,目标函数定义为:

\[\begin{equation}
\min_{\theta} \,\, \mathbb{E}_{(x,c) \sim {\mathcal{D}}, \, \epsilon \sim {\mathcal{N}}(0,1), \, t} \, \left[\, \left\| \, \epsilon - \epsilon_{\theta} (z_t, \tau(c), t) \, \right\|_2^2 \,\right] \, ,
\end{equation}
\]

其中
\(t\)
是时间步,
\(z_t\)
是距离潜在变量
\(z\)
\(t\)
步的潜在带噪声数据,
\(\tau\)
是文本编码器,
\(\epsilon_{\theta}\)
是潜在扩散模型。直观上,参数
\(\theta\)
被训练用于去噪给定文本提示
\(c\)
作为条件信息的潜在
\(z_t\)
。在推理阶段,一个随机噪声向量
\(z_T\)
通过潜在扩散模型进行了
\(T\)
次传递,并与标题
\(c\)
一起,得到去噪后的潜在变量
\(z_0\)
。随后,将
\(z_0\)
输入到一个预训练的解码器
\(D\)
中,以生成图像
\(x'=D(z_0)\)
,用于文本到图像的生成。

  • Low-rank adaptation

低秩适配方法(
LoRA
)是一种微调方法,用于以参数高效的方式将大型预训练模型调整到下游任务。给定预训练模型权重
\(\theta \in \mathbb{R}^{d \times k}\)

LoRA
引入一个新的参数
\(\delta \in \mathbb{R}^{d \times k}\)
,该参数被分解为两个矩阵,
\(\delta=BA\)
,其中
\(B \in \mathbb{R}^{d \times r}\)

\(A \in \mathbb{R}^{r \times k}\)
,且具有较小的
LoRA

\(r\)
,即
\(r \ll \min (d, k)\)

LoRA
权重添加到模型权重中以获得微调后的权重,即
\(\theta^{\text{(ft)}} = \theta \!+ \delta\)
,以适应下游任务。在训练过程中,
\(\theta\)
保持固定,而仅更新
\(\delta\)

DataDream method

论文的目标是通过利用扩散模型生成的合成图像来提高分类性能,至关重要的是将合成图像的分布与真实图像的分布对齐。通过将扩散模型调整为少量真实图像的数据集来实现这种对齐。

假设可以访问一个少量样本的数据集
\({\mathcal{D}}^{\text{fs}}=\{(x_i, y_i)\}_{i=1}^{KN}\)
,其中
\(x_i\)
是一张图像,
\(y_i \in \{1,2,\cdots\!, N\}\)
是它的标签,
\(K\)
是每个类别的样本数量,
\(N\)
是类别的数量。为了匹配真实数据的分布,使用少量样本的数据集
\({\mathcal{D}}^{\text{fs}}\)
进行微调。具体来说,在扩散模型的文本编码器和 U-net 中引入 LoRA 权重,在这里选择有效地调整注意力层的参数。对于每个注意力层,考虑查询、键、值和输出投影矩阵
\(W_q\)
,
\(W_k\)
,
\(W_v\)
,
\(W_o\)
,在每个矩阵中,线性投影被替换为

\[\begin{equation}
h_{l,\star} = W_{\star} h_{l-1} + B_{\star} A_{\star} h_{l-1}
\end{equation}
\]

其中
\(h\)
表示投影的输入/输出激活,最终得到每个注意力层
\(l\)
的可训练
LoRA
权重
\(\delta^{(l)} = \{A_{\star}, B_{\star} | \forall \star \in \{q, k, v, o\}\}\)
。为了简化符号,省略偏置权重。所有其他模型参数(包括
\(W_{\star}\)
)保持不变,而
\(\delta\)
权重则通过梯度下降进行优化。

为了从预训练的扩散模型
checkpoint
开始训练,权重矩阵
\(B_{\star}\)
被初始化为零,而
\(A_{\star}\)
则随机初始化。因此,组合的微调权重
\(B_{\star} A_{\star}\)
最初为零,并逐步学习对原始预训练权重的修改。在测试时,
LoRA
权重可以通过更新权重
\(W^{\text{(ft)}}_{\star} =W_{\star} + B_{\star} A_{\star}\)
集成到模型中,使得推理时间与预训练模型相同。与
DreamBooth
相比,不微调所有网络权重,也不添加保留损失,因为其正则化会阻碍与真实图像的强对齐。

进一步考虑两种设置:
1
)
\(\text{DataDream}_{\text{dset}}\)
,在该设置中,在整个数据集
\({\mathcal{D}}^{\text{fs}}\)
上训练扩散模型的
LoRA
权重,
2
)
\(\text{DataDream}_{\text{cls}}\)
,在该设置中,为数据集中的每个类别初始化
\(N\)

LoRA
权重
\(\{\delta_n|n=1,\cdots\!,N\}\)
,每组权重针对子集
\({\mathcal{D}}^{\text{fs}}_{n} = \{(x,y)| (x,y) \!\in {\mathcal{D}}^{\text{fs}}, y\!=\!n\}\)
进行训练。


\(\text{DataDream}_{\text{dset}}\)
设置中,原始模型参数
\(\theta\)
保持不变,仅对
LoRA
权重进行训练,目标函数为

\[\begin{equation}
\min_{\delta} \mathcal{L}_{\text{D}} = \min_{\delta} \,\, \mathbb{E}_{(x,y) \sim {\mathcal{D}}^{\text{fs}}, \, \epsilon \sim {\mathcal{N}}(0,1), \, t} \, \left[\, || \, \epsilon - \epsilon_{\theta\!, \delta} (z_t, \tau_{\delta}(C(y)), t) \, ||_2^2 \,\right] \, .
\label{eq:datadream_loss}
\end{equation}
\]


\(\text{DataDream}_{\text{cls}}\)
设置中,
\({\mathcal{D}}^{\text{fs}}_{n}\)

\(\delta_n\)
分别替代
\({\mathcal{D}}^{\text{fs}}\)

\(\delta\)
。由于使用的是文本到图像的扩散模型,通过函数
\(C\)
定义文本条件,该函数将标签
\(y\)
(即类名)映射到使用标准模板 "
a photo of a
[
CLS
]" 的提示。该提示会通过文本编码器传递,并在扩散模型的解码步骤中使用。

这两种设置各有不同的优势。在
\(\text{DataDream}_{\text{dset}}\)
中,类之间的
LoRA
权重共享允许在整个数据集内进行关于共性特征的知识转移。这对于那些在各类别中共享粗粒度特征的细粒度数据集是有益的。另一方面,
\(\text{DataDream}_{\text{cls}}\)
为学习每个类别的细节分配了更多的权重,这使得生成模型能够更好地与每个类别的数据分布对齐。

在将扩散模型适应于少样本数据集后,使用调整后的模型在相同的文本提示条件下为每个类别生成
500
张图像,该文本提示与
DataDream
使用的相同,从而形成一个合成数据集
\({\mathcal{D}}^{synth}\)
。在仅使用合成图像或合成与真实少样本图像的组合
\({\mathcal{D}}^{fs}\)
上训练分类器。

对于分类器的训练,调整了一个
CLIP
模型,类似于之前在少样本分类中的工作。为
CLIP ViT-B
/
16
模型的图像编码器和文本编码器添加了
LoRA
适配器。在同时使用合成图像和真实图像进行训练时,使用来自真实数据和合成数据的损失的加权平均。

\[\begin{equation}
\mathcal{L}_{\text{C}} = \,\,
\lambda \, \mathbb{E}_{(x,y) \sim {\mathcal{D}}^{\text{fs}}} \, \text{CE}(f(x),y) +
(1 \!-\! \lambda) \, \mathbb{E}_{(x,y) \sim {\mathcal{D}}^{\text{synth}}} \, \text{CE}(f(x),y) \, ,
\end{equation}
\]

其中
\(\lambda\)
是分配给来自真实数据的损失的权重,函数
\(\text{CE}\)
是交叉熵损失。

  • Implementation details

基于
Stable Diffusion
版本
2.1
实现了
DataDream
,计算基于三个随机种子。对于每个种子,从每个数据集的训练样本中随机抽样少量图像。在所有数据集上训练
200
个周期,批量大小为
8
,唯一的例外是
\(\text{DataDream}_{\text{dset}}\)

ImageNet
上训练
100
个周期。因此,
\(\text{DataDream}_{\text{dset}}\)

\(\text{DataDream}_{\text{cls}}\)
有相同的训练计算量,即每
\(N\)

\(\text{DataDream}_{\text{cls}}\)
适配器权重(每类一个)执行
\(S/N\)
次更新步骤,其中
\(S\)
是整个数据集的
\(\text{DataDream}_{\text{dset}}\)
的总步骤数。

使用
AdamW
作为优化器,学习率为
\(1e-4\)
,并采用余弦退火调度器。对
DataDream
中所有适配权重使用
LoRA
级别
\(r=16\)
。对于
DataDream
的合成图像生成,使用
50
次步骤和指导尺度
2.0
。如果未提及,则每类生成
500
张图像。对于分类器,使用
CLIP ViT-B
/
16
作为基础模型,并在
CLIP
的图像编码器和文本编码器上应用
LoRA
进行微调,级别为
16
。将分配给真实损失项的权重设置为
\(\lambda=0.8\)

Experiments




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.

前言

最近在
DotNetGuide技术社区交流群
看到有不少小伙伴问:
有没有一套代码能够同时在多个平台运行的框架推荐?
今天大姚给大家分享8个多端统一开发框架其中语言包括C#、C++、Vue、React、Dart、Kotlin等等(一套代码,可以运行到多个平台从而大幅减轻开发者的开发与维护负担),同学们可以按需选择对应框架(
排名不分先后,适合自己的才是最好的,有其他更好的推荐欢迎文末留言
)。

微信使用情况投票统计:
https://mp.weixin.qq.com/s/9DNgjTIUXHCr_yOfFwxEDg

uni-app

uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。

功能框架图

从下面uni-app功能框架图可看出,uni-app在跨平台的过程中,不牺牲平台特色,可优雅的调用平台专有能力,真正做到海纳百川、各取所长。

为什么要选择uni-app?

uni-app在开发者数量、案例、跨端抹平度、扩展灵活性、性能体验、周边生态、学习成本、开发成本等8大关键指标上拥有更强的优势。

Taro

Taro是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发微信/京东/百度/支付宝/字节跳动/ QQ 小程序/H5/React Native 等应用。

多端转换支持

Flutter

Flutter是由Google开发的一款开源、跨平台的UI(用户界面)框架,一份代码兼顾 Android、iOS、Web、Windows、macOS 和 Linux 六个平台,编译为原生机器代码,助力提升应用的流畅度并实现优美的动画效果。

主要特性

React Native

React Native由Facebook开发,允许开发者使用JavaScript和React来构建原生体验的移动应用,支持iOS和Android平台。

React Native不仅适用于 Android 和 iOS - 还有社区支持的项目将其应用于其他平台,例如:

Avalonia

Avalonia是一个强大的框架,使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制UI控件,确保在Windows、macOS、Linux、Android、iOS和WebAssembly等不同平台上具有一致的外观和行为。这意味着开发人员可以共享他们的UI代码,并在不同的目标平台上保持统一的外观和感觉。

Avalonia 已经成熟且可用于生产,并被 Schneider Electric、Unity、JetBrains 和 GitHub 等公司使用。

.NET MAUI

.NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移动和桌面应用。使用 .NET MAUI,您可以从单个共享代码库开发可在 Android、iOS、iPadOS、macOS 和 Windows 上运行的应用程序。

Uno

Uno平台是一个开源平台,用于快速构建单一代码库原生移动、Web、桌面和嵌入式应用程序。它允许 C# 和 WinUI XAML 和/或 C# 代码在所有目标平台上运行,同时允许您控制每个像素。它支持开箱即用的 Fluent、Material 和 Cupertino 设计系统。Uno 平台实现了越来越多的 WinRT 和 WinUI API,例如 Microsoft.UI.Xaml,使 WinUI 应用程序能够以本机性能在所有平台上运行。

Eto.Forms

Eto.Forms是一个.NET开源、跨平台的桌面和移动应用的统一框架,该框架允许开发者使用单一的UI代码库构建在多个平台上运行的应用程序,并利用各自平台的原生工具包,从而使应用程序在所有平台上看起来和工作都像原生应用一样。

支持的平台:支持Windows Forms、WPF、MonoMac和GTK#等桌面平台,以及正在开发中的iOS(使用Xamarin.iOS)和Android(使用Xamarin.Android)移动平台支持(尽管目前尚不完整)。