ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)

Algorithm

leetcode16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.

与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

思路

标签:排序和双指针
本题目因为要计算三个数,如果靠暴力枚举的话时间复杂度会到 O(n^3),需要降低时间复杂度。

首先进行数组排序,时间复杂度 O(nlogn)

在数组 nums 中,进行遍历,每遍历一个值利用其下标i,形成一个固定值 nums[i]

再使用前指针指向 start = i + 1 处,后指针指向 end = nums.length - 1 处,也就是结尾处

根据 sum = nums[i] + nums[start] + nums[end] 的结果,判断 sum 与目标 target 的距离,如果更近则更新结果 ans

同时判断 sum 与 target 的大小关系,因为数组有序,如果 sum > target 则 end–,如果 sum < target 则 start++,如果 sum == target 则说明距离为 0 直接返回结果

整个遍历过程,固定值为 n 次,双指针为 n 次,时间复杂度为 O(n^2)

总时间复杂度:O(nlogn) + O(n ^ 2) = O(n ^ 2)

代码如下:

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len = nums.size();
        int tmp = INT_MAX;
        int close;
        sort(nums.begin(), nums.end());
        for(int i = 0; i <len; i++){
            int L = i+1;
            int R = len-1;
            while(L<R){
                int sum = nums[i]+nums[L]+nums[R];
                if(abs(close-target)>abs(sum-target))
                    close = sum;
                if(sum == target)
                    break;
                else if(sum > target)
                    R--;
                else if(sum < target)
                    L++;
            }
        }
        return close;
    }
};

Review

Google的一篇文章:To prune, or not to prune: exploring the efficacy of pruning for model compression

根据剪枝粒度的不同,目前主要有层间剪枝、特征图剪枝、 k×k 核剪枝与核内剪枝等方法, 如下图所示。

ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第1张图片
层间剪枝直接减少网络的深度,特征图剪枝直接减少网络的宽度,它们都可以归类为
Filter-level Pruning。 这两种粗粒度的剪枝方法在减少网络参数方面效果明显,但同时存在网
络性能下降严重的问题。

k×k 核剪枝(kernel-level)与核内剪枝(fine-grained)两种细粒度方法在参数量与模型性能
之间取得了一定的平衡,但提高了方法的复杂度。

目前,模型剪枝比较多的还是两大类方法, 一个是连接剪枝,一个是通道剪枝。

1.连接剪枝
早期 Lecun 等人提出的 Optimal Brain Damage 方法就属于连接剪枝,深度学习技术训
练发展后,韩松等人是较早研究网络剪枝问题的,在 2015 的时候提出了 Deep
Compression,它们采用了 3 步策略,即首先训练一个模型,然后去掉权重值低于一个阈值
的连接,最后重新进行 finetune,如下图所示:

ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第2张图片
这样的过程需要进行多步迭代才能取得更好的效果,最后如果某些神经元的输入输出为零,则可以将神经元移除。
ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第3张图片
在这样的一个剪枝流程中, finetune 是至关重要的操作,而且不是从零开始,而是基
于之前训练好的模型。该框架中发现卷积层对于剪枝的敏感性远远大于全连接层,对于
VGG 等模型,可以获得只有原来体积 7.5%大小的剪枝结果。

剪掉连接是最直观的方法,但是它需要专门的硬件来支持,所以实际应用价值很受限。

2.通道剪枝(Channel Pruning)

通道剪枝是目前主流的模型剪枝方法,也是可以在目前的硬件上支持的方法,下面我们介绍两个经典思路。

第一个是利用相关指标直接衡量通道的重要性来指导剪枝。
第二个是利用重建误差来指导剪枝, 属于间接衡量一个通道的重要性。

首先我们介绍第一个框架, Network Slimming 算法通过 batch normalization 中的缩放因子 γ 来对不重要的通道进行裁剪, 可以实现在训练过程中自动剪枝。

