Skip to main content

欢迎来到FEMATHS小组学习日志

· 9 min read
Tanger
Academic rubbish | CV Engineers | Visual bubble | compute math | PINN | Mathematical model

FEMATHS 学习小组的故事可以追溯到 2023 年 6 月。当时,JoyBunzqqqqqqj1110Tanger 三人决定一起攻读与 PINNPhysics-Informed Neural Networks)相关的论文,以突破各自在学习中遇到的瓶颈。

但在阅读过程中,大家逐渐意识到:不仅 JoyBunzqqqqqqj1110 对 PINN 感到困惑,连相对熟悉一些的 Tanger 也有许多难以理解的地方。于是我们决定边读论文边做笔记。虽然这些笔记可能显得 粗浅、幼稚,甚至不乏理解上的偏差,但我们仍希望将它们整理出来。我们相信,通过写笔记的方式,可以尽可能清晰地梳理出 PINN 以及人工智能相关论文中的核心思想和原理。我们希望用最朴素的学习方法,把复杂的内容讲明白——用简单的努力,积累不平凡的价值

这,就是 FEMATHS 小组学习日志 的由来。

后来,随着 Tanger 面临考研与工作的压力,科研学习和小组学习日志一度中断。直到 2025 年,桂林电子科技大学数学与计算科学学院公布拟录取名单,Tanger 顺利被录取。这也成为重新启动学习日志的契机——新的笔记就此续写。

从入门到入土?不,是精通!科技论文完全指南:如何找到一篇合适的科技论文

· 8 min read
Tanger
Academic rubbish | CV Engineers | Visual bubble | compute math | PINN | Mathematical model

科技论文是学者与研究人员进行学术交流的重要方式。通过阅读科技论文,不仅可以了解当前领域的研究进展,还能提升自己对复杂问题的认知能力和理解力。在学习过程中,书籍 📕、网站 🖥、期刊论文等都是常见且有效的起点资源。


🔍 如何找到一篇合适的科技论文

在上一节中,我们已经了解了科技论文的基本结构及其产生过程,相信你对科技论文已有初步认识。接下来,我们将学习如何寻找一篇适合阅读的科技论文

找到一篇合适的论文,对于入门新领域、拓展知识视野具有重要意义。一篇好的论文能够帮助你:

  • 快速了解某个研究方向的基本概念;
  • 把握领域内的研究重点与热点问题;
  • 学习论文写作的结构与逻辑表达方式。

从入门到入土?不,是精通!科技论文完全指南:如何正确且高效地阅读一篇科技论文

· 8 min read
Tanger
Academic rubbish | CV Engineers | Visual bubble | compute math | PINN | Mathematical model

如果你读到这里,或许你已经准备好去探索这个领域的规律与本质。在我们看来,**认真对待每一篇论文,是科研之路的起点,更是最重要的一步。**正如教育部“长江学者”特聘教授尹芝南所言:

“阅读我们的文献,是从事科学研究的基础,也是我们研究生的必修课程。”

从头到尾逐字翻译或阅读一篇科技论文,实际上是效率最低的方式。经验丰富的科研人员通常会优先关注文章中最关键的信息,以进行快速判断其研究价值与相关性。

正确有效的阅读一篇科技论文

阅读一篇科技论文效率最低的方法就是从头到尾翻译。专家研究人员会从文章中较为关键的点进行查找发现。一般来说,大多数科技论文会分为五个部分,如图红色部分:

7.png

  • Abstract
  • Introduction
  • Method
  • Result
  • Discussion

从入门到入土?不,是精通!科技论文完全指南:如何写出一篇优秀的科技论文

· 18 min read
Tanger
Academic rubbish | CV Engineers | Visual bubble | compute math | PINN | Mathematical model

相信看到这里的朋友,已经对一篇科研论文(Research Article)的基本结构相当熟悉了。科研工作者最常撰写的文章类型之一就是采用 IMRaD 模式 的研究论文,即包括以下几个部分:

  • I – Introduction(引言)
  • M – Methods(方法)
  • R – Results(结果)
  • A – Abstract(摘要)
  • D – Discussion(讨论)

我将结合自己的经历,分享论文写作的一般流程。需要注意的是,论文的撰写顺序通常并不等同于其最终的排版结构。在科研实践中,写作往往是从已有的研究结果出发,逐步向前、向后延展的。

通常,在完成一段时间的实验或建模工作后,我们首先获得的是一组数据或研究结果。因此,写作往往是从 Results(结果) 开始,根据结果再去梳理并书写 Methods(方法),说明这些结果是如何得到的。随后撰写 Discussion(讨论),对结果进行分析和解释,进一步明确其意义与不足之处。

在此基础上,我们再回到前面,撰写 Introduction(引言),梳理研究背景、动机、已有工作与创新点。最后撰写 Abstract(摘要),对全文进行简洁总结。

相关GAN及其SRGAN消融试验

· 22 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

本文将从三部分,即GAN模型的理论部分,代码(实践)部分及SRGAN的消融试验部分展开介绍

1. GAN(Generative Adversarial Network)生成对抗网络

核心:由两个神经网络——生成器(Generator)和判别器(Discriminator)组成,通过博弈过程相互提升。 · 生成器:试图“伪造”以假乱真的数据。 · 判别器:判断输入是真实数据还是生成器伪造的。 · 训练目标:生成器希望骗过判别器,判别器希望准确识别真假。 本质上是一个最大最小问题:

minGmaxD Expdata[logD(x)]+Ezpz[log(1D(G(z)))]\min_G \max_D \ \mathbb{E}_{x \sim p_{\text{data}}} \left[ \log D(x) \right] + \mathbb{E}_{z \sim p_z} \left[ \log \left(1 - D(G(z)) \right) \right]

2. cGAN(Conditional GAN)条件生成对抗网络

核心:在GAN的基础上,引入“条件”信息(如标签、图像、文本等) · 生成器和判别器都接收条件变量 · G(z,y):在条件 y 下生成图像 · D(x,y):判断图像是否为在条件 y 下真实的 用途:图像翻译(如黑白图像上色)、语义图生成图像、文本生成图像 目标函数:

minGmaxD Ex,ypdata[logD(xy)]+Ezpz[log(1D(G(zy)))]\min_G \max_D \ \mathbb{E}_{x,y \sim p_{\text{data}}} \left[ \log D(x|y) \right] + \mathbb{E}_{z \sim p_z} \left[ \log \left(1 - D(G(z|y)) \right) \right]

3. SRGAN

目的:图像超分辨率,即将低分辨率图像(LR)还原成高分辨率图像(HR) · 生成器结构:使用残差网络(ResNet)进行细节重建。 · 判别器:区分生成的高分图像和真实高分图像。 损失函数包含: · 内容损失(如 MSE 或感知损失); · 对抗损失(判别器输出) · 感知损失(Perceptual Loss):在 VGG 网络的高层 feature 上计算差异,更贴近人类视觉感受

4. ESRGAN

基于SRGAN,具有如下优势:

  1. Residual-in-Residual Dense Block (RRDB):替换原 SRGAN 的残差块,结构更深,信息流更丰富。
  2. 对抗损失改进:采用 Relativistic average GAN(RaGAN),即判断“生成图是否比真实图更假”,而不是简单判断真假。
  3. 感知损失优化:使用未归一化的 VGG 特征图,避免图像过光滑。
  4. 训练技巧:使用多阶段训练,包括先训练内容损失,再加入对抗训练

总结(理论部分)

名称全称类型特点概述
GANGenerative Adversarial Net无监督生成对抗生成图像
cGANConditional GAN条件生成加入标签或条件进行控制
SRGANSuper-Resolution GAN图像超分辨率使用感知损失,生成自然高分图像
ESRGANEnhanced SRGAN图像超分辨率加强网络结构和损失函数,细节更佳

在后文的试验中,原始代码与数据集皆存放在GitHub仓库:https://github.com/zqqqqqqj1110/GAN_WB

GAN对抗神经网络及其变种(试验部分)

1. cGAN

本文以cGAN作为baseline,后续的gan变种模型皆由该部分代码变换而来,因此在这部分会讲的较为全面一些,后文可能会省略

1.1 安装需要的包与环境

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from skimage.metrics import peak_signal_noise_ratio, structural_similarity
import matplotlib.pyplot as plt
from pytorch_msssim import ms_ssim

device = torch.device("mps" if torch.backends.mps.is_available() else "cpu")
print(device)

作者使用的是MacOS,因此使用了mps,如果是cuda的话直接换成“cuda”即可,配置环境部分(gpu环境)可转到我的个人博客处查阅(或者在这更,懒了,小鸽一下^_^)

1.2 数据预处理

首先需要加载原始的数据集,接着在低分辨率下生成数据(下采样)与保存(通过双线性插值的方法),最后计算mean,std等标准指标(都是后面需要用到的,为了计算指标,不如手动自己计算一下)

# === 1. 加载原始 HR 数据 ===
hr_train = np.load("seasonal_split/HR_data_train_tm_Summer.npy")[:200]
hr_valid = np.load("seasonal_split/HR_data_valid_tm_Summer.npy")[:200]
hr_test = np.load("seasonal_split/HR_data_test_tm_Summer.npy")[:200]

# === 2. 生成 LR 数据(双线性插值至 16×16) ===
def downsample(hr_array, scale=4):
tensor = torch.tensor(hr_array, dtype=torch.float32)
return F.interpolate(tensor, scale_factor=1/scale, mode="bilinear", align_corners=False).numpy()

lr_train = downsample(hr_train)
lr_valid = downsample(hr_valid)
lr_test = downsample(hr_test)

# === 3. 保存为 .npy 文件 ===
np.save("tm/HR_data_train_40.npy", hr_train)
np.save("tm/LR_data_train_40.npy", lr_train)
np.save("tm/HR_data_valid_40.npy", hr_valid)
np.save("tm/LR_data_valid_40.npy", lr_valid)
np.save("tm/HR_data_test_40.npy", hr_test)
np.save("tm/LR_data_test_40.npy", lr_test)

# === 4. mean和std===
mean = np.mean(hr_train, axis=(0, 2, 3))[:, None, None]
std = np.std(hr_train, axis=(0, 2, 3))[:, None, None]
np.save("tm/mean_40.npy", mean)
np.save("tm/std_40.npy", std)

# 每个通道的 min 和 max(例如 2 个通道)
hr_min = hr_train.min(axis=(0, 2, 3)) # shape: (2,)
hr_max = hr_train.max(axis=(0, 2, 3))

# 保存为 .npy 文件,后续评估使用
np.save("tm/min_40.npy", hr_min.astype(np.float32))
np.save("tm/max_40.npy", hr_max.astype(np.float32))

print("完成:生成 LR/HR 切片、保存归一化参数,包括 test")

1.3 自定义数据集类

