Days 25 LruCache缓存处理&Bitmap二次采样

(一)LruCache缓存处理
1、加载图片的正确流程是:“内存-文件-网络 三层cache策略”
a、先从内存缓存中获取,取到则返回,取不到则进行下一步;
b、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
c、从网络下载图片,并更新到内存缓存和文件缓存。
2、内存缓存分类:四种级别由高到低依次为:强引用、软引用、弱引用和虚引用
a、强引用:(在Android中LruCache就是强引用缓存)
如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OOM异常,使程序异常终止,也不会回收具有强引用的对象来解决内存不足问题。
b、软引用(SoftReference):
软引用类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。使用软引用能防止内存泄露,增强程序的健壮性。
c、弱引用(WeakReference):
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
d、虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。
Demo1:
点击download更新图片,
若可以从强引用中获取,则从强引用中获取,
如不能从强引用中获取,则从软引用中寻找,如果找到且不为null,则将bitmap添加到强引用中,并从软引用中移除
如果在软引用中找不到,则开启线程(注意:下载图片等费时操作要在子线程中完成)从网络下载图片,并将bitmap添加到强引用中
代码如下:
MainActivity:

public class MainActivity extends AppCompatActivity {

    private ImageView ivPic = null;
    private String url = "http://ww2.sinaimg.cn/mw690/4b08ac5ejw1f8ffqk2hicj21ag1gxaz9.jpg";
    private Bitmap bitmap = null;
    private ProgressDialog pdshow = null;

    private LruCache<String,Bitmap> lruCache = null;

    private HashMap<String,SoftReference<Bitmap>> softMap = new HashMap<String,SoftReference<Bitmap>>();

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 0:
                    pdshow = new ProgressDialog(MainActivity.this);
                    pdshow.setTitle("title");
                    pdshow.setMessage("洪荒之力已开启......");
                    pdshow.setIcon(R.mipmap.ic_launcher);
                    pdshow.show();
                    break;
                case 1:
                    Bitmap bitmap = (Bitmap) msg.obj;
                    if(bitmap != null){
                        ivPic.setImageBitmap(bitmap);
                    }
                    pdshow.dismiss();
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
 //     Runtime.getRuntime().maxMemory()返回的是java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存
        int maxSize = (int) (Runtime.getRuntime().maxMemory()/8);

        lruCache = new LrucacheUtils(maxSize,softMap);
    }

    public Bitmap getBitmapFromMemoryByUrl(String url){
        bitmap = lruCache.get(url);
//        从强引用中拿数据
        if(bitmap != null){
            Log.d("test","---------强引用获取图片--------");
            return bitmap;
        }else{
//            从软引用中拿数据
            SoftReference<Bitmap> soft = softMap.get(url);
            if(soft != null){
//                一旦SoftReference保存了对一个Java对象的软引用后,
//                在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
//                另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null
                bitmap = soft.get();
                Log.d("test","===========软引用中获取图片============");
                if(bitmap != null){
                    lruCache.put(url,bitmap);
                    Log.d("test","*******从软引用中添加到强引用*******");

                    softMap.remove(url);
                    Log.d("test","++++++++添加到强引用中后从软引用中移除+++++");
                    return bitmap;
                }else{
//                    若对应bitmap为null,将此软引用移除
                    softMap.remove(url);
                }
            }
        }

        return null;
    }

    private void initView() {
        ivPic = (ImageView) findViewById(R.id.ivPic);
    }

    public void download(View view) {
        bitmap = getBitmapFromMemoryByUrl(url);

        if(bitmap != null){
            ivPic.setImageBitmap(bitmap);
            Log.d("test","--------从缓存中获取到数据-------");
        }else {
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    handler.sendEmptyMessage(0);

                    Message message = Message.obtain();
                    message.what = 1;
                    byte[] data = OkHttpUtils.getBytesByUrl(url);
                    bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
                    Log.d("test","----------从网络下载图片-------------");

//                  将数据放到强引用中(不然每次点download都会重新下载图片)
                    lruCache.put(url,bitmap);

                    message.obj = bitmap;
                    handler.sendMessage(message);
                }
            }.start();

        }
    }
}

LruCacheUtils:

public class LrucacheUtils extends LruCache<String,Bitmap> {
    private HashMap<String,SoftReference<Bitmap>> softMap = null;

//    maxSize 规定的最大存储空间
    public LrucacheUtils(int maxSize,HashMap<String,SoftReference<Bitmap>> softMap) {
        super(maxSize);
        this.softMap = softMap;
    }


}