文章的思路非常简单,有两点:
1.使用 batch normalization 中的缩放因子 γ 作为重要性评估因子, γ 越小则认为对应的channel 越不重要,从而可以裁剪掉。
2.在目标方程中增加一个关于 γ 的正则项,从而实现训练中自动剪枝,而不是训练完之后再剪枝。

ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第4张图片
整个训练的流程如下:
ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第5张图片
ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第6张图片

上表展示了在几个常用数据集上的结果,对 VGG 的剪枝率能达到 80%且精度不下降。
另外这个框架中还有一个重要的参数,即正则项权重,过大必然影响模型的性能,下图展示
了错误率与 λ 的关系。

ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第7张图片
可知道剪枝率超过 80%时会有明显下降,在此之前影响不大。
总的来说,此框架思想非常简单,模型压缩率也能达到 20 倍,是一个不错的 baseline。

Tips

C++中的智能指针
ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第8张图片
常规的指针类:就是叫浅复制,又叫浅拷贝,他的缺点很明显,就是两块内存指向同一个空间,当释放掉的时候,其中的一个指针不知道指向哪里,就变成了野指针,这是很严重的。

ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第9张图片
而值类型,也就是深拷贝(深复制),就比浅拷贝来的要安全很多,对于每个内存都指定一块新的空间去存储,但是比较耗费空间

而智能指针可以很好避免野指针,而且也能节省空间,就是需要写一个类三个代码如下:

首先是浅拷贝:

plain-ptr.h

//
// Created by zdq on 2020/5/8.
//

#ifndef C___PLAIN_PTR_H
#define C___PLAIN_PTR_H

class AHasPtr{
public:
    AHasPtr( int *p, int i):ptr(p),val(i){}

    int *get_ptr() const {return ptr;}
    int get_int() const {return val;}

    void set_ptr(int *p) {ptr = p;}
    void set_int(int i) {val = i;}

    int get_ptr_val() const {return *ptr;}
    void set_ptr_val(int val)const {*ptr = val;}

private:
    int val;
    int *ptr;

};



#endif //C___PLAIN_PTR_H

深拷贝头文件:

//
// Created by zdq on 2020/5/8.
//

#ifndef C___VALUE_PTR_H
#define C___VALUE_PTR_H

class CHasPtr{
public:
    CHasPtr(const int &p, int i):ptr(new int(p)),val(i){}
    CHasPtr(const CHasPtr &orig):ptr(new int (*orig.ptr)),val(orig.val){}
    CHasPtr& operator = (const CHasPtr&);
    ~CHasPtr() {delete ptr;}

    int *get_ptr() const {return ptr;}
    int get_int() const {return val;}

    void set_ptr(int *p) {ptr = p;}
    void set_int(int i) {val = i;}

    int get_ptr_val() const {return *ptr;}
    void set_ptr_val(int val)const {*ptr = val;}

private:
    int val;
    int *ptr;

};

CHasPtr& CHasPtr::operator = (const CHasPtr &rhs){
    *ptr = *rhs.ptr;
    val = rhs.val;
    return *this;
}
#endif //C___VALUE_PTR_H

智能指针头文件:

//
// Created by zdq on 2020/5/9.
//
#include 
#ifndef C___SMART_PTR_H
#define C___SMART_PTR_H

class U_Ptr{
    friend class BHasPtr;
private:
    int *ip;
    size_t use; //计数
    U_Ptr(int *p):ip(p), use(1) {}
    ~U_Ptr() {delete ip;}
};

class BHasPtr{
public:
    BHasPtr(int *p, int i):ptr(new U_Ptr(p)),val(i){}
    BHasPtr(const BHasPtr &orig):ptr(orig.ptr),val(orig.val) {
        ++ptr->use;
    }
    BHasPtr& operator=(const BHasPtr&);

    ~BHasPtr()
    {
        if(--ptr->use==0) delete ptr;
    }