为了后续方便训练,自定义数据集类,主要是为了变形等操作。最终目的是对每张图进行归一化(标准化)并用于 DataLoader 加载训练/验证数据

class WeatherDataset(Dataset):
def __init__(self, lr_path, hr_path, mean_path, std_path):
self.lr = np.load(lr_path)
self.hr = np.load(hr_path)
self.mean = np.load(mean_path).reshape(2, 1, 1)
self.std = np.load(std_path).reshape(2, 1, 1)

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

def __getitem__(self, idx):
lr = (self.lr[idx] - self.mean) / self.std
hr = (self.hr[idx] - self.mean) / self.std
return torch.tensor(lr, dtype=torch.float32), torch.tensor(hr, dtype=torch.float32)

1.4 定义生成器

定义cGAN等生成器部分,编码器通过两层卷积和 LeakyReLU 将输入图像从 16×16 压缩至 4×4,用于提取深层特征;解码器则通过四层反卷积(ConvTranspose)逐步上采样回 64×64,同时使用 BatchNorm 和 ReLU 激活保持稳定性和非线性表达能力。最终一层使用 Tanh 激活输出高分辨率图像

class GeneratorUNet(nn.Module):
def __init__(self, in_channels=2, out_channels=2, features=64):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv2d(in_channels, features, 4, 2, 1), # 16×16 → 8×8
nn.LeakyReLU(0.2),
nn.Conv2d(features, features * 2, 4, 2, 1), # 8×8 → 4×4
nn.BatchNorm2d(features * 2),
nn.LeakyReLU(0.2),
)
self.decoder = nn.Sequential(
nn.ConvTranspose2d(features * 2, features, 4, 2, 1), # 4×4 → 8×8
nn.BatchNorm2d(features),
nn.ReLU(),
nn.ConvTranspose2d(features, features, 4, 2, 1), # 8×8 → 16×16
nn.BatchNorm2d(features),
nn.ReLU(),
nn.ConvTranspose2d(features, features, 4, 2, 1), # 16×16 → 32×32
nn.BatchNorm2d(features),
nn.ReLU(),
nn.ConvTranspose2d(features, out_channels, 4, 2, 1), # 32×32 → 64×64
nn.Tanh()
)

def forward(self, x):
return self.decoder(self.encoder(x))

1.5 定义判别器

使用多个卷积层对输入图像局部区域进行真实性判别,输入为上采样后的 LR 图像与真实/生成 HR 图像的拼接结果。网络逐层下采样并输出一个 7×7 的判别图,对图像中各个局部 patch 给出是否真实的预测评分

import torch.nn as nn

class Discriminator(nn.Module):
def __init__(self, in_channels=4, features=64):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels, features, 4, 2, 1), # (B, 64, 32, 32)
nn.LeakyReLU(0.2),
nn.Conv2d(features, features * 2, 4, 2, 1), # (B, 128, 16, 16)
nn.BatchNorm2d(features * 2),
nn.LeakyReLU(0.2),
nn.Conv2d(features * 2, features * 4, 4, 2, 1), # (B, 256, 8, 8)
nn.BatchNorm2d(features * 4),
nn.LeakyReLU(0.2),
nn.Conv2d(features * 4, 1, 4, 1, 1), # (B, 1, 7, 7) => PatchGAN 输出
nn.Sigmoid()
)

def forward(self, lr_up, hr_or_fake):
# Ensure both inputs are [B, 2, 64, 64]
if lr_up.shape[-2:] != hr_or_fake.shape[-2:]:
raise ValueError(f"Shape mismatch: lr_up={lr_up.shape}, hr={hr_or_fake.shape}")
x = torch.cat([lr_up, hr_or_fake], dim=1) # => [B, 4, H, W]
return self.model(x)

1.6 计算指标

本文以SSIM,PSNR为例,对模型进行评估,需要注意的是归一化不应该是z-score,而是应该使用max-min归一化(吃过一次亏)

from skimage.metrics import peak_signal_noise_ratio, structural_similarity
import torch.nn.functional as F

def compute_psnr_ssim(pred, target):
# 转换为 numpy 格式,shape: (N, H, W, C)
pred = pred.detach().cpu().numpy().transpose(0, 2, 3, 1)
target = target.detach().cpu().numpy().transpose(0, 2, 3, 1)
data_range = max(target.max(), pred.max()) - min(target.min(), pred.min())
psnr_total, ssim_total = 0, 0
for p, t in zip(pred, target):
psnr_total += peak_signal_noise_ratio(t, p, data_range=data_range)
ssim_total += structural_similarity(t, p, channel_axis=-1, data_range=data_range)

return psnr_total / len(pred), ssim_total / len(pred)

def compute_rmse(pred, target):
return torch.sqrt(torch.mean((pred - target) ** 2))

def compute_mae(pred, target):
return torch.mean(torch.abs(pred - target))

# === 添加 SSIM Loss ===
def ssim_loss(pred, target, C1=0.01**2, C2=0.03**2):
mu_x = F.avg_pool2d(pred, 3, 1, 1)
mu_y = F.avg_pool2d(target, 3, 1, 1)
sigma_x = F.avg_pool2d(pred ** 2, 3, 1, 1) - mu_x ** 2
sigma_y = F.avg_pool2d(target ** 2, 3, 1, 1) - mu_y ** 2
sigma_xy = F.avg_pool2d(pred * target, 3, 1, 1) - mu_x * mu_y
ssim_n = (2 * mu_x * mu_y + C1) * (2 * sigma_xy + C2)
ssim_d = (mu_x ** 2 + mu_y ** 2 + C1) * (sigma_x + sigma_y + C2)
ssim_map = ssim_n / (ssim_d + 1e-8)
return 1 - ssim_map.mean()

# 加载 min/max
hr_min = np.load("tm/min_40.npy")[:, None, None]
hr_max = np.load("tm/max_40.npy")[:, None, None]

def minmax_scale(tensor):
# 缩放到 0~1
return (tensor - torch.tensor(hr_min, dtype=torch.float32).to(tensor.device)) / \
(torch.tensor(hr_max - hr_min, dtype=torch.float32).to(tensor.device))

1.7 加载数据集,准备训练

batch size为将n个样本为一组(一个批次)读取数据.将之前的训练集,测试集与评估集上传到数据类并加载,准备训练。需要注意的是batch size越大,对gpu的负担也越大,但是同时训练到的数据也越多

train_set = WeatherDataset(
"tm/LR_data_train_40.npy", "tm/HR_data_train_40.npy",
"tm/mean_40.npy", "tm/std_40.npy"
)
val_set = WeatherDataset(
"tm/LR_data_valid_40.npy", "tm/HR_data_valid_40.npy",
"tm/mean_40.npy", "tm/std_40.npy"
)
test_set = WeatherDataset(
"tm/LR_data_test_40.npy", "tm/HR_data_test_40.npy",
"tm/mean_40.npy", "tm/std_40.npy"
)

test_loader = DataLoader(test_set, batch_size=32, shuffle=False)
train_loader = DataLoader(train_set, batch_size=32, shuffle=True)
val_loader = DataLoader(val_set, batch_size=32, shuffle=False)

1.8 模型训练

首先先对模型进行初始化,最后一句print为形状,如果形状不对后续训练必失败。实例化生成器和判别器并送到设备(MPS 或 CPU);接着定义损失函数,MSELoss 用于像素级内容损失,BCELoss 用于 GAN 判别器对抗损失;最后定义优化器,使用ADAM进行优化并将学习率调为1e-4

# === 6. Model Initialization ===
G = GeneratorUNet().to(device)
D = Discriminator().to(device)
criterion_GAN = nn.BCEWithLogitsLoss()
criterion_L1 = nn.L1Loss()
opt_G = torch.optim.Adam(G.parameters(), lr=1e-4, betas=(0.5, 0.999), weight_decay=1e-4)
opt_D = torch.optim.Adam(D.parameters(), lr=1e-4, betas=(0.5, 0.999), weight_decay=1e-4)
with torch.no_grad():
dummy_input = torch.randn(1, 2, 16, 16).to(device)
dummy_output = G(dummy_input)
print(f"G output shape: {dummy_output.shape}") # 应该是 [1, 2, 64, 64]

准备好了之后,最后开始进行模型的训练与保存,训练时可以将每一轮epoch的指标都保存在数组中,方便后续画图;还需要注意在训练前准备标签构造,为对抗训练准备真假标签(用于 BCELoss)且判别器输出是 6×6 patch 的预测,匹配标签形状。

num_epochs = 200
train_psnrs, train_ssims, train_rmses, train_maes = [], [], [], []
val_psnrs, val_ssims, val_rmses, val_maes = [], [], [], []


for epoch in range(num_epochs):
G.train()
for lr, hr in train_loader:
lr, hr = lr.to(device), hr.to(device)

# === Forward Generator ===
fake = G(lr).to(device)
lr_up = F.interpolate(lr, size=hr.shape[-2:], mode="bilinear", align_corners=False)

# === Train Discriminator ===
D_real = D(lr_up, hr).to(device)
D_fake = D(lr_up, fake.detach()).to(device)
loss_D = (
criterion_GAN(D_real, torch.ones_like(D_real)) +
criterion_GAN(D_fake, torch.zeros_like(D_fake))
) * 0.5
opt_D.zero_grad()
loss_D.backward()
opt_D.step()

# === Train Generator ===
pred = D(lr_up, fake)
loss_ssim = ssim_loss(fake, hr)
loss_l1 = criterion_L1(fake, hr)
loss_gan = criterion_GAN(pred, torch.ones_like(pred))
loss_G = 0.01 * loss_gan + 1.0 * loss_ssim + 1.0 * loss_l1
opt_G.zero_grad()
loss_G.backward()
opt_G.step()


train_pred = G(lr)
train_pred = F.interpolate(train_pred, size=hr.shape[-2:], mode="bilinear", align_corners=False).to(device)
# 评估
train_pred_mm = minmax_scale(train_pred)
hr_mm = minmax_scale(hr)
psnr_train, ssim_train = compute_psnr_ssim(train_pred, hr)
rmse_train = compute_rmse(train_pred_mm, hr_mm)
mae_train = compute_mae(train_pred_mm, hr_mm)
# 添加保存
train_psnrs.append(psnr_train)
train_ssims.append(ssim_train)
train_rmses.append(rmse_train)
train_maes.append(mae_train)

G.eval()
with torch.no_grad():
val_lr, val_hr = next(iter(val_loader))
val_lr, val_hr = val_lr.to(device), val_hr.to(device)
pred_hr = G(val_lr)
pred_hr = F.interpolate(pred_hr, size=val_hr.shape[-2:], mode="bilinear", align_corners=False).to(device)
# 计算指标
pred_val_mm = minmax_scale(pred_hr)
val_hr_mm = minmax_scale(val_hr)
psnr_val, ssim_val = compute_psnr_ssim(pred_hr, val_hr)
rmse_val = compute_rmse(pred_val_mm, val_hr_mm)
mae_val = compute_mae(pred_val_mm, val_hr_mm)
# 添加保存
val_psnrs.append(psnr_val)
val_ssims.append(ssim_val)
val_rmses.append(rmse_val)
val_maes.append(mae_val)

