leetcodeday4寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000

第一次解题:

# @lc code=start
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        nums3=sorted(nums1+nums2)
        length= len(nums3)
        return nums3[length//2] if length%2==1 else (nums3[length//2]+nums3[length//2-1])/2

# @lc code=end

占用的内存太高了….

改进:

不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。

class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
def getKthElement(k):
“””
– 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
– 这里的 “/” 表示整除
– nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
– nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
– 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
– 这样 pivot 本身最大也只能是第 k-1 小的元素
– 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组
– 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组
– 由于我们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
“””

        index1, index2 = 0, 0
        while True:
            # 特殊情况
            if index1 == m:
                return nums2[index2 + k - 1]
            if index2 == n:
                return nums1[index1 + k - 1]
            if k == 1:
                return min(nums1[index1], nums2[index2])

            # 正常情况
            newIndex1 = min(index1 + k // 2 - 1, m - 1)
            newIndex2 = min(index2 + k // 2 - 1, n - 1)
            pivot1, pivot2 = nums1[newIndex1], nums2[newIndex2]
            if pivot1 <= pivot2:
                k -= newIndex1 - index1 + 1
                index1 = newIndex1 + 1
            else:
                k -= newIndex2 - index2 + 1
                index2 = newIndex2 + 1

    m, n = len(nums1), len(nums2)
    totalLength = m + n
    if totalLength % 2 == 1:
        return getKthElement((totalLength + 1) // 2)
    else:
        return (getKthElement(totalLength // 2) + getKthElement(totalLength // 2 + 1)) / 2

python中的取整:

  1. 向上取整:math.ceil()
  2. 向下取整:math.floor()、整除”//”
  3. 四舍五入:round()——奇数向远离0取整,偶数去尾取整;或言之:奇数进位,偶数去尾
  4. 向0取整:int()
  • 向上取整:math.ceil()
import math

math.ceil(-0.5)
>>> 0
 
math.ceil(-0.9)
>>> 0

math.ceil(0.3)
>>> 1

  • 向下取整:math.floor()、整除”//”
  • 
    math.floor(-0.3)
    >>> -1
     
    math.floor(0.9)
    >>> 0
    
    
    (-1) // 2  # -0.5
    >>> -1
     
    (-3) // 2  # -1.5
    >>> -2
     
    1 // 2    # 0.5 
    >>> 0
     
    3 // 2    # 1.5
    

    int()

    int(-0.5)>>> 0 int(-0.9)>>> 0 int(0.5)>>> 0 int(0.9)>>> 0一句话总结:int()函数是“向0取整”,取整方向总是让结果比小数的绝对值更小

  • round()
  • 
    round(-2.5)
    >>> -2
     
    round(-1.5)
    >>> -2
     
    round(-0.5)
    >>> 0
     
    round(0.5)
    >>> 0
     
    round(1.5)
    >>> 2
     
    

    C++函数(重点)

    • 函数和数组
    • 函数和结构
    • 函数和指针
    • 函数和string
    • 函数递归

    函数定义:

    typename functionnname(parameterlist){
         statements;
         return value;
    }

    函数原型:如果函数定义在函数调用之后,必须在函数调用前进行函数原型声明,函数原型描述了函数到编译器的接口,将函数返回值类型、参数类型和数量告诉编译器。 函数原型是一条语句,就是函数定义中的函数头,此外,在原型的参数中不要求提供变量名,有类型列表就足够了。

    函数参数和按值传递:

    函数和数组:

    1、输入为数组:

    定义:
    int sum_array (int array[],int n)
    实际情况是array是一个指针,在函数内部,array可以看成是数组
    调用:
    sum_array(cooke,4) 其中cooke是数组名

    2、使用指针处理数组:

    数组名==指针
    
    
    定义:
    int sum_array (int* array,int n)
    调用:
    sum_array(cooke,4) 其中cooke是数组名

    3、使用 输入为数组区间的函数:

     int sum_array (const int* start,const int* end ) 
    调用:
    
    sum_array(cooke,cooke+3) 其中cooke是数组名

    指针和const:

    将const用于指针有很微妙的地方,第一种方法是让指针指向一个常量对象,这样可以防止该指针来修改所指向的值,第二种方法是将指针本身声明为常量,防止改变指针指向的位置。

    但pt的声明并不意味着它指向的值实际上就是一个常量,而只意味着队医pt来说,是一个常量,因此可以通过修改age来 改变age值。

    注意:c++禁止将const地址付给非const的指针,除非使用强制准换。

    函数和二维数组:

    定义函数: 注意这个[4]中的数字必不可少
    int sum_array (int array[][4],int n)
    调用:
    
    sum_array (cooke,4)
    
    or
    定义:array是一个由4个指向int的指针组成的数组
    int sum_array (int (*array)[4],int n)
    

    函数和c风格字符串

    表示字符串方法有三种:

    char数组 、用括号引起的字符串常量、被设置为字符串的地址的char指针。

    字符串作为参数来传递,实际传递的是字符串的第一个字符的地址,因此,需要在函数中将表示字符串的形参声明为char * 类型

    int 函数名(const char * str,char ch)

    函数返回c风格字符串的函数

    在函数内部 声明一个字符串指针 : char * x = char [n+1],然后return x

    函数和结构:

    定义结构:

    struct tracal{
        int x;
        char y;
    }
    声明函数原型:
    tracal sum(tracal x)
    也可以传递结构的地址:
    tracal sum(tracal *x)
    
    

    函数和string 对象 array对象

    函数递归:

    函数指针:

    函数也有地址,可以通过指针获取到函数的地址,也可以通过函数指针调用函数

    1. 获取函数的地址:获取 函数地址很简单 ,只要 使用函数名(不加参数即可),例如think()是一个函数名,那么函数地址就是think。

    如果要将函数作为参数进行传递,必须是传递函数名;

    1. 声明函数指针:
    对于函数:double pam(int)
    声明一个函数指针:
    double (*pf) (int)
    和函数声明类似,不过是将函数名改为指针(加括号),
    
    (*pf) (int)意味着是一个 指向函数的指针
    
    
    1. 使用函数指针调用函数

    double (*pf) (int) 定义之后

    令: pf = pam(函数名)

    调用:x=(*pf)(5)

    typeof 别名

    python 中的序列

    python中序列是最基本的数据结构,他是一块用于存放多个值的连续内存空间。python中内置了5个常用的序列结构,分别是列表、元组、集合、字典和字符串

    1、序列:存放多个值的连续内存空间。

    序列包括:列表[ ],元组 ( ),字典{a:b},集合set={ a,b},字符串:”asdc”

    其中:集合和字典不支持索引、切片、相加和相乘操作。

    序列索引:正序索引,从0开始到n-1,倒序索引:从-(n-1)开始,到-1结束

    切片:name[start:end:step]

    相加:name1+name2

    乘法: name*2 表示name+name

    空序列: [None]

    检测某个元素是否是序列的成员: x in sequence

    计算序列的长度、最大小值: len( )\、max( )、 min( )

    其他内置函数:

    list()将序列转换为list
    str()将序列转换为字符串
    sum()计算元素和
    sorted()排序
    reversed()将原序列元素反向
    enumerate()将序列组合为一个索引序列

    python 中查看数据类型:

    内置函数isinstance(object, (type1,type2…))
    isinstance(‘content’, str)
    返回True or False

    使用内置函数type(object)
    print(type(1))
    print(type(‘content’))

    2、列表 list

    列表中的元素可以是任意各种类型的组合。

    list(seqence ) 将序列转换为列表

    listname=[a,v,bd,d]

    访问序列元素 :

    for i in list:

    for index, item in enumerate(list):

    enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

    enumerate(sequence, [start=0])
    sequence -- 一个序列、迭代器或其他支持迭代对象。
    start -- 下标起始位置。

    添加元素: list.append(“x”)

    添加多个元素: list.extend(seqence)

    根据元素值删除元素: list.remove(value)

    统计指定元素出现次数:list.count(obj)

    获取指定元素首次出现的下标:list.index(obj)

    统计元素和:sum(iterable[,start])

    start表示统计结果需要在 加上start值。 start — 指定相加的参数,如果没有设置这个值,默认为0。

    排序: list.sort(key=None,reverse=False)

    • key — 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。如设置key=str.lower,表示在排序时候不区分字母大小写。
    • reverse — 排序规则,reverse = True 降序, reverse = False 升序(默认)。
    # 获取列表的第二个元素
    def takeSecond(elem):
        return elem[1]
     
    # 列表
    random = [(2, 2), (3, 4), (4, 1), (1, 3)]
     
    # 指定第二个元素排序
    random.sort(key=takeSecond)

    个人理解:首先将list元素送到key定义的函数中 ,然后再把返回值进行排序。

    排序:使用内置的sorted函数: sorted(iterable,key=None,reverse=False)

    列表推导式:

    list=[expression for x in range]

    range是 range ()函数生成的range对象

    从x中筛选满足codition的x

    list = [expression for x in range if condition]

    二维列表:[[ ],[ ],[ ]]

    使用for循环创建:

    for i in range(4):   
       arr.append([])
       for j in range(5):
          arr[i].append[j]
    
    

    列表推导式创建:

    arr = [[j for j in range(6)] for i in range(4)]

    3、元组:不可变的列表,定义以后不可修改,只能重新定义

    A=(1,2,3,4)

    tuple 元组:不可修改,在python中,元组使用一对小括号将所有元素括起来,但小括号并不是必须的只需要将一组值用逗号分隔开,python就可以视为元组 B=1,2,3,4,5 也是元组

    创建元组 tuple(data)

    题外话:print(“ddd”,end=” “) //表示不换行输出

    因为元组不可修改,因此需要对元组中某个元素修改时需要重新定义元组:

    A=(“W”,”Q”)

    A=(“w”,”q”)

    元组推导式:和list类似,将[ ]变为( ),并在最外面还要嵌套一层tuple( ):

    x= (i for i in range) //这句生成的结果不是一个元组,而是一个生成器对象,因此需要千年套一层tuple()

    tuple(x)就是元组

    4、字典 dict( ) == c++中的Map

    dictionary={‘key’:’value’}

    创建字典:

    dictionary = dict(zip(list1,list2))

    zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

    dictionary = dict(‘key1’=’value1′,’key2’=’value2’,….)

    创建值为空的字典: dictionary = dict.fromkeys(list)

    获取指定键的值:

    dictionary.get(key[,default])

    遍历字典:

    dictionary.item() 返回“键-值”列表

    for item  in  dictionary.item(): 获得每一个键值对元组:(key,value)
    for key,value  in  dictionary.item(): 获得每一个键-值

    字典推导式:

    dictionary = {i:j for i,j in zip(list1,list2)}

    5、 集合 set():{ }集合中元素不重复,用于自动去重

    创建set :x = {1,2,3 }

    y=set(iteration)

    创建空集合 z =set( )

    集合的增删:

    setname.add(element) //添加元素

    setname.remove(element) //删除 元素

    setname.pop() //删除元素

    setname.clear() //清空set

    集合的交并差补运算: & | – ^

    实例: c = set1&set2

    6、字符串str()

    拼接字符串:+

    转换字符串 str(num)

    计算字符串长度 len(str)

    计算字符串所占字节数: len(str.encode())

    字符串编解码 str.encode() str.decode()

    截取字符串 string[start:end:step] (前闭后开)

    分割字符str.split(seq,maxsplit)

      seq — 分隔符,默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等。

    maxsplit 分割次数。默认为 -1, 即分隔所有。

    合并字符:string.jion(iterable) // string 合并符,用于合并时候的分隔符

    检索字符串

    str.count(sub[,start,end]) //统计sub出现的次数

    str.find (sub[,start,end])// 检索是否存在sub,不存在返回-1,否则返回首次出现的位置

    str.index (sub[,start,end])// 检索是否存在sub , 返回首次出现的位置 ,如不存在会报异常

    str.startwith(prefix[,start,end]) //判断是否以prefix开头,返回true or flase

    str.endwith(prefix[,start,end]) //判断是否以prefix结尾,返回true or flase

    大小写转换: str.lower() str.upper()

    去除字符串中的空格和特殊字符

    str.strip([chars]) // chars :要去除的字符,默认是去除空格、制表符\t,回车符\r 换行符\n

    str.lstrip([chars])

    str.rstrip([chars])

    leetcodeday3—无重复字符的最长子串

    给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

    示例 1:

    输入: s = "abcabcbb"
    输出: 3 
    解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

    示例 2:

    输入: s = "bbbbb"
    输出: 1
    解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

    示例 3:

    输入: s = "pwwkew"
    输出: 3
    解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
         请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
    
    示例 4:
    
    输入: s = ""
    输出: 0

    思路:

    我第一遍写的时候,想法是把字符串作为一个队列输入,当前输入到队列 中的j in 队列时,就把队列中第一个出列,并更新maxlenght。代码如下 :

    # @lc code=start
    class Solution:
        def lengthOfLongestSubstring(self, x: str) -> int:
            maxlength = 0
            lens = len(x)
            for i in range(lens-1):
                for j in range(i+1,lens):
                    if  x[j] in x[i:j] or  x[j]==x[i]:
                        maxlength = maxlength if maxlength>(j-i) else (j-i)
                        break
                    elif j == lens-1:
                        maxlength = maxlength if maxlength>(j-i+1) else (j-i+1)
                        break
                    else:
                        continue
            return maxlength
    
    # @lc code=end

    但是通过率不是100,因为没有考虑空字符串。

    Wrong Answer

    • 879/987 cases passed (N/A)

    Testcase

    " "

    可以在开始加一个判断 if ” ” in string

    class Solution:
        def lengthOfLongestSubstring(self, x: str) -> int:
            maxlength = 0
            lens = len(x)
            if " " in x[0:1]  or lens==1:
                return 1
            for i in range(lens-1):
                for j in range(i+1,lens):
                    if  x[j] in x[i:j] or  x[j]==x[i]:
                        maxlength = maxlength if maxlength>(j-i) else (j-i)
                        break
                    elif j == lens-1:
                        maxlength = maxlength if maxlength>(j-i+1) else (j-i+1)
                        break
                    else:
                        continue
            return maxlength
    

    Accepted

    • 987/987 cases passed (520 ms)
    • Your runtime beats 8.75 % of python3 submissions
    • Your memory usage beats 16.81 % of python3 submissions (15.2 MB)

    改进版:

    滑动窗口
    思路和算法

    我们先用一个例子考虑如何在较优的时间复杂度内通过本题。

    我们不妨以示例一中的字符串 \texttt{abcabcbb}abcabcbb 为例,找出从每一个字符开始的,不包含重复字符的最长子串,那么其中最长的那个字符串即为答案。对于示例一中的字符串,我们列举出这些结果,其中括号中表示选中的字符以及最长的字符串:

    以 \(\texttt{(a)bcabcbb}(a)bcabcbb\) 开始的最长字符串为 \(\texttt{(abc)abcbb}(abc)abcbb\);
    以 \(\texttt{a(b)cabcbb}a(b)cabcbb\) 开始的最长字符串为 \(\texttt{a(bca)bcbb}a(bca)bcbb\);
    以 \(\texttt{ab(c)abcbb}ab(c)abcbb\) 开始的最长字符串为 \(\texttt{ab(cab)cbb}ab(cab)cbb\);
    以 \(\texttt{abc(a)bcbb}abc(a)bcbb\) 开始的最长字符串为 \(\texttt{abc(abc)bb}abc(abc)bb\);
    以 \(\texttt{abca(b)cbb}abca(b)cbb\) 开始的最长字符串为 \(\texttt{abca(bc)bb}abca(bc)bb\);
    以 \(\texttt{abcab(c)bb}abcab(c)bb\) 开始的最长字符串为 \(\texttt{abcab(cb)b}abcab(cb)b\);
    以 \(\texttt{abcabc(b)b}abcabc(b)b\) 开始的最长字符串为\(\texttt{abcabc(b)b}abcabc(b)b\);
    以 \(\texttt{abcabcb(b)}abcabcb(b)\) 开始的最长字符串为 \(\texttt{abcabcb(b)}abcabcb(b)\)。
    发现了什么?如果我们依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的!这里的原因在于,假设我们选择字符串中的第 k个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为 r_k。那么当我们选择第 k+1 个字符作为起始位置时,首先从 k+1 到 r_k的字符显然是不重复的,并且由于少了原本的第 k个字符,我们可以尝试继续增大 r_k,直到右侧出现了重复字符为止。这样一来,我们就可以使用「滑动窗口」来解决这个问题了:

    我们使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着上文中「枚举子串的起始位置」,而右指针即为上文中的 r_k在每一步的操作中,我们会将左指针向右移动一格,表示我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;

    在枚举结束后,我们找到的最长的子串的长度即为答案

    class Solution:
        def lengthOfLongestSubstring(self, s: str) -> int:
            # 哈希集合,记录每个字符是否出现过
            occ = set()
            n = len(s)
            # 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
            rk, ans = -1, 0
            for i in range(n):
                if i != 0:
                    # 左指针向右移动一格,移除一个字符
                    occ.remove(s[i - 1])
                while rk + 1 < n and s[rk + 1] not in occ:
                    # 不断地移动右指针
                    occ.add(s[rk + 1])
                    rk += 1
                # 第 i 到 rk 个字符是一个极长的无重复字符子串
                ans = max(ans, rk - i + 1)
            return ans
    
    
    • 哈希Map 只需一次遍历, more efficiency
    •  Python 代码
    class Solution:
        def lengthOfLongestSubstring(self, s: str) -> int:
            k, res, c_dict = -1, 0, {}
            for i, c in enumerate(s):
                if c in c_dict and c_dict[c] > k:  # 字符c在字典中 且 上次出现的下标大于当前长度的起始下标
                    k = c_dict[c]
                    c_dict[c] = i
                else:
                    c_dict[c] = i
                    res = max(res, i-k)
            return res

    BicycleGAN-图像一对多转换测试

    2022年 1月
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31  

    CycleGAN、pix2pix、iGAN的主要贡献者最近在NIPS 2017上又推出了一篇文章Toward Multimodal Image-to-Image Translation(见https://junyanz.github.io/BicycleGAN/,https://arxiv.org/pdf/1711.11586.pdf),讨论如何从一张图像同时转换为多张风格不一成对的图像。

    Pix2pix 和 CycleGAN 是非常的流行GAN,不仅在学术界有许多变体,同时也有许多基于此的应用。但是,它们都有一个缺点——图像的输出看起来几乎总是相同的。例如,如果我们要执行斑马到马的转换,被转换的同一马的照片将始终具有相同的外观和色调。这是由于GAN固有的特性,它学会过滤了噪声的随机性。

    像pix2pix这样的图像转换(一对一)的方式是存在歧义的,因为不可能只对应一个输出。因此作者提出了一种一对多的输出,即将可能输出的图像是存在一定的分布特性的。

    论文的主要方法如下图所示:

    下图是 BicycleGAN 相关的模型和配置。图(a)是推理的配置,图像A与噪声相结合以生成图像B ^ ,可以将此看作是 cGAN 。在BicyleGAN中,形状为(256, 256, 3)的图像A是条件,而从潜在编码 z采样的噪声为大小为8的一维向量。图(b)是 pix2pix + 噪声 的训练配置。而图(c) 和 图(d) 的两个配置由 BicycleGAN 训练时使用:

    简而言之,BicycleGAN 可以找到潜在编码z与目标图像B之间的关系,因此生成器可以在给定不同的z时学会生成不同的图像B ^ 。如上图所示,BicycleGAN 通过组合 cVAE-GAN 和 cLR-GAN 这两种模型来做到这一点。

    cVAE-GAN
      VAE-GAN 的作者认为,L1 损失并不是衡量图像视觉质量的良好指标。例如,如果图像向右移动几个像素,则人眼看起来可能没有什么不同,但会导致较大的L1损失。因此使用 GAN 的鉴别器来学习目标函数,以判断伪造的图像是否真实,并使用 VAE 作为生成器,生成的图像更清晰。如果忽略上图(c)中的图像 A ,那就是 VAE-GAN ,由于以 A 为条件,其成为条件 cVAE-GAN 。训练步骤如下:

    • VAE 将真实图片 B编码为多元高斯分布的潜在编码,然后从它们中采样以创建噪声输入,此流程是标准的VAE工作流程;
    • 使用图像 A 作为条件及从潜矢量 z 采样的噪声用于生成伪图像B ^

    训练中的数据流为 B − > z − > B ^ ( 图(c) 中的实线箭头),总的损失函数由三个损失组成:

    对抗损失 \(L_{GAN}^{VAE}\)

    L1​重建损失 \(L_{1}^{VAE}(G)\)

    KL散度损失 \(L_{KL}(E)\)

    cLR-GAN(Conditional Latent Regressor GAN)
      在 cVAE-GAN 中,对真实图像B进行编码,以提供潜在矢量的真实样本并从中进行采样。但是,cLR-GAN 的处理方式有所不同,其首先使用生成器从随机噪声中生成伪图像 B^,然后对伪图像 B^ 进行编码,最后计算其与输入随机噪声差异。
    前向计算步骤如下:

    首先,类似于 cGAN ,随机产生一些噪声,然后串联图像A以生成伪图像 B ^ ,之后,使用来自 VAE-GAN 的同一编码器将伪图像 B ^ 编码为潜矢量。
    最后,从编码的潜矢量中采样 z ^ ,并用输入噪声 z 计算损失。数据流为 z −> B ^ −> z ^ ( 图(d) 中的实线箭头),有两个损失:

    对抗损失 \(L_{GAN}\)

    噪声 N(z) 与潜在编码之间的 L1损失 \(L_{1}^{latent}\)

    通过组合这两个数据流,在输出和潜在空间之间得到了一个双映射循环。 BicycleGAN 中的 bi 来自双映射(双向单射),这是一个数学术语,简单来说其表示一对一映射,并且是可逆的。在这种情况下,BicycleGAN 将输出映射到潜在空间,并且类似地从潜在空间映射到输出。总损失如下:

    最总的损失:

    可以分为两块来理解,第一块就是cVAE-GAN的训练,我们分析的基础就是鞋子纹理风格生成为例。

    鞋子纹理图片经过编码器得到编码后的latent z通过KL距离将其拉向我们事先定义好的分布N(z)上,将服从分布的z与鞋子草图A结合后送入生成器G中得到重构的鞋子纹理图。 此时为了衡量重构和真实的误差,这里用了L1损失和GAN的对抗思想实现,我们在后面损失函数分析部分再说。这样cVAE-GAN部分就可以训练了,cVAE GAN的重点还是在得到的embedding z

    另一块就是cLR-GAN的训练,将鞋子草图A和分布N(z)结合经过生成器G得到鞋子纹理图, 再通过对生成的纹理图编码后得到的z去趋近分布N(z)来反向矫正生成图,达到一个变相的循环。

    当这两部分训练的很好时,这个就是我们需要的BicycleGAN了,在检验训练效果时我们只需要,输入A加上N(z)就可以生成鞋子的纹理图了, 这个N(z)具体为什么怎么取将决定生成为纹理的风格了。

    一些细节

    • 这里有一个小trike就是z和图片A的结合送入生成器G的结合方法,文中给出了两种方法:一种直接concat在input的channel上,一种Unet在压缩的时候,每次结果都加。 我们通过图解可以更好理解。

    pytorch代码:https://github.com/junyanz/BicycleGAN

    c++ 分支语句和逻辑运算符

    • if语句
    • 逻辑运算符 && ! ||
    • cctype字符函数库
    • 条件运算符:?:
    • switch语句
    • continue和break
    • 读取数字的循环
    • 基本文件输入输出

    1、 if语句

    if (test_condition){ body } 如果test_condition为true,那么就执行 body语句。

    2、 if else 语句

    if(test_condition)
        {body}
    else
        {body}

    3、 if else if else 语句

    if(test_condition)
        {body}
    else if
        {body}
    
    else
        {body}

    易错点:赋值运算符=和比较运算符==,在test_condition中使用==表达等于

    逻辑表达式

    1、逻辑或 ||

    2、逻辑与 &&

    A&&B 只有A和B都为true时,表达式才为true\

    可以用来表示范围 A<20 && A> 30

    3、逻辑非 !

    !A

    注意: 逻辑运算符 &&和||优先级都低于关系运算符,!运算符则大于所有关系运算符和算数运算符。

    此外可以使用 and or not表示

    字符函数库 cctype #include <cctype>

    该头文件声明了一组用于对单个字符进行分类和转换的函数。

    条件运算符 ?:

    表达式1?表达式2:表达式3

    a>1?b=2:b=4 如果a>1,那么令b=2,否则b=4

    switch语句 case中的value必须是int整数

    switch(interger-expression){
        case value1: 
                 body;
                 break;
        case value2: 
                 body;
                 break;
        .............
        case valuen: 
                 body;
                 break;
    default:body
    }
    

    break和continue

    break用于switch语句,跳出switch;continue用于循环语句,用于跳出本次循环。

    写文本文件

    读取文件

    神经网络可视化工具

    2022年 1月
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31  

    来源:磐创AI分享

    神经网络可视化工具

    Convolution Visualizer

    https://ezyang.github.io/convolution-visualizer/index.html

    这种交互式可视化演示了各种卷积参数如何影响输入、权重和输出矩阵之间的形状和数据依赖性。将鼠标悬停在输入/输出上将突出显示相应的输出/输入,而将鼠标悬停在权重上将突出显示哪些输入与该权重相乘以计算输出。(严格来说,这里可视化的操作是相关性,而不是卷积,因为真正的卷积在执行相关性之前会翻转其权重。但是,大多数深度学习框架仍然称这些卷积,最终与梯度下降相同.)

    Weights & Biases

    https://docs.wandb.ai/v/zh-hans/

    Weights & Biases 可以帮助跟踪你的机器学习项目。使用我们的工具记录运行中的超参数和输出指标(Metric),然后对结果进行可视化和比较,并快速与同事分享你的发现。

    通过wandb,能够给你的机器学习项目带来强大的交互式可视化调试体验,能够自动化记录Python脚本中的图标,并且实时在网页仪表盘展示它的结果,例如,损失函数、准确率、召回率,它能够让你在最短的时间内完成机器学习项目可视化图片的制作。

    总结而言,wandb有4项核心功能:

    看板:跟踪训练过程,给出可视化结果
    报告:保存和共享训练过程中一些细节、有价值的信息
    调优:使用超参数调优来优化你训练的模型
    工具:数据集和模型版本化
    也就是说,wandb并不单纯的是一款数据可视化工具。它具有更为强大的模型和数据版本管理。此外,还可以对你训练的模型进行调优。

    draw_convnet

    一个用于画卷积神经网络的Python脚本

    https://github.com/gwding/draw_convnet

    NNSVG

    http://alexlenail.me/NN-SVG/LeNet.html

    PlotNeuralNet:用于为报告和演示绘制神经网络的 Latex 代码。

    https://github.com/HarisIqbal88/PlotNeuralNet

    Tensorboard

    https://www.tensorflow.org/tensorboard/graphs

    Caffe

    https://github.com/BVLC/caffe/blob/master/python/caffe/draw.py

    Matlab

    http://www.mathworks.com/help/nnet/ref/view.html

    Keras.js

    https://transcranial.github.io/keras-js/#/inception-v3

    DotNet

    https://github.com/martisak/dotnets

    Graphviz

    http://www.graphviz.org/

    ConX

    https://conx.readthedocs.io/en/latest/index.html

    ENNUI

    https://math.mit.edu/ennui/

    Neataptic

    https://wagenaartje.github.io/neataptic/

    pyTorch模型可视化

    visdom:

    在PyTorch深度学习中,最常用的模型可视化工具是Facebook(中文为脸书,目前已改名为Meta)公司开源的Visdom

    Visdom可以直接接受来自PyTorch的张量,而不用转化成NumPy中的数组,从而运行效率很高。此外,Visdom可以直接在内存中获取数据,毫秒级刷新,速度很快。

    Visdom的安装很简单,直接执行以下命令即可:

    pip install visdom

    开启服务,因为visdom本质上是一个类似于Jupyter Notebook 的Web服务器,在使用之前需要在终端打开服务,代码如下:

    python -m visdom.server

    正常执行后,根据提示在浏览器中输入相应地址即可,默认地址为:

    http://localhost:8097/

    实例

    本例通过使用PyTorch的可视化工具Visdom对手写数字数据集进行建模。

    步骤1:先导入模型需要的包,代码如下。

    import torch

    import torch.nn as nn

    import torch.nn.functional as F

    import torch.optim as optim

    from torchvision import datasets, transforms

    from visdom import Visdom

    步骤2:定义训练参数,代码如下。

    batch_size=200

    learning_rate=0.01

    epochs=10

    … …

    执行成功后,在visdom网页可以看到实时更新的训练过程的数据变化,每一个epoch测试数据更新一次,如图9-15所示。

    Visdom是由Plotly 提供的可视化支持,所以提供一下可视化的接口:

    • vis.scatter : 2D 或 3D 散点图
    • vis.line : 线图
    • vis.stem : 茎叶图
    • vis.heatmap : 热力图
    • vis.bar : 条形图
    • vis.histogram: 直方图
    • vis.boxplot : 箱型图
    • vis.surf : 表面图
    • vis.contour : 轮廓图
    • vis.quiver : 绘出二维矢量场
    • vis.image : 图片
    • vis.text : 文本
    • vis.mesh : 网格图
    • vis.save : 序列化状态

    更新损失函数

    在训练的时候我们每一批次都会打印一下训练的损失和测试的准确率,这样展示的图表是需要动态增加数据的,下面我们来模拟一下这种情况:

    x,y=0,0
    env2 = Visdom()
    pane1= env2.line(
        X=np.array([x]),
        Y=np.array([y]),
        opts=dict(title='dynamic data'))

    Setting up a new session…

    for i in range(10):
        time.sleep(1) #每隔一秒钟打印一次数据
        x+=i
        y=(y+i)*1.5
        print(x,y)
        env2.line(
            X=np.array([x]),
            Y=np.array([y]),
            win=pane1,#win参数确认使用哪一个pane
            update='append') #我们做的动作是追加

    TensorBoard

    pytorch也支持tensorboard的使用:

    Tensorboard的使用逻辑

    Tensorboard的工作流程简单来说是

    • 将代码运行过程中的,某些你关心的数据保存在一个文件夹中:
    这一步由代码中的writer完成
    • 再读取这个文件夹中的数据,用浏览器显示出来:
    这一步通过在命令行运行tensorboard完成。

    官方:

    https://pytorch.org/docs/stable/tensorboard.html?highlight=tensorboard

    其中可视化的主要功能如下:

    (1)Scalars:展示训练过程中的准确率、损失值、权重/偏置的变化情况。

    (2)Images:展示训练过程中记录的图像。

    (3)Audio:展示训练过程中记录的音频。

    (4)Graphs:展示模型的数据流图,以及训练在各个设备上消耗的内存和时间。

    (5)Distributions:展示训练过程中记录的数据的分部图。

    (6)Histograms:展示训练过程中记录的数据的柱状图。

    (7)Embeddings:展示词向量后的投影分部。

    动手练习:可视化模型参数

    步骤1:首先导入相关的第三方包,代码如下。

    import numpy as np

    from torch.utils.tensorboard import SummaryWriter

    步骤2:将loss写到Loss_Accuracy路径下面,代码如下。

    np.random.seed(10)

    writer = SummaryWriter(‘runs/Loss_Accuracy’)

    步骤3:然后将loss写到writer中,其中add_scalars()函数可以将不同的变量添加到同一个图,代码如下。

    for n_iter in range(100):

    writer.add_scalar(‘Loss/train’, np.random.random(), n_iter)

    writer.add_scalar(‘Loss/test’, np.random.random(), n_iter)

    writer.add_scalar(‘Accuracy/train’, np.random.random(), n_iter)

    writer.add_scalar(‘Accuracy/test’, np.random.random(), n_iter)

    代码体中要做的事

    首先导入tensorboard

    from torch.utils.tensorboard import SummaryWriter   

    这里的SummaryWriter的作用就是,将数据以特定的格式存储到刚刚提到的那个文件夹中。

    首先我们将其实例化

    writer = SummaryWriter('./path/to/log')

    这里传入的参数就是指向文件夹的路径,之后我们使用这个writer对象“拿出来”的任何数据都保存在这个路径之下。

    这个对象包含多个方法,比如针对数值,我们可以调用

    writer.add_scalar(tag, scalar_value, global_step=None, walltime=None)

    这里的tag指定可视化时这个变量的名字,scalar_value是你要存的值,global_step可以理解为x轴坐标。

    举一个简单的例子:

    for epoch in range(100)
        mAP = eval(model)
        writer.add_scalar('mAP', mAP, epoch)

    这样就会生成一个x轴跨度为100的折线图,y轴坐标代表着每一个epoch的mAP。这个折线图会保存在指定的路径下(但是现在还看不到)

    同理,除了数值,我们可能还会想看到模型训练过程中的图像。

     writer.add_image(tag, img_tensor, global_step=None, walltime=None, dataformats='CHW')
     writer.add_images(tag, img_tensor, global_step=None, walltime=None, dataformats='NCHW')

    可视化

    我们已经将关心的数据拿出来了,接下来我们只需要在命令行运行:

    tensorboard --logdir=./path/to/the/folder --port 8123

    然后打开浏览器,访问地址http://localhost:8123/即可。这里的8123只是随便一个例子,用其他的未被占用端口也没有任何问题,注意命令行的端口与浏览器访问的地址同步。

    如果发现不显示数据,注意检查一下路径是否正确,命令行这里注意是

    --logdir=./path/to/the/folder 

    而不是

    --logdir= './path/to/the/folder '

    另一点要注意的是tensorboard并不是实时显示(visdom是完全实时的),而是默认30秒刷新一次。

    细节

    1.变量归类

    命名变量的时候可以使用形如

    writer.add_scalar('loss/loss1', loss1, epoch)
    writer.add_scalar('loss/loss2', loss2, epoch)
    writer.add_scalar('loss/loss3', loss3, epoch)

    的格式,这样3个loss就会被显示在同一个section。

    2.同时显示多个折线图

    假如使用了两种学习率去训练同一个网络,想要比较它们训练过程中的loss曲线,只需要将两个日志文件夹放到同一目录下,并在命令行运行

    tensorboard --logdir=./path/to/the/root --port 8123

    物体检测中小物体问题

    摘自:3D视觉初学者

    0.介绍

    检测小物体是计算机视觉中最具挑战性和重要的问题之一。在这篇文章中,我们将讨论通过迭代数百种小物体检测模型在Roboflow上开发的一些策略。无人机在公共空中海上数据集中从上方看到的小物体

    为了提高模型在小对象上的性能,我们建议以下技术入手:

    • 提高图像拍摄分辨率
    • 增加模型的输入分辨率
    • 平铺图片
    • 通过扩充生成更多数据
    • 自动学习模型
    • 过滤掉多余的类

    小物件很难!例如,在EfficientDet中,小型对象的AP仅为12%,大型对象的AP为51%。那几乎是五倍的差异!那么,为什么很难检测小物体呢?一切都取决于模型。对象检测模型通过聚合卷积层中的像素来形成特征。

    PP-YOLO中用于对象检测的特征聚合

    如果地面物体本来就不大,而在进行训练时还会变小。因此,小物体最有可能出现数据标记错误,甚至可能会省略其标识。从经验和理论上讲,小物体都很难检测。

    提高图像拍摄分辨率

    分辨率,分辨率,分辨率……全都与分辨率有关。

    很小的物体在边界框内可能只包含几个像素,这意味着增加图像的分辨率以增加检测器可以从该边界框提取信息的丰富度,这非常重要。因此,如果可能我们建议捕获尽可能高分辨率的图像。

    增加模型的输入分辨率

    获得更高分辨率的图像后,就可以扩大模型的输入分辨率。警告:这将导致大型模型需要花费较长的训练时间,并且在开始部署时将较慢地推断出来。您可能需要进行实验以找出速度与性能之间的权衡。

    平铺图片

    检测小图像的另一种很好的策略是将图像平铺作为预处理步骤。平铺可以有效地将检测器放大到小物体上,但可以保持所需的小输入分辨率,以便能够进行快速推理。

    通过扩充生成更多数据

    数据扩充会从基本数据集中生成新图像。这对于防止模型过度拟合训练集非常有用。对于小物体检测,一些特别有用的增强包括随机裁剪,随机旋转和镶嵌增强。自动学习模型的锚定框

    锚定框是模型学习预测的原型边界框。也就是说,锚框可以预先设置,有时对于训练数据而言不是最佳的。自定义调整这些参数以适合我们需要完成的任务,这是很好的。YOLOv5模型架构会根据小伙伴的自定义数据自动为您完成此操作。我们要做的就只是是开始训练。

    过滤掉多余的类

    类管理是提高数据集质量的一项重要技术。如果有两个类明显重叠的类,则应从数据集中过滤一个。如果因为认为数据集中的小对象不值得检测,并希望将他们先去除。大家可以通过运行Roboflow Pro的高级数据集来快速识别所有这些小对象。可以通过Roboflow的管理工具来实现类遗漏和类重命名。

    c++ 循环和关系表达式

    1、for循环

    for循环主要完成以下部分:1、设置初始值 2、判断循环是否继续进行 3、执行循环操作 4、更新用于测试的值

    for(初始值;测试条件;更新测试值)//初始值可以 自定义变量   int x = 0

    for循环中更新值:i++ i– 也可以选择不同步长 i = i+2 i=i+4

    2、递增运算符 ++ 和 递减运算符 —

    a++ :先使用a,在a自增 ++a :先a自加一,在使用a

    for循环语句块:

    字符串的比较:

    string类字符串比较:

    3、while循环

    while(循环条件){
    body
    }

    4、类型别名

    方法一:

    方法二:

    5、do while 循环

    基于范围的循环 (C++11 新增)

    用于数组或者容器类,如vector 和 array。

    for ( int x : array) { body }

    如果要在循环体对 x进行修改:for (int &x : arrayname) 需要使用引用。

    循环和文本输入:

    1、使用原始cin输入:

    补救:使用cin.get(char):会读取空格,并赋给char

    文件尾条件 EOF

    二维数组

    初始化:

    使用二维数组: maxtemps[1][2]