    int *get_ptr() const {return ptr->ip;}
    int get_int() const {return val;}

    void set_ptr(int *p) {ptr->ip = p;}
    void set_int(int i) {val = i;}

    int get_ptr_val() const {return *ptr->ip;}
    void set_ptr_val(int val)const {*ptr->ip = val;}

private:
    int val;
//    int *ptr;
    U_Ptr *ptr;
};

BHasPtr& BHasPtr::operator=(const BHasPtr &rhs){
    ++rhs.ptr->use;
    if(--ptr->use == 0) delete ptr;

    ptr = rhs.ptr;
}
#endif //C___SMART_PTR_H

因为智能指针用类表示,而且是私有类,需要一个友元函数让外部函数调用U_Ptr智能指针类。注意还要有构造函数,析构函数,赋值运算函数三要素。

最后是主要函数:

#include
#include 
#include "plain-ptr.h"
#include "value-ptr.h"
#include "smart-ptr.h"
//#include

using namespace std;

void test_AHasPtr(){
    int i = 42;
    AHasPtr p1(&i, 42);
    AHasPtr p2 = p1;
    cout << p2.get_ptr_val() <<endl;

    p1.set_ptr_val(0);
    cout << p2.get_ptr_val() <<endl;

    int *ip = new int(42);
    AHasPtr ptr(ip, 10);
    cout << ptr.get_ptr_val() <<endl;
    delete ip;
    cout << ptr.get_ptr_val() <<endl;
}

void test_CHasPtr(){

    int obj = 0;
    CHasPtr ptr1(obj,42);
    CHasPtr ptr2(ptr1);
    cout << ptr1.get_ptr_val() << ", " << ptr1.get_int() <<endl;
    cout << ptr2.get_ptr_val() << ", " << ptr2.get_int() <<endl;
    ptr2.set_ptr_val(6);
    ptr2.set_int(48);
    cout << "修改以后:" << endl;
    cout << ptr1.get_ptr_val() << ", " << ptr1.get_int() <<endl;
    cout << ptr2.get_ptr_val() << ", " << ptr2.get_int() <<endl;
}

void test_BHasPtr(){
    int obj = 0;
    BHasPtr ptr1(&obj,42);
    BHasPtr ptr2(ptr1);
    cout << ptr1.get_ptr_val() << ", "<< ptr1.get_int() << endl;
    cout << ptr2.get_ptr_val() << ", "<< ptr2.get_int() << endl;
    cout << "修改之后: "<<endl;
    ptr2.set_ptr_val(2);
    ptr2.set_int(22);
    cout << ptr1.get_ptr_val() << ", " << ptr1.get_int() <<endl;
    cout << ptr2.get_ptr_val() << ", "<< ptr2.get_int() << endl;
}


// 命令行选项
int main() {
    cout << "常规指针"<<endl;
    test_AHasPtr();
    cout << endl<< "值型类: "<<endl;
    test_CHasPtr();
    cout << "智能指针:"<<endl;
    test_BHasPtr();
    return 0;
}

结果如下:
ARTS-22(leetcode16. 最接近的三数之和,模型剪枝To prune or not to prune,C++智能指针初识,操作系统是怎么启动的, 何为爱一个人)_第10张图片
要确保用 new 动态分配的内存空间在程序的各条执行路径都能被释放是一件麻烦的事情。C++ 11 模板库的 头文件中定义的智能指针,即 shared _ptr 模板,就是用来部分解决这个问题的。

只要将 new 运算符返回的指针 p 交给一个 shared_ptr 对象“托管”,就不必担心在哪里写delete p语句——实际上根本不需要编写这条语句,托管 p 的 shared_ptr 对象在消亡时会自动执行delete p。而且,该 shared_ptr 对象能像指针 p —样使用,即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。

通过 shared_ptr 的构造函数,可以让 shared_ptr 对象托管一个 new 运算符返回的指针,写法如下:
shared_ptr ptr(new T); // T 可以是 int、char、类等各种类型