# === Print summary ===
print(f"Epoch {epoch+1}: "
f"Train PSNR={psnr_train:.2f}, SSIM={ssim_train:.4f}, RMSE={rmse_train:.4f}, MAE={mae_train:.4f} | "
f"Valid PSNR={psnr_val:.2f}, SSIM={ssim_val:.4f}, RMSE={rmse_val:.4f}, MAE={mae_val:.4f}")

1.9 模型验证

训练完了之后对保存好了的模型进行test验证,查看评估指标的表现

import matplotlib.pyplot as plt
from torch.utils.data import DataLoader

# === 1. 加载测试集 ===
test_set = WeatherDataset(
"tm/LR_data_test_40.npy", "tm/HR_data_test_40.npy",
"tm/mean_40.npy", "tm/std_40.npy"
)
test_loader = DataLoader(test_set, batch_size=8, shuffle=False)

# === 2. 在测试集上评估模型 ===
G.eval()
test_psnrs, test_ssims, test_rmses, test_maes = [], [], [], []
images_to_show = [] # 原图、真实图、预测图(反归一化后)

# 反归一化函数
mean = np.load("tm/mean_40.npy")
std = np.load("tm/std_40.npy")
def denormalize(tensor):
return tensor * torch.tensor(std).to(tensor.device) + torch.tensor(mean).to(tensor.device)

with torch.no_grad():
for test_lr, test_hr in test_loader:
test_lr, test_hr = test_lr.to(device), test_hr.to(device)
pred_test = G(test_lr)
pred_test = F.interpolate(pred_test, size=test_hr.shape[-2:], mode="bilinear", align_corners=False)

# 计算指标(归一化下)
pred_mm = minmax_scale(pred_test)
hr_mm = minmax_scale(test_hr)
psnr, ssim = compute_psnr_ssim(pred_test, test_hr)
rmse = compute_rmse(pred_mm, hr_mm)
mae = compute_mae(pred_mm, hr_mm)
test_psnrs.append(psnr)
test_ssims.append(ssim)
test_rmses.append(rmse.cpu().item())
test_maes.append(mae.cpu().item())

# 可视化:选取前1张图,反归一化
for i in range(1):
lr_img = F.interpolate(test_lr[i:i+1], size=(64, 64), mode="bilinear", align_corners=False)
images_to_show.append((
denormalize(lr_img[0].cpu()),
denormalize(test_hr[i].cpu()),
denormalize(pred_test[i].cpu())
))

# === 3. 打印测试集平均指标 ===
print(f"Test Set Evaluation cGAN Winter:")
print(f"PSNR: {np.mean(test_psnrs):.2f}")
print(f"SSIM: {np.mean(test_ssims):.4f}")
print(f"RMSE: {np.mean(test_rmses):.4f}")
print(f"MAE: {np.mean(test_maes):.4f}")

常用指标计算公式

  1. PSNR(峰值信噪比)
PSNR=10log10(MAX2MSE)\text{PSNR} = 10 \cdot \log_{10} \left( \frac{MAX^2}{\text{MSE}} \right)
  1. SSIM(结构相似性)
SSIM(x,y)=(2μxμy+C1)(2σxy+C2)(μx2+μy2+C1)(σx2+σy2+C2)\text{SSIM}(x, y) = \frac{(2\mu_x \mu_y + C_1)(2\sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1)(\sigma_x^2 + \sigma_y^2 + C_2)}
  1. RMSE(均方根误差)
RMSE=1ni=1n(xiyi)2\text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (x_i - y_i)^2}
  1. MAE(平均绝对误差)
MAE=1ni=1nxiyi\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |x_i - y_i|

1.10 保存

这部分就不多说了,提供一下评估指标的保存方式,接着想可视化等等皆可

# 将数组保存为 .npy 文件
np.save("1_ssim_train.npy", train_ssims)
np.save("1_psnr_train.npy", train_psnrs)
np.save("1_ssim_valid.npy", val_ssims)
np.save("1_psnr_valid.npy", val_psnrs)

print("✅ 已保存为 1_ssim_train.npy, 1_psnr_train.npy, 1_ssim_valid.npy, 1_psnr_valid.npy")

# 确保每个 tensor 都通过 .detach() 断开计算图,之后转移到 CPU 并转换为 NumPy 数组
train_maes_cpu = [mae.detach().cpu().numpy() for mae in train_maes]
train_rmses_cpu = [rmse.detach().cpu().numpy() for rmse in train_rmses]
valid_maes_cpu = [mae.detach().cpu().numpy() for mae in val_maes]
valid_rmses_cpu = [rmse.detach().cpu().numpy() for rmse in val_rmses]

# 保存为 .npy 文件
np.save("1_mae_train.npy", train_maes_cpu)
np.save("1_rmes_train.npy", train_rmses_cpu)
np.save("1_mae_valid.npy", valid_maes_cpu)
np.save("1_rmes_valid.npy", valid_rmses_cpu)

# .npy 文件的列表
files = [
"1_mae_train.npy", "1_mae_valid.npy",
"1_psnr_train.npy", "1_psnr_valid.npy",
"1_rmes_train.npy", "1_rmes_valid.npy",
"1_ssim_train.npy", "1_ssim_valid.npy"
]

# 创建一个字典来存储数据
data_dict = {}

# 加载每个 .npy 文件并存入字典
for file in files:
data_dict[file] = np.load(file)

# 将字典转换为 pandas DataFrame
df = pd.DataFrame(data_dict)

# 将 DataFrame 保存为 CSV 文件
df.to_csv("cGAN_data_Winter.csv", index=False)

2. SRGAN

大部分可类比过来,具体就去看原代码,这里就讲一些不一样的部分,比如说生成器与判别器

2.1 生成器模型 SRResNetGenerator

该网络基本结构模仿 SRResNet,内含: · Initial Layer:9x9 大卷积核 + PReLU · 8 个残差块(每块带有两层卷积 + BN + PReLU) · 残差连接(ResNet 风格) 上采样层: · 使用 PixelShuffle 实现图像上采样(从 16x16 → 64x64) · 最终卷积 + Tanh:输出范围规范为 [-1, 1]

class SRResNetGenerator(nn.Module):
def __init__(self, in_channels=2, out_channels=2, features=64):
super().__init__()
self.initial = nn.Sequential(
nn.Conv2d(in_channels, features, kernel_size=9, padding=4),
nn.PReLU()
)

# 8 Residual Blocks
res_blocks = []
for _ in range(8):
res_blocks.append(nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features),
nn.PReLU(),
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features)
))
self.res_blocks = nn.Sequential(*res_blocks)

self.mid_conv = nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features)
)

self.upsample = nn.Sequential(
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.PReLU(),
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.PReLU(),
)

self.final = nn.Sequential(
nn.Conv2d(features, out_channels, kernel_size=9, padding=4),
nn.Tanh()
)

def forward(self, x):
x1 = self.initial(x)
res = x1
for block in self.res_blocks:
res = res + block(res)
out = self.mid_conv(res)
out = out + x1
out = self.upsample(out)
return self.final(out)

2.2 定义判别器 Discriminator

该判别器用于判断输入图像是否为真实高分图像,具体如下: · 多层卷积+BN+LeakyReLU,最后输出为一个值 · 类似 PatchGAN 风格(结果为二维特征图) · 最后一层采用 Sigmoid 激活,输出概率

import torch.nn as nn

class Discriminator(nn.Module):
def __init__(self, in_channels=4, features=64):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels, features, 4, 2, 1), # (B, 64, 32, 32)
nn.LeakyReLU(0.2),
nn.Conv2d(features, features * 2, 4, 2, 1), # (B, 128, 16, 16)
nn.BatchNorm2d(features * 2),
nn.LeakyReLU(0.2),
nn.Conv2d(features * 2, features * 4, 4, 2, 1), # (B, 256, 8, 8)
nn.BatchNorm2d(features * 4),
nn.LeakyReLU(0.2),
nn.Conv2d(features * 4, 1, 4, 1, 1), # (B, 1, 7, 7) => PatchGAN 输出
nn.Sigmoid()
)

def forward(self, lr_up, hr_or_fake):
# 确保为[B, 2, 64, 64]
if lr_up.shape[-2:] != hr_or_fake.shape[-2:]:
raise ValueError(f"Shape mismatch: lr_up={lr_up.shape}, hr={hr_or_fake.shape}")
x = torch.cat([lr_up, hr_or_fake], dim=1) # [B, 4, H, W]
return self.model(x)

3. ESRGAN

3.1 生成器模型RDBNet

使用多个 残差密集块(RRDB),用于深层次特征提取与稳定训练;每个 RRDB 由三个去 BN 的 DenseBlock 组成,通过局部和全局残差连接增强梯度传播、抑制信息丢失。整体流程为:输入图像经卷积提取初步特征 → 多层 RRDB 提取深层表示 → 上采样模块逐步恢复空间分辨率 → 最终输出

import torch
import torch.nn as nn

# Dense Block(去掉BN + 残差缩放)
class DenseBlock(nn.Module):
def __init__(self, channels=64, growth_channels=32):
super().__init__()
self.layers = nn.ModuleList()
for i in range(5):
self.layers.append(nn.Conv2d(channels + i * growth_channels, growth_channels, 3, 1, 1))
self.lrelu = nn.LeakyReLU(0.2, inplace=True)
self.conv_last = nn.Conv2d(channels + 5 * growth_channels, channels, 3, 1, 1)

def forward(self, x):
inputs = [x]
for conv in self.layers:
out = self.lrelu(conv(torch.cat(inputs, dim=1)))
inputs.append(out)
out = self.conv_last(torch.cat(inputs, dim=1))
return x + out * 0.2 # Local residual

# RRDB
class RRDB(nn.Module):
def __init__(self, channels, growth_channels=32):
super().__init__()
self.block1 = DenseBlock(channels, growth_channels)
self.block2 = DenseBlock(channels, growth_channels)
self.block3 = DenseBlock(channels, growth_channels)

def forward(self, x):
out = self.block1(x)
out = self.block2(out)
out = self.block3(out)
return x + out * 0.2 # Global residual

# ESRGAN Generator (RRDBNet)
class RRDBNet(nn.Module):
def __init__(self, in_channels=2, out_channels=2, features=64, num_blocks=8):
super().__init__()
self.conv_first = nn.Conv2d(in_channels, features, 3, 1, 1)

# RRDB trunk
self.rrdb_blocks = nn.Sequential(*[RRDB(features) for _ in range(num_blocks)])
self.trunk_conv = nn.Conv2d(features, features, 3, 1, 1)

