项目编排心得

  • 写一个庞大的项目时,可以把model和其它设置所需参数,集中到一个config file,在主程序读取该file并作为一个字典变量自始至终传递,就避免了过多的参数传递,而且把所有函数的参数细节用一个很轻松简洁的方式提供了接口。而且当项目开源作供他人修改以作他用时,可以保留一些默认值而不需要去考虑他们
  • 要善于利用pandas处理csv文件作为中间处理结果和日志等

基础python部分

  • 查看python版本 python -V
  • python2和python3支持的字符串不同,python2注释也不支持中文
  • python 想知道包所在的位置,用print(xxx.__file__)
  • 查看python解释器的位置:
  • import sys
  • sys.executable
  • python用pool进行多进程处理时报错可能会报不出来,应该先debug再用多进程
  • 想记录程序运行时间可以如下所示
import time
start_time = time.time()
do_something()
end_time = time.time()
runningtime = end_time - start_time
  • python如果相乘忘了加乘号,会出现:
  • ‘TypeError: ‘float’ object is not callable’
  • 向set中加元素是add,向list中加元素是append,连接两个list可以用+,比如:list3 = list1 + list2list3 += list4
  • 这样构造dict,当键不存在时,会返回默认值0
from collections import defaultdict
a = defaultdict(int)
a['hah']
  • del a, b会删除a、b变量,清除内存占用
  • 要活用
[xx(i) for i in someset if yy(i) > 0]

json

  • json 文件load时应该是:
with open(json_file_path, 'r') as json_file:
        dict_dataset = json.load(json_file)
  • json 写入应该是:
json.dump(mydict, open(os.path.join(output_path ,filename.json'), 'w'))

cv2

  • cv2.imread()如果读不到图片暂时是不会报错的,需要检验读出来的对象是不是None,读不到返回的是None。cv2.imwrite文件名如果后缀出错,就会报错没有对应extension的writer
  • cv2.imwrite('hah.jpg',img)
  • cv2.imwrite()如果路径不存在不会报错不会创建,什么都不会做;而且如果文件名为png,即使单通道也会写入为3通道
  • cv2.imread()读到的是nparray形式的图片,所以如果想在图片中定位,要注意,0索引对应行索引,对应点距离图片上边缘的距离,也就是h/y,1索引对应列索引,对应点距离图片左边缘的距离,也就是w/x,2索引对应channel;而PIL.Image.open()读到的图片,以及输入torch的网络的图片,采取的索引的顺序是不一样的,需要注意这一点
  • 想在图片上画框可以这样:
    是inplace地改变图片,而且x1,y1是正常的x1,y1,不需要做索引变换,最后一个数是画框的宽度,倒数第二个是颜色
cv2.rectangle(img,(int(x1),int(y1)),(int(x2),int(y2)),(0,255,0),1)

matplotlib:

  • 要可视化nparray代表的图片可以如下所示,和cv2兼容的索引顺序
import matplotlib.pyplot as plt
plt.imshow(img)
  • 如果在jupyter notebook中运行上述却只是显示<matplotlib.image.AxesImage at 0x1fb80c59518>,可以运行%matplotlib inline

plt.figure()
plt.plot(a,c)
plt.savefig('hah2.png')
  • 画直方图 plt.hist(array_or_list, num_of_bins)

os shutil

  • os.path.join('xx', 'xx', ...)
  • os.listdir('xx')返回xx路径下的所有文件、文件夹的名字组成的列表
  • os.walk('xx')
  • 将path1路径下名为file_name的文件复制到path2路径下并且重命名为new_name shutil.copyfile(os.path.join(path1,file_name), os.path.join(path2,new_name))

numpy

  • numpy生成mask要注意x和y要对调,也就是说第二个索引才是横轴
  • numpy使用如a[0:100]的索引形式时,即使超出索引上限也不会报错,而是全取。相对于,如果a只有50个元素,上述代码相当于a[:]
  • a = np.linspace(10,1,19)返回值为array([10. , 9.5, 9. , 8.5, 8. , 7.5, 7. , 6.5, 6. , 5.5, 5. , 4.5, 4. , 3.5, 3. , 2.5, 2. , 1.5, 1. ])
  • np.where()
  • np.expand_dims()
  • np.repeat()
  • a = np.repeat(np.expand_dims(np.linspace(0,511,512).astype(int), axis=0), 512, axis=0)
  • np.cumsum()
  • np.unique()

pandas部分:

  • import pandas as pd
  • 读csv:csvdf = pd.read_csv('xxx.csv'),另外可以设置header参数,默认是以csv文件的第一行作为列名,若传header为None,则认为csv中没有列名,第一行作为数据读取,并且自己给一个数字的列名吗,如果想改变列名,可以mydf.columns = ['File_name', 'bbox_x1', 'bbox_y1', 'bbox_x2', 'bbox_y2', 'class']
  • 取df的行数csvdf.shape[0]
  • 取出某一列的全部数据:df['xxx'].values,返回值是一个array,每一项是一个数据,string或者int等等,df[‘xxx’]返回值是Series,可以直接myseries[0]取出数据
  • 取出一行:df.iloc[i],这样取的返回值不是DataFrame,是Series,可以直接myseries['xxx']取出数据,而DataFrame不行
  • 取出几行作为新的df:df[0:3]
  • for i, row in my_df.iterrows():,row是Series
  • to_dict()方法:myseries,to_dict()会返回一个字典,mydf.to_dict(orient='records')会返回字典的列表,每一行是每一项,orient可以是别的,返回值的形式不同
  • pd.DataFrame(list(dict()))若该函数的输入是一个有相同键的字典的列表,列表的每一项会作为构造出的df的行,每一个键会作为列
  • 可以这样构造DataFrame:这为数据处理提供了一个思路,也即在遍历原先df行的时候(iterrows())把每一行的处理结果append到一个list里面去,最后利用这个list赋值给df
annotation2 = pd.DataFrame()
a = [1,2,3]
annotation2['File_name'] = a
annotation2['abbox_x1'] = a
annotation2['abbox_y1'] = a
annotation2['abbox_x2'] = a
annotation2['abbox_y2'] = a
  • 有一个df1,有’id’ ‘ann’ 等列,但是缺少了’file_name’,而df2同时有’id’和’filename’,只是行的顺序和df1不同。如果想利用df2为df1加上’filename’这一列,可以先遍历df2,遍历过程中将filename和id取出来append到一个空的list,然后利用这两个list构造一个df3(流程如上一点),然后利用pd.merge(df1,df3)就可以完成

用以下方法可以返回File_name为该值的匹配行组成的DataFrame,注意match_dict的值应该为list

match_dict = {'File_name':['000652_04_02_266.png']}
imgline = ann_all.loc[ann_all.isin(match_dict).any(1)]

也可以这样子:ann[ann['xxx']==1]

pytorch部分:

  • pytorch代码报错一些bus error或者insufficient shared memory问题是因为pytorch默认使用共享内存来将数据转移到显存中,所以可以通过调小dataloader的numworker来解决,可以调为0,还有,unable to write to file </ torch> 是因为对根目录没有写权限,但是共享内存溢出时pytorch会将一部分共享内存中的数据保存为根目录下的临时文件
  • pytorch如果想在自己的dataset和dataloader返回文件名字符串,要么自己写一个collate_fn,要么把字符串列表保存为dataset的私有变量,__getitem__里返回索引,在外面用索引从dataset的字符串列表里取出文件名,不可从__getitem__里返回文件名,会报错
  • github代码下载太慢总是断开可以用G码云关联github进行下载
  • coco格式的dataset,不能指定类别id为0,否则会报错
  • tensor 有mytensor.tolist()方法可以返回list
  • dataset的构造方法:
    注意__init__()里面要有self。
import torch
from torch.utils.data import Dataset
class GenDataset(Dataset):
    def __init__(self, name_list):
        self.name_list = name_list

    def __len__(self):
        return len(self.name_list)

    def __getitem__(self, i):
        return {'idx': self.name_list[i]}
  • 测试dataset是否有问题:
import matplotlib.pyplot as plt
a =iter(dataloader).next()
b = torchvision.utils.make_grid(a['A'])
b = np.transpose(b, (1, 2, 0))
plt.imshow(b)
  • dataloader的numworkers 如果不是0可能会出错
  • torch的复制方法

图片操作

  • pytorch torchvision.transforms有两类,一类是random的,一类是可控制的,如果希望对pair的图片进行相同的transform(一般在segmention和SR的任务中),应该使用可控制的,结合np.random.random()和random.randint(),具体的函数在pytorch官方文档,多数是以PILImage作为输入,示例见下方:
if np.random.random() < 0.5:
    img = TF.hflip(img)
    mask = TF.hflip(mask)

if np.random.random() < 0.5:
    translate_x = random.randint(-8,8)
    translate_y = random.randint(-8,8)
    img = TF.affine(img, angle=0, translate=(translate_x, translate_y), scale=1, shear=0, resample=0, fillcolor=None)

if np.random.random() < 0.5:
    crop_x = random.randint(-5,5)
    crop_y = random.randint(-5,5)
    img = TF.resized_crop(img, crop_y, crop_x, 512-2*crop_y, 512-2*crop_x, (512,512))
    mask = TF.resized_crop(mask, crop_y, crop_x, 512-2*crop_y, 512-2*crop_x, (512,512))

tensorboard

  • 想在远端服务器使用tensorboard,服务器采取keyboard交互,网上那些隧道的方法尝试没一个成功的,所以把log文件cp到本地,本地使用tensorboard,简单粗暴,tensorboard加到项目中的方式:
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(comment=f'LR_{lr}_BS_{batch_size}_SCALE_{img_scale}')
global_step = 0
for epoch in range(epochs):
	for i, batch in enumerate(dataloader):
		############
		# do something here
		# get some loss 、 accuracy or image term here
		#############
		writer.add_scalar('loss/train', loss, global_step)
		writer.add_scalar('acc/train', acc, global_step)
		writer.add_image('images', imgs, global_step)
		if global_step % do_eval_freq == 0:
			############
			# do eval here
			# get some accuracy term here
			##########
			writer.add_scalar('acc/test', acc, global_step)
		global_step += 1
		
writer.close()

注意,如果add_scalar()的数值输入却传入了一个字符串,由于pytorch源码的疏忽,会出现:NameError: name 'workspace' is not defined的报错

多GPU并行

  • 如果模型被dataparallel包住想取出来或者取模型的某些属性,用net.module可以
  • device1 = torch.device('cuda:1')model.to(device1)可以实现指定GPU,也可以借助
import os
os.environ['CUDA_VISIBLE_DEVICES'] = "0,1,2,3,4"

还可以命令行先输入:

CUDA_VISIBLE_DEVICES = 0,1,2,3,4
  • loss模块不要并行
  • 并行处理方法:
device_for_data = torch.device('cuda:0' if cuda else 'cpu')
device_for_model = torch.device('cuda' if cuda else 'cpu')

if cuda:
    generator = DataParallel(generator)
    generator.to(device_for_model)
    discriminator = DataParallel(discriminator)
    discriminator.to(device_for_model)

FloatTensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor
real_imgs = Variable(imgs.type(FloatTensor)).to(device_for_data)