File size: 10,750 Bytes
29691f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
import torch
import torchvision.transforms as T
from PIL import Image
import numpy as np
from typing import Dict, List, Optional, Tuple
import random


def get_transform(config: dict, mode: str = 'train') -> T.Compose:
    """获取数据预处理变换"""
    preprocessing = config.get('preprocessing', {})
    target_size = preprocessing.get('target_size', 512)
    resize_mode = preprocessing.get('resize_mode', 'center_crop')
    
    transforms_list = []
    
    # 训练和验证的不同变换
    if mode == 'train':
        # 随机裁剪
        if preprocessing.get('random_crop', True):
            transforms_list.append(T.RandomResizedCrop(
                target_size,
                scale=(0.8, 1.0),
                ratio=(0.8, 1.2)
            ))
        else:
            transforms_list.append(T.Resize(target_size, interpolation=T.InterpolationMode.BILINEAR))
        
        # 随机水平翻转
        if preprocessing.get('random_flip', True):
            transforms_list.append(T.RandomHorizontalFlip(p=0.5))
        
        # 颜色抖动
        if preprocessing.get('color_jitter', 0.05) > 0:
            color_jitter = preprocessing['color_jitter']
            transforms_list.append(T.ColorJitter(
                brightness=color_jitter,
                contrast=color_jitter,
                saturation=color_jitter,
                hue=min(0.1, color_jitter)
            ))
        
        # 随机旋转
        if preprocessing.get('random_rotation', 0.0) > 0:
            max_angle = preprocessing['random_rotation']
            transforms_list.append(T.RandomRotation(degrees=(-max_angle, max_angle)))
    
    else:  # 验证/测试模式
        # 中心裁剪
        if resize_mode == 'center_crop':
            transforms_list.extend([
                T.Resize(target_size, interpolation=T.InterpolationMode.BILINEAR),
                T.CenterCrop(target_size)
            ])
        elif resize_mode == 'resize':
            transforms_list.append(T.Resize(target_size, interpolation=T.InterpolationMode.BILINEAR))
        elif resize_mode == 'random_crop':
            transforms_list.append(T.RandomCrop(target_size))
        else:
            raise ValueError(f"未知的resize_mode: {resize_mode}")
    
    # 转换为Tensor
    transforms_list.append(T.ToTensor())
    
    # 归一化
    normalize_config = preprocessing.get('normalize', {})
    mean = normalize_config.get('mean', [0.5, 0.5, 0.5])
    std = normalize_config.get('std', [0.5, 0.5, 0.5])
    transforms_list.append(T.Normalize(mean=mean, std=std))
    
    return T.Compose(transforms_list)


class TextPreprocessor:
    """文本预处理器"""
    def __init__(self, config: dict):
        self.config = config.get('preprocessing', {})
        
        # 文本处理参数
        self.max_length = self.config.get('max_length', 77)
        self.truncation = self.config.get('truncation', True)
        self.padding = self.config.get('padding', 'max_length')
        
        # 尝试加载tokenizer
        self.tokenizer = None
        self._init_tokenizer()
    
    def _init_tokenizer(self):
        """初始化tokenizer"""
        try:
            from transformers import CLIPTokenizer
            tokenizer_name = self.config.get('tokenizer', 'openai/clip-vit-base-patch32')
            self.tokenizer = CLIPTokenizer.from_pretrained(tokenizer_name)
        except ImportError:
            print("警告: 未安装transformers,无法使用CLIP tokenizer")
        except Exception as e:
            print(f"加载tokenizer失败: {e}")
    
    def preprocess_text(self, text: str) -> Dict:
        """预处理文本"""
        if self.tokenizer is not None:
            # 使用CLIP tokenizer
            inputs = self.tokenizer(
                text,
                max_length=self.max_length,
                padding=self.padding,
                truncation=self.truncation,
                return_tensors="pt"
            )
            return {
                'input_ids': inputs['input_ids'].squeeze(0),
                'attention_mask': inputs['attention_mask'].squeeze(0)
            }
        else:
            # 简单的文本处理
            return {
                'text': text,
                'length': len(text)
            }
    
    def batch_preprocess(self, texts: List[str]) -> Dict:
        """批量预处理文本"""
        if self.tokenizer is not None:
            inputs = self.tokenizer(
                texts,
                max_length=self.max_length,
                padding=self.padding,
                truncation=self.truncation,
                return_tensors="pt"
            )
            return inputs
        else:
            return {'texts': texts}