此后,ptr 就可以像 T* 类型的指针一样使用,即 *ptr 就是用 new 动态分配的那个对象。

多个 shared_ptr 对象可以共同托管一个指针 p,当所有曾经托管 p 的 shared_ptr 对象都解除了对其的托管时,就会执行delete p。

C++中重载输入输出操作符

#include
#include 

using namespace std;
//C++primer 当中最常用的例子
class Sale_item{
    friend ostream& operator<<(ostream& out , const Sale_item& s);
    friend istream& operator>>(istream& in,  Sale_item& s);
public:
    Sale_item(string book, unsigned units, double price):isbn(book),units_sold(units),revenue(price*units){}
    Sale_item():units_sold(0),revenue(0.0){}
private:
    string isbn;
    unsigned units_sold;
    double revenue;
};

ostream& operator<<(ostream& out , const Sale_item& s){
    out << s.isbn << "\t" << s.units_sold << "\t" << s.revenue;
    return out;
}

istream& operator>>(istream& in,  Sale_item& s){
    double price;
    in >> s.isbn >> s.units_sold >> price;
    s.revenue = s.units_sold * price;
    return in;
}

// 命令行选项
int main() {
    cout << "hello"<<endl;
    int b;

    Sale_item item("0-201-783-X",2,25.00);
    cout << item <<endl;

    cin >> item;
    cout << item <<endl;
    return 0;
}

操作系统中的一些知识

开机启动的时候做了什么事情?取址执行,操作系统是在磁盘中,需要转移到内存中排列好去执行。由第一段代码:引导扇区bootsect.s汇编代码去完成,汇编可以严格控制结果。

按照顺序就是boot,setup, head, main, mem_init,第一将操作系统从磁盘中读入内存进来,才能取址执行,然后就是初始化,因为OS是去管理计算机硬件设备的软件系统,针对每个设备,要获得相应的数据结构去管理。这就是操作系统启动的大致过程。

操作系统的特征有并发,共享,他们互为存在条件,共享中有互斥共享方式(对摄像头设备的共享使用),同时共享方式(对硬盘资源的共享使用)

特征还有虚拟和异步。没有并发和共享,就谈不上虚拟和异步,因此并发和共享是操作系统的两个最基本的特征。

中断和异常

1.当中断发生时,CPU立即进入核心态

2.当中断发生后,当前运行的进程暂停运行,并由操作系统内核对终端进行处理

3.对于不同的中断信号,会进行不同的处理

发生了中断,就意味着需要操作系统介入,开展管理工作。由于操作系统的管理工作(进程切换,分配IO设备等)需要使用特权指令,因此CPU要从用户态转为核心态。中断可以使CPU从用户态切换为核心态,使操作系统获得计算机的控制权。有了中断,才能实现多道程序并发执行。

“Share”

何为爱一个人?何为好的亲密关系?

对方是一个实实在在的个人,而且如果是一个很爱你的人,她的情绪不是平白无故而来的,对方向你述说,一定是希望你能够听得进去她的情绪,由来是为什么不重要,而是理解对方情绪变化以及当时的程度情况,强调的如果是很难受,很郁闷,那一定是很难受很郁闷,并且一定是渴望被理解,希望所爱的人能够理解自己的难受并给自己真心实意的关心,寄托的对方如果没有接纳,没有成为所期望的后盾,就会失望。如果一而再再而三地出现,那真的失望透顶了,这是一门功课,可能自己想的多做的少,且没有切中要点,这到底是情商低还是脑子笨?我知道自己主动去拥有这份处理的能力是很重要的,但有时候也想要对方在事情发生的时候说一下具体希望自己如何去做,然后可能会学得更快更好,我希望能够成为一个能够让心爱的人安心的男子汉,我会努力的!

你可能感兴趣的:(ARTS专栏)