Demo2:
点击download下载图片,点击save根据url将下载好的图片保存到缓存中,点击get从缓存中根据指定url获取对应的缓存图片,点击delete从缓存中根据指定url删除对应的缓存图片。
MainActivity:

public class MainActivity extends AppCompatActivity {
    private ImageView ivPic = null;
    private String url = "http://ww3.sinaimg.cn/mw690/4b08ac5ejw1f8ab0ibt6jj22yo4g0npj.jpg";

    private LruCacheUtils  lruCacheUtiles = null;
//    从网络上下载的图片
    private Bitmap bitmap = null;
//    从缓存中获取的图片
    private Bitmap lruCacheBitmap = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        lruCacheUtiles = new LruCacheUtils();
        lruCacheUtiles.initLruche();
    }

    public void clickMe(View view){
        switch (view.getId()){
//            从网络上下载指定图片
            case R.id.btnDown:
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        byte[] data = OkHttpUtils.getBytesByUrl(url);
                        bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
                        Log.d("test","图片下载完毕"+bitmap);
                    }
                }.start();
                break;

//            将网络上下载的图片保存到lruCache对象
            case R.id.btnSave:
                if(bitmap != null){
                    lruCacheUtiles.setBitmap2URL(url,bitmap);
                    Log.d("test","图片已保存");
                }
                break;

//            从lruCache对象中根据指定url对应的缓存图片,并将缓存图片设置给ImageView控件
            case R.id.btnGet:
                lruCacheBitmap = lruCacheUtiles.getBitmapByURL(url);
                Log.d("test","取出来的图片");
                ivPic.setImageBitmap(lruCacheBitmap);
                break;

//            从lruCache对象中删除指定url对应的图片资源
            case R.id.btnDel:
                lruCacheUtiles.deleteBitmapByURL(url);
                break;
        }
    }

    private void initView() {
        ivPic = (ImageView) findViewById(R.id.ivPic);
    }
}

LruCacheUtils:

public class LruCacheUtils {
    private LruCache<String,Bitmap> lruCache = null;

//    初始化缓存
    public LruCache initLruche(){
        int maxSize = 4 * 1024 * 1024;
        lruCache = new LruCache<>(maxSize);
        return lruCache;
    }

//    获取指定url的Bitmap对象
    public Bitmap getBitmapByURL(String url){
        if(lruCache != null){
            return lruCache.get(url);
        }
        return null;
    }

//  把Bitmap对象放入key值为url的缓存中
    public void setBitmap2URL(String url,Bitmap bitmap){
        if(getBitmapByURL(url) == null){
            lruCache.put(url,bitmap);
        }
    }

//  从缓存中删除指定url的Bitmap对象
    public void deleteBitmapByURL(String url){
        if(getBitmapByURL(url) != null){
            lruCache.remove(url);
        }
    }
}