# Upsampling blocks
self.upsample = nn.Sequential(
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.LeakyReLU(0.2, inplace=True)
)

self.conv_last = nn.Conv2d(features, out_channels, 3, 1, 1)

def forward(self, x):
fea = self.conv_first(x)
trunk = self.trunk_conv(self.rrdb_blocks(fea))
fea = fea + trunk
out = self.upsample(fea)
out = self.conv_last(out)
return out

3.2 判别器模型ESRDiscriminator

ESRDiscriminator 是一个深层 PatchGAN 判别器,通过逐层卷积下采样提取图像局部特征,并对 LR 图像与 HR 图像的拼接输入进行真假判别

class ESRDiscriminator(nn.Module):
def __init__(self, in_channels=4, base_features=64):
super().__init__()
def block(in_f, out_f, stride):
return nn.Sequential(
nn.Conv2d(in_f, out_f, 3, stride, 1),
nn.LeakyReLU(0.2, inplace=True)
)

layers = [
block(in_channels, base_features, 1),
block(base_features, base_features, 2),
block(base_features, base_features * 2, 1),
block(base_features * 2, base_features * 2, 2),
block(base_features * 2, base_features * 4, 1),
block(base_features * 4, base_features * 4, 2),
block(base_features * 4, base_features * 8, 1),
block(base_features * 8, base_features * 8, 2),
nn.Conv2d(base_features * 8, 1, 3, 1, 1) # PatchGAN 输出
]
self.model = nn.Sequential(*layers)

def forward(self, lr_up, hr_or_fake):
if lr_up.shape[-2:] != hr_or_fake.shape[-2:]:
raise ValueError(f"Shape mismatch: lr_up={lr_up.shape}, hr={hr_or_fake.shape}")
x = torch.cat([lr_up, hr_or_fake], dim=1) # 拼接输入 [B, 4, H, W]
return self.model(x)

SRGAN及其消融试验

以SRGAN为baseline,本文对比了四种情况,即

1. 损失函数改进版

除了使用传统的 MSE 和对抗损失,还使用了VGG perceptual loss(基于 VGG 网络的中间层);MS-SSIM(多尺度结构相似性指标)

2. 通道注意力机制版

在生成器的残差块或特征融合模块中引入 Channel Attention 模块,同时引入 Squeeze-and-Excitation(SE)通道注意力模块,并在每个残差块之后插入 SEBlock(features)

class SEBlock(nn.Module):
def __init__(self, channel, reduction=16):
super(SEBlock, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc1 = nn.Conv2d(channel, channel // reduction, 1, bias=False)
self.relu = nn.ReLU(inplace=True)
self.fc2 = nn.Conv2d(channel // reduction, channel, 1, bias=False)
self.sigmoid = nn.Sigmoid()

def forward(self, x):
# Squeeze: Global Average Pooling
y = self.avg_pool(x)
# Excitation: Fully connected layers
y = self.fc1(y)
y = self.relu(y)
y = self.fc2(y)
y = self.sigmoid(y)
return x * y




class SRResNetGenerator(nn.Module):
def __init__(self, in_channels=2, out_channels=2, features=64):
super(SRResNetGenerator, self).__init__()
self.initial = nn.Sequential(
nn.Conv2d(in_channels, features, kernel_size=9, padding=4),
nn.PReLU()
)

# 8 Residual Blocks with SE Blocks
res_blocks = []
for _ in range(8):
res_blocks.append(nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features),
nn.PReLU(),
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features),
SEBlock(features) # Add SE Block after each residual block
))
self.res_blocks = nn.Sequential(*res_blocks)

self.mid_conv = nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features)
)

self.upsample = nn.Sequential(
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.PReLU(),
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.PReLU(),
)

self.final = nn.Sequential(
nn.Conv2d(features, out_channels, kernel_size=9, padding=4),
nn.Tanh()
)

def forward(self, x):
x1 = self.initial(x)
res = x1
for block in self.res_blocks:
res = res + block(res)
out = self.mid_conv(res)
out = out + x1
out = self.upsample(out)
return self.final(out)

3. 多尺度特征融合版

网络结构中引入多尺度特征处理模块(如使用 3x3、5x5、7x7 不同卷积核或金字塔结构),并将不同尺度特征拼接或加权融合,最后添加至 SRResNetGenerator 内部结构中

class MultiScaleFeatureFusion(nn.Module):
def __init__(self, in_channels, features):
super(MultiScaleFeatureFusion, self).__init__()
# 使用膨胀卷积增加感受野
self.scale1 = nn.Conv2d(in_channels, features, kernel_size=3, padding=1, dilation=1)
self.scale2 = nn.Conv2d(in_channels, features, kernel_size=5, padding=2, dilation=1)
self.scale3 = nn.Conv2d(in_channels, features, kernel_size=7, padding=3, dilation=1)

self.fusion = nn.Conv2d(features * 3, features, kernel_size=1)

def forward(self, x):
x1 = self.scale1(x)
x2 = self.scale2(x)
x3 = self.scale3(x)
fused = torch.cat([x1, x2, x3], dim=1)
return self.fusion(fused)


class SRResNetGenerator(nn.Module):
def __init__(self, in_channels=2, out_channels=2, features=64):
super(SRResNetGenerator, self).__init__()
self.initial = nn.Sequential(
nn.Conv2d(in_channels, features, kernel_size=9, padding=4),
nn.PReLU()
)

# Multi-scale feature fusion module
self.multi_scale_fusion = MultiScaleFeatureFusion(in_channels=features, features=features)

# 修改残差块,使用膨胀卷积
res_blocks = []
for _ in range(8): # 保持16个残差块
res_blocks.append(nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, padding=2, dilation=2), # 使用膨胀卷积
nn.BatchNorm2d(features),
nn.PReLU(),
nn.Conv2d(features, features, kernel_size=3, padding=2, dilation=2), # 使用膨胀卷积
nn.BatchNorm2d(features)
))
self.res_blocks = nn.Sequential(*res_blocks)

self.mid_conv = nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, padding=1),
nn.BatchNorm2d(features)
)

self.upsample = nn.Sequential(
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.PReLU(),
nn.Conv2d(features, features * 4, 3, 1, 1),
nn.PixelShuffle(2),
nn.PReLU(),
)

self.final = nn.Sequential(
nn.Conv2d(features, out_channels, kernel_size=9, padding=4),
nn.Tanh()
)

def forward(self, x):
x1 = self.initial(x)

# Multi-scale feature fusion
x2 = self.multi_scale_fusion(x1)

res = x1 + x2 # Combine initial and multi-scale fused features

for block in self.res_blocks:
res = res + block(res) # Residual learning

out = self.mid_conv(res)
out = out + x1 # Add skip connection from initial input
out = self.upsample(out)
return self.final(out)

4. 融合

同时采用: · 感知 + MS-SSIM + MSE + GAN 损失; · 多尺度结构; · 通道注意力;

5. 总结

实验名称改进点类型主要操作作用
1. 损失函数改进损失函数层面引入 多种损失组合,如感知损失(VGG)、MS-SSIM 等更符合人类视觉感知,提升图像质量
2. 通道注意力机制网络结构层面在生成器中加入 通道注意力模块(如 SE/CA)自适应关注重要特征通道,提升表示能力
3. 多尺度特征融合网络结构层面引入多个尺度(不同尺寸卷积核或下采样路径)进行特征融合更好保留图像纹理和细节
4. 融合模型综合增强同时引入注意力 + 多尺度 + 改进损失协同提升性能,验证组合优势

Automated and Context-Aware Repair of Color-Related Accessibility Issues for Android Apps

· 12 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

1. 摘要

约 15% 的全球人口受到各种残障或视力障碍的影响,但许多移动端的用户体验(UX)设计师和开发者在开发 App 时并未重视可访问性问题。这意味着每七个人中就有一个用户在使用 App 时面临不平等的体验,这不仅影响用户,也可能违反相关法规。实际上,如果 App 开发时考虑可访问性,不仅能提升整体用户体验,还能提升商业价值。因此,已有不少研究和检测工具被提出用于识别可访问性问题。

然而,与检测相比,修复工作明显滞后,尤其是“颜色相关的可访问性问题”——比如文字对比度不足和图片对比度不佳,这类问题极大地影响了低视力用户和老年用户的使用体验,而当前的修复方法对此无能为力。

为此,我们提出了 Iris:一种自动化且具备上下文感知能力的方法,用于修复颜色相关的可访问性问题。该方法通过设计一致性的颜色替换策略和属性定位算法,在修复问题的同时保持 UI 风格的一致性。实验显示,Iris 可达到 91.38% 的修复成功率,且效率较高。用户调研也表明其结果令人满意,开发者反馈积极。我们在 GitHub 上提交的 40 个 Pull Request 中已有 9 个被合并,另有 4 个正在积极沟通后续修复。Iris 工具现已开源,旨在推动移动可访问性修复领域的进一步研究。

2. introduction

懒的讲了,感觉没啥用。反正大意就是解决这三个问题

1. 修复后必须保持原始 UI 页面的设计风格一致性; 2. 必须准确定位待修复的 UI 组件及其颜色属性; 3. 修复结果需被真实用户和开发者接受,具备实用性。

作者提出了 iris,可以弄一个流程图体现该作者的研究工作

                  [1] APK 文件

┌───────────────────────────────┐
│ Xbot 检测工具 │ ← 基于 Accessibility Scanner
└───────────────────────────────┘

输出内容包括:
- 无障碍检测报告(JSON / XML)
- UI 页面截图(PNG)

┌────────────────────────────────────────┐
│ Iris 修复系统 │
└────────────────────────────────────────┘

1. 颜色参考数据库(Reference DB)
- 收集 9978 个 App 的 UI 配色
- 用于推荐合适的替换颜色

2. 上下文感知颜色选择(Context-aware)
- 保持风格一致性(HSV 模型、色轮协调)
- 决定修改 foreground 还是 background

3. 属性定位模块(Attribute-to-Repair)
- 定位 XML 或 Java 中待修复组件
- 分析 textColor、background 等属性

4. 图像修复模块(仅针对图标)
- 判断功能性 vs 装饰性图像
- 调整颜色或替换 vector 图标


┌───────────────────────────────┐
│ 修复后的 APK 输出 │
└───────────────────────────────┘

3. Preliminary

3.1 标准

需要解决以下问题:

  1. 普通文本要求前景色与背景色的对比度 ≥4.5:1*
  2. 大号加粗文本则要求 ≥3:1

3.2 颜色实现方式

在 xml 文件中,颜色一般是这样表达的

<TextView android:textColor="#80ff0000" />
<Button android:background="#80ff0000" />

提出 iris 用于修复这样的问题

3.3 检测工具提供的输入

这部分有很多,但是这个作者毫无创新,只是单纯的将各种工具整合而已(也有可能我不是这个方向的,不知道主流是什么样的^_^)