class ImagePreprocessor:
    """图像预处理器"""
    def __init__(self, config: dict):
        self.config = config.get('preprocessing', {})
        self.transform = get_transform(config, mode='train')
    
    def preprocess_image(self, image: Image.Image) -> torch.Tensor:
        """预处理单张图像"""
        return self.transform(image)
    
    def batch_preprocess(self, images: List[Image.Image]) -> torch.Tensor:
        """批量预处理图像"""
        return torch.stack([self.transform(img) for img in images])
    
    def preprocess_for_vae(self, image: torch.Tensor) -> torch.Tensor:
        """为VAE编码预处理图像"""
        # VAE期望输入在[-1, 1]范围内
        return image * 2.0 - 1.0
    
    def postprocess_from_vae(self, latents: torch.Tensor) -> torch.Tensor:
        """从VAE解码后处理图像"""
        # 将[-1, 1]范围转换回[0, 1]
        return (latents + 1.0) / 2.0


class DataPreprocessor:
    """数据预处理器(整合文本和图像处理)"""
    def __init__(self, config: dict):
        self.config = config
        self.image_preprocessor = ImagePreprocessor(config)
        self.text_preprocessor = TextPreprocessor(config)
        
        # 文本编码器
        self.text_encoder = None
        self._init_text_encoder()
    
    def _init_text_encoder(self):
        """初始化文本编码器"""
        try:
            from transformers import CLIPTextModel
            model_name = self.config.get('preprocessing', {}).get('text_encoder', 'openai/clip-vit-base-patch32')
            self.text_encoder = CLIPTextModel.from_pretrained(model_name)
            
            # 冻结参数
            for param in self.text_encoder.parameters():
                param.requires_grad = False
            
            # 设置为评估模式
            self.text_encoder.eval()
            
            print(f"已加载文本编码器: {model_name}")
        except Exception as e:
            print(f"加载文本编码器失败: {e}")
    
    def encode_text(self, text: str) -> torch.Tensor:
        """编码文本为嵌入向量"""
        if self.text_encoder is None:
            raise ValueError("文本编码器未初始化")
        
        # 预处理文本
        inputs = self.text_preprocessor.preprocess_text(text)
        
        # 编码
        with torch.no_grad():
            if 'input_ids' in inputs:
                outputs = self.text_encoder(
                    input_ids=inputs['input_ids'].unsqueeze(0),
                    attention_mask=inputs['attention_mask'].unsqueeze(0) if 'attention_mask' in inputs else None
                )
                return outputs.last_hidden_state.squeeze(0)
            else:
                # 回退到简单的嵌入
                return torch.randn(77, 768)  # 默认维度
    
    def batch_encode_text(self, texts: List[str]) -> torch.Tensor:
        """批量编码文本"""
        if self.text_encoder is None:
            raise ValueError("文本编码器未初始化")
        
        # 预处理文本
        inputs = self.text_preprocessor.batch_preprocess(texts)
        
        # 编码
        with torch.no_grad():
            if 'input_ids' in inputs:
                outputs = self.text_encoder(
                    input_ids=inputs['input_ids'],
                    attention_mask=inputs.get('attention_mask', None)
                )
                return outputs.last_hidden_state
            else:
                # 回退到简单的嵌入
                batch_size = len(texts)
                return torch.randn(batch_size, 77, 768)
    
    def preprocess_batch(self, batch: List[Dict]) -> Dict:
        """预处理批次数据"""
        images = [item['image'] for item in batch]
        texts = [item['text'] for item in batch]
        
        # 预处理图像
        image_tensors = self.image_preprocessor.batch_preprocess(images)
        
        # 编码文本
        if self.text_encoder is not None:
            text_embeddings = self.batch_encode_text(texts)
        else:
            text_embeddings = None
        
        return {
            'images': image_tensors,
            'text_embeddings': text_embeddings,
            'texts': texts,
            'image_paths': [item.get('image_path', '') for item in batch]
        }


def test_preprocessing():
    """测试预处理"""
    import yaml
    from PIL import Image
    
    # 创建测试图像
    test_image = Image.new('RGB', (512, 512), color='red')
    
    # 加载配置
    with open('configs/data/laion_filtered.yaml', 'r') as f:
        config = yaml.safe_load(f)
    
    # 测试图像预处理
    image_preprocessor = ImagePreprocessor(config)
    processed_image = image_preprocessor.preprocess_image(test_image)
    
    print(f"原始图像: {test_image.size}")
    print(f"处理后图像形状: {processed_image.shape}")
    
    # 测试文本预处理
    text_preprocessor = TextPreprocessor(config)
    processed_text = text_preprocessor.preprocess_text("A red square image")
    
    print(f"文本处理结果: {processed_text}")
    
    # 测试数据预处理器
    data_preprocessor = DataPreprocessor(config)
    
    test_batch = [
        {'image': test_image, 'text': "A red square"},
        {'image': test_image, 'text': "A blue circle"}
    ]
    
    processed_batch = data_preprocessor.preprocess_batch(test_batch)
    
    print(f"批次图像形状: {processed_batch['images'].shape}")
    print(f"文本嵌入形状: {processed_batch['text_embeddings'].shape if processed_batch['text_embeddings'] is not None else 'None'}")
    
    return processed_batch


if __name__ == '__main__':
    test_preprocessing()