(二)Bitmap二次采样:
参考自:http://www.th7.cn/Program/Android/201503/405000.shtml
意义:BitmapFactory解码一张图片时,有时会遇到OOM错误。这往往是由于图片过大造成的。要想正常使用,则需要分配更少的内存空间来存储。
Android使用BitmapFactory.Options解决加载大图片内存溢出问题;
1、BitmapFactory.Options.inJustDecodeBounds:
注意:并不是在此属性的true和false之间才能对图片进行操作,此属性仅代表是否返回实际的Bitmap
如果该值设为true那么将不返回实际的bitmap不给其分配内存空间而里面只包括一些解码边界信息即图片大小信息。[通过设置inJustDecodeBounds为true,获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度),然后计算一个inSampleSize(缩放值),然后就可以取图片了,这里要注意的是,inSampleSize 可能小于0,必须做判断]。
2、BitmapFactory.Options.inSampleSize:设置缩放值
设置恰当的inSampleSize可以使BitmapFactory分配更少的空间以消除该错误
3、BitmapFactory.Option.inPreferredConfig:设置单位像素占用的字节数
inPreferredConfig的值为Bitmap.Config类型,Bitmap.Config类是个枚举类型
可以为以下值:
(1)、Bitmap.Config ALPHA_8 :
(2)、Bitmap.Config ARGB_4444 :不推荐使用
(3)、Bitmap.Config ARGB_8888:
一个像素占用四个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节。这是一种高质量的图片格式,电脑上普通采用的格式。它也是Android手机上一个Bitmap的默认格式。
(4)、Bitmap.Config RGB_565:一个像素占用2个字节,没有alpha(A)值,能够达到比较好的呈现效果,相对于ARGB_8888来说也能减少一半的内存开销。
Bitmap占用内存的计算:
Android中一张图片(Bitmap)占用的内存主要和以下几个因素有关:图片长度,图片宽度,单位像素占用的字节数。
一张图片(Bitmap)占用的内存=图片长度图片宽度单位像素占用的字节数(图片长度和图片宽度的单位是像素)。图片(Bitmap)占用的内存和屏幕密度(Density)无关。
创建一个Bitmap时,其单位像素占用的字节数由其参数BitmapFactory.Options的inPreferredConfig变量决定。
**备注:**ARGB指的是一种色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue,其实所有的可见色都是红绿蓝组成的,所以红绿蓝又称为三原色。 ARGB就是:透明度 红色 绿色 蓝色。
步骤:
先设置BitmapFactory.Options的inJustDecodeBounds为true
通过BitmapFactory.decodeByteArray()方法返回图片大小
设置缩放值inSampleSize
设置单位像素占用的字节数inPreferredConfig
设置BitmapFactory.Options的inJustDecodeBounds为false
再调用BitmapFactory.decodeByteArray()方法返回的即为二次采样后的Bitmap对象
代码:

public class BitmapUtils {
    public static Bitmap getOptionBitmapByByteArray(byte[] data){

        BitmapFactory.Options options = new BitmapFactory.Options();

        options.inJustDecodeBounds = true;
//        若options.inJustDecodeBounds为true,
//       那么将不返回实际的bitmap不给其分配内存空间而里面只包括一些解码边界信息即图片大小信息,也就不会那么频繁的发生OOM了
        BitmapFactory.decodeByteArray(data,0,data.length,options);

//        设置缩放值
        options.inSampleSize = 3;
//        设置单位像素占用的字节数
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;

//        设置options.inJustDecodeBounds为false
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeByteArray(data,0,data.length,options);
    }
}

Demo:
MainActiviy:

public class MainActivity extends AppCompatActivity {

    private ListView lvShow = null;
    private List<DataInfo> list = null;
    private MyAdapter adapter = null;

    String[] urls={
            "http://img2.duitang.com/uploads/item/201210/16/20121016190338_z3jFA.jpeg",
            "http://m.chanyouji.cn/tips/hanju.jpg",
            "http://m.chanyouji.cn/articles/534/51b6a7a15a4c35948f09cfb4ec6bfe69.jpg",
            "http://m.chanyouji.cn/articles/578/50e0f1407caccc6e7379d5699df8582e.jpg",
            "http://m.chanyouji.cn/articles/587/e14f0f98ab6c0551a077a0acc63e1a55.jpg",
            "http://m.chanyouji.cn/articles/596/46eab7447b19db0759c3f29c40661595.jpg",
            "http://m.chanyouji.cn/articles/610/56fa2310ba3935bc9bb3d97c668e5052.jpg",
            "http://m.chanyouji.cn/articles/573/eb25779a20b80a891397325aeeebc715.jpg",
            "http://p.chanyouji.cn/58129/1375088377136p180kqdqfb1pfet5815hr1qf4107s8.jpg",
            "http://m.chanyouji.cn/articles/609/b67b9b38ef6fdd70788f12d1fcd226bd.jpg"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();

        setData();

        adapter = new MyAdapter(list,this);
        lvShow.setAdapter(adapter);
    }

    private void setData() {
        list = new ArrayList<DataInfo>();
        DataInfo dataInfo = null;
        for (int i = 0;i<urls.length;i++){
            String title = "title"+i;
            String url = urls[i];
            dataInfo = new DataInfo(title,url);
            list.add(dataInfo);
        }
    }

    private void initView() {
        lvShow = (ListView) findViewById(R.id.lvShow);
    }
}

MyAdapter:

public class MyAdapter extends BaseAdapter {
    private List<DataInfo> list = null;
    private Context context = null;
    private LruCacheUtils lruCacheUtils = null;
    private Handler handler = null;