4. Approach

这块是 iris 的核心部分,主要流程如下

  1. 参考数据库构建(Reference DB Construction)
  2. 上下文感知的颜色选择(Context-aware Color Selection)
  3. 待修复属性定位(Attribute-to-Repair Localization)

4.1 Reference DB Construction

目的:提供颜色替换时的“设计参考”。

流程

  • 使用 Xbot 对近 9978 个 APK 检测;
  • 收集其中未出现颜色对比度问题的 UI 组件;
  • 根据组件类型分类(如 Button、TextView 等);
  • 从 UI 截图中提取每个组件的前景色和背景色;
  • 使用** **getcolors() 获取最常出现的两种颜色;
  • 对比颜色值,保留满足对比度要求的颜色对,构建数据库。

优势:使用真实 App 中“被认可的配色”作为修复备选,提高了颜色风格的一致性

4.2 Context-Aware Color Selection

算法流程在原文中给了,简单来说就是

  • 输入:当前问题颜色、颜色候选集、页面色调类型、偏转角;
  • 筛选候选颜色集,计算最小“色调距离”;
  • 如果没有合适候选色,就使用 HSV 微调获得备选颜色;
  • 返回最接近原设计的最优替换色。

4.3 Attribute-to-Repair Localization

根据 Xbot 报告,精准找到需要修改的 XML 属性或代码

定位方式:

  • 组件 ID(resource-id)存在时:可直接在反编译的 layout 中精确查找;
  • 仅有 bounds 坐标时:通过坐标在 layout tree 中找到组件,再通过文本等信息比对 XML 结构中的** **android:text属性,进行模糊定位。

5. 复现

前文我们知道了该论文有两部分组成,Xbot 和 iris 部分,因此我们一点一点来,先讲 Xbot 部分

5.1 Xbot

5.1.1 下载原代码

首先把 GitHub 仓库给 down 下来

仓库地址:https://github.com/tjusenchen/Xbot

这是一个很老的代码,需要用到 py2 来运行,mac 上是不能通过 conda 下载 py2 版本的,因此这般选择 pyenv(也是一种管理工具,和 conda 差不多),如果是 windows 用户的话可忽略

5.1.2 安装模拟器

模拟器也要和仓库中的 readme 中一样,选择 Android 7.1.1+PIX,如果太新的话会导致兼容性问题。

在创建好了模拟器之后,可以通过该命令检查是否连上了

adb devices

跳出来你的模拟器名字就 ok 了,如果你没看到的话,有很大原因是你没启动(我因为这个问题卡了一下午,md)

当然,使用的前提是你得先把 adb 下了,可以通过如下命令查看

adb version

Android Debug Bridge version 1.0.41
Version ...

系统默认一般是会带有的,路径如下

macOS:~/Library/Android/sdk/platform-tools/adb