    public MyAdapter(List<DataInfo> list, Context context) {
        this.list = list;
        this.context = context;

        lruCacheUtils = new LruCacheUtils();
        lruCacheUtils.initLruCache();

        handler = new Handler();
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int i) {
        return list.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(final int i, View view, ViewGroup viewGroup) {
        View v = null;
        final ViewHolder holder ;
        if(view == null){
            holder = new ViewHolder();
            v = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
            holder.txtTitle = (TextView) v.findViewById(R.id.txtTitle);
            holder.ivPic = (ImageView) v.findViewById(R.id.ivPic);

            v.setTag(holder);
        }else{
            v = view;
            holder = (ViewHolder) v.getTag();
        }

        DataInfo dataInfo = list.get(i);
        if(dataInfo != null){
            String title = dataInfo.getTitle();
            final String url = dataInfo.getUrl();
            holder.txtTitle.setText(title);

//            先从缓存中获取图片,获取不到指定图片再从网络下载
            if(lruCacheUtils.getBitmapByUrl(url) != null){
                Log.d("test","从缓存中获取图片");
                holder.ivPic.setImageBitmap(lruCacheUtils.getBitmapByUrl(url));
            }else{
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        byte[] data = OkHttpUtils.getBytesByUrl(url);
//                        ???通过二次采样获得的Bitmap
                        final Bitmap bitmap = BitmapUtils.getOptionBitmapByByteArray(data);
                        Log.d("test","网络下载的图片"+i);

                        if (bitmap != null){
                            Log.d("test","将图片保存到缓存中"+i);
                            lruCacheUtils.setBitmap2Url(url,bitmap);
                        }

//                        **重点:**子线程不能更新UI,通过handler的post方法将更新操作添加到主线程
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                holder.ivPic.setImageBitmap(bitmap);
                            }
                        });
                    }
                }.start();
            }
        }

        return v;
    }

    class ViewHolder{
        TextView txtTitle;
        ImageView ivPic;
    }
}
帮我检查代码,并说明代码用到的表格、建模的特征、给出的结果分别有哪些:import pandas as pd import numpy as np import lightgbm as lgb from lightgbm import early_stopping, log_evaluation import gc import os import chardet from sklearn.model_selection import train_test_split from tqdm.auto import tqdm # 使用auto版本自动选择界面 import joblib from datetime import datetime import dask.dataframe as dd # 添加Dask支持大数据处理 from dask.diagnostics import ProgressBar # 1. 增强数据加载函数(优化内存和IO) def load_data_safely(file_path, usecols=None, dtype=None, chunksize=500000): """安全高效加载大型CSV文件,自动处理编码""" if not os.path.exists(file_path): print(f"⚠️ 文件不存在: {file_path}") return pd.DataFrame() try: # 高效检测编码 with open(file_path, 'rb') as f: detector = chardet.UniversalDetector() for line in f: detector.feed(line) if detector.done or f.tell() > 100000: break detector.close() encoding = detector.result['encoding'] if detector.result['confidence'] > 0.6 else 'utf-8' # 使用Dask处理大文件 ddf = dd.read_csv(file_path, encoding=encoding, usecols=usecols, dtype=dtype, blocksize=chunksize, low_memory=False) with ProgressBar(): df = ddf.compute() # 优化分类列内存 if dtype: for col, col_type in dtype.items(): if col in df.columns and col_type == 'category': df[col] = df[col].astype('category').cat.as_ordered() return df except Exception as e: print(f"⚠️ 加载 {file_path} 失败: {str(e)}") return pd.DataFrame() # 2. 优化历史数据加载(并行处理) def load_historical_data(days=32): """并行加载历史数据,自动跳过缺失文件""" from concurrent.futures import ThreadPoolExecutor def load_day(day): day_str = f"{day:02d}" results = {} # 曝光数据 see_path = f'see_{day_str}.csv' if os.path.exists(see_path): results['see'] = load_data_safely( see_path, usecols=['did', 'vid'], dtype={'did': 'category', 'vid': 'category'} ) # 点击数据 click_path = f'click_{day_str}.csv' if os.path.exists(click_path): click = load_data_safely( click_path, usecols=['did', 'vid', 'click_time'], dtype={'did': 'category', 'vid': 'category'} ) if not click.empty and 'click_time' in click: click['date'] = pd.to_datetime(click['click_time']).dt.date click.drop(columns=['click_time'], inplace=True, errors='ignore') results['click'] = click # 播放数据 play_path = f'play_{day_str}.csv' if os.path.exists(play_path): results['play'] = load_data_safely( play_path, usecols=['did', 'vid', 'play_time'], dtype={'did': 'category', 'vid': 'category', 'play_time': 'float32'} ) return results with ThreadPoolExecutor(max_workers=8) as executor: futures = [executor.submit(load_day, day) for day in range(1, days+1)] results = [f.result() for f in tqdm(futures, desc="加载历史数据", total=days)] # 合并结果 see_list = [r['see'] for r in results if 'see' in r and not r['see'].empty] click_list = [r['click'] for r in results if 'click' in r and not r['click'].empty] play_list = [r['play'] for r in results if 'play' in r and not r['play'].empty] hist_exposure = pd.concat(see_list).drop_duplicates(['did', 'vid']) if see_list else pd.DataFrame() hist_click = pd.concat(click_list).drop_duplicates(['did', 'vid']) if click_list else pd.DataFrame() hist_play = pd.concat(play_list).drop_duplicates(['did', 'vid']) if play_list else pd.DataFrame() return hist_exposure, hist_click, hist_play # 3. 优化点击数据集构建(内存友好的负采样) def build_click_dataset(hist_exposure, hist_click, sample_ratio=0.1): """使用Bloom Filter进行高效负样本采样""" if hist_exposure.empty or hist_click.empty: print("⚠️ 历史曝光或点击数据为空,无法构建数据集") return pd.DataFrame() # 标记正样本 hist_click = hist_click[['did', 'vid']].copy() hist_click['label'] = 1 # 创建Bloom Filter存储正样本 from pybloom_live import ScalableBloomFilter bloom = ScalableBloomFilter(mode=ScalableBloomFilter.SMALL_SET_GROWTH) # 添加正样本 for _, row in tqdm(hist_click.iterrows(), total=len(hist_click), desc="构建布隆过滤器"): bloom.add((row['did'], row['vid'])) # 采样负样本 negative_samples = [] chunk_size = 500000 for i in range(0, len(hist_exposure), chunk_size): chunk = hist_exposure.iloc[i:i+chunk_size] for _, row in tqdm(chunk.iterrows(), total=len(chunk), desc="采样负样本"): if sample_ratio > np.random.random() and (row['did'], row['vid']) not in bloom: negative_samples.append([row['did'], row['vid'], 0]) # 构建负样本DataFrame negative_df = pd.DataFrame(negative_samples, columns=['did', 'vid', 'label']) click_data = pd.concat([hist_click, negative_df], ignore_index=True) return click_data # 4. 优化特征工程(延迟计算) def add_click_features(df, did_features, vid_info, hist_click, hist_play): """按需计算特征,避免中间大DataFrame""" if df.empty: return df # 基础特征 if not did_features.empty and 'did' in did_features.columns: # 优化内存合并 df = df.merge(did_features.add_suffix('_user'), left_on='did', right_index=True, how='left') if not vid_info.empty and 'vid' in vid_info.columns: vid_info_sub = vid_info[[c for c in vid_info.columns if c != 'item_duration']] df = df.merge(vid_info_sub.add_suffix('_item'), left_on='vid', right_index=True, how='left') # 按需计算统计特征 def calc_stat_feature(df, source, group_col, target_col, feature_name, agg_func='size'): if source.empty or group_col not in source or (agg_func != 'size' and target_col not in source): df[feature_name] = 0 return # 使用预聚合缓存 cache_file = f"{feature_name}_cache.pkl" if os.path.exists(cache_file): stats = joblib.load(cache_file) else: stats = source.groupby(group_col) if agg_func == 'size': stats = stats.size().rename(feature_name) else: stats = stats[target_col].agg(agg_func).rename(feature_name) joblib.dump(stats, cache_file) if group_col == 'did': df = df.merge(stats, left_on='did', right_index=True, how='left') else: df = df.merge(stats, left_on='vid', right_index=True, how='left') df[feature_name].fillna(0, inplace=True) return df # 计算用户特征 df = calc_stat_feature(df, hist_click, 'did', None, 'user_click_count') df = calc_stat_feature(df, hist_play, 'did', 'play_time', 'user_total_play', 'sum') # 计算物品特征 df = calc_stat_feature(df, hist_click, 'vid', None, 'video_click_count') df = calc_stat_feature(df, hist_play, 'vid', 'play_time', 'avg_play_time', 'mean') # 时间特征 if 'date' in df: df['day_of_week'] = pd.to_datetime(df['date']).dt.dayofweek.astype('int8') df['hour'] = pd.to_datetime(df['date']).dt.hour.astype('int8') # 释放内存 gc.collect() return df # 5. 模型训练函数封装 def train_lgb_model(X, y, categorical_features, params, model_name="模型"): if X.empty or y.empty: print(f"⚠️ {model_name}训练数据为空") return None X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y if 'binary' in params['objective'] else None ) train_data = lgb.Dataset(X_train, label=y_train, categorical_feature=categorical_features, free_raw_data=False) val_data = lgb.Dataset(X_val, label=y_val, categorical_feature=categorical_features, free_raw_data=False) print(f"开始训练{model_name}...") model = lgb.train( params, train_data, num_boost_round=10000, valid_sets=[train_data, val_data], valid_names=['train', 'valid'], callbacks=[ early_stopping(stopping_rounds=100, verbose=True), log_evaluation(period=50), lgb.reset_parameter(learning_rate=lambda iter: params['learning_rate'] * (0.99 ** iter)) ] ) return model # 主程序优化 def main(): # 配置优化 #pd.set_option('future.no_silent_downcasting', True) gc.enable() # 核心数据类型 dtypes = {'did': 'category', 'vid': 'category', 'play_time': 'float32'} for i in range(88): dtypes[f'f{i}'] = 'float32' # 核心数据加载 print("高效加载核心数据...") did_features = load_data_safely('did_features_table.csv', dtype=dtypes) vid_info = load_data_safely('vid_info_table.csv', dtype=dtypes) # 历史数据加载 print("并行加载历史数据...") hist_exposure, hist_click, hist_play = load_historical_data(days=15) # 减少天数提高速度 # 点击模型训练 if not hist_exposure.empty and not hist_click.empty: print("构建点击数据集...") click_train_data = build_click_dataset(hist_exposure, hist_click, sample_ratio=0.05) # 降低采样率 print("构建点击特征...") click_train_data = add_click_features( click_train_data, did_features, vid_info, hist_click, hist_play ) # 获取分类特征 base_categorical = ['item_cid', 'item_type', 'item_assetSource', 'item_classify', 'item_isIntact', 'sid', 'stype', 'day_of_week', 'hour'] categorical_features = [c for c in base_categorical if c in click_train_data] # 训练模型 click_params = { 'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'num_leaves': 127, 'learning_rate': 0.1, 'feature_fraction': 0.7, 'bagging_freq': 5, 'min_data_in_leaf': 100, 'verbosity': -1 } model_click = train_lgb_model( click_train_data.drop(columns=['label', 'did', 'vid', 'date'], errors='ignore'), click_train_data['label'], categorical_features, click_params, "点击预测模型" ) else: model_click = None # 完播率模型训练 if not hist_play.empty and not vid_info.empty: print("构建完播率数据集...") play_data = hist_play[['did', 'vid', 'play_time']].copy() play_data = play_data.merge( vid_info[['vid', 'item_duration']], on='vid', how='left' ) play_data['completion_rate'] = play_data['play_time'] / play_data['item_duration'] # 添加特征 play_data = add_click_features( play_data, did_features, vid_info, hist_click, hist_play ) # 训练模型 play_params = { 'objective': 'regression', 'metric': 'mae', 'boosting_type': 'gbdt', 'num_leaves': 63, 'learning_rate': 0.05, 'feature_fraction': 0.7, 'bagging_freq': 5, 'min_data_in_leaf': 50, 'verbosity': -1 } model_play = train_lgb_model( play_data.drop(columns=['completion_rate', 'did', 'vid', 'play_time', 'item_duration'], errors='ignore'), play_data['completion_rate'], categorical_features, play_params, "完播率预测模型" ) else: model_play = None # 保存模型 for model, name in zip([model_click, model_play], ['click_model.txt', 'play_model.txt']): if model: model.save_model(name) joblib.dump(base_categorical, 'categorical_features.pkl') # 测试预测 print("加载预测数据...") test_users = load_data_safely('testA_pred_did.csv', dtype={'did': 'category'}) test_exposure = load_data_safely('testA_did_show.csv', dtype={'did': 'category', 'vid': 'category'}) if not test_users.empty and not test_exposure.empty: print("生成测试预测...") # 预测逻辑保持不变... else: print("⚠️ 预测数据加载失败") if __name__ == "__main__": main()
最新发布
07-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值