Windows:C:\Users<用户名>\AppData\Local\Android\Sdk\platform-tools\adb`

如果发现没的话也问题不大,去官网下一个

官网地址:https://developer.android.com/studio/releases/platform-tools

然后解压添加到环境变量一条龙就 ok 了

5.1.3 安装 apk 文件

这部分就不用多说了,把 adb 命名下载好了之后就 adb install,例如

adb install path/to/your_app.apk

需要安装两个,分别是 GAS 和 Vol 的这两个 apk,都是在仓库里给了的,直接安装就行。

**尤其需要注意!!!**GAS 下载了之后,需要现在模拟器里设置- accessibility-GAS,打开服务。打开之后会跳出一个蓝色框框,然后再代码中修改你的这个悬浮窗的坐标,不然会造成闪退的情况!!!!!!修改的代码叫做 explore_activaity.py,scan_and_return 函数中

还有一个 vol 就没啥好说的了,下了就行,这是一个景点经典用 apk

5.1.4 运行 Xbot

1. 准备工作:

  1. Python 环境
  • 推荐使用** ****Python 2.7**
  • 所需库基本为标准库:如** os, commands, csv, shutil, **time
  1. Genymotion 模拟器准备
  • 已安装** ****Genymotion Desktop**
  • 启动一个设备(例如** **127.0.0.1:6555
  • 模拟器 root 权限已开启(adb root 可运行)

2. 文件与路径结构检查

目录结构(假设在** **Xbot-main 下运行)

├── code/                      # Python 脚本主目录
│ ├── run_xbot.py # 主运行脚本
│ ├── explore_activity.py # 动态探索逻辑
│ ├── repkg_apk.py # 重打包逻辑(已用可选)
├── apks/ # 存放待测 APK 的目录
│ └── xxx.apk
├── config/ # 相关配置
│ ├── coolapk.keystore
│ ├── libs/
│ │ └── android-platforms/
├── results/ # 自动创建,输出目录
├── storydroid/ # 自动创建,用于参数记录等

3. APK 文件命名注意:

  • .apk 文件放在** **apks/ 中,**不要加中文名或特殊字符**
  • 如** **a2dp.Vol_133.apk

4. 代码准备工作

  1. 确认** **run_xbot.py 开头几项路径为你本机配置,例如
java_home_path = '/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/'
sdk_platform_path = '/Users/yourname/.../android-platforms/'
lib_home_path = '/Users/yourname/.../libs/'

  1. 清理旧 outputs(可选)
rm -rf results/ storydroid/

5. 执行命令

cd Xbot-main/code

python run_xbot.py 127.0.0.1:6555 ../main-folder/apks/

# adb
adb devices. # 查看设备名字

6. 运行 xml 修复

Xbot 只有原始 apk 页面的 Xbot,还需要通过 issues 来生成新的 xml 以让 iris 知道哪个组件有问题

python3 Xbot-main/code/txt2irisxml.py \
Xbot-main/results/outputs/a2dp.Vol_133/issues/a2dp.Vol.EditDevice/a2dp.Vol.EditDevice.txt \
Xbot-main/results/outputs/a2dp.Vol_133/issues/a2dp.Vol.EditDevice/AccessibilityScanner.xml

贴出 txt2irisxml 代码,放在 code 目录下面


# -*- coding: utf-8 -*-
import os
import sys
import xml.etree.ElementTree as ET

def parse_txt(txt_path):
with open(txt_path, 'r', encoding='utf-8') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]

issues = []
i = 0
while i < len(lines):
line = lines[i]

# --- Item label / Missing label
if "Item label" in line:
i += 1
res_id = lines[i] if i < len(lines) else "unknown"
issues.append({
"Type": "MissingLabel",
"ResourceID": res_id,
"Class": "android.widget.EditText"
})
i += 1

# --- Text contrast
elif "Text contrast" in line:
i += 1
res_id = lines[i] if i < len(lines) else "unknown"
i += 1
ratio = "N/A"
if i < len(lines) and "contrast ratio is" in lines[i]:
try:
ratio = lines[i].split("contrast ratio is")[1].split(".")[0] + "." + lines[i].split("contrast ratio is")[1].split(".")[1][:2]
except:
pass
issues.append({
"Type": "LowTextContrast",
"ResourceID": res_id,
"Class": "android.widget.TextView",
"ContrastRatio": ratio
})
i += 1

# --- Touch target too small
elif "Touch target" in line:
i += 1
res_id = lines[i] if i < len(lines) else "unknown"
issues.append({
"Type": "TouchTargetTooSmall",
"ResourceID": res_id,
"Class": "android.widget.Button"
})
i += 1

else:
i += 1

return issues

def write_xml(issues, output_path):
root = ET.Element("AccessibilityIssues")

for issue in issues:
item = ET.SubElement(root, "Issue")
ET.SubElement(item, "Type").text = issue.get("Type", "Unknown")
ET.SubElement(item, "ResourceID").text = issue.get("ResourceID", "unknown")
ET.SubElement(item, "Class").text = issue.get("Class", "android.view.View")
if "ContrastRatio" in issue:
ET.SubElement(item, "ContrastRatio").text = issue["ContrastRatio"]

tree = ET.ElementTree(root)
os.makedirs(os.path.dirname(output_path), exist_ok=True)
tree.write(output_path, encoding="utf-8", xml_declaration=True)
print(f"✅ Created XML: {output_path}")

if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python txt2irisxml.py input.txt output.xml")
sys.exit(1)

txt_file = sys.argv[1]
xml_file = sys.argv[2]
issues = parse_txt(txt_file)
write_xml(issues, xml_file)

7. 运行结果

在/results/apk_name 中。主要需要的是 outputs 这个文件夹

5.2 IRIS

5.2.1. 解压 zip 文件

cd /root
unzip iris-mobile-master.zip
mv iris-mobile-master iris-mobiles

同时需要将之前 Xbot 的输出 outputs 存放在根目录下,zip 是原始在 GitHub 中下载的

GitHub 地址:https://github.com/tjuyuxinzhang/iris-mobile

  1. 复制 Xbot 输出和 APK 到 IRIS 指定位置
mkdir -p /root/iris-mobile/code/data/xbot_output
cp -r /root/outputs/a2dp.Vol_133 /root/iris-mobile/code/data/xbot_output/

mkdir -p /root/iris-mobile/apks
cp /root/outputs/a2dp.Vol_133.apk /root/iris-mobile/apks/

5.2.2 修改一些路径代码

  1. repair_repack_class.py
results_folder = "/root/iris-mobile"
resultPath = '/root/iris-mobile/refDB' # 若没有这个文件夹可先建空目录
outputsPath = '/root/outputs'
  1. harmonizationTs2.py

把 250 行的 plt 部分注释了(如果用的是虚拟机,无图形页面的话)

5.2.3 切换目录并运行 IRIS 修复脚本

cd /root/iris-mobile/code
python2 repair_repack_class.py --app_name a2dp.Vol_133

5.2.4 运行结果

需要包含这些东西

  1. decompiling
decompiling...
I: Using Apktool 2.4.1 on a2dp.Vol_133.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /root/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
act_T_Alpha
  1. color layout
id_bound_colorSet
{'Path': (['#817278', '#F9EEF0'], 4.5, 'ManageData', '', 'Text contrast'), 'Action:': (['#F7ECEE', '#F9EEF0'], 3, 'CustomIntentMaker', 'TextView', 'Text contrast'), 'title': (['#2979FF', '#F9EEF0'], 4.5, 'Preferences', '', 'Text contrast'), 'Data:': (['#FFFFFF', '#F9EEF0'], 3, 'CustomIntentMaker', 'TextView', 'Text contrast'), 'empty': (['#FFFFFF', '#F9EEF0'], 3, 'ProviderList', '', 'Text contrast'), 'Type:': (['#FFFFFF', '#F9EEF0'], 3, 'CustomIntentMaker', 'TextView', 'Text contrast'), 'Output': (['#817278', '#F9EEF0'], 4.5, 'ManageData', '', 'Text contrast'), 'pi_tv_name': (['#FFFFFF', '#F9EEF0'], 3, 'PackagesChooser', '', 'Text contrast')}
waitChangeColor_self
{'pi_tv_name': ('#585858', ['#FFFFFF', '#F9EEF0'], 3), 'title': ('#2153AE', ['#2979FF', '#F9EEF0'], 4.5), 'Data:': ('#585858', ['#FFFFFF', '#F9EEF0'], 3), 'Output': ('#504348', ['#817278', '#F9EEF0'], 4.5), 'Action:': ('', ['#F7ECEE', '#F9EEF0'], 3), 'Type:': ('#585858', ['#FFFFFF', '#F9EEF0'], 3), 'Path': ('#504348', ['#817278', '#F9EEF0'], 4.5), 'empty': ('#585858', ['#FFFFFF', '#F9EEF0'], 3)}
colorToChange_self
{'Path': '#504348', 'title': '#2153AE', 'Data:': '#585858', 'empty': '#585858', 'Type:': '#585858', 'Output': '#504348', 'pi_tv_name': '#585858'}
['#F7ECEE', '#F9EEF0']
[]
colorToChange_Layout
{'Path': '#504348', 'title': '#2153AE', 'Action:': '#000000', 'Data:': '#585858', 'empty': '#585858', 'Type:': '#585858', 'Output': '#504348', 'pi_tv_name': '#585858'}
imageId_Name
{}
{('Data:', 'TextView'): '[0,332][263,436]', ('Path', 'TextView'): '[0,524][532,575]', ('Type:', 'TextView'): '[0,453][263,557]', ('pi_tv_name', 'TextView'): '[173,1610][954,1741]', ('Action:', 'TextView'): '[0,211][263,315]', ('title', 'TextView'): '[42,1756][626,1794]', ('empty', 'TextView'): '[53,263][1027,601]', ('Output', 'TextView'): '[465,575][614,669]'}
{}
[]
{'Path': '#504348', '[0,453][263,557]': '#585858', 'title': '#2153AE', '[0,332][263,436]': '#585858', 'empty': '#585858', '[0,211][263,315]': '#000000', 'Output': '#504348', 'pi_tv_name': '#585858'}
waitChangeColor_self
{'pi_tv_name': ('#585858', ['#FFFFFF', '#F9EEF0'], 3), 'title': ('#2153AE', ['#2979FF', '#F9EEF0'], 4.5), 'Data:': ('#585858', ['#FFFFFF', '#F9EEF0'], 3), 'Output': ('#504348', ['#817278', '#F9EEF0'], 4.5), 'Action:': ('', ['#F7ECEE', '#F9EEF0'], 3), 'Type:': ('#585858', ['#FFFFFF', '#F9EEF0'], 3), 'Path': ('#504348', ['#817278', '#F9EEF0'], 4.5), 'empty': ('#585858', ['#FFFFFF', '#F9EEF0'], 3)}
{}
recompile...
('time cost', 327.10186195373535, 's')

5.2.5 修复后的 apk 文件

/root/iris-mobile/repackaged/a2dp.Vol_133.apk

5.2.6 将其放在 mac 上的 apk 文件夹中,签名下载运行(Xbot 再次比对)

  1. 签名 apk
/Users/qijiazhou/Library/Android/sdk/build-tools/36.0.0/apksigner sign \
--ks ~/Desktop/test-key.jks \
--ks-key-alias testkey \
--ks-pass pass:123456 \
--key-pass pass:123456 \
--out ~/Desktop/apk/a2dp.Vol_133_signed.apk \
~/Desktop/apk/a2dp.Vol_133.apk
  1. 安装测试
adb install -r ~/Desktop/apk/a2dp.Vol_133_signed.apk

接着就是再运行 Xbot 的过程,将其放入 main- folder/apks 文件夹中

6. Experiments

用户调查就不说了,按照复现流程走下来应该是能一样的。当然,此处可以讲一下修复成功率计算公式评价指标

Repire = 修复成功的问题数/该app原始问题数 * 100%

linux操作指南之上游分析

· 6 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

1. 安装linux

这就不多说了,自己搞一个虚拟机,我用的是Centos7。

ps:如果使用的是学校集群的话,注意在修改密码中改一下自己的密码,开启后账号为:root,密码自定义(注意是暗文,你敲进去是不会显示的)结束了enter即可

2. 预先安装

首先要安装anaconda,为了不污染环境

-- 安装linux安装包(如果报错自己去anaconda网站找地址)
wget https://repo.anaconda.com/archive/Anaconda3-2023.07-Linux-x86_64.sh

-- 解压anaconda,后面的就是刚刚安装好的名字
bash Anaconda3-2023.07-Linux-x86_64.sh

-- 更新环境变量
source ~/.bashrc

至此,anaconda安装完成。我们再创建一个环境,专门用于生信分析

tips:anaconda教程请查看TensorFlow1教程,需要包含的步骤为:

  1. create一个虚拟环境
  2. 设置镜像中除了最后一个,其他都跑
  3. 下载fastqc

3. 安装分析软件

这些软件因人而异,不一定需要全下载

-- 最主要的分析fast格式的软件
conda install fastqc

-- 有时候fastq不止一个文件,联合分析
conda install multigc

-- 高通量测序数据中去除接头序列和其他不需要的序列
conda install -c bioconda cutadapt

-- 用于将sra转化为fastq文件的脚本
conda install sra-tools

cellranger单独拎出来讲,这个软件应该使用到的频率比较大

-- 下载安装包(上官网找,复制粘贴即可,如果报错和anaconda一样)
wget -O cellranger-8.0.1.tar.gz "https://cf.10xgenomics.com/releases/cell-exp/cellranger-8.0.1.tar.gz?Expires=1724187641&Key-Pair-Id=APKAI7S6A5RYOXBWRPDA&Signature=Nym8cB6esJ3Zk7nueAU7BG3hhF2IZBp9FpD4OE5gW0a7C-m4ob7lChp0W-j7ydgnEBYafZ~igPGR~DzUq4CxsXS4XwkENuKhwn7Xr7RbdO~wGGh03fWzYyYt~Y7FK~V~73DzJEplvcls0p~KdbcQYvb7NflwtO9YMY~FnO4fB2VmFf5QBMdpXbPubMG~jNWE58ki7zr6ilsMGAfEnI6Po4cpZKKe1VCN7zJeipUKqS9qj~jGRpIGjHV9abluAPeodE-zCk0F-bsMpXSx6x~avzQfrN6ViJoRNEBemaB7anzMOTJ7L3XEP~QprOaJKKobFWZBGz4MBXokQBxByAZObQ__"

-- 解压,xyz为版本号
tar -xzvf cellranger-x.y.z.tar.gz

-- 配置环境变量
source ~/.bashrc

-- 查看是否成功
cellranger --version

-------如果失败,进入到vim中写,vim对于初学者来说比较复杂,建议认真读教程,不要多做也不要少做-------
-- 1. 打开文件
vim ~/.bashrc
-- 2. 进入编辑模式 ,按键盘i键
-- 3. 添加cellranger的路径,将光标移到最后一行,添加内容(-8.0.1是cellranger文件夹的名字,看自己下载的名字是什么)
export PATH=$PATH:/root/cellranger-8.0.1/bin
-- 4. 先按Esc退出编辑模式,然后输入:wq,最后按enter
-- 5. 重加载
source ~/.bashrc
-- 6.查看是否成功
cellranger --version

4. 测序数据质量评估

ok,开始正式的质量评估

4.1 cutadapt

去除接头序列

在测序过程中,接头序列会附加到 DNA 或 RNA 片段的两端。cutadapt 可以从测序读段(reads)中识别并去除这些接头序列。

去除低质量碱基

除了接头序列,还可以去除位于读段两端的低质量碱基,保证下游分析中使用的是高质量的数据。

处理指定长度的序列

可以设置参数来过滤特定长度的序列,比如去除过短或过长的序列,这有助于控制下游数据分析中的数据质量。

双端测序数据处理

cutadapt 支持双端测序数据,可以同时处理两端的序列,并保持双端读段的配对关系。

ps:在做cutadapt之前,首先是需要通过fastqc的质控报告观测是否需要去除的,具体在Adapter Content这一栏中,如果是打钩就不需要,打叉的话分情况讨论:

  1. 数据数据集上down下来的数据一般会显示测序的平台是啥,这时候使用平台默认的就ok了
  2. 自己的数据也知道用的测序平台是啥,同上

如果用的是illumina的话,read1为AGATCGGAAGAGC,read2为AATGATACGGCGACC。如果用的是其他测序平台的话,建议翻看手册查询(一般都会给出的)

-- 检查接头序列
cutadapt --detect-adapters -o /path/to/trimmed_output.fq /path/to/input.fq

-- 本数据为read2(illumina),read1同理,反正都要处理
cutadapt -a AGATCGGAAGAGC -q 20 --minimum-length 50 \
-j 4 \
-o /root/rowdata/Rhesus-Liver-1_L1_2_trimmed.fq.gz \
/root/rowdata/Rhesus-Liver-1_L1_2.fq.gz


在预处理完数据之后,就可以通过fastqc可视化数据质量了,可以百度网页结果

fastqc -t 4 -o ~/fastqc_output /root/rowdata/Rhesus-Liver-1_L1_2.fq.gz

4.2 cellranger

  1. 创建自定义参考基因组
-- 这一步是因为我的gtf中,Curated Genomic包含空格,因此需要替换为Curated_Genomic
sed 's/Curated Genomic/Curated_Genomic/g' GCF_003339765.1_Mmul_10_genomic.gtf > fixed_GCF_003339765.1_Mmul_10_genomic.gtf

-- 设置核心数,按照自己的配置来定,一般max-1就ok了
export CELLRANGER_NUM_THREADS=15

-- 如果没有处理过gtf的话,原名就ok,如果sed了,那就改成自己处理后的gtf文件名,output-dir为文件输出的位置,可自定义
cellranger mkref --genome=mmul_ref \
--fasta=/root/compare/GCF_003339765.1_Mmul_10_genomic.fna \
--genes=/root/compare/fixed_GCF_003339765.1_Mmul_10_genomic.gtf \
--output-dir=/root/compare/mmul_ref


  1. 使用 cellranger count 进行比对和分析
cellranger count \
--id=liver_analysis \ # 输出文件夹的名称
--transcriptome=/root/compare/mmul_ref \ # 参考基因组路径
--fastqs=/root/liver_PE \ # 存放FASTQ文件的目录
--sample=sample_name \ # 样本名称,一般为liver_PE-1或liver_PE-2这样的
--expect-cells=1000 \ # 预期细胞数,可根据实验设定调整(也可不用,默认1k)
--localcores=16 \ # 使用的核心数
--localmem=180 # 使用的内存(GB)
--create-bam=true # 通常默认,但是当发生该参数缺少的报错时可以显式添加

需要注意的是,如果我有liver_PE-1和liver_PE-2,那需要分别跑两次,然后对其进行合并

  1. 使用cellranger arr对结果进行合并

首先船舰csv文件,列出每个样本的molecule_info.h5 文件路径,内容如下:

library_id,molecule_h5
liver_PE-1,/root/liver_analysis1/outs/molecule_info.h5
liver_PE-2,/root/liver_analysis2/outs/molecule_info.h5

随后对其进行合并

cellranger aggr \
--id=liver_combined_analysis \
--csv=aggregation.csv \
--normalize=mapped \
--localcores=16 \
--localmem=180

生成一个新的文件夹 liver_combined_analysis,其中包含合并后的 filtered_feature_bc_matrix 文件夹。


ps:在此推荐两个小工具了,下载和安装就百度吧,基本上都有教程的

  1. winscp用于本地到服务器上的文件传输(可视化,很方便,并且也可以在这里增删改查文件)
  2. finalshell可以复制粘贴代码(学校集群cv不了),并且旁边有当前内存,储存空间,cpu占用等有用的信息

质控与聚类

· 6 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

1. 测出数据部分

在通过前文的处理之后,我们得到了两个输出文件,分别为raw_feature_bc_matrix和filter_feature_bc_matrix。前者为原始数据,后者为cellranger经过自己处理后的数据,后续的分析会基于filter_feature_bc_matrix文件夹(上游比对分析产生的三个文件)。文件夹目录如下

--filter_feature_bc_matrix
----barcodes.tsv
----features.tsv
----matrix.mtx

逐一解释:

**barcodes.tsv:**细胞标签

**features.tsv:**基因ID

**matrix.mtx:**表达数据

后续我们会使用seurat(R语言)进行分析

2. 质量控制

2.1 数据预处理

导入包的过程忽略了

首先我们先加载数据集,得到的数据应该如下所示:

这是一个稀疏矩阵(这很重要,在后续分析中稀疏矩阵占用内存会比疏密矩阵小很多,不然在大数据集的情况下内存会爆炸);行代表基因(或者特征,取决于数据类型);列代表细胞。值代表特定细胞中某个基因的原始 UMI(Unique Molecular Identifier)计数。

接着,我们需要先进行初步的筛选并查看数据分布,小提琴图与箱线图都是常用的办法,在此我们选择最为常用的小提琴图(更明了)。默认筛选条件为:

if(表达基因数<200 or 表达基因<3个细胞)
delete

可能来自一定比例的低质量细胞(比如细胞破碎造成细胞质RNA流失)。由于线粒体比单个的转录本大,不容易在破碎的细胞膜中漏出,从而导致测序结果显示线粒体基因的比例在细胞内占比过高。因此,质量控制这一步的目的就是把这些低质量的细胞去除掉。最后计算线粒体基因占所有基因比例后,做小提琴图

我们也可以绘制散点图,查看线粒体和基因数异常分布的数据点

得到初步的分布情况之后,我们就需要根据可视化的情况进行筛选了,在此再进行条件过滤

if(表达基因数目<200 or 表达基因数目>5000)
delete
if(线粒体基因占比 > 20%)
delete

这里的阈值都需要自己做条件,请按照可视化后的结果自己调整,举个栗子,心肌细胞,本身就含有较多的线粒体,可以根据实际情况适当的调整阈值

高变异基因

这步的筛选可以理解为:如果有些基因的表达量都非常高并且每个细胞中表达水平十分相似,那就说明这一些基因的变异度很低,无用可以筛除。高变异基因的筛选方法有:基于方差(vst),基于离散程度,基于负二项分布,基于熵值等等。vst是较为常用的办法。在此默认我们保留2000个基因,得到结果如下

可以看到,我们对标准化与未标准化的数据都进行了比较,标准化后的数据表现更好,使用log1p,即表达量的对数加1;使用其他办法如标准化到10000也是可以的

2. 聚类分析

2.1 降维

在进行聚类之前,我们需要首先对数据进行降维,降维之后可以将很多不必要的特征删除。PCA是最为常用的办法,我们可以通过前文筛选出的2000个变量进行降维

在降维之后,我们可以查看PC_1与PC_2中单个细胞之间表现出相关性

也可以查看在两个维度中,数据的分布情况。以主成分PC1、PC2为例,展示主成分分析得到的细胞分布图。

最后画出碎石图,这直接决定了后续我们聚类所采用的参数

如何选择参数?

很简单,寻找拐点位置,并且这个拐点(比如说dim=20)的解释量必须要大于90%。

for dim in range(50):
if dim_for_exp > 90%:
break

tips:有人在论文中做过实验,一般我们取30左右进行,其实25-35都不会很影响后续的分析,但这个必须按照自己画出来的碎石图进行判断

当然,也可以通过Jackstraw分析判断选取成分数量

只要高于虚线,dim都是可取的,本文由于电脑配置原因跑起来太慢了,因此只画了20个==

也可以绘制维度热力图,探索数据集中异质性的主要来源(主打一个图多好看)

2.2 聚类

目前,主流的聚类办法有两种,分别为umap和t-sne。前者聚的更紧凑,后者

Umap聚类效果更为紧凑,簇间近且边缘轮廓也较为靠近

T-sne相反

你以为这就结束了?大错特错!聚类有很多重要参数,不同的参数聚出来的效果是截然不同的。接下来依次说明:

Neighbors:单个细胞认为自己邻居数量的半径,这个参数可以由上文的pca得出

ClustersNumber:簇数量,可以通过leiden算法得出,其中的resolution也需要自己手动控制

理论上来说,簇数量是由真实世界决定的,比如说表皮细胞,有多少个就聚多少个簇,上文的所有办法都是为这个服务

当然,在前期我们不知道数量的时候,可以通过决策树来决定resolution的值到底是多少,以次先做一个大致的观察。原则是不要交叉,交叉就暂停。整一张抽象的图,按照这样的话就取0.2


需要代码直接联系本人哦,下方评论区or 邮箱:zhouqijia1110@gmail.com

差异基因与细胞标注

· 4 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

在单细胞RNA测序分析中,聚类之后筛选差异基因的主要目的是为了深入理解不同细胞群体之间的生物学差异。首先先看我们筛选出来的数据并对其进行解释

**p_val:**基因表达量差异P值(一般不看这个)

**p_val_adj:**校正后的P值(一般看这个)

**avg_log2FC:**基因在该细胞簇中与其他细胞簇表达量差异倍数的log值,一般大于2是最好的效果,说明差异很大

**pct.1:**在该细胞簇中表达该基因的细胞数量占比

**pct.2:**在其他细胞簇中表达该基因的细胞数量占比平均值

**cluster:**在哪一类簇中

**gene:**名字

**myroc:**roc评分,范围从[0,1] ,越大越好


我们做差异基因检查的目的是什么呢?

  1. 验证聚类结果

聚类过程将细胞分为不同的群体,但这些群体是否具有生物学意义需要进一步验证。筛选出差异基因可以帮助确定这些聚类是否合理。并且,如果在不同的聚类之间找到显著差异的基因,说明这些群体之间可能存在生物学差异,证明聚类是合理的。

  1. 标记细胞群体

放在后文讲,这就是细胞标注

  1. 下游分析

这些基因是下游基因的基础(GO分析,KEGG,拟时序,cell-cell communication等)

  1. 优化聚类

通过筛选差异基因,可以对初步聚类结果进行评估


如何对差异基因进行评估?首先,前者的roc评分可以作为一个依据,其次,可以通过可视化进行观察

该基因在其他簇中的分布情况(小提琴图与聚类特征标注图)

通过小提琴图可以观测出,ITBG1这个基因其实在每个簇中都有,差异基因是通过比较其他簇得出的,这就说明其实这个基因的差异性不是很明显,相反RTKN2更有差异性,我们做到差异基因这一步的时候,必须要有该簇的分布与其他簇不同的结果,这样后续细胞标记出的结果也会更好

特征可视化也可以看出,ITGB1在整张图上都是有的,这就说明他的差异性并不是很显著

4. 细胞标注

这步很重要,并且这和聚类效果息息相关,在细胞标注中,分为singleR自动标注与手动标注两种,后者的精确值是最高的,所以如果没有特殊情况的话,强烈要求使用手动标注。

手动标注同时需要十分强大的生物知识(我不会QAQ)。具体来说步骤如下:

  1. 查看差异基因后,查询数据库和文献。文献先不提了,数据库可以使用这三个:Cellmarker,singleCellBase, HCA, MCA。数据库中包含人,鼠,猴等等,其中人的最多。
  2. 评估可以使用交叉验证,即使用多个标志基因组合进行注释并进行交叉对比
  3. 对于某一类的细胞群体,我们还可以手动为每个细胞群体赋予一个标签,如“CD4+ T细胞”、“中性粒细胞”等

tips:对于聚类来说,我们可以先对其进行一个大的聚类,将细胞大群先注释出来,随后在细化聚类


经验:log2FC的本质就是簇中对比差异性,越大就越说明与其他簇的分布情况不同,相对应的,这在找marker的时候也更方便,而p值只是一个假设检验的值。所以在寻找差异基因的筛选条件时,优先log2FC(云经验,哥们也没做过细胞标注)


需要代码直接联系本人哦,下方评论区or 邮箱:zhouqijia1110@gmail.com

富集分析

· 5 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

通过前文我们可以提取出差异基因,然而差异基因的数量较多,对其进行分析会十分冗长,因此我们可以采取富集分析的方式进行归类。富集的意思是表示差异基因或者差异物质中注释到某个代谢通路的基因或者物质数目在所有差异基因或者物质中的比例显著大于背景基因或物质中注释到某个代谢通路的基因或物质数目在所有背景基因或者物质中的比例。简而言之一句话概括:该差异基因在特定的通路上占比很大

5.1 GO富集分析

主要用来看基因的三个方面,分别是分子功能、细胞组分、参与的生物过程。

举例,铁离子结合的GO term是GO:0005506,如果我们对所得到的差异基因进行GO富集分析后得到该term富集,则我们可以认为我们所研究的现象可能与铁离子结合有关系

进行go分析时,可以得到如下数据:

参数解释:

category: Gene Ontology数据库中唯一的标号信息

**over_represented_pvalue:**富集分析P值,P 值越小越显著

under_represented_pvalue:

**numDEInCat:**该功能类下的差异基因数目

**numInCat:**该功能类下的基因数目

**term:**Gene Ontology功能的描述信息

**ontology:**该GO的类别(CC,细胞组分;BP,生物进程;MF,分子功能)。

接着,还可以可视化DAG图,分支代表包含关系,从上至下所定义的功能范围越来越小,一般选取GO富集分析的结果前5位作为有向无环图的主节点,颜色的深浅代表富集程度。概括的说, 可以分析GO terms在富集分析中是否显著,并且terms是如何相互关联的

最后,也可以通过柱状图将ontology进行分类,以柱状图呈现出来

5.2 KEGG分析

在生物体内,不同基因相互协调行使其生物学功能,通过Pathway显著性富集能确定差异表达基因参与的最主要生化代谢途径和信号转导途径。KEGG(Kyoto Encyclopedia of Genes and Genomes)是有关Pathway的主要公共数据库(Kanehisa,2008)。Pathway显著性富集分析以KEGG Pathway为单位,应用超几何检验,找出与整个基因组背景相比,在差异表达基因中显著性富集的Pathway

通过KEGG分析后,我们可以得出如下表格(注意:使用enrichKEGG后传入的是一个对象,建议转为数据框,方便后续的作图与分析)

表格有些大,主要是geneID太长了,分别解释各字段含义:

**ID:**KEGG的PATHWAY数据库中途径标识

**Description:**该通路的描述

**GeneRAatio:**富集到该通路里的差异基因数/全部可以富集到KEGG里的差异基因数,比例越高说明越富集

**BgRatio:**该通路的全部基因数/该物种全部有KEGG信息的基因数

**pvalue:**p值,不过多解释了

**p.adjust:**矫校正过的p值

**qvalue:**q值

**geneID:**富集到该通路里的基因的名称

**Count:**富集到该通路中的差异基因的数目

接着,我们就可以画出点图与柱状图了。先说前者,这个只需要一行代码就行

在绘制柱状图的时候,同时还需要注意柱子颜色的分类。在其他资料中观察到他是按照通路类别进行分类的,一共有如下七种:

  • Metabolism(代谢)
  • Genetic Information Processing(遗传信息处理)
  • Environmental Information Processing(环境信息处理)
  • Cellular Processes(细胞过程)
  • Organismal Systems(有机体系统)
  • Human Diseases(人类疾病)
  • Drug Development(药物开发)

我们可以通过KEGG官网,以id为key进行辨别

  1. 登录KEGG PATHWAY Database (genome.jp)
  2. 输入id
  3. 查看类型后手动标注

以两个我瞎编的为例,柱状图最后应该长这样

批次效应

用于多样本整合时才需要,不过多赘述(因为我没有)

5.3 Reactome

这个其实和前面两个是一样的,区别仅仅是换了一个数据库

# 使用ReactomePA进行通路富集分析(只有human)
reactome_results <- enrichPathway(gene = entrez_genes,
organism = "human",
pvalueCutoff = 0.05,
readable = TRUE)

我是做的猴子,这个数据库听说只有人鼠,具体还有什么可以自己百度一下

5.4 GSEA

这块内容其实和前文的GO与KEGG分析相同,主要不同在于GO富集分析是先筛选差异基因,再判断差异基因在哪些注释的通路存在富集;这涉及到阈值的设定,存在一定主观性并且只能用于表达变化较大的基因,即我们定义的显著差异基因。GSEA则不局限于差异基因,从基因集的富集角度出发,理论上更容易囊括细微但协调性的变化对生物通路的影响。

图表与上文是一样的,不过多赘述

(参考自一文掌握单基因GSEA富集分析 | gseaGO and gseaKEGG-CSDN博客

PPI分析

· 2 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

这两块代码含量都比较少,大部分通过在线分析就可以出结果

构建差异表达基因编码的蛋白质之间的相互作用网络,识别关键调控蛋白质或蛋白质复合物。

输出的是PPI网络图及其分析结果,发现核心蛋白质。

在线分析网站:356 items (Macaca mulatta) - STRING interaction network (string-db.org)

可选选项:

  1. 隐藏无关联节点
  2. 节点多少可由score设置
  3. 保存为tsv文件(as tabular test output),进入到cytoscape进行美化
  4. 多个选Multiple proteins;单个选Protein by name

8 TF分析

通过转录因子分析,识别在细胞亚群中可能调控这些差异表达基因的关键转录因子。输出潜在的调控转录因子及其靶基因网络。 推荐网站:BART,以高变基因为key(但是分析速度很慢,暂且未找到理由,可能是因为我的数据量太大,也有可能是因为TF分析本身就很慢(计算相关性网络非常缓慢,我用的是R语言,读者如果有条件的话可以用python进行计算))

拟时序分析

· 4 min read
zqqqj
super bug engineer 4 nlp,robot,cv,ml and ds

在进行了聚类之后,其实各细胞是否具有同种生存状态是未知的。拟时序分析的目的就在于将细胞分为不同的分支,将各点(细胞)体现在不同的时间坐标中,从而了解各细胞的状态定位

在做拟时序分析的时候,采取的是机器学习方法(无监督和有监督),因此需要一定的生物学知识对图标进行判断,图中主要是为了表达细胞之间(簇)表达谱系的连续性,因此方向未必与现实情况相同(需要在代码中加入reserve)

举个例子:B细胞不会分化为NK细胞,但在图中就会如此,这就是reserve的作用

本文主要采取无监督的方法进行分析


tips:无监督就是没有真实数据,有监督就是包含一定的真实数据

  • 无监督数据:常见的如单细胞 RNA 测序数据,在特定的发育阶段采集了样本,但不确定细胞的确切时间顺序。
  • 有监督数据:例如药物处理实验,在不同时间点采集了单细胞样本,记录了每个样本的处理时间,通过这些时间点信息可以进行有监督的拟时序分析。

首先,使用monocol2创建CellDataset对象后,就有了拟时分析结果的可视化,我们可以将其分为:

state状态:

代表了细胞在某一生物学过程中所处的不同阶段。例如,在细胞分化过程中,初始的未分化状态、不同分化路径中的中间状态,以及终末分化状态,都会被标记为不同的“state”。

Pseudotime时间:

这是算法模拟出来的一个时间轴,越接近0就说明越早

Seurat分群结果:

这其实就是之前用umap聚出来的为基准了,按其基因表达模式进行分类

细胞注释结果(前文没有注释,如果想要的话可以直接color_by=''labels'')

state状态分支()

其实就是讲state状态分开来,更直观的看出各类处在什么分支上

在筛选完差异基因后,可以观察单个差异基因的表达情况。选择差异基因的原因的在整个分化过程中更加关键,能够揭示不同状态或分支间的生物学差异。

横轴代表拟时间,通常用来模拟细胞从一个初始状态到最终状态的进程;纵轴代表基因表达量,上面的位置表示在特定拟时间点上,基因的表达水平。

分支点设定在1的时候,表示关注在第一个分支点处的基因表达变化,细胞分类为3说明分为三个不同的簇。颜色通常表示基因的表达水平,颜色越深,表达越高;颜色越浅,表达越低。热图通过颜色变化来展示基因在不同细胞分支中的表达情况

这个热图用于展示在细胞分化过程中,不同分支上的基因表达模式。通过观察基因在分支上的表达差异,可以获得这些基因在不同细胞命运中的潜在角色,即哪些基因是上调的,哪些是下调的,从而反映基因表达的动态变化。

(图片分辨率或许有一些问题,自己画的时候可以调整一下width和height)

Physics Informed Deep Learning (Part I):Data-driven Solutions of Nonlinear Partial Differential Equations

· 4 min read
Tanger
Academic rubbish | CV Engineers | Visual bubble | compute math | PINN | Mathematical model

这是一篇关于使用数据驱动方法实现的 Physics-Informed Deep Learning(PINN)经典论文。

论文的来源

    首先,本人通过搜索很多 PINN 的论文,发现许多论文都在引用这篇论文,在好奇心的驱使下就在 google 学术上搜索了这篇论文,我们可以看到出现了两个版本,从标题名上看大致相同,作者也没变化。据开组会时,覃老师介绍说可能是因为前面这个版本是相当于没有正式发表还处于一个草稿阶段,后面那篇是经过整理并发表到了比较好的期刊中,我们可以从引用量(比较粗的红线)以及 easyScholar (比较细的红线)打上的标签还有作者希望我们引用这项工作的论文排名(作者更希望我们引用 2019 年正式分布的那篇)中看到区别,但不妨碍这几篇论文的优秀性,总的来说 M Raissi 等人的工作是非常出色的。

    他们也将这篇论文的工作产生的代码无私的奉献了出来,可以通过访问 Github 来查看相关代码,上面的代码是 tensorflow 的 1 版本写的,到现在 tensorflow 已经不支持 1 版本的 python 包安装,所以可能需要将上面的代码写成 2 版本的形式才能运行。 👉点我查看 Github 仓库

    这篇论文被视为数据驱动的 PINN 的研究工作的思想源头,有打算实现数据驱动的 PINN 工作的人可以先稍微熟悉该论文。他的论文分为三个部分,现在来介绍第一部分。原论文(Physics Informed Deep Learning (Part I): Data-driven Solutions of Nonlinear Partial Differential Equations)

    想要明白 PINN,先从 PINN 是什么说起,PINN 其实是 Physics Informed Deep Learning 的缩写,中文中比较准确的翻译是物理信息深度学习,人话就是结合了物理信息的深度学习模型。

    其实将机器学习用在求解偏微分方程上不是什么难事,但是直接用肯定是不行的,主要面对了几个问题,只要能很好的解决以下问题,那问题就迎刃而解了。

  • 数据采集:数据采集的成本太高几乎无法实现,尤其是对小样本的数据样本,绝大多数的机器学习技术缺乏鲁棒性,无法提供任何收敛保证。

  • 实际

Abstract(摘要)

    We introduce physics informed neural networks– neural networks that are trained to solve supervised learning tasks while respecting any given law of physics described by general nonlinear partial differential equations. In this two part treatise, we present our developments in the context of solving two main classes of problems: data-driven solution and data-driven discovery of partial differential equations. Depending on the nature and arrangement of the available data, we devise two distinct classes of algorithms, namely continuous time and discrete time models. The resulting neural networks form a new class of data-efficient universal function approximators that naturally encode any underlying physical laws as prior information. In this first part, we demonstrate how these networks can be used to infer solutions to partial differential equations, and obtain physics-informed surrogate models that are fully differentiable with respect to all input coordinates and free parameters.

    摘要的内容如上,建议多读几次,本笔记比较粗浅,有时间建议看看原文

    翻译成中文(deepl 翻译):我们介绍了物理学上的神经网络 - 神经网络被训练来解决监督学习任务,同时尊重由一般非线性偏微分方程描述的任何特定的物理学规律。在这两部分论文中,我们介绍了我们在解决两类主要问题方面的发展:数据驱动的解决方案和数据驱动的偏微分方程的发现。根据可用数据的性质和安排,我们设计了两类不同的算法,即连续时间和离散时间模型。由此产生的神经网络形成了一类新的数据高效的通用函数近似器,自然地将任何潜在的物理规律编码为先验信息。在这第一部分,我们展示了这些网络如何被用来推断偏微分方程的解决方案,并获得物理信息的代用模型,这些模型对于所有输入坐标和自由参数是完全可微的。